[
  {
    "path": ".fpm_openwrt",
    "content": "-s dir\n--name sing-box\n--category net\n--license GPL-3.0-or-later\n--description \"The universal proxy platform.\"\n--url \"https://sing-box.sagernet.org/\"\n--maintainer \"nekohasekai <contact-git@sekai.icu>\"\n--no-deb-generate-changes\n\n--config-files /etc/config/sing-box\n--config-files /etc/sing-box/config.json\n\n--depends ca-bundle\n--depends kmod-inet-diag\n--depends kmod-tun\n--depends firewall4\n--depends kmod-nft-queue\n\n--before-remove release/config/openwrt.prerm\n\nrelease/config/config.json=/etc/sing-box/config.json\n\nrelease/config/openwrt.conf=/etc/config/sing-box\nrelease/config/openwrt.init=/etc/init.d/sing-box\nrelease/config/openwrt.keep=/lib/upgrade/keep.d/sing-box\n\nrelease/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash\nrelease/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish\nrelease/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box\n\nLICENSE=/usr/share/licenses/sing-box/LICENSE\n"
  },
  {
    "path": ".fpm_pacman",
    "content": "-s dir\n--name sing-box\n--category net\n--license GPL-3.0-or-later\n--description \"The universal proxy platform.\"\n--url \"https://sing-box.sagernet.org/\"\n--maintainer \"nekohasekai <contact-git@sekai.icu>\"\n--config-files etc/sing-box/config.json\n--after-install release/config/sing-box.postinst\n\nrelease/config/config.json=/etc/sing-box/config.json\n\nrelease/config/sing-box.service=/usr/lib/systemd/system/sing-box.service\nrelease/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service\nrelease/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf\nrelease/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules\nrelease/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf\n\nrelease/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash\nrelease/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish\nrelease/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box\n\nLICENSE=/usr/share/licenses/sing-box/LICENSE\n"
  },
  {
    "path": ".fpm_systemd",
    "content": "-s dir\n--name sing-box\n--category net\n--license GPL-3.0-or-later\n--description \"The universal proxy platform.\"\n--url \"https://sing-box.sagernet.org/\"\n--maintainer \"nekohasekai <contact-git@sekai.icu>\"\n--deb-field \"Bug: https://github.com/SagerNet/sing-box/issues\"\n--no-deb-generate-changes\n--config-files /etc/sing-box/config.json\n--after-install release/config/sing-box.postinst\n\nrelease/config/config.json=/etc/sing-box/config.json\n\nrelease/config/sing-box.service=/usr/lib/systemd/system/sing-box.service\nrelease/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service\nrelease/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf\nrelease/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules\nrelease/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf\n\nrelease/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash\nrelease/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish\nrelease/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box\n\nLICENSE=/usr/share/licenses/sing-box/LICENSE\n"
  },
  {
    "path": ".github/CRONET_GO_VERSION",
    "content": "ea7cd33752aed62603775af3df946c1b83f4b0b3\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: nekohasekai"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report\ndescription: \"Report sing-box bug\"\nbody:\n  - type: dropdown\n    attributes:\n      label: Operating system\n      description: Operating system type\n      options:\n        - iOS\n        - macOS\n        - Apple tvOS\n        - Android\n        - Windows\n        - Linux\n        - Others\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: System version\n      description: Please provide the operating system version\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Installation type\n      description: Please provide the sing-box installation type\n      options:\n        - Original sing-box Command Line\n        - sing-box for iOS Graphical Client\n        - sing-box for macOS Graphical Client\n        - sing-box for Apple tvOS Graphical Client\n        - sing-box for Android Graphical Client\n        - Third-party graphical clients that advertise themselves as using sing-box (Windows)\n        - Third-party graphical clients that advertise themselves as using sing-box (Android)\n        - Others\n    validations:\n      required: true\n  - type: input\n    attributes:\n      description: Graphical client version\n      label: If you are using a graphical client, please provide the version of the client.\n  - type: textarea\n    attributes:\n      label: Version\n      description: If you are using the original command line program, please provide the output of the `sing-box version` command.\n      render: shell\n  - type: textarea\n    attributes:\n      label: Description\n      description: Please provide a detailed description of the error.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Reproduction\n      description: Please provide the steps to reproduce the error, including the configuration files and procedures that can locally (not dependent on the remote server) reproduce the error using the original command line program of sing-box.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Logs\n      description: |-\n        In addition, if you encounter a crash with the graphical client, please also provide crash logs.\n        For Apple platform clients, please check `Settings - View Service Log` for crash logs.\n        For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.\n      render: shell\n  - type: checkboxes\n    id: supporter\n    attributes:\n      label: Supporter\n      options:\n        - label: I am a [sponsor](https://github.com/sponsors/nekohasekai/)\n  - type: checkboxes\n    attributes:\n      label: Integrity requirements\n      description: |-\n        Please check all of the following options to prove that you have read and understood the requirements, otherwise this issue will be closed.\n        Sing-box is not a project aimed to please users who can't make any meaningful contributions and gain unethical influence. If you deceive here to deliberately waste the time of the developers, you will be permanently blocked.\n      options:\n        - label: I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.\n          required: true\n        - label: I confirm that I have provided the server and client configuration files and process that can be reproduced locally, instead of a complicated client configuration file that has been stripped of sensitive data.\n          required: true\n        - label: I confirm that I have provided the simplest configuration that can be used to reproduce the error I reported, instead of depending on remote servers, TUN, graphical interface clients, or other closed-source software.\n          required: true\n        - label: I confirm that I have provided the complete configuration files and logs, rather than just providing parts I think are useful out of confidence in my own intelligence.\n          required: true"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report_zh.yml",
    "content": "name: 错误反馈\ndescription: \"提交 sing-box 漏洞\"\nbody:\n  - type: dropdown\n    attributes:\n      label: 操作系统\n      description: 请提供操作系统类型\n      options:\n        - iOS\n        - macOS\n        - Apple tvOS\n        - Android\n        - Windows\n        - Linux\n        - 其他\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: 系统版本\n      description: 请提供操作系统版本\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: 安装类型\n      description: 请提供该 sing-box 安装类型\n      options:\n        - sing-box 原始命令行程序\n        - sing-box for iOS 图形客户端程序\n        - sing-box for macOS 图形客户端程序\n        - sing-box for Apple tvOS 图形客户端程序\n        - sing-box for Android 图形客户端程序\n        - 宣传使用 sing-box 的第三方图形客户端程序 (Windows)\n        - 宣传使用 sing-box 的第三方图形客户端程序 (Android)\n        - 其他\n    validations:\n      required: true\n  - type: input\n    attributes:\n      description: 图形客户端版本\n      label: 如果您使用图形客户端程序，请提供该程序版本。\n  - type: textarea\n    attributes:\n      label: 版本\n      description: 如果您使用原始命令行程序，请提供 `sing-box version` 命令的输出。\n      render: shell\n  - type: textarea\n    attributes:\n      label: 描述\n      description: 请提供错误的详细描述。\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 重现方式\n      description: 请提供重现错误的步骤，必须包括可以在本地（不依赖与远程服务器）使用 sing-box 原始命令行程序重现错误的配置文件与流程。\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 日志\n      description: |-\n        此外，如果您遭遇图形界面应用程序崩溃，请附加提供崩溃日志。\n        对于 Apple 平台图形客户端程序，请检查 `Settings - View Service Log` 以导出崩溃日志。\n        对于 Android 图形客户端程序，请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。\n      render: shell\n  - type: checkboxes\n    id: supporter\n    attributes:\n      label: 支持我们\n      options:\n        - label: 我已经 [赞助](https://github.com/sponsors/nekohasekai/)\n  - type: checkboxes\n    attributes:\n      label: 完整性要求\n      description: |-\n        请勾选以下所有选项以证明您已经阅读并理解了以下要求，否则该 issue 将被关闭。\n        sing-box 不是讨好无法作出任何意义上的贡献的最终用户并获取非道德影响力的项目，如果您在此处欺骗以故意浪费开发者的时间，您将被永久封锁。\n      options:\n        - label: 我保证阅读了文档，了解所有我编写的配置文件项的含义，而不是大量堆砌看似有用的选项或默认值。\n          required: true\n        - label: 我保证提供了可以在本地重现该问题的服务器、客户端配置文件与流程，而不是一个脱敏的复杂客户端配置文件。\n          required: true\n        - label: 我保证提供了可用于重现我报告的错误的最简配置，而不是依赖远程服务器、TUN、图形界面客户端或者其他闭源软件。\n          required: true\n        - label: 我保证提供了完整的配置文件与日志，而不是出于对自身智力的自信而仅提供了部分认为有用的部分。\n          required: true\n"
  },
  {
    "path": ".github/build_alpine_apk.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\nARCHITECTURE=\"$1\"\nVERSION=\"$2\"\nBINARY_PATH=\"$3\"\nOUTPUT_PATH=\"$4\"\n\nif [ -z \"$ARCHITECTURE\" ] || [ -z \"$VERSION\" ] || [ -z \"$BINARY_PATH\" ] || [ -z \"$OUTPUT_PATH\" ]; then\n  echo \"Usage: $0 <architecture> <version> <binary_path> <output_path>\"\n  exit 1\nfi\n\nPROJECT=$(cd \"$(dirname \"$0\")/..\"; pwd)\n\n# Convert version to APK format:\n#   1.13.0-beta.8  -> 1.13.0_beta8-r0\n#   1.13.0-rc.3    -> 1.13.0_rc3-r0\n#   1.13.0         -> 1.13.0-r0\nAPK_VERSION=$(echo \"$VERSION\" | sed -E 's/-([a-z]+)\\.([0-9]+)/_\\1\\2/')\nAPK_VERSION=\"${APK_VERSION}-r0\"\n\nROOT_DIR=$(mktemp -d)\ntrap 'rm -rf \"$ROOT_DIR\"' EXIT\n\n# Binary\ninstall -Dm755 \"$BINARY_PATH\" \"$ROOT_DIR/usr/bin/sing-box\"\n\n# Config files\ninstall -Dm644 \"$PROJECT/release/config/config.json\" \"$ROOT_DIR/etc/sing-box/config.json\"\ninstall -Dm755 \"$PROJECT/release/config/sing-box.initd\" \"$ROOT_DIR/etc/init.d/sing-box\"\ninstall -Dm644 \"$PROJECT/release/config/sing-box.confd\" \"$ROOT_DIR/etc/conf.d/sing-box\"\n\n# Service files\ninstall -Dm644 \"$PROJECT/release/config/sing-box.service\" \"$ROOT_DIR/usr/lib/systemd/system/sing-box.service\"\ninstall -Dm644 \"$PROJECT/release/config/sing-box@.service\" \"$ROOT_DIR/usr/lib/systemd/system/sing-box@.service\"\n\n# Completions\ninstall -Dm644 \"$PROJECT/release/completions/sing-box.bash\" \"$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash\"\ninstall -Dm644 \"$PROJECT/release/completions/sing-box.fish\" \"$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish\"\ninstall -Dm644 \"$PROJECT/release/completions/sing-box.zsh\" \"$ROOT_DIR/usr/share/zsh/site-functions/_sing-box\"\n\n# License\ninstall -Dm644 \"$PROJECT/LICENSE\" \"$ROOT_DIR/usr/share/licenses/sing-box/LICENSE\"\n\n# APK metadata\nPACKAGES_DIR=\"$ROOT_DIR/lib/apk/packages\"\nmkdir -p \"$PACKAGES_DIR\"\n\n# .conffiles\ncat > \"$PACKAGES_DIR/.conffiles\" <<'EOF'\n/etc/conf.d/sing-box\n/etc/init.d/sing-box\n/etc/sing-box/config.json\nEOF\n\n# .conffiles_static (sha256 checksums)\nwhile IFS= read -r conffile; do\n  sha256=$(sha256sum \"$ROOT_DIR$conffile\" | cut -d' ' -f1)\n  echo \"$conffile $sha256\"\ndone < \"$PACKAGES_DIR/.conffiles\" > \"$PACKAGES_DIR/.conffiles_static\"\n\n# .list (all files, excluding lib/apk/packages/ metadata)\n(cd \"$ROOT_DIR\" && find . -type f -o -type l) \\\n  | sed 's|^\\./|/|' \\\n  | grep -v '^/lib/apk/packages/' \\\n  | sort > \"$PACKAGES_DIR/.list\"\n\n# Build APK\napk mkpkg \\\n  --info \"name:sing-box\" \\\n  --info \"version:${APK_VERSION}\" \\\n  --info \"description:The universal proxy platform.\" \\\n  --info \"arch:${ARCHITECTURE}\" \\\n  --info \"license:GPL-3.0-or-later with name use or association addition\" \\\n  --info \"origin:sing-box\" \\\n  --info \"url:https://sing-box.sagernet.org/\" \\\n  --info \"maintainer:nekohasekai <contact-git@sekai.icu>\" \\\n  --files \"$ROOT_DIR\" \\\n  --output \"$OUTPUT_PATH\"\n"
  },
  {
    "path": ".github/build_openwrt_apk.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\nARCHITECTURE=\"$1\"\nVERSION=\"$2\"\nBINARY_PATH=\"$3\"\nOUTPUT_PATH=\"$4\"\n\nif [ -z \"$ARCHITECTURE\" ] || [ -z \"$VERSION\" ] || [ -z \"$BINARY_PATH\" ] || [ -z \"$OUTPUT_PATH\" ]; then\n  echo \"Usage: $0 <architecture> <version> <binary_path> <output_path>\"\n  exit 1\nfi\n\nPROJECT=$(cd \"$(dirname \"$0\")/..\"; pwd)\n\n# Convert version to APK format:\n#   1.13.0-beta.8  -> 1.13.0_beta8-r0\n#   1.13.0-rc.3    -> 1.13.0_rc3-r0\n#   1.13.0         -> 1.13.0-r0\nAPK_VERSION=$(echo \"$VERSION\" | sed -E 's/-([a-z]+)\\.([0-9]+)/_\\1\\2/')\nAPK_VERSION=\"${APK_VERSION}-r0\"\n\nROOT_DIR=$(mktemp -d)\ntrap 'rm -rf \"$ROOT_DIR\"' EXIT\n\n# Binary\ninstall -Dm755 \"$BINARY_PATH\" \"$ROOT_DIR/usr/bin/sing-box\"\n\n# Config files\ninstall -Dm644 \"$PROJECT/release/config/config.json\" \"$ROOT_DIR/etc/sing-box/config.json\"\ninstall -Dm644 \"$PROJECT/release/config/openwrt.conf\" \"$ROOT_DIR/etc/config/sing-box\"\ninstall -Dm755 \"$PROJECT/release/config/openwrt.init\" \"$ROOT_DIR/etc/init.d/sing-box\"\ninstall -Dm644 \"$PROJECT/release/config/openwrt.keep\" \"$ROOT_DIR/lib/upgrade/keep.d/sing-box\"\n\n# Completions\ninstall -Dm644 \"$PROJECT/release/completions/sing-box.bash\" \"$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash\"\ninstall -Dm644 \"$PROJECT/release/completions/sing-box.fish\" \"$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish\"\ninstall -Dm644 \"$PROJECT/release/completions/sing-box.zsh\" \"$ROOT_DIR/usr/share/zsh/site-functions/_sing-box\"\n\n# License\ninstall -Dm644 \"$PROJECT/LICENSE\" \"$ROOT_DIR/usr/share/licenses/sing-box/LICENSE\"\n\n# APK metadata\nPACKAGES_DIR=\"$ROOT_DIR/lib/apk/packages\"\nmkdir -p \"$PACKAGES_DIR\"\n\n# .conffiles\ncat > \"$PACKAGES_DIR/.conffiles\" <<'EOF'\n/etc/config/sing-box\n/etc/sing-box/config.json\nEOF\n\n# .conffiles_static (sha256 checksums)\nwhile IFS= read -r conffile; do\n  sha256=$(sha256sum \"$ROOT_DIR$conffile\" | cut -d' ' -f1)\n  echo \"$conffile $sha256\"\ndone < \"$PACKAGES_DIR/.conffiles\" > \"$PACKAGES_DIR/.conffiles_static\"\n\n# .list (all files, excluding lib/apk/packages/ metadata)\n(cd \"$ROOT_DIR\" && find . -type f -o -type l) \\\n  | sed 's|^\\./|/|' \\\n  | grep -v '^/lib/apk/packages/' \\\n  | sort > \"$PACKAGES_DIR/.list\"\n\n# Build APK\napk mkpkg \\\n  --info \"name:sing-box\" \\\n  --info \"version:${APK_VERSION}\" \\\n  --info \"description:The universal proxy platform.\" \\\n  --info \"arch:${ARCHITECTURE}\" \\\n  --info \"license:GPL-3.0-or-later\" \\\n  --info \"origin:sing-box\" \\\n  --info \"url:https://sing-box.sagernet.org/\" \\\n  --info \"maintainer:nekohasekai <contact-git@sekai.icu>\" \\\n  --info \"depends:ca-bundle kmod-inet-diag kmod-tun firewall4 kmod-nft-queue\" \\\n  --info \"provider-priority:100\" \\\n  --script \"pre-deinstall:${PROJECT}/release/config/openwrt.prerm\" \\\n  --files \"$ROOT_DIR\" \\\n  --output \"$OUTPUT_PATH\"\n"
  },
  {
    "path": ".github/deb2ipk.sh",
    "content": "#!/usr/bin/env bash\n# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683\n\nset -e -o pipefail\n\nPROJECT=$(dirname \"$0\")/../..\nTMP_PATH=`mktemp -d`\ncp $2 $TMP_PATH\npushd $TMP_PATH\n\nDEB_NAME=`ls *.deb`\nar x $DEB_NAME\n\nmkdir control\npushd control\ntar xf ../control.tar.gz\nrm md5sums\nsed \"s/Architecture:\\\\ \\w*/Architecture:\\\\ $1/g\" ./control -i\ncat control\ntar czf ../control.tar.gz ./*\npopd\n\nDEB_NAME=${DEB_NAME%.deb}\ntar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary\npopd\n\ncp $TMP_PATH/$DEB_NAME.ipk $3\nrm -r $TMP_PATH\n"
  },
  {
    "path": ".github/renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"commitMessagePrefix\": \"[dependencies]\",\n  \"extends\": [\n    \"config:base\",\n    \":disableRateLimiting\"\n  ],\n  \"baseBranches\": [\n    \"unstable\"\n  ],\n  \"golang\": {\n    \"enabled\": false\n  },\n  \"packageRules\": [\n    {\n      \"matchManagers\": [\n        \"github-actions\"\n      ],\n      \"groupName\": \"github-actions\"\n    },\n    {\n      \"matchManagers\": [\n        \"dockerfile\"\n      ],\n      \"groupName\": \"Dockerfile\"\n    }\n  ]\n}"
  },
  {
    "path": ".github/setup_go_for_macos1013.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nVERSION=\"1.25.8\"\nPATCH_COMMITS=(\n  \"afe69d3cec1c6dcf0f1797b20546795730850070\"\n  \"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938\"\n)\nCURL_ARGS=(\n  -fL\n  --silent\n  --show-error\n)\n\nif [[ -n \"${GITHUB_TOKEN:-}\" ]]; then\n  CURL_ARGS+=(-H \"Authorization: Bearer ${GITHUB_TOKEN}\")\nfi\n\nmkdir -p \"$HOME/go\"\ncd \"$HOME/go\"\nwget \"https://dl.google.com/go/go${VERSION}.darwin-arm64.tar.gz\"\ntar -xzf \"go${VERSION}.darwin-arm64.tar.gz\"\n#cp -a go go_bootstrap\nmv go go_osx\ncd go_osx\n\n# these patch URLs only work on golang1.25.x\n# that means after golang1.26 release it must be changed\n# see: https://github.com/SagerNet/go/commits/release-branch.go1.25/\n# revert:\n# 33d3f603c1: \"cmd/link/internal/ld: use 12.0.0 OS/SDK versions for macOS linking\"\n# 937368f84e: \"crypto/x509: change how we retrieve chains on darwin\"\n\nfor patch_commit in \"${PATCH_COMMITS[@]}\"; do\n  curl \"${CURL_ARGS[@]}\" \"https://github.com/SagerNet/go/commit/${patch_commit}.diff\" | patch --verbose -p 1\ndone\n\n# Rebuild is not needed: we build with CGO_ENABLED=1, so Apple's external\n# linker handles LC_BUILD_VERSION via MACOSX_DEPLOYMENT_TARGET, and the\n# stdlib (crypto/x509) is compiled from patched src automatically.\n#cd src\n#GOROOT_BOOTSTRAP=\"$HOME/go/go_bootstrap\" ./make.bash\n#cd ../..\n#rm -rf go_bootstrap \"go${VERSION}.darwin-arm64.tar.gz\"\n"
  },
  {
    "path": ".github/setup_go_for_windows7.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nVERSION=\"1.25.8\"\nPATCH_COMMITS=(\n  \"466f6c7a29bc098b0d4c987b803c779222894a11\"\n  \"1bdabae205052afe1dadb2ad6f1ba612cdbc532a\"\n  \"a90777dcf692dd2168577853ba743b4338721b06\"\n  \"f6bddda4e8ff58a957462a1a09562924d5f3d05c\"\n  \"bed309eff415bcb3c77dd4bc3277b682b89a388d\"\n  \"34b899c2fb39b092db4fa67c4417e41dc046be4b\"\n)\nCURL_ARGS=(\n  -fL\n  --silent\n  --show-error\n)\n\nif [[ -n \"${GITHUB_TOKEN:-}\" ]]; then\n  CURL_ARGS+=(-H \"Authorization: Bearer ${GITHUB_TOKEN}\")\nfi\n\nmkdir -p \"$HOME/go\"\ncd \"$HOME/go\"\nwget \"https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz\"\ntar -xzf \"go${VERSION}.linux-amd64.tar.gz\"\nmv go go_win7\ncd go_win7\n\n# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557\n# these patch URLs only work on golang1.25.x\n# that means after golang1.26 release it must be changed\n# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/\n# revert:\n# 693def151adff1af707d82d28f55dba81ceb08e1: \"crypto/rand,runtime: switch RtlGenRandom for ProcessPrng\"\n# 7c1157f9544922e96945196b47b95664b1e39108: \"net: remove sysSocket fallback for Windows 7\"\n# 48042aa09c2f878c4faa576948b07fe625c4707a: \"syscall: remove Windows 7 console handle workaround\"\n# a17d959debdb04cd550016a3501dd09d50cd62e7: \"runtime: always use LoadLibraryEx to load system libraries\"\n# fixes:\n# bed309eff415bcb3c77dd4bc3277b682b89a388d: \"Fix os.RemoveAll not working on Windows7\"\n# 34b899c2fb39b092db4fa67c4417e41dc046be4b: \"Revert \\\"os: remove 5ms sleep on Windows in (*Process).Wait\\\"\"\n\nfor patch_commit in \"${PATCH_COMMITS[@]}\"; do\n  curl \"${CURL_ARGS[@]}\" \"https://github.com/MetaCubeX/go/commit/${patch_commit}.diff\" | patch --verbose -p 1\ndone\n"
  },
  {
    "path": ".github/update_clients.sh",
    "content": "#!/usr/bin/env bash\n\nPROJECTS=$(dirname \"$0\")/../..\n\nfunction updateClient() {\n  pushd clients/$1\n  git fetch\n  git reset FETCH_HEAD --hard\n  popd\n  git add clients/$1\n}\n\nupdateClient \"apple\"\nupdateClient \"android\"\n"
  },
  {
    "path": ".github/update_cronet.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\nSCRIPT_DIR=$(dirname \"$0\")\nPROJECTS=$SCRIPT_DIR/../..\n\ngit -C $PROJECTS/cronet-go fetch origin main\ngit -C $PROJECTS/cronet-go fetch origin go\ngo get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go)\ngo get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go)\ngo mod tidy\ngit -C $PROJECTS/cronet-go rev-parse origin/go > \"$SCRIPT_DIR/CRONET_GO_VERSION\"\n"
  },
  {
    "path": ".github/update_cronet_dev.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\nSCRIPT_DIR=$(dirname \"$0\")\nPROJECTS=$SCRIPT_DIR/../..\n\ngit -C $PROJECTS/cronet-go fetch origin dev\ngit -C $PROJECTS/cronet-go fetch origin go_dev\ngo get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev)\ngo get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev)\ngo mod tidy\ngit -C $PROJECTS/cronet-go rev-parse origin/dev > \"$SCRIPT_DIR/CRONET_GO_VERSION\"\n"
  },
  {
    "path": ".github/update_dependencies.sh",
    "content": "#!/usr/bin/env bash\n\nPROJECTS=$(dirname \"$0\")/../..\ngo get -x github.com/sagernet/$1@$(git -C $PROJECTS/$1 rev-parse HEAD)\ngo mod tidy\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"Version name\"\n        required: true\n        type: string\n      build:\n        description: \"Build type\"\n        required: true\n        type: choice\n        default: \"All\"\n        options:\n          - All\n          - Binary\n          - Android\n          - Apple\n          - app-store\n          - iOS\n          - macOS\n          - tvOS\n          - macOS-standalone\n          - publish-android\n  push:\n    branches:\n      - stable\n      - testing\n      - unstable\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}\n  cancel-in-progress: true\n\njobs:\n  calculate_version:\n    name: Calculate version\n    runs-on: ubuntu-latest\n    outputs:\n      version: ${{ steps.outputs.outputs.version }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ~1.25.8\n      - name: Check input version\n        if: github.event_name == 'workflow_dispatch'\n        run: |-\n          echo \"version=${{ inputs.version }}\"\n          echo \"version=${{ inputs.version }}\" >> \"$GITHUB_ENV\"\n      - name: Calculate version\n        if: github.event_name != 'workflow_dispatch'\n        run: |-\n          go run -v ./cmd/internal/read_tag --ci --nightly\n      - name: Set outputs\n        id: outputs\n        run: |-\n          echo \"version=$version\" >> \"$GITHUB_OUTPUT\"\n  build:\n    name: Build binary\n    if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'\n    runs-on: ubuntu-latest\n    needs:\n      - calculate_version\n    strategy:\n      matrix:\n        include:\n          - { os: linux, arch: amd64, variant: purego, naive: true }\n          - { os: linux, arch: amd64, variant: glibc, naive: true }\n          - { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, alpine: x86_64, openwrt: \"x86_64\" }\n\n          - { os: linux, arch: arm64, variant: purego, naive: true }\n          - { os: linux, arch: arm64, variant: glibc, naive: true }\n          - { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, alpine: aarch64, openwrt: \"aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic\" }\n\n          - { os: linux, arch: \"386\", go386: sse2 }\n          - { os: linux, arch: \"386\", variant: glibc, naive: true, go386: sse2 }\n          - { os: linux, arch: \"386\", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, alpine: x86, openwrt: \"i386_pentium4\" }\n\n          - { os: linux, arch: arm, goarm: \"7\" }\n          - { os: linux, arch: arm, variant: glibc, naive: true, goarm: \"7\" }\n          - { os: linux, arch: arm, variant: musl, naive: true, goarm: \"7\", debian: armhf, rpm: armv7hl, pacman: armv7hl, alpine: armv7, openwrt: \"arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4\" }\n\n          - { os: linux, arch: mipsle, gomips: hardfloat, naive: true, variant: glibc }\n          - { os: linux, arch: mipsle, gomips: softfloat, naive: true, variant: musl, debian: mipsel, rpm: mipsel, openwrt: \"mipsel_24kc mipsel_74kc mipsel_mips32\" }\n          - { os: linux, arch: mips64le, gomips: hardfloat, naive: true, variant: glibc, debian: mips64el, rpm: mips64el }\n          - { os: linux, arch: riscv64, naive: true, variant: glibc }\n          - { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: riscv64, alpine: riscv64, openwrt: \"riscv64_generic\" }\n          - { os: linux, arch: loong64, naive: true, variant: glibc }\n          - { os: linux, arch: loong64, naive: true, variant: musl, debian: loongarch64, rpm: loongarch64, alpine: loongarch64, openwrt: \"loongarch64_generic\" }\n\n          - { os: linux, arch: \"386\", go386: softfloat, openwrt: \"i386_pentium-mmx\" }\n          - { os: linux, arch: arm, goarm: \"5\", openwrt: \"arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale\" }\n          - { os: linux, arch: arm, goarm: \"6\", debian: armel, rpm: armv6hl, openwrt: \"arm_arm1176jzf-s_vfp\" }\n          - { os: linux, arch: mips, gomips: softfloat, openwrt: \"mips_24kc mips_4kec mips_mips32\" }\n          - { os: linux, arch: mipsle, gomips: hardfloat, openwrt: \"mipsel_24kc_24kf\" }\n          - { os: linux, arch: mipsle, gomips: softfloat }\n          - { os: linux, arch: mips64, gomips: softfloat, openwrt: \"mips64_mips64r2 mips64_octeonplus\" }\n          - { os: linux, arch: mips64le, gomips: hardfloat }\n          - { os: linux, arch: mips64le, gomips: softfloat, openwrt: \"mips64el_mips64r2\" }\n          - { os: linux, arch: s390x, debian: s390x, rpm: s390x }\n          - { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }\n          - { os: linux, arch: riscv64 }\n          - { os: linux, arch: loong64 }\n\n          - { os: windows, arch: amd64, legacy_win7: true, legacy_name: \"windows-7\" }\n          - { os: windows, arch: \"386\", legacy_win7: true, legacy_name: \"windows-7\" }\n\n          - { os: android, arch: arm64, ndk: \"aarch64-linux-android23\" }\n          - { os: android, arch: arm, ndk: \"armv7a-linux-androideabi23\" }\n          - { os: android, arch: amd64, ndk: \"x86_64-linux-android23\" }\n          - { os: android, arch: \"386\", ndk: \"i686-linux-android23\" }\n    steps:\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n      - name: Setup Go\n        if: ${{ ! matrix.legacy_win7 }}\n        uses: actions/setup-go@v5\n        with:\n          go-version: ~1.25.8\n      - name: Cache Go for Windows 7\n        if: matrix.legacy_win7\n        id: cache-go-for-windows7\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/go/go_win7\n          key: go_win7_1258\n      - name: Setup Go for Windows 7\n        if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n        run: |-\n          .github/setup_go_for_windows7.sh\n      - name: Setup Go for Windows 7\n        if: matrix.legacy_win7\n        run: |-\n          echo \"PATH=$HOME/go/go_win7/bin:$PATH\" >> $GITHUB_ENV\n          echo \"GOROOT=$HOME/go/go_win7\" >> $GITHUB_ENV\n      - name: Setup Android NDK\n        if: matrix.os == 'android'\n        uses: nttld/setup-ndk@v1\n        with:\n          ndk-version: r28\n          local-cache: true\n      - name: Clone cronet-go\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)\n          git init ~/cronet-go\n          git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git\n          git -C ~/cronet-go fetch --depth=1 origin \"$CRONET_GO_VERSION\"\n          git -C ~/cronet-go checkout FETCH_HEAD\n          git -C ~/cronet-go submodule update --init --recursive --depth=1\n      - name: Regenerate Debian keyring\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg\n          cd ~/cronet-go\n          GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh\n      - name: Cache Chromium toolchain\n        if: matrix.naive\n        id: cache-chromium-toolchain\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/cronet-go/naiveproxy/src/third_party/llvm-build/\n            ~/cronet-go/naiveproxy/src/gn/out/\n            ~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/\n            ~/cronet-go/naiveproxy/src/out/sysroot-build/\n          key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }}\n      - name: Download Chromium toolchain\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          cd ~/cronet-go\n          if [[ \"${{ matrix.variant }}\" == \"musl\" ]]; then\n            go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain\n          else\n            go run ./cmd/build-naive --target=linux/${{ matrix.arch }} download-toolchain\n          fi\n      - name: Set Chromium toolchain environment\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          cd ~/cronet-go\n          if [[ \"${{ matrix.variant }}\" == \"musl\" ]]; then\n            go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV\n          else\n            go run ./cmd/build-naive --target=linux/${{ matrix.arch }} env >> $GITHUB_ENV\n          fi\n      - name: Set tag\n        run: |-\n          git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo \"PUBLISHED=false\" >> \"$GITHUB_ENV\"\n          git tag v${{ needs.calculate_version.outputs.version }} -f\n      - name: Set build tags\n        run: |\n          set -xeuo pipefail\n          if [[ \"${{ matrix.naive }}\" == \"true\" ]]; then\n            TAGS=$(cat release/DEFAULT_BUILD_TAGS)\n          else\n            TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)\n          fi\n          if [[ \"${{ matrix.variant }}\" == \"purego\" ]]; then\n            TAGS=\"${TAGS},with_purego\"\n          elif [[ \"${{ matrix.variant }}\" == \"musl\" ]]; then\n            TAGS=\"${TAGS},with_musl\"\n          fi\n          echo \"BUILD_TAGS=${TAGS}\" >> \"${GITHUB_ENV}\"\n      - name: Set shared ldflags\n        run: |\n          echo \"LDFLAGS_SHARED=$(cat release/LDFLAGS)\" >> \"${GITHUB_ENV}\"\n      - name: Build (purego)\n        if: matrix.variant == 'purego'\n        run: |\n          set -xeuo pipefail\n          mkdir -p dist\n          go build -v -trimpath -o dist/sing-box -tags \"${BUILD_TAGS}\" \\\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=\" \\\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"0\"\n          GOOS: ${{ matrix.os }}\n          GOARCH: ${{ matrix.arch }}\n          GO386: ${{ matrix.go386 }}\n          GOARM: ${{ matrix.goarm }}\n          GOMIPS: ${{ matrix.gomips }}\n          GOMIPS64: ${{ matrix.gomips }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Extract libcronet.so\n        if: matrix.variant == 'purego' && matrix.naive\n        run: |\n          cd ~/cronet-go\n          CGO_ENABLED=0 go run -v ./cmd/build-naive extract-lib --target ${{ matrix.os }}/${{ matrix.arch }} -o $GITHUB_WORKSPACE/dist\n      - name: Build (glibc)\n        if: matrix.variant == 'glibc'\n        run: |\n          set -xeuo pipefail\n          mkdir -p dist\n          go build -v -trimpath -o dist/sing-box -tags \"${BUILD_TAGS}\" \\\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=\" \\\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"1\"\n          GOOS: linux\n          GOARCH: ${{ matrix.arch }}\n          GO386: ${{ matrix.go386 }}\n          GOARM: ${{ matrix.goarm }}\n          GOMIPS: ${{ matrix.gomips }}\n          GOMIPS64: ${{ matrix.gomips }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Build (musl)\n        if: matrix.variant == 'musl'\n        run: |\n          set -xeuo pipefail\n          mkdir -p dist\n          go build -v -trimpath -o dist/sing-box -tags \"${BUILD_TAGS}\" \\\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=\" \\\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"1\"\n          GOOS: linux\n          GOARCH: ${{ matrix.arch }}\n          GO386: ${{ matrix.go386 }}\n          GOARM: ${{ matrix.goarm }}\n          GOMIPS: ${{ matrix.gomips }}\n          GOMIPS64: ${{ matrix.gomips }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Build (non-variant)\n        if: matrix.os != 'android' && matrix.variant == ''\n        run: |\n          set -xeuo pipefail\n          mkdir -p dist\n          go build -v -trimpath -o dist/sing-box -tags \"${BUILD_TAGS}\" \\\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=\" \\\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"0\"\n          GOOS: ${{ matrix.os }}\n          GOARCH: ${{ matrix.arch }}\n          GO386: ${{ matrix.go386 }}\n          GOARM: ${{ matrix.goarm }}\n          GOMIPS: ${{ matrix.gomips }}\n          GOMIPS64: ${{ matrix.gomips }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Build Android\n        if: matrix.os == 'android'\n        run: |\n          set -xeuo pipefail\n          go install -v ./cmd/internal/build\n          export CC='${{ matrix.ndk }}-clang'\n          export CXX=\"${CC}++\"\n          mkdir -p dist\n          GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags \"${BUILD_TAGS}\" \\\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=\" \\\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"1\"\n          BUILD_GOOS: ${{ matrix.os }}\n          BUILD_GOARCH: ${{ matrix.arch }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Set name\n        run: |-\n          DIR_NAME=\"sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}\"\n          if [[ -n \"${{ matrix.goarm }}\" ]]; then\n            DIR_NAME=\"${DIR_NAME}v${{ matrix.goarm }}\"\n          elif [[ -n \"${{ matrix.go386 }}\" && \"${{ matrix.go386 }}\" != 'sse2' ]]; then\n            DIR_NAME=\"${DIR_NAME}-${{ matrix.go386 }}\"\n          elif [[ -n \"${{ matrix.gomips }}\" && \"${{ matrix.gomips }}\" != 'hardfloat' ]]; then\n            DIR_NAME=\"${DIR_NAME}-${{ matrix.gomips }}\"\n          elif [[ -n \"${{ matrix.legacy_name }}\" ]]; then\n            DIR_NAME=\"${DIR_NAME}-legacy-${{ matrix.legacy_name }}\"\n          fi\n          if [[ \"${{ matrix.variant }}\" == \"glibc\" ]]; then\n            DIR_NAME=\"${DIR_NAME}-glibc\"\n          elif [[ \"${{ matrix.variant }}\" == \"musl\" ]]; then\n            DIR_NAME=\"${DIR_NAME}-musl\"\n          fi\n          echo \"DIR_NAME=${DIR_NAME}\" >> \"${GITHUB_ENV}\"\n          PKG_VERSION=\"${{ needs.calculate_version.outputs.version }}\"\n          PKG_VERSION=\"${PKG_VERSION//-/\\~}\"\n          echo \"PKG_VERSION=${PKG_VERSION}\" >> \"${GITHUB_ENV}\"\n      - name: Package DEB\n        if: matrix.debian != ''\n        run: |\n          set -xeuo pipefail\n          sudo gem install fpm\n          sudo apt-get update\n          sudo apt-get install -y debsigs\n          cp .fpm_systemd .fpm\n          fpm -t deb \\\n            -v \"$PKG_VERSION\" \\\n            -p \"dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb\" \\\n            --architecture ${{ matrix.debian }} \\\n            dist/sing-box=/usr/bin/sing-box\n          curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'\n          sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'\n          rm -rf $HOME/.gnupg\n          gpg --pinentry-mode loopback --passphrase \"${{ secrets.GPG_PASSPHRASE }}\" --import <<EOF\n          ${{ secrets.GPG_KEY }}\n          EOF\n          debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase \"${{ secrets.GPG_PASSPHRASE }}\"' dist/*.deb\n      - name: Package RPM\n        if: matrix.rpm != ''\n        run: |-\n          set -xeuo pipefail\n          sudo gem install fpm\n          cp .fpm_systemd .fpm\n          fpm -t rpm \\\n            -v \"$PKG_VERSION\" \\\n            -p \"dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm\" \\\n            --architecture ${{ matrix.rpm }} \\\n            dist/sing-box=/usr/bin/sing-box\n          cat > $HOME/.rpmmacros <<EOF\n          %_gpg_name ${{ secrets.GPG_KEY_ID }}\n          %_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}\n          EOF\n          gpg --pinentry-mode loopback --passphrase \"${{ secrets.GPG_PASSPHRASE }}\" --import <<EOF\n          ${{ secrets.GPG_KEY }}\n          EOF\n          rpmsign --addsign dist/*.rpm\n      - name: Package Pacman\n        if: matrix.pacman != ''\n        run: |-\n          set -xeuo pipefail\n          sudo gem install fpm\n          sudo apt-get update\n          sudo apt-get install -y libarchive-tools\n          cp .fpm_pacman .fpm\n          fpm -t pacman \\\n            -v \"$PKG_VERSION\" \\\n            -p \"dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst\" \\\n            --architecture ${{ matrix.pacman }} \\\n            dist/sing-box=/usr/bin/sing-box\n      - name: Package OpenWrt\n        if: matrix.openwrt != ''\n        run: |-\n          set -xeuo pipefail\n          sudo gem install fpm\n          cp .fpm_openwrt .fpm\n          fpm -t deb \\\n            -v \"$PKG_VERSION\" \\\n            -p \"dist/openwrt.deb\" \\\n            --architecture all \\\n            dist/sing-box=/usr/bin/sing-box\n          for architecture in ${{ matrix.openwrt }}; do\n            .github/deb2ipk.sh \"$architecture\" \"dist/openwrt.deb\" \"dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk\"\n          done\n          rm \"dist/openwrt.deb\"\n      - name: Install apk-tools\n        if: matrix.openwrt != '' || matrix.alpine != ''\n        run: |-\n          docker run --rm -v /usr/local/bin:/mnt alpine:edge sh -c \"apk add --no-cache apk-tools-static && cp /sbin/apk.static /mnt/apk && chmod +x /mnt/apk\"\n      - name: Package OpenWrt APK\n        if: matrix.openwrt != ''\n        run: |-\n          set -xeuo pipefail\n          for architecture in ${{ matrix.openwrt }}; do\n            .github/build_openwrt_apk.sh \\\n              \"$architecture\" \\\n              \"${{ needs.calculate_version.outputs.version }}\" \\\n              \"dist/sing-box\" \\\n              \"dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.apk\"\n          done\n      - name: Package Alpine APK\n        if: matrix.alpine != ''\n        run: |-\n          set -xeuo pipefail\n          .github/build_alpine_apk.sh \\\n            \"${{ matrix.alpine }}\" \\\n            \"${{ needs.calculate_version.outputs.version }}\" \\\n            \"dist/sing-box\" \\\n            \"dist/sing-box_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.alpine }}.apk\"\n      - name: Archive\n        run: |\n          set -xeuo pipefail\n          cd dist\n          mkdir -p \"${DIR_NAME}\"\n          cp ../LICENSE \"${DIR_NAME}\"\n          if [ '${{ matrix.os }}' = 'windows' ]; then\n            cp sing-box \"${DIR_NAME}/sing-box.exe\"\n            zip -r \"${DIR_NAME}.zip\" \"${DIR_NAME}\"\n          else\n            cp sing-box \"${DIR_NAME}\"\n            if [ -f libcronet.so ]; then\n              cp libcronet.so \"${DIR_NAME}\"\n            fi\n            tar -czvf \"${DIR_NAME}.tar.gz\" \"${DIR_NAME}\"\n          fi\n          rm -r \"${DIR_NAME}\"\n      - name: Cleanup\n        run: rm -f dist/sing-box dist/libcronet.so\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}${{ matrix.variant && format('-{0}', matrix.variant) }}\n          path: \"dist\"\n  build_darwin:\n    name: Build Darwin binaries\n    if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'\n    runs-on: macos-latest\n    needs:\n      - calculate_version\n    strategy:\n      matrix:\n        include:\n          - { arch: amd64 }\n          - { arch: arm64 }\n          - { arch: amd64, legacy_osx: true, legacy_name: \"macos-10.13\" }\n    steps:\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n      - name: Setup Go\n        if: ${{ ! matrix.legacy_osx }}\n        uses: actions/setup-go@v5\n        with:\n          go-version: ^1.25.3\n      - name: Cache Go for macOS 10.13\n        if: matrix.legacy_osx\n        id: cache-go-for-macos1013\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/go/go_osx\n          key: go_osx_1258\n      - name: Setup Go for macOS 10.13\n        if: matrix.legacy_osx && steps.cache-go-for-macos1013.outputs.cache-hit != 'true'\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n        run: |-\n          .github/setup_go_for_macos1013.sh\n      - name: Setup Go for macOS 10.13\n        if: matrix.legacy_osx\n        run: |-\n          echo \"PATH=$HOME/go/go_osx/bin:$PATH\" >> $GITHUB_ENV\n          echo \"GOROOT=$HOME/go/go_osx\" >> $GITHUB_ENV\n      - name: Set tag\n        run: |-\n          git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo \"PUBLISHED=false\" >> \"$GITHUB_ENV\"\n          git tag v${{ needs.calculate_version.outputs.version }} -f\n      - name: Set build tags\n        run: |\n          set -xeuo pipefail\n          if [[ \"${{ matrix.legacy_osx }}\" != \"true\" ]]; then\n            TAGS=$(cat release/DEFAULT_BUILD_TAGS)\n          else\n            TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)\n          fi\n          echo \"BUILD_TAGS=${TAGS}\" >> \"${GITHUB_ENV}\"\n      - name: Set shared ldflags\n        run: |\n          echo \"LDFLAGS_SHARED=$(cat release/LDFLAGS)\" >> \"${GITHUB_ENV}\"\n      - name: Build\n        run: |\n          set -xeuo pipefail\n          mkdir -p dist\n          go build -v -trimpath -o dist/sing-box -tags \"${BUILD_TAGS}\" \\\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=\" \\\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"1\"\n          GOOS: darwin\n          GOARCH: ${{ matrix.arch }}\n          MACOSX_DEPLOYMENT_TARGET: ${{ matrix.legacy_osx && '10.13' || '' }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Set name\n        run: |-\n          DIR_NAME=\"sing-box-${{ needs.calculate_version.outputs.version }}-darwin-${{ matrix.arch }}\"\n          if [[ -n \"${{ matrix.legacy_name }}\" ]]; then\n            DIR_NAME=\"${DIR_NAME}-legacy-${{ matrix.legacy_name }}\"\n          fi\n          echo \"DIR_NAME=${DIR_NAME}\" >> \"${GITHUB_ENV}\"\n      - name: Archive\n        run: |\n          set -xeuo pipefail\n          cd dist\n          mkdir -p \"${DIR_NAME}\"\n          cp ../LICENSE \"${DIR_NAME}\"\n          cp sing-box \"${DIR_NAME}\"\n          tar -czvf \"${DIR_NAME}.tar.gz\" \"${DIR_NAME}\"\n          rm -r \"${DIR_NAME}\"\n      - name: Cleanup\n        run: rm dist/sing-box\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: binary-darwin_${{ matrix.arch }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}\n          path: \"dist\"\n  build_windows:\n    name: Build Windows binaries\n    if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'\n    runs-on: windows-latest\n    needs:\n      - calculate_version\n    strategy:\n      matrix:\n        include:\n          - { arch: amd64, naive: true }\n          - { arch: \"386\" }\n          - { arch: arm64, naive: true }\n    steps:\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ^1.25.4\n      - name: Set tag\n        run: |-\n          git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo \"PUBLISHED=false\" >> \"$env:GITHUB_ENV\"\n          git tag v${{ needs.calculate_version.outputs.version }} -f\n      - name: Build\n        if: matrix.naive\n        run: |\n          $TAGS = Get-Content release/DEFAULT_BUILD_TAGS_WINDOWS\n          $LDFLAGS_SHARED = Get-Content release/LDFLAGS\n          mkdir -p dist\n          go build -v -trimpath -o dist/sing-box.exe -tags \"$TAGS\" `\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=\" `\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"0\"\n          GOOS: windows\n          GOARCH: ${{ matrix.arch }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Build\n        if: ${{ !matrix.naive }}\n        run: |\n          $TAGS = Get-Content release/DEFAULT_BUILD_TAGS_OTHERS\n          $LDFLAGS_SHARED = Get-Content release/LDFLAGS\n          mkdir -p dist\n          go build -v -trimpath -o dist/sing-box.exe -tags \"$TAGS\" `\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=\" `\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"0\"\n          GOOS: windows\n          GOARCH: ${{ matrix.arch }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Extract libcronet.dll\n        if: matrix.naive\n        run: |\n          $CRONET_GO_VERSION = Get-Content .github/CRONET_GO_VERSION\n          $env:CGO_ENABLED = \"0\"\n          go run -v \"github.com/sagernet/cronet-go/cmd/build-naive@$CRONET_GO_VERSION\" extract-lib --target windows/${{ matrix.arch }} -o dist\n      - name: Archive\n        if: matrix.naive\n        run: |\n          $DIR_NAME = \"sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}\"\n          mkdir \"dist/$DIR_NAME\"\n          Copy-Item LICENSE \"dist/$DIR_NAME\"\n          Copy-Item \"dist/sing-box.exe\" \"dist/$DIR_NAME\"\n          Copy-Item \"dist/libcronet.dll\" \"dist/$DIR_NAME\"\n          Compress-Archive -Path \"dist/$DIR_NAME\" -DestinationPath \"dist/$DIR_NAME.zip\"\n          Remove-Item -Recurse \"dist/$DIR_NAME\"\n      - name: Archive\n        if: ${{ !matrix.naive }}\n        run: |\n          $DIR_NAME = \"sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}\"\n          mkdir \"dist/$DIR_NAME\"\n          Copy-Item LICENSE \"dist/$DIR_NAME\"\n          Copy-Item \"dist/sing-box.exe\" \"dist/$DIR_NAME\"\n          Compress-Archive -Path \"dist/$DIR_NAME\" -DestinationPath \"dist/$DIR_NAME.zip\"\n          Remove-Item -Recurse \"dist/$DIR_NAME\"\n      - name: Cleanup\n        if: matrix.naive\n        run: Remove-Item dist/sing-box.exe, dist/libcronet.dll\n      - name: Cleanup\n        if: ${{ !matrix.naive }}\n        run: Remove-Item dist/sing-box.exe\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: binary-windows_${{ matrix.arch }}\n          path: \"dist\"\n  build_android:\n    name: Build Android\n    if: (github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android') && github.ref != 'refs/heads/oldstable'\n    runs-on: ubuntu-latest\n    needs:\n      - calculate_version\n    steps:\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n          submodules: 'recursive'\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ~1.25.8\n      - name: Setup Android NDK\n        id: setup-ndk\n        uses: nttld/setup-ndk@v1\n        with:\n          ndk-version: r28\n      - name: Setup OpenJDK\n        run: |-\n          sudo apt update && sudo apt install -y openjdk-17-jdk-headless\n          /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version\n      - name: Set tag\n        run: |-\n          git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo \"PUBLISHED=false\" >> \"$GITHUB_ENV\"\n          git tag v${{ needs.calculate_version.outputs.version }} -f\n      - name: Build library\n        run: |-\n          make lib_install\n          export PATH=\"$PATH:$(go env GOPATH)/bin\"\n          make lib_android\n        env:\n          JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64\n          ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}\n      - name: Checkout main branch\n        if: github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch'\n        run: |-\n          cd clients/android\n          git checkout main\n      - name: Checkout dev branch\n        if: github.ref == 'refs/heads/testing'\n        run: |-\n          cd clients/android\n          git checkout dev\n      - name: Gradle cache\n        uses: actions/cache@v4\n        with:\n          path: ~/.gradle\n          key: gradle-${{ hashFiles('**/*.gradle') }}\n      - name: Update version\n        if: github.event_name == 'workflow_dispatch'\n        run: |-\n          go run -v ./cmd/internal/update_android_version --ci\n      - name: Update nightly version\n        if: github.event_name != 'workflow_dispatch'\n        run: |-\n          go run -v ./cmd/internal/update_android_version --ci --nightly\n      - name: Build\n        run: |-\n          mkdir clients/android/app/libs\n          cp *.aar clients/android/app/libs\n          cd clients/android\n          ./gradlew :app:assembleOtherRelease :app:assembleOtherLegacyRelease\n        env:\n          JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64\n          ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}\n          LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}\n      - name: Prepare upload\n        run: |-\n          mkdir -p dist\n          #cp clients/android/app/build/outputs/apk/play/release/*.apk dist\n          cp clients/android/app/build/outputs/apk/other/release/*.apk dist\n          cp clients/android/app/build/outputs/apk/otherLegacy/release/*.apk dist\n          VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2)\n          VERSION_NAME=$(grep VERSION_NAME clients/android/version.properties | cut -d= -f2)\n          cat > dist/SFA-version-metadata.json << EOF\n          {\n            \"version_code\": ${VERSION_CODE},\n            \"version_name\": \"${VERSION_NAME}\"\n          }\n          EOF\n          cat dist/SFA-version-metadata.json\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: binary-android-apks\n          path: 'dist'\n  publish_android:\n    name: Publish Android\n    if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android' && github.ref != 'refs/heads/oldstable'\n    runs-on: ubuntu-latest\n    needs:\n      - calculate_version\n    steps:\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n          submodules: 'recursive'\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ~1.25.8\n      - name: Setup Android NDK\n        id: setup-ndk\n        uses: nttld/setup-ndk@v1\n        with:\n          ndk-version: r28\n      - name: Setup OpenJDK\n        run: |-\n          sudo apt update && sudo apt install -y openjdk-17-jdk-headless\n          /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version\n      - name: Set tag\n        run: |-\n          git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo \"PUBLISHED=false\" >> \"$GITHUB_ENV\"\n          git tag v${{ needs.calculate_version.outputs.version }} -f\n      - name: Build library\n        run: |-\n          make lib_install\n          export PATH=\"$PATH:$(go env GOPATH)/bin\"\n          make lib_android\n        env:\n          JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64\n          ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}\n      - name: Checkout main branch\n        if: github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch'\n        run: |-\n          cd clients/android\n          git checkout main\n      - name: Checkout dev branch\n        if: github.ref == 'refs/heads/testing'\n        run: |-\n          cd clients/android\n          git checkout dev\n      - name: Gradle cache\n        uses: actions/cache@v4\n        with:\n          path: ~/.gradle\n          key: gradle-${{ hashFiles('**/*.gradle') }}\n      - name: Build\n        run: |-\n          go run -v ./cmd/internal/update_android_version --ci\n          mkdir clients/android/app/libs\n          cp *.aar clients/android/app/libs\n          cd clients/android\n          echo -n \"$SERVICE_ACCOUNT_CREDENTIALS\" | base64 --decode > service-account-credentials.json\n          ./gradlew :app:publishPlayReleaseBundle\n        env:\n          JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64\n          ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}\n          LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}\n          SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}\n  build_apple:\n    name: Build Apple clients\n    runs-on: macos-26\n    if: false # github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone'\n    needs:\n      - calculate_version\n    strategy:\n      matrix:\n        include:\n          - name: iOS\n            if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}\n            platform: ios\n            scheme: SFI\n            destination: 'generic/platform=iOS'\n            archive: build/SFI.xcarchive\n            upload: SFI/Upload.plist\n          - name: macOS\n            if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}\n            platform: macos\n            scheme: SFM\n            destination: 'generic/platform=macOS'\n            archive: build/SFM.xcarchive\n            upload: SFI/Upload.plist\n          - name: tvOS\n            if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}\n            platform: tvos\n            scheme: SFT\n            destination: 'generic/platform=tvOS'\n            archive: build/SFT.xcarchive\n            upload: SFI/Upload.plist\n          - name: macOS-standalone\n            if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}\n            platform: macos\n            scheme: SFM.System\n            destination: 'generic/platform=macOS'\n            archive: build/SFM.System.xcarchive\n            export: SFM.System/Export.plist\n            export_path: build/SFM.System\n    steps:\n      - name: Checkout\n        if: matrix.if\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n          submodules: 'recursive'\n      - name: Setup Go\n        if: matrix.if\n        uses: actions/setup-go@v5\n        with:\n          go-version: ~1.25.8\n      - name: Set tag\n        if: matrix.if\n        run: |-\n          git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo \"PUBLISHED=false\" >> \"$GITHUB_ENV\"\n          git tag v${{ needs.calculate_version.outputs.version }} -f\n          echo \"VERSION=${{ needs.calculate_version.outputs.version }}\" >> \"$GITHUB_ENV\"\n      - name: Checkout main branch\n        if: matrix.if && github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch'\n        run: |-\n          cd clients/apple\n          git checkout main\n      - name: Checkout dev branch\n        if: matrix.if && github.ref == 'refs/heads/testing'\n        run: |-\n          cd clients/apple\n          git checkout dev\n      - name: Setup certificates\n        if: matrix.if\n        run: |-\n          CERTIFICATE_PATH=$RUNNER_TEMP/Certificates.p12\n          KEYCHAIN_PATH=$RUNNER_TEMP/certificates.keychain-db\n          echo -n \"$CERTIFICATES_P12\" | base64 --decode -o $CERTIFICATE_PATH\n          security create-keychain -p \"$KEYCHAIN_PASSWORD\" $KEYCHAIN_PATH\n          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH\n          security unlock-keychain -p \"$KEYCHAIN_PASSWORD\" $KEYCHAIN_PATH\n          security import $CERTIFICATE_PATH -P \"$P12_PASSWORD\" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH\n          security set-key-partition-list -S apple-tool:,apple: -k \"$KEYCHAIN_PASSWORD\" $KEYCHAIN_PATH\n          security list-keychain -d user -s $KEYCHAIN_PATH\n\n          PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip\n          echo -n \"$PROVISIONING_PROFILES\" | base64 --decode -o $PROFILES_ZIP_PATH\n\n          PROFILES_PATH=\"$HOME/Library/MobileDevice/Provisioning Profiles\"\n          mkdir -p \"$PROFILES_PATH\"\n          unzip $PROFILES_ZIP_PATH -d \"$PROFILES_PATH\"\n\n          ASC_KEY_PATH=$RUNNER_TEMP/Key.p12\n          echo -n \"$ASC_KEY\" | base64 --decode -o $ASC_KEY_PATH\n\n          xcrun notarytool store-credentials \"notarytool-password\" \\\n            --key $ASC_KEY_PATH \\\n            --key-id $ASC_KEY_ID \\\n            --issuer $ASC_KEY_ISSUER_ID\n\n          echo \"ASC_KEY_PATH=$ASC_KEY_PATH\" >> \"$GITHUB_ENV\"\n          echo \"ASC_KEY_ID=$ASC_KEY_ID\" >> \"$GITHUB_ENV\"\n          echo \"ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID\" >> \"$GITHUB_ENV\"\n        env:\n          CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}\n          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}\n          KEYCHAIN_PASSWORD: ${{ secrets.P12_PASSWORD }}\n          PROVISIONING_PROFILES: ${{ secrets.PROVISIONING_PROFILES }}\n          ASC_KEY: ${{ secrets.ASC_KEY }}\n          ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}\n          ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}\n      - name: Build library\n        if: matrix.if\n        run: |-\n          make lib_install\n          export PATH=\"$PATH:$(go env GOPATH)/bin\"\n          go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }}\n          mv Libbox.xcframework clients/apple\n      - name: Update macOS version\n        if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch'\n        run: |-\n          MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)\n          echo \"MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION\"\n          echo \"MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION\" >> \"$GITHUB_ENV\"\n      - name: Update version\n        if: matrix.if && matrix.name != 'iOS'\n        run: |-\n          go run -v ./cmd/internal/update_apple_version --ci\n      - name: Build\n        if: matrix.if\n        run: |-\n          cd clients/apple\n          xcodebuild archive \\\n            -scheme \"${{ matrix.scheme }}\" \\\n            -configuration Release \\\n            -destination \"${{ matrix.destination }}\" \\\n            -archivePath \"${{ matrix.archive }}\" \\\n            -allowProvisioningUpdates \\\n            -authenticationKeyPath $ASC_KEY_PATH \\\n            -authenticationKeyID $ASC_KEY_ID \\\n            -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID\n      - name: Upload to App Store Connect\n        if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'\n        run: |-\n          go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }}\n          cd clients/apple\n          xcodebuild -exportArchive \\\n            -archivePath \"${{ matrix.archive }}\" \\\n            -exportOptionsPlist ${{ matrix.upload }} \\\n            -allowProvisioningUpdates \\\n            -authenticationKeyPath $ASC_KEY_PATH \\\n            -authenticationKeyID $ASC_KEY_ID \\\n            -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID\n      - name: Publish to TestFlight\n        if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/testing'\n        run: |-\n          go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}\n      - name: Build image\n        if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'\n        run: |-\n          pushd clients/apple\n          xcodebuild -exportArchive \\\n          \t-archivePath \"${{ matrix.archive }}\" \\\n          \t-exportOptionsPlist ${{ matrix.export }} \\\n          \t-exportPath \"${{ matrix.export_path }}\"\n          brew install create-dmg\n          create-dmg \\\n          \t--volname \"sing-box\" \\\n          \t--volicon \"${{ matrix.export_path }}/SFM.app/Contents/Resources/AppIcon.icns\" \\\n          \t--icon \"SFM.app\" 0 0 \\\n          \t\t--hide-extension \"SFM.app\" \\\n          \t\t--app-drop-link 0 0 \\\n          \t\t--skip-jenkins \\\n          \tSFM.dmg \"${{ matrix.export_path }}/SFM.app\"\n          xcrun notarytool submit \"SFM.dmg\" --wait --keychain-profile \"notarytool-password\"\n          cd \"${{ matrix.archive }}\"\n          zip -r SFM.dSYMs.zip dSYMs\n          popd\n\n          mkdir -p dist\n          cp clients/apple/SFM.dmg \"dist/SFM-${VERSION}-universal.dmg\"\n          cp \"clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip\" \"dist/SFM-${VERSION}-universal.dSYMs.zip\"\n      - name: Upload image\n        if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'\n        uses: actions/upload-artifact@v4\n        with:\n          name: binary-macos-dmg\n          path: 'dist'\n  upload:\n    name: Upload builds\n    if: \"!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')\"\n    runs-on: ubuntu-latest\n    needs:\n      - calculate_version\n      - build\n      - build_darwin\n      - build_windows\n      - build_android\n      - build_apple\n    steps:\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n      - name: Cache ghr\n        uses: actions/cache@v4\n        id: cache-ghr\n        with:\n          path: |\n            ~/go/bin/ghr\n          key: ghr\n      - name: Setup ghr\n        if: steps.cache-ghr.outputs.cache-hit != 'true'\n        run: |-\n          cd $HOME\n          git clone https://github.com/nekohasekai/ghr ghr\n          cd ghr\n          go install -v .\n      - name: Set tag\n        run: |-\n          git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo \"PUBLISHED=false\" >> \"$GITHUB_ENV\"\n          git tag v${{ needs.calculate_version.outputs.version }} -f\n          echo \"VERSION=${{ needs.calculate_version.outputs.version }}\" >> \"$GITHUB_ENV\"\n      - name: Download builds\n        uses: actions/download-artifact@v5\n        with:\n          path: dist\n          merge-multiple: true\n      - name: Upload builds\n        if: ${{ env.PUBLISHED == 'false' }}\n        run: |-\n          export PATH=\"$PATH:$HOME/go/bin\"\n          ghr --replace --draft --prerelease -p 5 \"v${VERSION}\" dist\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Replace builds\n        if: ${{ env.PUBLISHED != 'false' }}\n        run: |-\n          export PATH=\"$PATH:$HOME/go/bin\"\n          ghr --replace -p 5 \"v${VERSION}\" dist\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/docker.yml",
    "content": "name: Publish Docker Images\n\non:\n  #push:\n  #  branches:\n  #    - stable\n  #    - testing\n  release:\n    types:\n      - published\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: \"The tag version you want to build\"\n\nenv:\n  REGISTRY_IMAGE: ghcr.io/sagernet/sing-box\n\njobs:\n  build_binary:\n    name: Build binary\n    if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: true\n      matrix:\n        include:\n          # Naive-enabled builds (musl)\n          - { arch: amd64, naive: true, docker_platform: \"linux/amd64\" }\n          - { arch: arm64, naive: true, docker_platform: \"linux/arm64\" }\n          - { arch: \"386\", naive: true, docker_platform: \"linux/386\" }\n          - { arch: arm, goarm: \"7\", naive: true, docker_platform: \"linux/arm/v7\" }\n          - { arch: mipsle, gomips: softfloat, naive: true, docker_platform: \"linux/mipsle\" }\n          - { arch: riscv64, naive: true, docker_platform: \"linux/riscv64\" }\n          - { arch: loong64, naive: true, docker_platform: \"linux/loong64\" }\n          # Non-naive builds\n          - { arch: arm, goarm: \"6\", docker_platform: \"linux/arm/v6\" }\n          - { arch: ppc64le, docker_platform: \"linux/ppc64le\" }\n          - { arch: s390x, docker_platform: \"linux/s390x\" }\n    steps:\n      - name: Get commit to build\n        id: ref\n        run: |-\n          if [[ -z \"${{ github.event.inputs.tag }}\" ]]; then\n            ref=\"${{ github.ref_name }}\"\n          else\n            ref=\"${{ github.event.inputs.tag }}\"\n          fi\n          echo \"ref=$ref\"\n          echo \"ref=$ref\" >> $GITHUB_OUTPUT\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          ref: ${{ steps.ref.outputs.ref }}\n          fetch-depth: 0\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ~1.25.8\n      - name: Clone cronet-go\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)\n          git init ~/cronet-go\n          git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git\n          git -C ~/cronet-go fetch --depth=1 origin \"$CRONET_GO_VERSION\"\n          git -C ~/cronet-go checkout FETCH_HEAD\n          git -C ~/cronet-go submodule update --init --recursive --depth=1\n      - name: Regenerate Debian keyring\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg\n          cd ~/cronet-go\n          GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh\n      - name: Cache Chromium toolchain\n        if: matrix.naive\n        id: cache-chromium-toolchain\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/cronet-go/naiveproxy/src/third_party/llvm-build/\n            ~/cronet-go/naiveproxy/src/gn/out/\n            ~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/\n            ~/cronet-go/naiveproxy/src/out/sysroot-build/\n          key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}\n      - name: Download Chromium toolchain\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          cd ~/cronet-go\n          go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain\n      - name: Set version\n        run: |\n          set -xeuo pipefail\n          VERSION=$(go run ./cmd/internal/read_tag)\n          echo \"VERSION=${VERSION}\" >> \"${GITHUB_ENV}\"\n      - name: Set Chromium toolchain environment\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          cd ~/cronet-go\n          go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV\n      - name: Set build tags\n        run: |\n          set -xeuo pipefail\n          if [[ \"${{ matrix.naive }}\" == \"true\" ]]; then\n            TAGS=\"$(cat release/DEFAULT_BUILD_TAGS),with_musl\"\n          else\n            TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)\n          fi\n          echo \"BUILD_TAGS=${TAGS}\" >> \"${GITHUB_ENV}\"\n      - name: Set shared ldflags\n        run: |\n          echo \"LDFLAGS_SHARED=$(cat release/LDFLAGS)\" >> \"${GITHUB_ENV}\"\n      - name: Build (naive)\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          go build -v -trimpath -o sing-box -tags \"${BUILD_TAGS}\" \\\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=\" \\\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"1\"\n          GOOS: linux\n          GOARCH: ${{ matrix.arch }}\n          GOARM: ${{ matrix.goarm }}\n          GOMIPS: ${{ matrix.gomips }}\n      - name: Build (non-naive)\n        if: ${{ ! matrix.naive }}\n        run: |\n          set -xeuo pipefail\n          go build -v -trimpath -o sing-box -tags \"${BUILD_TAGS}\" \\\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=\" \\\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"0\"\n          GOOS: linux\n          GOARCH: ${{ matrix.arch }}\n          GOARM: ${{ matrix.goarm }}\n      - name: Prepare artifact\n        run: |\n          platform=${{ matrix.docker_platform }}\n          echo \"PLATFORM_PAIR=${platform//\\//-}\" >> $GITHUB_ENV\n          # Rename binary to include arch info for Dockerfile.binary\n          BINARY_NAME=\"sing-box-${{ matrix.arch }}\"\n          if [[ -n \"${{ matrix.goarm }}\" ]]; then\n            BINARY_NAME=\"${BINARY_NAME}v${{ matrix.goarm }}\"\n          fi\n          mv sing-box \"${BINARY_NAME}\"\n          echo \"BINARY_NAME=${BINARY_NAME}\" >> $GITHUB_ENV\n      - name: Upload binary\n        uses: actions/upload-artifact@v4\n        with:\n          name: binary-${{ env.PLATFORM_PAIR }}\n          path: ${{ env.BINARY_NAME }}\n          if-no-files-found: error\n          retention-days: 1\n  build_docker:\n    name: Build Docker image\n    runs-on: ubuntu-latest\n    needs:\n      - build_binary\n    strategy:\n      fail-fast: true\n      matrix:\n        include:\n          - { platform: \"linux/amd64\" }\n          - { platform: \"linux/arm/v6\" }\n          - { platform: \"linux/arm/v7\" }\n          - { platform: \"linux/arm64\" }\n          - { platform: \"linux/386\" }\n          # mipsle: no base Docker image available for this platform\n          - { platform: \"linux/ppc64le\" }\n          - { platform: \"linux/riscv64\" }\n          - { platform: \"linux/s390x\" }\n          - { platform: \"linux/loong64\", base_image: \"ghcr.io/loong64/alpine:edge\" }\n    steps:\n      - name: Get commit to build\n        id: ref\n        run: |-\n          if [[ -z \"${{ github.event.inputs.tag }}\" ]]; then\n            ref=\"${{ github.ref_name }}\"\n          else\n            ref=\"${{ github.event.inputs.tag }}\"\n          fi\n          echo \"ref=$ref\"\n          echo \"ref=$ref\" >> $GITHUB_OUTPUT\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          ref: ${{ steps.ref.outputs.ref }}\n          fetch-depth: 0\n      - name: Prepare\n        run: |\n          platform=${{ matrix.platform }}\n          echo \"PLATFORM_PAIR=${platform//\\//-}\" >> $GITHUB_ENV\n      - name: Download binary\n        uses: actions/download-artifact@v5\n        with:\n          name: binary-${{ env.PLATFORM_PAIR }}\n          path: .\n      - name: Prepare binary\n        run: |\n          # Find and make the binary executable\n          chmod +x sing-box-*\n          ls -la sing-box-*\n      - name: Setup QEMU\n        uses: docker/setup-qemu-action@v3\n      - name: Setup Docker Buildx\n        uses: docker/setup-buildx-action@v3\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n      - name: Docker meta\n        id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: ${{ env.REGISTRY_IMAGE }}\n      - name: Build and push by digest\n        id: build\n        uses: docker/build-push-action@v6\n        with:\n          platforms: ${{ matrix.platform }}\n          context: .\n          file: Dockerfile.binary\n          build-args: |\n            BASE_IMAGE=${{ matrix.base_image || 'alpine' }}\n          labels: ${{ steps.meta.outputs.labels }}\n          outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true\n      - name: Export digest\n        run: |\n          mkdir -p /tmp/digests\n          digest=\"${{ steps.build.outputs.digest }}\"\n          touch \"/tmp/digests/${digest#sha256:}\"\n      - name: Upload digest\n        uses: actions/upload-artifact@v4\n        with:\n          name: digests-${{ env.PLATFORM_PAIR }}\n          path: /tmp/digests/*\n          if-no-files-found: error\n          retention-days: 1\n  merge:\n    if: github.event_name != 'push'\n    runs-on: ubuntu-latest\n    needs:\n      - build_docker\n    steps:\n      - name: Get commit to build\n        id: ref\n        run: |-\n          if [[ -z \"${{ github.event.inputs.tag }}\" ]]; then\n            ref=\"${{ github.ref_name }}\"\n          else\n            ref=\"${{ github.event.inputs.tag }}\"\n          fi\n          echo \"ref=$ref\"\n          echo \"ref=$ref\" >> $GITHUB_OUTPUT\n          if [[ $ref == *\"-\"* ]]; then\n            latest=latest-beta\n          else\n            latest=latest\n          fi\n          echo \"latest=$latest\"\n          echo \"latest=$latest\" >> $GITHUB_OUTPUT\n      - name: Download digests\n        uses: actions/download-artifact@v5\n        with:\n          path: /tmp/digests\n          pattern: digests-*\n          merge-multiple: true\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n      - name: Create manifest list and push\n        if: github.event_name != 'push'\n        working-directory: /tmp/digests\n        run: |\n          docker buildx imagetools create \\\n            -t \"${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}\" \\\n            -t \"${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}\" \\\n            $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)\n      - name: Inspect image\n        if: github.event_name != 'push'\n        run: |\n          docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}\n          docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n    branches:\n      - oldstable\n      - stable\n      - testing\n      - unstable\n    paths-ignore:\n      - '**.md'\n      - '.github/**'\n      - '!.github/workflows/lint.yml'\n  pull_request:\n    branches:\n      - oldstable\n      - stable\n      - testing\n      - unstable\n\njobs:\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ^1.25\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v8\n        with:\n          version: latest\n          args: --timeout=30m\n          install-mode: binary\n          verify: false\n"
  },
  {
    "path": ".github/workflows/linux.yml",
    "content": "name: Build Linux Packages\n\non:\n  #push:\n  #  branches:\n  #    - stable\n  #    - testing\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"Version name\"\n        required: true\n        type: string\n      forceBeta:\n        description: \"Force beta\"\n        required: false\n        type: boolean\n        default: false\n  release:\n    types:\n      - published\n\njobs:\n  calculate_version:\n    name: Calculate version\n    if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'\n    runs-on: ubuntu-latest\n    outputs:\n      version: ${{ steps.outputs.outputs.version }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ~1.25.8\n      - name: Check input version\n        if: github.event_name == 'workflow_dispatch'\n        run: |-\n          echo \"version=${{ inputs.version }}\"\n          echo \"version=${{ inputs.version }}\" >> \"$GITHUB_ENV\"\n      - name: Calculate version\n        if: github.event_name != 'workflow_dispatch'\n        run: |-\n          go run -v ./cmd/internal/read_tag --ci --nightly\n      - name: Set outputs\n        id: outputs\n        run: |-\n          echo \"version=$version\" >> \"$GITHUB_OUTPUT\"\n  build:\n    name: Build binary\n    runs-on: ubuntu-latest\n    needs:\n      - calculate_version\n    strategy:\n      matrix:\n        include:\n          # Naive-enabled builds (musl)\n          - { os: linux, arch: amd64, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64 }\n          - { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 }\n          - { os: linux, arch: \"386\", naive: true, debian: i386, rpm: i386 }\n          - { os: linux, arch: arm, goarm: \"7\", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl }\n          - { os: linux, arch: mipsle, gomips: softfloat, naive: true, debian: mipsel, rpm: mipsel }\n          - { os: linux, arch: riscv64, naive: true, debian: riscv64, rpm: riscv64 }\n          - { os: linux, arch: loong64, naive: true, debian: loongarch64, rpm: loongarch64 }\n          # Non-naive builds (unsupported architectures)\n          - { os: linux, arch: arm, goarm: \"6\", debian: armel, rpm: armv6hl }\n          - { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }\n          - { os: linux, arch: s390x, debian: s390x, rpm: s390x }\n          - { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }\n    steps:\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ~1.25.8\n      - name: Clone cronet-go\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)\n          git init ~/cronet-go\n          git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git\n          git -C ~/cronet-go fetch --depth=1 origin \"$CRONET_GO_VERSION\"\n          git -C ~/cronet-go checkout FETCH_HEAD\n          git -C ~/cronet-go submodule update --init --recursive --depth=1\n      - name: Regenerate Debian keyring\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg\n          cd ~/cronet-go\n          GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh\n      - name: Cache Chromium toolchain\n        if: matrix.naive\n        id: cache-chromium-toolchain\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/cronet-go/naiveproxy/src/third_party/llvm-build/\n            ~/cronet-go/naiveproxy/src/gn/out/\n            ~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/\n            ~/cronet-go/naiveproxy/src/out/sysroot-build/\n          key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}\n      - name: Download Chromium toolchain\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          cd ~/cronet-go\n          go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain\n      - name: Set Chromium toolchain environment\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          cd ~/cronet-go\n          go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV\n      - name: Set tag\n        run: |-\n          git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo \"PUBLISHED=false\" >> \"$GITHUB_ENV\"\n          git tag v${{ needs.calculate_version.outputs.version }} -f\n      - name: Set build tags\n        run: |\n          set -xeuo pipefail\n          if [[ \"${{ matrix.naive }}\" == \"true\" ]]; then\n            TAGS=\"$(cat release/DEFAULT_BUILD_TAGS),with_musl\"\n          else\n            TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)\n          fi\n          echo \"BUILD_TAGS=${TAGS}\" >> \"${GITHUB_ENV}\"\n      - name: Set shared ldflags\n        run: |\n          echo \"LDFLAGS_SHARED=$(cat release/LDFLAGS)\" >> \"${GITHUB_ENV}\"\n      - name: Build (naive)\n        if: matrix.naive\n        run: |\n          set -xeuo pipefail\n          mkdir -p dist\n          go build -v -trimpath -o dist/sing-box -tags \"${BUILD_TAGS}\" \\\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=\" \\\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"1\"\n          GOOS: linux\n          GOARCH: ${{ matrix.arch }}\n          GOARM: ${{ matrix.goarm }}\n          GOMIPS: ${{ matrix.gomips }}\n          GOMIPS64: ${{ matrix.gomips }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Build (non-naive)\n        if: ${{ ! matrix.naive }}\n        run: |\n          set -xeuo pipefail\n          mkdir -p dist\n          go build -v -trimpath -o dist/sing-box -tags \"${BUILD_TAGS}\" \\\n          -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=\" \\\n          ./cmd/sing-box\n        env:\n          CGO_ENABLED: \"0\"\n          GOOS: ${{ matrix.os }}\n          GOARCH: ${{ matrix.arch }}\n          GOARM: ${{ matrix.goarm }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Set mtime\n        run: |-\n          TZ=UTC touch -t '197001010000' dist/sing-box\n      - name: Set name\n        if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta\n        run: |-\n          echo \"NAME=sing-box\" >> \"$GITHUB_ENV\"\n      - name: Set beta name\n        if: contains(needs.calculate_version.outputs.version, '-') || inputs.forceBeta\n        run: |-\n          echo \"NAME=sing-box-beta\" >> \"$GITHUB_ENV\"\n      - name: Set version\n        run: |-\n          PKG_VERSION=\"${{ needs.calculate_version.outputs.version }}\"\n          PKG_VERSION=\"${PKG_VERSION//-/\\~}\"\n          echo \"PKG_VERSION=${PKG_VERSION}\" >> \"${GITHUB_ENV}\"\n      - name: Package DEB\n        if: matrix.debian != ''\n        run: |\n          set -xeuo pipefail\n          sudo gem install fpm\n          sudo apt-get install -y debsigs\n          cp .fpm_systemd .fpm\n          fpm -t deb \\\n            --name \"${NAME}\" \\\n            -v \"$PKG_VERSION\" \\\n            -p \"dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb\" \\\n            --architecture ${{ matrix.debian }} \\\n            dist/sing-box=/usr/bin/sing-box\n          curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'\n          sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'\n          rm -rf $HOME/.gnupg\n          gpg --pinentry-mode loopback --passphrase \"${{ secrets.GPG_PASSPHRASE }}\" --import <<EOF\n          ${{ secrets.GPG_KEY }}\n          EOF\n          debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase \"${{ secrets.GPG_PASSPHRASE }}\"' dist/*.deb\n      - name: Package RPM\n        if: matrix.rpm != ''\n        run: |-\n          set -xeuo pipefail\n          sudo gem install fpm\n          cp .fpm_systemd .fpm\n          fpm -t rpm \\\n            --name \"${NAME}\" \\\n            -v \"$PKG_VERSION\" \\\n            -p \"dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm\" \\\n            --architecture ${{ matrix.rpm }} \\\n            dist/sing-box=/usr/bin/sing-box\n          cat > $HOME/.rpmmacros <<EOF\n          %_gpg_name ${{ secrets.GPG_KEY_ID }}\n          %_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}\n          EOF\n          gpg --pinentry-mode loopback --passphrase \"${{ secrets.GPG_PASSPHRASE }}\" --import <<EOF\n          ${{ secrets.GPG_KEY }}\n          EOF\n          rpmsign --addsign dist/*.rpm\n      - name: Cleanup\n        run: rm dist/sing-box\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}\n          path: \"dist\"\n  upload:\n    name: Upload builds\n    runs-on: ubuntu-latest\n    needs:\n      - calculate_version\n      - build\n    steps:\n      - name: Checkout\n        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          fetch-depth: 0\n      - name: Set tag\n        run: |-\n          git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo \"PUBLISHED=false\" >> \"$GITHUB_ENV\"\n          git tag v${{ needs.calculate_version.outputs.version }} -f\n          echo \"VERSION=${{ needs.calculate_version.outputs.version }}\" >> \"$GITHUB_ENV\"\n      - name: Download builds\n        uses: actions/download-artifact@v5\n        with:\n          path: dist\n          merge-multiple: true\n      - name: Publish packages\n        if: github.event_name != 'push'\n        run: |-\n          ls dist | xargs -I {} curl -F \"package=@dist/{}\" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Mark stale issues and pull requests\n\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v9\n        with:\n          stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'\n          days-before-stale: 60\n          days-before-close: 5\n          exempt-issue-labels: 'bug,enhancement'\n"
  },
  {
    "path": ".gitignore",
    "content": "/.idea/\n/vendor/\n/*.json\n/*.srs\n/*.db\n/site/\n/bin/\n/dist/\n/sing-box\n/sing-box.exe\n/build/\n/*.jar\n/*.aar\n/*.xcframework/\n/experimental/libbox/*.aar\n/experimental/libbox/*.xcframework/\n/experimental/libbox/*.nupkg\n.DS_Store\n/config.d/\n/venv/\nCLAUDE.md\nAGENTS.md\n/.claude/\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"clients/apple\"]\n\tpath = clients/apple\n\turl = https://github.com/SagerNet/sing-box-for-apple.git\n[submodule \"clients/android\"]\n\tpath = clients/android\n\turl = https://github.com/SagerNet/sing-box-for-android.git\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nrun:\n  go: \"1.25\"\n  build-tags:\n    - with_gvisor\n    - with_quic\n    - with_dhcp\n    - with_wireguard\n    - with_utls\n    - with_acme\n    - with_clash_api\n    - with_tailscale\n    - with_ccm\n    - with_ocm\n    - badlinkname\n    - tfogo_checklinkname0\nlinters:\n  default: none\n  enable:\n    - govet\n    - ineffassign\n    - paralleltest\n    - staticcheck\n  settings:\n    staticcheck:\n      checks:\n        - all\n        - -S1000\n        - -S1008\n        - -S1017\n        - -ST1003\n        - -QF1001\n        - -QF1003\n        - -QF1008\n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - common-false-positives\n      - legacy\n      - std-error-handling\n    paths:\n      - transport/simple-obfs\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gci\n    - gofumpt\n  settings:\n    gci:\n      sections:\n        - standard\n        - prefix(github.com/sagernet/)\n        - default\n      custom-order: true\n  exclusions:\n    generated: lax\n    paths:\n      - transport/simple-obfs\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder\nLABEL maintainer=\"nekohasekai <contact-git@sekai.icu>\"\nCOPY . /go/src/github.com/sagernet/sing-box\nWORKDIR /go/src/github.com/sagernet/sing-box\nARG TARGETOS TARGETARCH\nARG GOPROXY=\"\"\nENV GOPROXY ${GOPROXY}\nENV CGO_ENABLED=0\nENV GOOS=$TARGETOS\nENV GOARCH=$TARGETARCH\nRUN set -ex \\\n    && apk add git build-base \\\n    && export COMMIT=$(git rev-parse --short HEAD) \\\n    && export VERSION=$(go run ./cmd/internal/read_tag) \\\n    && export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \\\n    && export LDFLAGS_SHARED=$(cat release/LDFLAGS) \\\n    && go build -v -trimpath -tags \"$TAGS\" \\\n        -o /go/bin/sing-box \\\n        -ldflags \"-X \\\"github.com/sagernet/sing-box/constant.Version=$VERSION\\\" $LDFLAGS_SHARED -s -w -buildid=\" \\\n        ./cmd/sing-box\nFROM --platform=$TARGETPLATFORM alpine AS dist\nLABEL maintainer=\"nekohasekai <contact-git@sekai.icu>\"\nRUN set -ex \\\n    && apk add --no-cache --upgrade bash tzdata ca-certificates nftables\nCOPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box\nENTRYPOINT [\"sing-box\"]\n"
  },
  {
    "path": "Dockerfile.binary",
    "content": "ARG BASE_IMAGE=alpine\nFROM ${BASE_IMAGE}\nARG TARGETARCH\nARG TARGETVARIANT\nLABEL maintainer=\"nekohasekai <contact-git@sekai.icu>\"\nRUN set -ex \\\n    && if command -v apk > /dev/null; then \\\n        apk add --no-cache --upgrade bash tzdata ca-certificates nftables; \\\n    else \\\n        apt-get update && apt-get install -y --no-install-recommends bash tzdata ca-certificates nftables \\\n        && rm -rf /var/lib/apt/lists/*; \\\n    fi\nCOPY sing-box-${TARGETARCH}${TARGETVARIANT} /usr/local/bin/sing-box\nENTRYPOINT [\"sing-box\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (C) 2022 by nekohasekai <contact-sagernet@sekai.icu>\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n\nIn addition, no derivative work may use the name or imply association\nwith this application without prior consent.\n"
  },
  {
    "path": "Makefile",
    "content": "NAME = sing-box\nCOMMIT = $(shell git rev-parse --short HEAD)\nTAGS ?= $(shell cat release/DEFAULT_BUILD_TAGS_OTHERS)\n\nGOHOSTOS = $(shell go env GOHOSTOS)\nGOHOSTARCH = $(shell go env GOHOSTARCH)\nVERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)\n\nLDFLAGS_SHARED = $(shell cat release/LDFLAGS)\nPARAMS = -v -trimpath -ldflags \"-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' $(LDFLAGS_SHARED) -s -w -buildid=\"\nMAIN_PARAMS = $(PARAMS) -tags \"$(TAGS)\"\nMAIN = ./cmd/sing-box\nPREFIX ?= $(shell go env GOPATH)\nSING_FFI ?= sing-ffi\nLIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json\n\n.PHONY: test release docs build\n\nbuild:\n\texport GOTOOLCHAIN=local && \\\n\tgo build $(MAIN_PARAMS) $(MAIN)\n\nrace:\n\texport GOTOOLCHAIN=local && \\\n\tgo build -race $(MAIN_PARAMS) $(MAIN)\n\nci_build:\n\texport GOTOOLCHAIN=local && \\\n\tgo build $(PARAMS) $(MAIN) && \\\n\tgo build $(MAIN_PARAMS) $(MAIN)\n\ngenerate_completions:\n\tgo run -v --tags \"$(TAGS),generate,generate_completions\" $(MAIN)\n\ninstall:\n\tgo build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)\n\nfmt:\n\t@gofumpt -l -w .\n\t@gofmt -s -w .\n\t@gci write --custom-order -s standard -s \"prefix(github.com/sagernet/)\" -s \"default\" .\n\nfmt_docs:\n\tgo run ./cmd/internal/format_docs\n\nfmt_install:\n\tgo install -v mvdan.cc/gofumpt@latest\n\tgo install -v github.com/daixiang0/gci@latest\n\nlint:\n\tGOOS=linux golangci-lint run ./...\n\tGOOS=android golangci-lint run ./...\n\tGOOS=windows golangci-lint run ./...\n\tGOOS=darwin golangci-lint run ./...\n\tGOOS=freebsd golangci-lint run ./...\n\nlint_install:\n\tgo install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest\n\nproto:\n\t@go run ./cmd/internal/protogen\n\t@gofumpt -l -w .\n\t@gofumpt -l -w .\n\nproto_install:\n\tgo install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest\n\tgo install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest\n\nupdate_certificates:\n\tgo run ./cmd/internal/update_certificates\n\nrelease:\n\tgo run ./cmd/internal/build goreleaser release --clean --skip publish\n\tmkdir dist/release\n\tmv dist/*.tar.gz \\\n\t\tdist/*.zip \\\n\t\tdist/*.deb \\\n\t\tdist/*.rpm \\\n\t\tdist/*_amd64.pkg.tar.zst \\\n\t\tdist/*_arm64.pkg.tar.zst \\\n\t\tdist/release\n\tghr --replace --draft --prerelease -p 5 \"v${VERSION}\" dist/release\n\trm -r dist/release\n\nrelease_repo:\n\tgo run ./cmd/internal/build goreleaser release -f .goreleaser.fury.yaml --clean\n\nrelease_install:\n\tgo install -v github.com/tcnksm/ghr@latest\n\nupdate_android_version:\n\tgo run ./cmd/internal/update_android_version\n\nbuild_android:\n\tcd ../sing-box-for-android && ./gradlew :app:clean :app:assembleOtherRelease :app:assembleOtherLegacyRelease && ./gradlew --stop\n\nupload_android:\n\tmkdir -p dist/release_android\n\tcp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android\n\tcp ../sing-box-for-android/app/build/outputs/apk/otherLegacy/release/*.apk dist/release_android\n\tghr --replace --draft --prerelease -p 5 \"v${VERSION}\" dist/release_android\n\trm -rf dist/release_android\n\nrelease_android: lib_android update_android_version build_android upload_android\n\npublish_android:\n\tcd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop\n\n# TODO: find why and remove `-destination 'generic/platform=iOS'`\n# TODO: remove xcode clean when fix control widget fixed\nbuild_ios:\n\tcd ../sing-box-for-apple && \\\n\trm -rf build/SFI.xcarchive && \\\n\txcodebuild clean -scheme SFI && \\\n\txcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e \"Archive Succeeded\" -e \"ARCHIVE FAILED\" -e \"❌\"\n\nupload_ios_app_store:\n\tcd ../sing-box-for-apple && \\\n\txcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates\n\nexport_ios_ipa:\n\tcd ../sing-box-for-apple && \\\n\txcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \\\n\tcp build/SFI/sing-box.ipa dist/SFI.ipa\n\nupload_ios_ipa:\n\tcd dist && \\\n\tcp SFI.ipa \"SFI-${VERSION}.ipa\" && \\\n\tghr --replace --draft --prerelease \"v${VERSION}\" \"SFI-${VERSION}.ipa\"\n\nrelease_ios: build_ios upload_ios_app_store\n\nbuild_macos:\n\tcd ../sing-box-for-apple && \\\n\trm -rf build/SFM.xcarchive && \\\n\txcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e \"Archive Succeeded\" -e \"ARCHIVE FAILED\" -e \"❌\"\n\nupload_macos_app_store:\n\tcd ../sing-box-for-apple && \\\n\txcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates\n\nrelease_macos: build_macos upload_macos_app_store\n\nbuild_macos_standalone:\n\t$(MAKE) -C ../sing-box-for-apple archive_macos_standalone\n\nbuild_macos_dmg:\n\t$(MAKE) -C ../sing-box-for-apple build_macos_dmg\n\nbuild_macos_pkg:\n\t$(MAKE) -C ../sing-box-for-apple build_macos_pkg\n\nnotarize_macos_dmg:\n\t$(MAKE) -C ../sing-box-for-apple notarize_macos_dmg\n\nnotarize_macos_pkg:\n\t$(MAKE) -C ../sing-box-for-apple notarize_macos_pkg\n\nupload_macos_dmg:\n\tmkdir -p dist/SFM\n\tcp ../sing-box-for-apple/build/SFM-Apple.dmg \"dist/SFM/SFM-${VERSION}-Apple.dmg\"\n\tcp ../sing-box-for-apple/build/SFM-Intel.dmg \"dist/SFM/SFM-${VERSION}-Intel.dmg\"\n\tcp ../sing-box-for-apple/build/SFM-Universal.dmg \"dist/SFM/SFM-${VERSION}-Universal.dmg\"\n\tghr --replace --draft --prerelease \"v${VERSION}\" \"dist/SFM/SFM-${VERSION}-Apple.dmg\"\n\tghr --replace --draft --prerelease \"v${VERSION}\" \"dist/SFM/SFM-${VERSION}-Intel.dmg\"\n\tghr --replace --draft --prerelease \"v${VERSION}\" \"dist/SFM/SFM-${VERSION}-Universal.dmg\"\n\nupload_macos_pkg:\n\tmkdir -p dist/SFM\n\tcp ../sing-box-for-apple/build/SFM-Apple.pkg \"dist/SFM/SFM-${VERSION}-Apple.pkg\"\n\tcp ../sing-box-for-apple/build/SFM-Intel.pkg \"dist/SFM/SFM-${VERSION}-Intel.pkg\"\n\tcp ../sing-box-for-apple/build/SFM-Universal.pkg \"dist/SFM/SFM-${VERSION}-Universal.pkg\"\n\tghr --replace --draft --prerelease \"v${VERSION}\" \"dist/SFM/SFM-${VERSION}-Apple.pkg\"\n\tghr --replace --draft --prerelease \"v${VERSION}\" \"dist/SFM/SFM-${VERSION}-Intel.pkg\"\n\tghr --replace --draft --prerelease \"v${VERSION}\" \"dist/SFM/SFM-${VERSION}-Universal.pkg\"\n\nupload_macos_dsyms:\n\tmkdir -p dist/SFM\n\tcd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs\n\tcp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip \"dist/SFM/SFM-${VERSION}.dSYMs.zip\"\n\tghr --replace --draft --prerelease \"v${VERSION}\" \"dist/SFM/SFM-${VERSION}.dSYMs.zip\"\n\nrelease_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms\n\nbuild_tvos:\n\tcd ../sing-box-for-apple && \\\n\trm -rf build/SFT.xcarchive && \\\n\txcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e \"Archive Succeeded\" -e \"ARCHIVE FAILED\" -e \"❌\"\n\nupload_tvos_app_store:\n\tcd ../sing-box-for-apple && \\\n\txcodebuild -exportArchive -archivePath \"build/SFT.xcarchive\" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates\n\nexport_tvos_ipa:\n\tcd ../sing-box-for-apple && \\\n\txcodebuild -exportArchive -archivePath \"build/SFT.xcarchive\" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \\\n\tcp build/SFT/sing-box.ipa dist/SFT.ipa\n\nupload_tvos_ipa:\n\tcd dist && \\\n\tcp SFT.ipa \"SFT-${VERSION}.ipa\" && \\\n\tghr --replace --draft --prerelease \"v${VERSION}\" \"SFT-${VERSION}.ipa\"\n\nrelease_tvos: build_tvos upload_tvos_app_store\n\nupdate_apple_version:\n\tgo run ./cmd/internal/update_apple_version\n\nupdate_macos_version:\n\tMACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version\n\nrelease_apple: lib_apple update_apple_version release_ios release_macos release_tvos release_macos_standalone\n\nrelease_apple_beta: update_apple_version release_ios release_macos release_tvos\n\npublish_testflight:\n\tgo run -v ./cmd/internal/app_store_connect publish_testflight $(filter-out $@,$(MAKECMDGOALS))\n\nprepare_app_store:\n\tgo run -v ./cmd/internal/app_store_connect prepare_app_store\n\npublish_app_store:\n\tgo run -v ./cmd/internal/app_store_connect publish_app_store\n\ntest:\n\t@go test -v ./... && \\\n\tcd test && \\\n\tgo mod tidy && \\\n\tgo test -v -tags \"$(TAGS_TEST)\" .\n\ntest_stdio:\n\t@go test -v ./... && \\\n\tcd test && \\\n\tgo mod tidy && \\\n\tgo test -v -tags \"$(TAGS_TEST),force_stdio\" .\n\nlib_android:\n\tgo run ./cmd/internal/build_libbox -target android\n\nlib_apple:\n\tgo run ./cmd/internal/build_libbox -target apple\n\nlib_windows:\n\t$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type csharp\n\nlib_android_new:\n\t$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type android\n\nlib_apple_new:\n\t$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple\n\nlib_install:\n\tgo install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.12\n\tgo install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12\n\ndocs:\n\tvenv/bin/mkdocs serve\n\npublish_docs:\n\tvenv/bin/mkdocs gh-deploy -m \"Update\" --force --ignore-version --no-history\n\ndocs_install:\n\tpython3 -m venv venv\n\tsource ./venv/bin/activate && pip install --force-reinstall mkdocs-material==\"9.7.2\" mkdocs-static-i18n==\"1.2.*\"\n\nclean:\n\trm -rf bin dist sing-box\n\trm -f $(shell go env GOPATH)/sing-box\n\nupdate:\n\tgit fetch\n\tgit reset FETCH_HEAD --hard\n\tgit clean -fdx\n\n%:\n\t@:\n"
  },
  {
    "path": "README.md",
    "content": "> Sponsored by [Warp](https://go.warp.dev/sing-box), built for coding with multiple AI agents\n\n<a href=\"https://go.warp.dev/sing-box\">\n<img alt=\"Warp sponsorship\" width=\"400\" src=\"https://github.com/warpdotdev/brand-assets/raw/refs/heads/main/Github/Sponsor/Warp-Github-LG-02.png\">\n</a>\n\n---\n\n# sing-box\n\nThe universal proxy platform.\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/sing-box.svg)](https://repology.org/project/sing-box/versions)\n\n## Documentation\n\nhttps://sing-box.sagernet.org\n\n## License\n\n```\nCopyright (C) 2022 by nekohasekai <contact-sagernet@sekai.icu>\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n\nIn addition, no derivative work may use the name or imply association\nwith this application without prior consent.\n```"
  },
  {
    "path": "adapter/certificate.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\n\t\"github.com/sagernet/sing/service\"\n)\n\ntype CertificateStore interface {\n\tLifecycleService\n\tPool() *x509.CertPool\n}\n\nfunc RootPoolFromContext(ctx context.Context) *x509.CertPool {\n\tstore := service.FromContext[CertificateStore](ctx)\n\tif store == nil {\n\t\treturn nil\n\t}\n\treturn store.Pool()\n}\n"
  },
  {
    "path": "adapter/connections.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype ConnectionManager interface {\n\tLifecycle\n\tCount() int\n\tCloseAll()\n\tTrackConn(conn net.Conn) net.Conn\n\tTrackPacketConn(conn net.PacketConn) net.PacketConn\n\tNewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)\n\tNewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)\n}\n"
  },
  {
    "path": "adapter/dns.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"github.com/miekg/dns\"\n)\n\ntype DNSRouter interface {\n\tLifecycle\n\tExchange(ctx context.Context, message *dns.Msg, options DNSQueryOptions) (*dns.Msg, error)\n\tLookup(ctx context.Context, domain string, options DNSQueryOptions) ([]netip.Addr, error)\n\tClearCache()\n\tLookupReverseMapping(ip netip.Addr) (string, bool)\n\tResetNetwork()\n}\n\ntype DNSClient interface {\n\tStart()\n\tExchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)\n\tLookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)\n\tClearCache()\n}\n\ntype DNSQueryOptions struct {\n\tTransport      DNSTransport\n\tStrategy       C.DomainStrategy\n\tLookupStrategy C.DomainStrategy\n\tDisableCache   bool\n\tRewriteTTL     *uint32\n\tClientSubnet   netip.Prefix\n}\n\nfunc DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {\n\tif options == nil {\n\t\treturn &DNSQueryOptions{}, nil\n\t}\n\ttransportManager := service.FromContext[DNSTransportManager](ctx)\n\ttransport, loaded := transportManager.Transport(options.Server)\n\tif !loaded {\n\t\treturn nil, E.New(\"domain resolver not found: \" + options.Server)\n\t}\n\treturn &DNSQueryOptions{\n\t\tTransport:    transport,\n\t\tStrategy:     C.DomainStrategy(options.Strategy),\n\t\tDisableCache: options.DisableCache,\n\t\tRewriteTTL:   options.RewriteTTL,\n\t\tClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),\n\t}, nil\n}\n\ntype RDRCStore interface {\n\tLoadRDRC(transportName string, qName string, qType uint16) (rejected bool)\n\tSaveRDRC(transportName string, qName string, qType uint16) error\n\tSaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)\n}\n\ntype DNSTransport interface {\n\tLifecycle\n\tType() string\n\tTag() string\n\tDependencies() []string\n\tReset()\n\tExchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)\n}\n\ntype LegacyDNSTransport interface {\n\tLegacyStrategy() C.DomainStrategy\n\tLegacyClientSubnet() netip.Prefix\n}\n\ntype DNSTransportRegistry interface {\n\toption.DNSTransportOptionsRegistry\n\tCreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)\n}\n\ntype DNSTransportManager interface {\n\tLifecycle\n\tTransports() []DNSTransport\n\tTransport(tag string) (DNSTransport, bool)\n\tDefault() DNSTransport\n\tFakeIP() FakeIPTransport\n\tRemove(tag string) error\n\tCreate(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) error\n}\n"
  },
  {
    "path": "adapter/endpoint/adapter.go",
    "content": "package endpoint\n\nimport \"github.com/sagernet/sing-box/option\"\n\ntype Adapter struct {\n\tendpointType string\n\tendpointTag  string\n\tnetwork      []string\n\tdependencies []string\n}\n\nfunc NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter {\n\treturn Adapter{\n\t\tendpointType: endpointType,\n\t\tendpointTag:  endpointTag,\n\t\tnetwork:      network,\n\t\tdependencies: dependencies,\n\t}\n}\n\nfunc NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter {\n\tvar dependencies []string\n\tif dialOptions.Detour != \"\" {\n\t\tdependencies = []string{dialOptions.Detour}\n\t}\n\treturn NewAdapter(endpointType, endpointTag, network, dependencies)\n}\n\nfunc (a *Adapter) Type() string {\n\treturn a.endpointType\n}\n\nfunc (a *Adapter) Tag() string {\n\treturn a.endpointTag\n}\n\nfunc (a *Adapter) Network() []string {\n\treturn a.network\n}\n\nfunc (a *Adapter) Dependencies() []string {\n\treturn a.dependencies\n}\n"
  },
  {
    "path": "adapter/endpoint/manager.go",
    "content": "package endpoint\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/taskmonitor\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ adapter.EndpointManager = (*Manager)(nil)\n\ntype Manager struct {\n\tlogger        log.ContextLogger\n\tregistry      adapter.EndpointRegistry\n\taccess        sync.Mutex\n\tstarted       bool\n\tstage         adapter.StartStage\n\tendpoints     []adapter.Endpoint\n\tendpointByTag map[string]adapter.Endpoint\n}\n\nfunc NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager {\n\treturn &Manager{\n\t\tlogger:        logger,\n\t\tregistry:      registry,\n\t\tendpointByTag: make(map[string]adapter.Endpoint),\n\t}\n}\n\nfunc (m *Manager) Start(stage adapter.StartStage) error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tif m.started && m.stage >= stage {\n\t\tpanic(\"already started\")\n\t}\n\tm.started = true\n\tm.stage = stage\n\tif stage == adapter.StartStateStart {\n\t\t// started with outbound manager\n\t\treturn nil\n\t}\n\tfor _, endpoint := range m.endpoints {\n\t\tname := \"endpoint/\" + endpoint.Type() + \"[\" + endpoint.Tag() + \"]\"\n\t\tm.logger.Trace(stage, \" \", name)\n\t\tstartTime := time.Now()\n\t\terr := adapter.LegacyStart(endpoint, stage)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, stage, \" \", name)\n\t\t}\n\t\tm.logger.Trace(stage, \" \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Close() error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tif !m.started {\n\t\treturn nil\n\t}\n\tm.started = false\n\tendpoints := m.endpoints\n\tm.endpoints = nil\n\tmonitor := taskmonitor.New(m.logger, C.StopTimeout)\n\tvar err error\n\tfor _, endpoint := range endpoints {\n\t\tname := \"endpoint/\" + endpoint.Type() + \"[\" + endpoint.Tag() + \"]\"\n\t\tm.logger.Trace(\"close \", name)\n\t\tstartTime := time.Now()\n\t\tmonitor.Start(\"close \", name)\n\t\terr = E.Append(err, endpoint.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close \", name)\n\t\t})\n\t\tmonitor.Finish()\n\t\tm.logger.Trace(\"close \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Endpoints() []adapter.Endpoint {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\treturn m.endpoints\n}\n\nfunc (m *Manager) Get(tag string) (adapter.Endpoint, bool) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tendpoint, found := m.endpointByTag[tag]\n\treturn endpoint, found\n}\n\nfunc (m *Manager) Remove(tag string) error {\n\tm.access.Lock()\n\tendpoint, found := m.endpointByTag[tag]\n\tif !found {\n\t\tm.access.Unlock()\n\t\treturn os.ErrInvalid\n\t}\n\tdelete(m.endpointByTag, tag)\n\tindex := common.Index(m.endpoints, func(it adapter.Endpoint) bool {\n\t\treturn it == endpoint\n\t})\n\tif index == -1 {\n\t\tpanic(\"invalid endpoint index\")\n\t}\n\tm.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...)\n\tstarted := m.started\n\tm.access.Unlock()\n\tif started {\n\t\treturn endpoint.Close()\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {\n\tendpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tif m.started {\n\t\tname := \"endpoint/\" + endpoint.Type() + \"[\" + endpoint.Tag() + \"]\"\n\t\tfor _, stage := range adapter.ListStartStages {\n\t\t\tm.logger.Trace(stage, \" \", name)\n\t\t\tstartTime := time.Now()\n\t\t\terr = adapter.LegacyStart(endpoint, stage)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, stage, \" \", name)\n\t\t\t}\n\t\t\tm.logger.Trace(stage, \" \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t\t}\n\t}\n\tif existsEndpoint, loaded := m.endpointByTag[tag]; loaded {\n\t\tif m.started {\n\t\t\terr = existsEndpoint.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"close endpoint/\", existsEndpoint.Type(), \"[\", existsEndpoint.Tag(), \"]\")\n\t\t\t}\n\t\t}\n\t\texistsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool {\n\t\t\treturn it == existsEndpoint\n\t\t})\n\t\tif existsIndex == -1 {\n\t\t\tpanic(\"invalid endpoint index\")\n\t\t}\n\t\tm.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...)\n\t}\n\tm.endpoints = append(m.endpoints, endpoint)\n\tm.endpointByTag[tag] = endpoint\n\treturn nil\n}\n"
  },
  {
    "path": "adapter/endpoint/registry.go",
    "content": "package endpoint\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error)\n\nfunc Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {\n\tregistry.register(outboundType, func() any {\n\t\treturn new(Options)\n\t}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) {\n\t\tvar options *Options\n\t\tif rawOptions != nil {\n\t\t\toptions = rawOptions.(*Options)\n\t\t}\n\t\treturn constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))\n\t})\n}\n\nvar _ adapter.EndpointRegistry = (*Registry)(nil)\n\ntype (\n\toptionsConstructorFunc func() any\n\tconstructorFunc        func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error)\n)\n\ntype Registry struct {\n\taccess      sync.Mutex\n\toptionsType map[string]optionsConstructorFunc\n\tconstructor map[string]constructorFunc\n}\n\nfunc NewRegistry() *Registry {\n\treturn &Registry{\n\t\toptionsType: make(map[string]optionsConstructorFunc),\n\t\tconstructor: make(map[string]constructorFunc),\n\t}\n}\n\nfunc (m *Registry) CreateOptions(outboundType string) (any, bool) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\toptionsConstructor, loaded := m.optionsType[outboundType]\n\tif !loaded {\n\t\treturn nil, false\n\t}\n\treturn optionsConstructor(), true\n}\n\nfunc (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tconstructor, loaded := m.constructor[outboundType]\n\tif !loaded {\n\t\treturn nil, E.New(\"outbound type not found: \" + outboundType)\n\t}\n\treturn constructor(ctx, router, logger, tag, options)\n}\n\nfunc (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tm.optionsType[outboundType] = optionsConstructor\n\tm.constructor[outboundType] = constructor\n}\n"
  },
  {
    "path": "adapter/endpoint.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n)\n\ntype Endpoint interface {\n\tLifecycle\n\tType() string\n\tTag() string\n\tOutbound\n}\n\ntype EndpointRegistry interface {\n\toption.EndpointOptionsRegistry\n\tCreate(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error)\n}\n\ntype EndpointManager interface {\n\tLifecycle\n\tEndpoints() []Endpoint\n\tGet(tag string) (Endpoint, bool)\n\tRemove(tag string) error\n\tCreate(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error\n}\n"
  },
  {
    "path": "adapter/experimental.go",
    "content": "package adapter\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common/observable\"\n\t\"github.com/sagernet/sing/common/varbin\"\n)\n\ntype ClashServer interface {\n\tLifecycleService\n\tConnectionTracker\n\tMode() string\n\tModeList() []string\n\tSetModeUpdateHook(hook *observable.Subscriber[struct{}])\n\tHistoryStorage() URLTestHistoryStorage\n}\n\ntype URLTestHistory struct {\n\tTime  time.Time `json:\"time\"`\n\tDelay uint16    `json:\"delay\"`\n}\n\ntype URLTestHistoryStorage interface {\n\tSetHook(hook *observable.Subscriber[struct{}])\n\tLoadURLTestHistory(tag string) *URLTestHistory\n\tDeleteURLTestHistory(tag string)\n\tStoreURLTestHistory(tag string, history *URLTestHistory)\n\tClose() error\n}\n\ntype V2RayServer interface {\n\tLifecycleService\n\tStatsService() ConnectionTracker\n}\n\ntype CacheFile interface {\n\tLifecycleService\n\n\tStoreFakeIP() bool\n\tFakeIPStorage\n\n\tStoreRDRC() bool\n\tRDRCStore\n\n\tLoadMode() string\n\tStoreMode(mode string) error\n\tLoadSelected(group string) string\n\tStoreSelected(group string, selected string) error\n\tLoadGroupExpand(group string) (isExpand bool, loaded bool)\n\tStoreGroupExpand(group string, expand bool) error\n\tLoadRuleSet(tag string) *SavedBinary\n\tSaveRuleSet(tag string, set *SavedBinary) error\n}\n\ntype SavedBinary struct {\n\tContent     []byte\n\tLastUpdated time.Time\n\tLastEtag    string\n}\n\nfunc (s *SavedBinary) MarshalBinary() ([]byte, error) {\n\tvar buffer bytes.Buffer\n\terr := binary.Write(&buffer, binary.BigEndian, uint8(1))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t_, err = varbin.WriteUvarint(&buffer, uint64(len(s.Content)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t_, err = buffer.Write(s.Content)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t_, err = varbin.WriteUvarint(&buffer, uint64(len(s.LastEtag)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t_, err = buffer.WriteString(s.LastEtag)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn buffer.Bytes(), nil\n}\n\nfunc (s *SavedBinary) UnmarshalBinary(data []byte) error {\n\treader := bytes.NewReader(data)\n\tvar version uint8\n\terr := binary.Read(reader, binary.BigEndian, &version)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontentLength, err := binary.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.Content = make([]byte, contentLength)\n\t_, err = io.ReadFull(reader, s.Content)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar lastUpdated int64\n\terr = binary.Read(reader, binary.BigEndian, &lastUpdated)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.LastUpdated = time.Unix(lastUpdated, 0)\n\tetagLength, err := binary.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\tetagBytes := make([]byte, etagLength)\n\t_, err = io.ReadFull(reader, etagBytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.LastEtag = string(etagBytes)\n\treturn nil\n}\n\ntype OutboundGroup interface {\n\tOutbound\n\tNow() string\n\tAll() []string\n}\n\ntype URLTestGroup interface {\n\tOutboundGroup\n\tURLTest(ctx context.Context) (map[string]uint16, error)\n}\n\nfunc OutboundTag(detour Outbound) string {\n\tif group, isGroup := detour.(OutboundGroup); isGroup {\n\t\treturn group.Now()\n\t}\n\treturn detour.Tag()\n}\n"
  },
  {
    "path": "adapter/fakeip.go",
    "content": "package adapter\n\nimport (\n\t\"net/netip\"\n\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\ntype FakeIPStore interface {\n\tSimpleLifecycle\n\tContains(address netip.Addr) bool\n\tCreate(domain string, isIPv6 bool) (netip.Addr, error)\n\tLookup(address netip.Addr) (string, bool)\n\tReset() error\n}\n\ntype FakeIPStorage interface {\n\tFakeIPMetadata() *FakeIPMetadata\n\tFakeIPSaveMetadata(metadata *FakeIPMetadata) error\n\tFakeIPSaveMetadataAsync(metadata *FakeIPMetadata)\n\tFakeIPStore(address netip.Addr, domain string) error\n\tFakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger)\n\tFakeIPLoad(address netip.Addr) (string, bool)\n\tFakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bool)\n\tFakeIPReset() error\n}\n\ntype FakeIPTransport interface {\n\tDNSTransport\n\tStore() FakeIPStore\n}\n"
  },
  {
    "path": "adapter/fakeip_metadata.go",
    "content": "package adapter\n\nimport (\n\t\"bytes\"\n\t\"encoding\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"net/netip\"\n\n\t\"github.com/sagernet/sing/common\"\n)\n\ntype FakeIPMetadata struct {\n\tInet4Range   netip.Prefix\n\tInet6Range   netip.Prefix\n\tInet4Current netip.Addr\n\tInet6Current netip.Addr\n}\n\nfunc (m *FakeIPMetadata) MarshalBinary() (data []byte, err error) {\n\tvar buffer bytes.Buffer\n\tfor _, marshaler := range []encoding.BinaryMarshaler{m.Inet4Range, m.Inet6Range, m.Inet4Current, m.Inet6Current} {\n\t\tdata, err = marshaler.MarshalBinary()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tcommon.Must(binary.Write(&buffer, binary.BigEndian, uint16(len(data))))\n\t\tbuffer.Write(data)\n\t}\n\tdata = buffer.Bytes()\n\treturn\n}\n\nfunc (m *FakeIPMetadata) UnmarshalBinary(data []byte) error {\n\treader := bytes.NewReader(data)\n\tfor _, unmarshaler := range []encoding.BinaryUnmarshaler{&m.Inet4Range, &m.Inet6Range, &m.Inet4Current, &m.Inet6Current} {\n\t\tvar length uint16\n\t\tcommon.Must(binary.Read(reader, binary.BigEndian, &length))\n\t\telement := make([]byte, length)\n\t\t_, err := io.ReadFull(reader, element)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = unmarshaler.UnmarshalBinary(element)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "adapter/handler.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\n// Deprecated\ntype ConnectionHandler interface {\n\tNewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error\n}\n\ntype ConnectionHandlerEx interface {\n\tNewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)\n}\n\n// Deprecated: use PacketHandlerEx instead\ntype PacketHandler interface {\n\tNewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error\n}\n\ntype PacketHandlerEx interface {\n\tNewPacketEx(buffer *buf.Buffer, source M.Socksaddr)\n}\n\n// Deprecated: use OOBPacketHandlerEx instead\ntype OOBPacketHandler interface {\n\tNewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error\n}\n\ntype OOBPacketHandlerEx interface {\n\tNewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr)\n}\n\n// Deprecated\ntype PacketConnectionHandler interface {\n\tNewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error\n}\n\ntype PacketConnectionHandlerEx interface {\n\tNewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)\n}\n\n// Deprecated: use TCPConnectionHandlerEx instead\n//\n//nolint:staticcheck\ntype UpstreamHandlerAdapter interface {\n\tN.TCPConnectionHandler\n\tN.UDPConnectionHandler\n\tE.Handler\n}\n\ntype UpstreamHandlerAdapterEx interface {\n\tN.TCPConnectionHandlerEx\n\tN.UDPConnectionHandlerEx\n}\n"
  },
  {
    "path": "adapter/inbound/adapter.go",
    "content": "package inbound\n\ntype Adapter struct {\n\tinboundType string\n\tinboundTag  string\n}\n\nfunc NewAdapter(inboundType string, inboundTag string) Adapter {\n\treturn Adapter{\n\t\tinboundType: inboundType,\n\t\tinboundTag:  inboundTag,\n\t}\n}\n\nfunc (a *Adapter) Type() string {\n\treturn a.inboundType\n}\n\nfunc (a *Adapter) Tag() string {\n\treturn a.inboundTag\n}\n"
  },
  {
    "path": "adapter/inbound/manager.go",
    "content": "package inbound\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/taskmonitor\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ adapter.InboundManager = (*Manager)(nil)\n\ntype Manager struct {\n\tlogger       log.ContextLogger\n\tregistry     adapter.InboundRegistry\n\tendpoint     adapter.EndpointManager\n\taccess       sync.Mutex\n\tstarted      bool\n\tstage        adapter.StartStage\n\tinbounds     []adapter.Inbound\n\tinboundByTag map[string]adapter.Inbound\n}\n\nfunc NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager {\n\treturn &Manager{\n\t\tlogger:       logger,\n\t\tregistry:     registry,\n\t\tendpoint:     endpoint,\n\t\tinboundByTag: make(map[string]adapter.Inbound),\n\t}\n}\n\nfunc (m *Manager) Start(stage adapter.StartStage) error {\n\tm.access.Lock()\n\tif m.started && m.stage >= stage {\n\t\tpanic(\"already started\")\n\t}\n\tm.started = true\n\tm.stage = stage\n\tinbounds := m.inbounds\n\tm.access.Unlock()\n\tfor _, inbound := range inbounds {\n\t\tname := \"inbound/\" + inbound.Type() + \"[\" + inbound.Tag() + \"]\"\n\t\tm.logger.Trace(stage, \" \", name)\n\t\tstartTime := time.Now()\n\t\terr := adapter.LegacyStart(inbound, stage)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, stage, \" \", name)\n\t\t}\n\t\tm.logger.Trace(stage, \" \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Close() error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tif !m.started {\n\t\treturn nil\n\t}\n\tm.started = false\n\tinbounds := m.inbounds\n\tm.inbounds = nil\n\tmonitor := taskmonitor.New(m.logger, C.StopTimeout)\n\tvar err error\n\tfor _, inbound := range inbounds {\n\t\tname := \"inbound/\" + inbound.Type() + \"[\" + inbound.Tag() + \"]\"\n\t\tm.logger.Trace(\"close \", name)\n\t\tstartTime := time.Now()\n\t\tmonitor.Start(\"close \", name)\n\t\terr = E.Append(err, inbound.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close \", name)\n\t\t})\n\t\tmonitor.Finish()\n\t\tm.logger.Trace(\"close \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Inbounds() []adapter.Inbound {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\treturn m.inbounds\n}\n\nfunc (m *Manager) Get(tag string) (adapter.Inbound, bool) {\n\tm.access.Lock()\n\tinbound, found := m.inboundByTag[tag]\n\tm.access.Unlock()\n\tif found {\n\t\treturn inbound, true\n\t}\n\treturn m.endpoint.Get(tag)\n}\n\nfunc (m *Manager) Remove(tag string) error {\n\tm.access.Lock()\n\tinbound, found := m.inboundByTag[tag]\n\tif !found {\n\t\tm.access.Unlock()\n\t\treturn os.ErrInvalid\n\t}\n\tdelete(m.inboundByTag, tag)\n\tindex := common.Index(m.inbounds, func(it adapter.Inbound) bool {\n\t\treturn it == inbound\n\t})\n\tif index == -1 {\n\t\tpanic(\"invalid inbound index\")\n\t}\n\tm.inbounds = append(m.inbounds[:index], m.inbounds[index+1:]...)\n\tstarted := m.started\n\tm.access.Unlock()\n\tif started {\n\t\treturn inbound.Close()\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {\n\tinbound, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tif m.started {\n\t\tname := \"inbound/\" + inbound.Type() + \"[\" + inbound.Tag() + \"]\"\n\t\tfor _, stage := range adapter.ListStartStages {\n\t\t\tm.logger.Trace(stage, \" \", name)\n\t\t\tstartTime := time.Now()\n\t\t\terr = adapter.LegacyStart(inbound, stage)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, stage, \" \", name)\n\t\t\t}\n\t\t\tm.logger.Trace(stage, \" \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t\t}\n\t}\n\tif existsInbound, loaded := m.inboundByTag[tag]; loaded {\n\t\tif m.started {\n\t\t\terr = existsInbound.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"close inbound/\", existsInbound.Type(), \"[\", existsInbound.Tag(), \"]\")\n\t\t\t}\n\t\t}\n\t\texistsIndex := common.Index(m.inbounds, func(it adapter.Inbound) bool {\n\t\t\treturn it == existsInbound\n\t\t})\n\t\tif existsIndex == -1 {\n\t\t\tpanic(\"invalid inbound index\")\n\t\t}\n\t\tm.inbounds = append(m.inbounds[:existsIndex], m.inbounds[existsIndex+1:]...)\n\t}\n\tm.inbounds = append(m.inbounds, inbound)\n\tm.inboundByTag[tag] = inbound\n\treturn nil\n}\n"
  },
  {
    "path": "adapter/inbound/registry.go",
    "content": "package inbound\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error)\n\nfunc Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {\n\tregistry.register(outboundType, func() any {\n\t\treturn new(Options)\n\t}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) {\n\t\tvar options *Options\n\t\tif rawOptions != nil {\n\t\t\toptions = rawOptions.(*Options)\n\t\t}\n\t\treturn constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))\n\t})\n}\n\nvar _ adapter.InboundRegistry = (*Registry)(nil)\n\ntype (\n\toptionsConstructorFunc func() any\n\tconstructorFunc        func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error)\n)\n\ntype Registry struct {\n\taccess      sync.Mutex\n\toptionsType map[string]optionsConstructorFunc\n\tconstructor map[string]constructorFunc\n}\n\nfunc NewRegistry() *Registry {\n\treturn &Registry{\n\t\toptionsType: make(map[string]optionsConstructorFunc),\n\t\tconstructor: make(map[string]constructorFunc),\n\t}\n}\n\nfunc (m *Registry) CreateOptions(outboundType string) (any, bool) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\toptionsConstructor, loaded := m.optionsType[outboundType]\n\tif !loaded {\n\t\treturn nil, false\n\t}\n\treturn optionsConstructor(), true\n}\n\nfunc (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tconstructor, loaded := m.constructor[outboundType]\n\tif !loaded {\n\t\treturn nil, E.New(\"outbound type not found: \" + outboundType)\n\t}\n\treturn constructor(ctx, router, logger, tag, options)\n}\n\nfunc (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tm.optionsType[outboundType] = optionsConstructor\n\tm.constructor[outboundType] = constructor\n}\n"
  },
  {
    "path": "adapter/inbound.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"time\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n)\n\ntype Inbound interface {\n\tLifecycle\n\tType() string\n\tTag() string\n}\n\ntype TCPInjectableInbound interface {\n\tInbound\n\tConnectionHandlerEx\n}\n\ntype UDPInjectableInbound interface {\n\tInbound\n\tPacketConnectionHandlerEx\n}\n\ntype InboundRegistry interface {\n\toption.InboundOptionsRegistry\n\tCreate(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)\n}\n\ntype InboundManager interface {\n\tLifecycle\n\tInbounds() []Inbound\n\tGet(tag string) (Inbound, bool)\n\tRemove(tag string) error\n\tCreate(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) error\n}\n\ntype InboundContext struct {\n\tInbound     string\n\tInboundType string\n\tIPVersion   uint8\n\tNetwork     string\n\tSource      M.Socksaddr\n\tDestination M.Socksaddr\n\tUser        string\n\tOutbound    string\n\n\t// sniffer\n\n\tProtocol     string\n\tDomain       string\n\tClient       string\n\tSniffContext any\n\tSnifferNames []string\n\tSniffError   error\n\n\t// cache\n\n\t// Deprecated: implement in rule action\n\tInboundDetour             string\n\tLastInbound               string\n\tOriginDestination         M.Socksaddr\n\tRouteOriginalDestination  M.Socksaddr\n\tUDPDisableDomainUnmapping bool\n\tUDPConnect                bool\n\tUDPTimeout                time.Duration\n\tTLSFragment               bool\n\tTLSFragmentFallbackDelay  time.Duration\n\tTLSRecordFragment         bool\n\n\tNetworkStrategy     *C.NetworkStrategy\n\tNetworkType         []C.InterfaceType\n\tFallbackNetworkType []C.InterfaceType\n\tFallbackDelay       time.Duration\n\n\tDestinationAddresses []netip.Addr\n\tSourceGeoIPCode      string\n\tGeoIPCode            string\n\tProcessInfo          *ConnectionOwner\n\tSourceMACAddress     net.HardwareAddr\n\tSourceHostname       string\n\tQueryType            uint16\n\tFakeIP               bool\n\n\t// rule cache\n\n\tIPCIDRMatchSource bool\n\tIPCIDRAcceptEmpty bool\n\n\tSourceAddressMatch           bool\n\tSourcePortMatch              bool\n\tDestinationAddressMatch      bool\n\tDestinationPortMatch         bool\n\tDidMatch                     bool\n\tIgnoreDestinationIPCIDRMatch bool\n}\n\nfunc (c *InboundContext) ResetRuleCache() {\n\tc.IPCIDRMatchSource = false\n\tc.IPCIDRAcceptEmpty = false\n\tc.SourceAddressMatch = false\n\tc.SourcePortMatch = false\n\tc.DestinationAddressMatch = false\n\tc.DestinationPortMatch = false\n\tc.DidMatch = false\n}\n\ntype inboundContextKey struct{}\n\nfunc WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {\n\treturn context.WithValue(ctx, (*inboundContextKey)(nil), inboundContext)\n}\n\nfunc ContextFrom(ctx context.Context) *InboundContext {\n\tmetadata := ctx.Value((*inboundContextKey)(nil))\n\tif metadata == nil {\n\t\treturn nil\n\t}\n\treturn metadata.(*InboundContext)\n}\n\nfunc ExtendContext(ctx context.Context) (context.Context, *InboundContext) {\n\tvar newMetadata InboundContext\n\tif metadata := ContextFrom(ctx); metadata != nil {\n\t\tnewMetadata = *metadata\n\t}\n\treturn WithContext(ctx, &newMetadata), &newMetadata\n}\n\nfunc OverrideContext(ctx context.Context) context.Context {\n\tif metadata := ContextFrom(ctx); metadata != nil {\n\t\tnewMetadata := *metadata\n\t\treturn WithContext(ctx, &newMetadata)\n\t}\n\treturn ctx\n}\n"
  },
  {
    "path": "adapter/lifecycle.go",
    "content": "package adapter\n\nimport (\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\ntype SimpleLifecycle interface {\n\tStart() error\n\tClose() error\n}\n\ntype StartStage uint8\n\nconst (\n\tStartStateInitialize StartStage = iota\n\tStartStateStart\n\tStartStatePostStart\n\tStartStateStarted\n)\n\nvar ListStartStages = []StartStage{\n\tStartStateInitialize,\n\tStartStateStart,\n\tStartStatePostStart,\n\tStartStateStarted,\n}\n\nfunc (s StartStage) String() string {\n\tswitch s {\n\tcase StartStateInitialize:\n\t\treturn \"initialize\"\n\tcase StartStateStart:\n\t\treturn \"start\"\n\tcase StartStatePostStart:\n\t\treturn \"post-start\"\n\tcase StartStateStarted:\n\t\treturn \"finish-start\"\n\tdefault:\n\t\tpanic(\"unknown stage\")\n\t}\n}\n\ntype Lifecycle interface {\n\tStart(stage StartStage) error\n\tClose() error\n}\n\ntype LifecycleService interface {\n\tName() string\n\tLifecycle\n}\n\nfunc getServiceName(service any) string {\n\tif named, ok := service.(interface {\n\t\tType() string\n\t\tTag() string\n\t}); ok {\n\t\ttag := named.Tag()\n\t\tif tag != \"\" {\n\t\t\treturn named.Type() + \"[\" + tag + \"]\"\n\t\t}\n\t\treturn named.Type()\n\t}\n\tt := reflect.TypeOf(service)\n\tif t.Kind() == reflect.Ptr {\n\t\tt = t.Elem()\n\t}\n\treturn strings.ToLower(t.Name())\n}\n\nfunc Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) error {\n\tfor _, service := range services {\n\t\tname := getServiceName(service)\n\t\tlogger.Trace(stage, \" \", name)\n\t\tstartTime := time.Now()\n\t\terr := service.Start(stage)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlogger.Trace(stage, \" \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t}\n\treturn nil\n}\n\nfunc StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error {\n\tfor _, service := range services {\n\t\tlogger.Trace(stage, \" \", service.Name())\n\t\tstartTime := time.Now()\n\t\terr := service.Start(stage)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, stage.String(), \" \", service.Name())\n\t\t}\n\t\tlogger.Trace(stage, \" \", service.Name(), \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "adapter/lifecycle_legacy.go",
    "content": "package adapter\n\nfunc LegacyStart(starter any, stage StartStage) error {\n\tif lifecycle, isLifecycle := starter.(Lifecycle); isLifecycle {\n\t\treturn lifecycle.Start(stage)\n\t}\n\tswitch stage {\n\tcase StartStateInitialize:\n\t\tif preStarter, isPreStarter := starter.(interface {\n\t\t\tPreStart() error\n\t\t}); isPreStarter {\n\t\t\treturn preStarter.PreStart()\n\t\t}\n\tcase StartStateStart:\n\t\tif starter, isStarter := starter.(interface {\n\t\t\tStart() error\n\t\t}); isStarter {\n\t\t\treturn starter.Start()\n\t\t}\n\tcase StartStateStarted:\n\t\tif postStarter, isPostStarter := starter.(interface {\n\t\t\tPostStart() error\n\t\t}); isPostStarter {\n\t\t\treturn postStarter.PostStart()\n\t\t}\n\t}\n\treturn nil\n}\n\ntype lifecycleServiceWrapper struct {\n\tSimpleLifecycle\n\tname string\n}\n\nfunc NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {\n\treturn &lifecycleServiceWrapper{\n\t\tSimpleLifecycle: service,\n\t\tname:            name,\n\t}\n}\n\nfunc (l *lifecycleServiceWrapper) Name() string {\n\treturn l.name\n}\n\nfunc (l *lifecycleServiceWrapper) Start(stage StartStage) error {\n\treturn LegacyStart(l.SimpleLifecycle, stage)\n}\n\nfunc (l *lifecycleServiceWrapper) Close() error {\n\treturn l.SimpleLifecycle.Close()\n}\n"
  },
  {
    "path": "adapter/neighbor.go",
    "content": "package adapter\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n)\n\ntype NeighborEntry struct {\n\tAddress    netip.Addr\n\tMACAddress net.HardwareAddr\n\tHostname   string\n}\n\ntype NeighborResolver interface {\n\tLookupMAC(address netip.Addr) (net.HardwareAddr, bool)\n\tLookupHostname(address netip.Addr) (string, bool)\n\tStart() error\n\tClose() error\n}\n\ntype NeighborUpdateListener interface {\n\tUpdateNeighborTable(entries []NeighborEntry)\n}\n"
  },
  {
    "path": "adapter/network.go",
    "content": "package adapter\n\nimport (\n\t\"time\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common/control\"\n)\n\ntype NetworkManager interface {\n\tLifecycle\n\tInitialize(ruleSets []RuleSet)\n\tInterfaceFinder() control.InterfaceFinder\n\tUpdateInterfaces() error\n\tDefaultNetworkInterface() *NetworkInterface\n\tNetworkInterfaces() []NetworkInterface\n\tAutoDetectInterface() bool\n\tAutoDetectInterfaceFunc() control.Func\n\tProtectFunc() control.Func\n\tDefaultOptions() NetworkOptions\n\tRegisterAutoRedirectOutputMark(mark uint32) error\n\tAutoRedirectOutputMark() uint32\n\tAutoRedirectOutputMarkFunc() control.Func\n\tNetworkMonitor() tun.NetworkUpdateMonitor\n\tInterfaceMonitor() tun.DefaultInterfaceMonitor\n\tPackageManager() tun.PackageManager\n\tNeedWIFIState() bool\n\tWIFIState() WIFIState\n\tUpdateWIFIState()\n\tResetNetwork()\n}\n\ntype NetworkOptions struct {\n\tBindInterface        string\n\tRoutingMark          uint32\n\tDomainResolver       string\n\tDomainResolveOptions DNSQueryOptions\n\tNetworkStrategy      *C.NetworkStrategy\n\tNetworkType          []C.InterfaceType\n\tFallbackNetworkType  []C.InterfaceType\n\tFallbackDelay        time.Duration\n}\n\ntype InterfaceUpdateListener interface {\n\tInterfaceUpdated()\n}\n\ntype WIFIState struct {\n\tSSID  string\n\tBSSID string\n}\n\ntype NetworkInterface struct {\n\tcontrol.Interface\n\tType        C.InterfaceType\n\tDNSServers  []string\n\tExpensive   bool\n\tConstrained bool\n}\n"
  },
  {
    "path": "adapter/outbound/adapter.go",
    "content": "package outbound\n\nimport (\n\t\"github.com/sagernet/sing-box/option\"\n)\n\ntype Adapter struct {\n\toutboundType string\n\toutboundTag  string\n\tnetwork      []string\n\tdependencies []string\n}\n\nfunc NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter {\n\treturn Adapter{\n\t\toutboundType: outboundType,\n\t\toutboundTag:  outboundTag,\n\t\tnetwork:      network,\n\t\tdependencies: dependencies,\n\t}\n}\n\nfunc NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter {\n\tvar dependencies []string\n\tif dialOptions.Detour != \"\" {\n\t\tdependencies = []string{dialOptions.Detour}\n\t}\n\treturn NewAdapter(outboundType, outboundTag, network, dependencies)\n}\n\nfunc (a *Adapter) Type() string {\n\treturn a.outboundType\n}\n\nfunc (a *Adapter) Tag() string {\n\treturn a.outboundTag\n}\n\nfunc (a *Adapter) Network() []string {\n\treturn a.network\n}\n\nfunc (a *Adapter) Dependencies() []string {\n\treturn a.dependencies\n}\n"
  },
  {
    "path": "adapter/outbound/manager.go",
    "content": "package outbound\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/taskmonitor\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\nvar _ adapter.OutboundManager = (*Manager)(nil)\n\ntype Manager struct {\n\tlogger                  log.ContextLogger\n\tregistry                adapter.OutboundRegistry\n\tendpoint                adapter.EndpointManager\n\tdefaultTag              string\n\taccess                  sync.RWMutex\n\tstarted                 bool\n\tstage                   adapter.StartStage\n\toutbounds               []adapter.Outbound\n\toutboundByTag           map[string]adapter.Outbound\n\tdependByTag             map[string][]string\n\tdefaultOutbound         adapter.Outbound\n\tdefaultOutboundFallback func() (adapter.Outbound, error)\n}\n\nfunc NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {\n\treturn &Manager{\n\t\tlogger:        logger,\n\t\tregistry:      registry,\n\t\tendpoint:      endpoint,\n\t\tdefaultTag:    defaultTag,\n\t\toutboundByTag: make(map[string]adapter.Outbound),\n\t\tdependByTag:   make(map[string][]string),\n\t}\n}\n\nfunc (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, error)) {\n\tm.defaultOutboundFallback = defaultOutboundFallback\n}\n\nfunc (m *Manager) Start(stage adapter.StartStage) error {\n\tm.access.Lock()\n\tif m.started && m.stage >= stage {\n\t\tpanic(\"already started\")\n\t}\n\tm.started = true\n\tm.stage = stage\n\tif stage == adapter.StartStateStart {\n\t\tif m.defaultTag != \"\" && m.defaultOutbound == nil {\n\t\t\tdefaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)\n\t\t\tif !loaded {\n\t\t\t\tm.access.Unlock()\n\t\t\t\treturn E.New(\"default outbound not found: \", m.defaultTag)\n\t\t\t}\n\t\t\tm.defaultOutbound = defaultEndpoint\n\t\t}\n\t\tif m.defaultOutbound == nil {\n\t\t\tdirectOutbound, err := m.defaultOutboundFallback()\n\t\t\tif err != nil {\n\t\t\t\tm.access.Unlock()\n\t\t\t\treturn E.Cause(err, \"create direct outbound for fallback\")\n\t\t\t}\n\t\t\tm.outbounds = append(m.outbounds, directOutbound)\n\t\t\tm.outboundByTag[directOutbound.Tag()] = directOutbound\n\t\t\tm.defaultOutbound = directOutbound\n\t\t}\n\t\toutbounds := m.outbounds\n\t\tm.access.Unlock()\n\t\treturn m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))\n\t} else {\n\t\toutbounds := m.outbounds\n\t\tm.access.Unlock()\n\t\tfor _, outbound := range outbounds {\n\t\t\tname := \"outbound/\" + outbound.Type() + \"[\" + outbound.Tag() + \"]\"\n\t\t\tm.logger.Trace(stage, \" \", name)\n\t\t\tstartTime := time.Now()\n\t\t\terr := adapter.LegacyStart(outbound, stage)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, stage, \" \", name)\n\t\t\t}\n\t\t\tm.logger.Trace(stage, \" \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {\n\tmonitor := taskmonitor.New(m.logger, C.StartTimeout)\n\tstarted := make(map[string]bool)\n\tfor {\n\t\tcanContinue := false\n\tstartOne:\n\t\tfor _, outboundToStart := range outbounds {\n\t\t\toutboundTag := outboundToStart.Tag()\n\t\t\tif started[outboundTag] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdependencies := outboundToStart.Dependencies()\n\t\t\tfor _, dependency := range dependencies {\n\t\t\t\tif !started[dependency] {\n\t\t\t\t\tcontinue startOne\n\t\t\t\t}\n\t\t\t}\n\t\t\tstarted[outboundTag] = true\n\t\t\tcanContinue = true\n\t\t\tname := \"outbound/\" + outboundToStart.Type() + \"[\" + outboundTag + \"]\"\n\t\t\tif starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {\n\t\t\t\tm.logger.Trace(\"start \", name)\n\t\t\t\tstartTime := time.Now()\n\t\t\t\tmonitor.Start(\"start \", name)\n\t\t\t\terr := starter.Start(adapter.StartStateStart)\n\t\t\t\tmonitor.Finish()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn E.Cause(err, \"start \", name)\n\t\t\t\t}\n\t\t\t\tm.logger.Trace(\"start \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t\t\t} else if starter, isStarter := outboundToStart.(interface {\n\t\t\t\tStart() error\n\t\t\t}); isStarter {\n\t\t\t\tm.logger.Trace(\"start \", name)\n\t\t\t\tstartTime := time.Now()\n\t\t\t\tmonitor.Start(\"start \", name)\n\t\t\t\terr := starter.Start()\n\t\t\t\tmonitor.Finish()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn E.Cause(err, \"start \", name)\n\t\t\t\t}\n\t\t\t\tm.logger.Trace(\"start \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t\t\t}\n\t\t}\n\t\tif len(started) == len(outbounds) {\n\t\t\tbreak\n\t\t}\n\t\tif canContinue {\n\t\t\tcontinue\n\t\t}\n\t\tcurrentOutbound := common.Find(outbounds, func(it adapter.Outbound) bool {\n\t\t\treturn !started[it.Tag()]\n\t\t})\n\t\tvar lintOutbound func(oTree []string, oCurrent adapter.Outbound) error\n\t\tlintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {\n\t\t\tproblemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {\n\t\t\t\treturn !started[it]\n\t\t\t})\n\t\t\tif common.Contains(oTree, problemOutboundTag) {\n\t\t\t\treturn E.New(\"circular outbound dependency: \", strings.Join(oTree, \" -> \"), \" -> \", problemOutboundTag)\n\t\t\t}\n\t\t\tm.access.Lock()\n\t\t\tproblemOutbound := m.outboundByTag[problemOutboundTag]\n\t\t\tm.access.Unlock()\n\t\t\tif problemOutbound == nil {\n\t\t\t\treturn E.New(\"dependency[\", problemOutboundTag, \"] not found for outbound[\", oCurrent.Tag(), \"]\")\n\t\t\t}\n\t\t\treturn lintOutbound(append(oTree, problemOutboundTag), problemOutbound)\n\t\t}\n\t\treturn lintOutbound([]string{currentOutbound.Tag()}, currentOutbound)\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Close() error {\n\tmonitor := taskmonitor.New(m.logger, C.StopTimeout)\n\tm.access.Lock()\n\tif !m.started {\n\t\tm.access.Unlock()\n\t\treturn nil\n\t}\n\tm.started = false\n\toutbounds := m.outbounds\n\tm.outbounds = nil\n\tm.access.Unlock()\n\tvar err error\n\tfor _, outbound := range outbounds {\n\t\tif closer, isCloser := outbound.(io.Closer); isCloser {\n\t\t\tname := \"outbound/\" + outbound.Type() + \"[\" + outbound.Tag() + \"]\"\n\t\t\tm.logger.Trace(\"close \", name)\n\t\t\tstartTime := time.Now()\n\t\t\tmonitor.Start(\"close \", name)\n\t\t\terr = E.Append(err, closer.Close(), func(err error) error {\n\t\t\t\treturn E.Cause(err, \"close \", name)\n\t\t\t})\n\t\t\tmonitor.Finish()\n\t\t\tm.logger.Trace(\"close \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Outbounds() []adapter.Outbound {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\treturn m.outbounds\n}\n\nfunc (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {\n\tm.access.RLock()\n\toutbound, found := m.outboundByTag[tag]\n\tm.access.RUnlock()\n\tif found {\n\t\treturn outbound, true\n\t}\n\treturn m.endpoint.Get(tag)\n}\n\nfunc (m *Manager) Default() adapter.Outbound {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\treturn m.defaultOutbound\n}\n\nfunc (m *Manager) Remove(tag string) error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\toutbound, found := m.outboundByTag[tag]\n\tif !found {\n\t\treturn os.ErrInvalid\n\t}\n\tdelete(m.outboundByTag, tag)\n\tindex := common.Index(m.outbounds, func(it adapter.Outbound) bool {\n\t\treturn it == outbound\n\t})\n\tif index == -1 {\n\t\tpanic(\"invalid inbound index\")\n\t}\n\tm.outbounds = append(m.outbounds[:index], m.outbounds[index+1:]...)\n\tstarted := m.started\n\tif m.defaultOutbound == outbound {\n\t\tif len(m.outbounds) > 0 {\n\t\t\tm.defaultOutbound = m.outbounds[0]\n\t\t\tm.logger.Info(\"updated default outbound to \", m.defaultOutbound.Tag())\n\t\t} else {\n\t\t\tm.defaultOutbound = nil\n\t\t}\n\t}\n\tdependBy := m.dependByTag[tag]\n\tif len(dependBy) > 0 {\n\t\treturn E.New(\"outbound[\", tag, \"] is depended by \", strings.Join(dependBy, \", \"))\n\t}\n\tdependencies := outbound.Dependencies()\n\tfor _, dependency := range dependencies {\n\t\tif len(m.dependByTag[dependency]) == 1 {\n\t\t\tdelete(m.dependByTag, dependency)\n\t\t} else {\n\t\t\tm.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool {\n\t\t\t\treturn it != tag\n\t\t\t})\n\t\t}\n\t}\n\tif started {\n\t\treturn common.Close(outbound)\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, inboundType string, options any) error {\n\tif tag == \"\" {\n\t\treturn os.ErrInvalid\n\t}\n\toutbound, err := m.registry.CreateOutbound(ctx, router, logger, tag, inboundType, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif m.started {\n\t\tname := \"outbound/\" + outbound.Type() + \"[\" + outbound.Tag() + \"]\"\n\t\tfor _, stage := range adapter.ListStartStages {\n\t\t\tm.logger.Trace(stage, \" \", name)\n\t\t\tstartTime := time.Now()\n\t\t\terr = adapter.LegacyStart(outbound, stage)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, stage, \" \", name)\n\t\t\t}\n\t\t\tm.logger.Trace(stage, \" \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t\t}\n\t}\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tif existsOutbound, loaded := m.outboundByTag[tag]; loaded {\n\t\tif m.started {\n\t\t\terr = common.Close(existsOutbound)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"close outbound/\", existsOutbound.Type(), \"[\", existsOutbound.Tag(), \"]\")\n\t\t\t}\n\t\t}\n\t\texistsIndex := common.Index(m.outbounds, func(it adapter.Outbound) bool {\n\t\t\treturn it == existsOutbound\n\t\t})\n\t\tif existsIndex == -1 {\n\t\t\tpanic(\"invalid inbound index\")\n\t\t}\n\t\tm.outbounds = append(m.outbounds[:existsIndex], m.outbounds[existsIndex+1:]...)\n\t}\n\tm.outbounds = append(m.outbounds, outbound)\n\tm.outboundByTag[tag] = outbound\n\tdependencies := outbound.Dependencies()\n\tfor _, dependency := range dependencies {\n\t\tm.dependByTag[dependency] = append(m.dependByTag[dependency], tag)\n\t}\n\tif tag == m.defaultTag || (m.defaultTag == \"\" && m.defaultOutbound == nil) {\n\t\tm.defaultOutbound = outbound\n\t\tif m.started {\n\t\t\tm.logger.Info(\"updated default outbound to \", outbound.Tag())\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "adapter/outbound/registry.go",
    "content": "package outbound\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error)\n\nfunc Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {\n\tregistry.register(outboundType, func() any {\n\t\treturn new(Options)\n\t}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) {\n\t\tvar options *Options\n\t\tif rawOptions != nil {\n\t\t\toptions = rawOptions.(*Options)\n\t\t}\n\t\treturn constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))\n\t})\n}\n\nvar _ adapter.OutboundRegistry = (*Registry)(nil)\n\ntype (\n\toptionsConstructorFunc func() any\n\tconstructorFunc        func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error)\n)\n\ntype Registry struct {\n\taccess       sync.Mutex\n\toptionsType  map[string]optionsConstructorFunc\n\tconstructors map[string]constructorFunc\n}\n\nfunc NewRegistry() *Registry {\n\treturn &Registry{\n\t\toptionsType:  make(map[string]optionsConstructorFunc),\n\t\tconstructors: make(map[string]constructorFunc),\n\t}\n}\n\nfunc (r *Registry) CreateOptions(outboundType string) (any, bool) {\n\tr.access.Lock()\n\tdefer r.access.Unlock()\n\toptionsConstructor, loaded := r.optionsType[outboundType]\n\tif !loaded {\n\t\treturn nil, false\n\t}\n\treturn optionsConstructor(), true\n}\n\nfunc (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {\n\tr.access.Lock()\n\tdefer r.access.Unlock()\n\tconstructor, loaded := r.constructors[outboundType]\n\tif !loaded {\n\t\treturn nil, E.New(\"outbound type not found: \" + outboundType)\n\t}\n\treturn constructor(ctx, router, logger, tag, options)\n}\n\nfunc (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {\n\tr.access.Lock()\n\tdefer r.access.Unlock()\n\tr.optionsType[outboundType] = optionsConstructor\n\tr.constructors[outboundType] = constructor\n}\n"
  },
  {
    "path": "adapter/outbound.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-tun\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\n// Note: for proxy protocols, outbound creates early connections by default.\n\ntype Outbound interface {\n\tType() string\n\tTag() string\n\tNetwork() []string\n\tDependencies() []string\n\tN.Dialer\n}\n\ntype OutboundWithPreferredRoutes interface {\n\tOutbound\n\tPreferredDomain(domain string) bool\n\tPreferredAddress(address netip.Addr) bool\n}\n\ntype DirectRouteOutbound interface {\n\tOutbound\n\tNewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)\n}\n\ntype OutboundRegistry interface {\n\toption.OutboundOptionsRegistry\n\tCreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)\n}\n\ntype OutboundManager interface {\n\tLifecycle\n\tOutbounds() []Outbound\n\tOutbound(tag string) (Outbound, bool)\n\tDefault() Outbound\n\tRemove(tag string) error\n\tCreate(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error\n}\n"
  },
  {
    "path": "adapter/platform.go",
    "content": "package adapter\n\nimport (\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\ntype PlatformInterface interface {\n\tInitialize(networkManager NetworkManager) error\n\n\tUsePlatformAutoDetectInterfaceControl() bool\n\tAutoDetectInterfaceControl(fd int) error\n\n\tUsePlatformInterface() bool\n\tOpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)\n\n\tUsePlatformDefaultInterfaceMonitor() bool\n\tCreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor\n\n\tUsePlatformNetworkInterfaces() bool\n\tNetworkInterfaces() ([]NetworkInterface, error)\n\n\tUnderNetworkExtension() bool\n\tNetworkExtensionIncludeAllNetworks() bool\n\n\tClearDNSCache()\n\tRequestPermissionForWIFIState() error\n\tReadWIFIState() WIFIState\n\tSystemCertificates() []string\n\n\tUsePlatformConnectionOwnerFinder() bool\n\tFindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error)\n\n\tUsePlatformWIFIMonitor() bool\n\n\tUsePlatformNotification() bool\n\tSendNotification(notification *Notification) error\n\n\tUsePlatformNeighborResolver() bool\n\tStartNeighborMonitor(listener NeighborUpdateListener) error\n\tCloseNeighborMonitor(listener NeighborUpdateListener) error\n}\n\ntype FindConnectionOwnerRequest struct {\n\tIpProtocol         int32\n\tSourceAddress      string\n\tSourcePort         int32\n\tDestinationAddress string\n\tDestinationPort    int32\n}\n\ntype ConnectionOwner struct {\n\tProcessID          uint32\n\tUserId             int32\n\tUserName           string\n\tProcessPath        string\n\tAndroidPackageName string\n}\n\ntype Notification struct {\n\tIdentifier string\n\tTypeName   string\n\tTypeID     int32\n\tTitle      string\n\tSubtitle   string\n\tBody       string\n\tOpenURL    string\n}\n\ntype SystemProxyStatus struct {\n\tAvailable bool\n\tEnabled   bool\n}\n"
  },
  {
    "path": "adapter/prestart.go",
    "content": "package adapter\n"
  },
  {
    "path": "adapter/router.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-tun\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\n\t\"go4.org/netipx\"\n)\n\ntype Router interface {\n\tLifecycle\n\tConnectionRouter\n\tPreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error)\n\tConnectionRouterEx\n\tRuleSet(tag string) (RuleSet, bool)\n\tRules() []Rule\n\tNeedFindProcess() bool\n\tNeedFindNeighbor() bool\n\tNeighborResolver() NeighborResolver\n\tAppendTracker(tracker ConnectionTracker)\n\tResetNetwork()\n}\n\ntype ConnectionTracker interface {\n\tRoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn\n\tRoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn\n}\n\n// Deprecated: Use ConnectionRouterEx instead.\ntype ConnectionRouter interface {\n\tRouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error\n\tRoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error\n}\n\ntype ConnectionRouterEx interface {\n\tConnectionRouter\n\tRouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)\n\tRoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)\n}\n\ntype RuleSet interface {\n\tName() string\n\tStartContext(ctx context.Context, startContext *HTTPStartContext) error\n\tPostStart() error\n\tMetadata() RuleSetMetadata\n\tExtractIPSet() []*netipx.IPSet\n\tIncRef()\n\tDecRef()\n\tCleanup()\n\tRegisterCallback(callback RuleSetUpdateCallback) *list.Element[RuleSetUpdateCallback]\n\tUnregisterCallback(element *list.Element[RuleSetUpdateCallback])\n\tClose() error\n\tHeadlessRule\n}\n\ntype RuleSetUpdateCallback func(it RuleSet)\n\ntype RuleSetMetadata struct {\n\tContainsProcessRule bool\n\tContainsWIFIRule    bool\n\tContainsIPCIDRRule  bool\n}\ntype HTTPStartContext struct {\n\tctx             context.Context\n\taccess          sync.Mutex\n\thttpClientCache map[string]*http.Client\n}\n\nfunc NewHTTPStartContext(ctx context.Context) *HTTPStartContext {\n\treturn &HTTPStartContext{\n\t\tctx:             ctx,\n\t\thttpClientCache: make(map[string]*http.Client),\n\t}\n}\n\nfunc (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tif httpClient, loaded := c.httpClientCache[detour]; loaded {\n\t\treturn httpClient\n\t}\n\thttpClient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tForceAttemptHTTP2:   true,\n\t\t\tTLSHandshakeTimeout: C.TCPTimeout,\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\treturn dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t\t\t},\n\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\tTime:    ntp.TimeFuncFromContext(c.ctx),\n\t\t\t\tRootCAs: RootPoolFromContext(c.ctx),\n\t\t\t},\n\t\t},\n\t}\n\tc.httpClientCache[detour] = httpClient\n\treturn httpClient\n}\n\nfunc (c *HTTPStartContext) Close() {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tfor _, client := range c.httpClientCache {\n\t\tclient.CloseIdleConnections()\n\t}\n}\n"
  },
  {
    "path": "adapter/rule.go",
    "content": "package adapter\n\nimport (\n\tC \"github.com/sagernet/sing-box/constant\"\n)\n\ntype HeadlessRule interface {\n\tMatch(metadata *InboundContext) bool\n\tString() string\n}\n\ntype Rule interface {\n\tHeadlessRule\n\tSimpleLifecycle\n\tType() string\n\tAction() RuleAction\n}\n\ntype DNSRule interface {\n\tRule\n\tWithAddressLimit() bool\n\tMatchAddressLimit(metadata *InboundContext) bool\n}\n\ntype RuleAction interface {\n\tType() string\n\tString() string\n}\n\nfunc IsFinalAction(action RuleAction) bool {\n\tswitch action.Type() {\n\tcase C.RuleActionTypeSniff, C.RuleActionTypeResolve:\n\t\treturn false\n\tdefault:\n\t\treturn true\n\t}\n}\n"
  },
  {
    "path": "adapter/service/adapter.go",
    "content": "package service\n\ntype Adapter struct {\n\tserviceType string\n\tserviceTag  string\n}\n\nfunc NewAdapter(serviceType string, serviceTag string) Adapter {\n\treturn Adapter{\n\t\tserviceType: serviceType,\n\t\tserviceTag:  serviceTag,\n\t}\n}\n\nfunc (a *Adapter) Type() string {\n\treturn a.serviceType\n}\n\nfunc (a *Adapter) Tag() string {\n\treturn a.serviceTag\n}\n"
  },
  {
    "path": "adapter/service/manager.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/taskmonitor\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ adapter.ServiceManager = (*Manager)(nil)\n\ntype Manager struct {\n\tlogger       log.ContextLogger\n\tregistry     adapter.ServiceRegistry\n\taccess       sync.Mutex\n\tstarted      bool\n\tstage        adapter.StartStage\n\tservices     []adapter.Service\n\tserviceByTag map[string]adapter.Service\n}\n\nfunc NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {\n\treturn &Manager{\n\t\tlogger:       logger,\n\t\tregistry:     registry,\n\t\tserviceByTag: make(map[string]adapter.Service),\n\t}\n}\n\nfunc (m *Manager) Start(stage adapter.StartStage) error {\n\tm.access.Lock()\n\tif m.started && m.stage >= stage {\n\t\tpanic(\"already started\")\n\t}\n\tm.started = true\n\tm.stage = stage\n\tservices := m.services\n\tm.access.Unlock()\n\tfor _, service := range services {\n\t\tname := \"service/\" + service.Type() + \"[\" + service.Tag() + \"]\"\n\t\tm.logger.Trace(stage, \" \", name)\n\t\tstartTime := time.Now()\n\t\terr := adapter.LegacyStart(service, stage)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, stage, \" \", name)\n\t\t}\n\t\tm.logger.Trace(stage, \" \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Close() error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tif !m.started {\n\t\treturn nil\n\t}\n\tm.started = false\n\tservices := m.services\n\tm.services = nil\n\tmonitor := taskmonitor.New(m.logger, C.StopTimeout)\n\tvar err error\n\tfor _, service := range services {\n\t\tname := \"service/\" + service.Type() + \"[\" + service.Tag() + \"]\"\n\t\tm.logger.Trace(\"close \", name)\n\t\tstartTime := time.Now()\n\t\tmonitor.Start(\"close \", name)\n\t\terr = E.Append(err, service.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close \", name)\n\t\t})\n\t\tmonitor.Finish()\n\t\tm.logger.Trace(\"close \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Services() []adapter.Service {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\treturn m.services\n}\n\nfunc (m *Manager) Get(tag string) (adapter.Service, bool) {\n\tm.access.Lock()\n\tservice, found := m.serviceByTag[tag]\n\tm.access.Unlock()\n\treturn service, found\n}\n\nfunc (m *Manager) Remove(tag string) error {\n\tm.access.Lock()\n\tservice, found := m.serviceByTag[tag]\n\tif !found {\n\t\tm.access.Unlock()\n\t\treturn os.ErrInvalid\n\t}\n\tdelete(m.serviceByTag, tag)\n\tindex := common.Index(m.services, func(it adapter.Service) bool {\n\t\treturn it == service\n\t})\n\tif index == -1 {\n\t\tpanic(\"invalid service index\")\n\t}\n\tm.services = append(m.services[:index], m.services[index+1:]...)\n\tstarted := m.started\n\tm.access.Unlock()\n\tif started {\n\t\treturn service.Close()\n\t}\n\treturn nil\n}\n\nfunc (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {\n\tservice, err := m.registry.Create(ctx, logger, tag, serviceType, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tif m.started {\n\t\tname := \"service/\" + service.Type() + \"[\" + service.Tag() + \"]\"\n\t\tfor _, stage := range adapter.ListStartStages {\n\t\t\tm.logger.Trace(stage, \" \", name)\n\t\t\tstartTime := time.Now()\n\t\t\terr = adapter.LegacyStart(service, stage)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, stage, \" \", name)\n\t\t\t}\n\t\t\tm.logger.Trace(stage, \" \", name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t\t}\n\t}\n\tif existsService, loaded := m.serviceByTag[tag]; loaded {\n\t\tif m.started {\n\t\t\terr = existsService.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"close service/\", existsService.Type(), \"[\", existsService.Tag(), \"]\")\n\t\t\t}\n\t\t}\n\t\texistsIndex := common.Index(m.services, func(it adapter.Service) bool {\n\t\t\treturn it == existsService\n\t\t})\n\t\tif existsIndex == -1 {\n\t\t\tpanic(\"invalid service index\")\n\t\t}\n\t\tm.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)\n\t}\n\tm.services = append(m.services, service)\n\tm.serviceByTag[tag] = service\n\treturn nil\n}\n"
  },
  {
    "path": "adapter/service/registry.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error)\n\nfunc Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {\n\tregistry.register(outboundType, func() any {\n\t\treturn new(Options)\n\t}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) {\n\t\tvar options *Options\n\t\tif rawOptions != nil {\n\t\t\toptions = rawOptions.(*Options)\n\t\t}\n\t\treturn constructor(ctx, logger, tag, common.PtrValueOrDefault(options))\n\t})\n}\n\nvar _ adapter.ServiceRegistry = (*Registry)(nil)\n\ntype (\n\toptionsConstructorFunc func() any\n\tconstructorFunc        func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error)\n)\n\ntype Registry struct {\n\taccess      sync.Mutex\n\toptionsType map[string]optionsConstructorFunc\n\tconstructor map[string]constructorFunc\n}\n\nfunc NewRegistry() *Registry {\n\treturn &Registry{\n\t\toptionsType: make(map[string]optionsConstructorFunc),\n\t\tconstructor: make(map[string]constructorFunc),\n\t}\n}\n\nfunc (m *Registry) CreateOptions(outboundType string) (any, bool) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\toptionsConstructor, loaded := m.optionsType[outboundType]\n\tif !loaded {\n\t\treturn nil, false\n\t}\n\treturn optionsConstructor(), true\n}\n\nfunc (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tconstructor, loaded := m.constructor[outboundType]\n\tif !loaded {\n\t\treturn nil, E.New(\"outbound type not found: \" + outboundType)\n\t}\n\treturn constructor(ctx, logger, tag, options)\n}\n\nfunc (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tm.optionsType[outboundType] = optionsConstructor\n\tm.constructor[outboundType] = constructor\n}\n"
  },
  {
    "path": "adapter/service.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n)\n\ntype Service interface {\n\tLifecycle\n\tType() string\n\tTag() string\n}\n\ntype ServiceRegistry interface {\n\toption.ServiceOptionsRegistry\n\tCreate(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error)\n}\n\ntype ServiceManager interface {\n\tLifecycle\n\tServices() []Service\n\tGet(tag string) (Service, bool)\n\tRemove(tag string) error\n\tCreate(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error\n}\n"
  },
  {
    "path": "adapter/ssm.go",
    "content": "package adapter\n\nimport (\n\t\"net\"\n\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype ManagedSSMServer interface {\n\tInbound\n\tSetTracker(tracker SSMTracker)\n\tUpdateUsers(users []string, uPSKs []string) error\n}\n\ntype SSMTracker interface {\n\tTrackConnection(conn net.Conn, metadata InboundContext) net.Conn\n\tTrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn\n}\n"
  },
  {
    "path": "adapter/time.go",
    "content": "package adapter\n\nimport \"time\"\n\ntype TimeService interface {\n\tSimpleLifecycle\n\tTimeFunc() func() time.Time\n}\n"
  },
  {
    "path": "adapter/upstream.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype (\n\tConnectionHandlerFuncEx       = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)\n\tPacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)\n)\n\nfunc NewUpstreamHandlerEx(\n\tmetadata InboundContext,\n\tconnectionHandler ConnectionHandlerFuncEx,\n\tpacketHandler PacketConnectionHandlerFuncEx,\n) UpstreamHandlerAdapterEx {\n\treturn &myUpstreamHandlerWrapperEx{\n\t\tmetadata:          metadata,\n\t\tconnectionHandler: connectionHandler,\n\t\tpacketHandler:     packetHandler,\n\t}\n}\n\nvar _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil)\n\ntype myUpstreamHandlerWrapperEx struct {\n\tmetadata          InboundContext\n\tconnectionHandler ConnectionHandlerFuncEx\n\tpacketHandler     PacketConnectionHandlerFuncEx\n}\n\nfunc (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tmyMetadata := w.metadata\n\tif source.IsValid() {\n\t\tmyMetadata.Source = source\n\t}\n\tif destination.IsValid() {\n\t\tmyMetadata.Destination = destination\n\t}\n\tw.connectionHandler(ctx, conn, myMetadata, onClose)\n}\n\nfunc (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tmyMetadata := w.metadata\n\tif source.IsValid() {\n\t\tmyMetadata.Source = source\n\t}\n\tif destination.IsValid() {\n\t\tmyMetadata.Destination = destination\n\t}\n\tw.packetHandler(ctx, conn, myMetadata, onClose)\n}\n\nvar _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil)\n\ntype myUpstreamContextHandlerWrapperEx struct {\n\tconnectionHandler ConnectionHandlerFuncEx\n\tpacketHandler     PacketConnectionHandlerFuncEx\n}\n\nfunc NewUpstreamContextHandlerEx(\n\tconnectionHandler ConnectionHandlerFuncEx,\n\tpacketHandler PacketConnectionHandlerFuncEx,\n) UpstreamHandlerAdapterEx {\n\treturn &myUpstreamContextHandlerWrapperEx{\n\t\tconnectionHandler: connectionHandler,\n\t\tpacketHandler:     packetHandler,\n\t}\n}\n\nfunc (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\t_, myMetadata := ExtendContext(ctx)\n\tif source.IsValid() {\n\t\tmyMetadata.Source = source\n\t}\n\tif destination.IsValid() {\n\t\tmyMetadata.Destination = destination\n\t}\n\tw.connectionHandler(ctx, conn, *myMetadata, onClose)\n}\n\nfunc (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\t_, myMetadata := ExtendContext(ctx)\n\tif source.IsValid() {\n\t\tmyMetadata.Source = source\n\t}\n\tif destination.IsValid() {\n\t\tmyMetadata.Destination = destination\n\t}\n\tw.packetHandler(ctx, conn, *myMetadata, onClose)\n}\n\nfunc NewRouteHandlerEx(\n\tmetadata InboundContext,\n\trouter ConnectionRouterEx,\n) UpstreamHandlerAdapterEx {\n\treturn &routeHandlerWrapperEx{\n\t\tmetadata: metadata,\n\t\trouter:   router,\n\t}\n}\n\nvar _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil)\n\ntype routeHandlerWrapperEx struct {\n\tmetadata InboundContext\n\trouter   ConnectionRouterEx\n}\n\nfunc (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tif source.IsValid() {\n\t\tr.metadata.Source = source\n\t}\n\tif destination.IsValid() {\n\t\tr.metadata.Destination = destination\n\t}\n\tr.router.RouteConnectionEx(ctx, conn, r.metadata, onClose)\n}\n\nfunc (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tif source.IsValid() {\n\t\tr.metadata.Source = source\n\t}\n\tif destination.IsValid() {\n\t\tr.metadata.Destination = destination\n\t}\n\tr.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose)\n}\n\nfunc NewRouteContextHandlerEx(\n\trouter ConnectionRouterEx,\n) UpstreamHandlerAdapterEx {\n\treturn &routeContextHandlerWrapperEx{\n\t\trouter: router,\n\t}\n}\n\nvar _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil)\n\ntype routeContextHandlerWrapperEx struct {\n\trouter ConnectionRouterEx\n}\n\nfunc (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\t_, metadata := ExtendContext(ctx)\n\tif source.IsValid() {\n\t\tmetadata.Source = source\n\t}\n\tif destination.IsValid() {\n\t\tmetadata.Destination = destination\n\t}\n\tr.router.RouteConnectionEx(ctx, conn, *metadata, onClose)\n}\n\nfunc (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\t_, metadata := ExtendContext(ctx)\n\tif source.IsValid() {\n\t\tmetadata.Source = source\n\t}\n\tif destination.IsValid() {\n\t\tmetadata.Destination = destination\n\t}\n\tr.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose)\n}\n"
  },
  {
    "path": "adapter/upstream_legacy.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype (\n\t// Deprecated\n\tConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error\n\t// Deprecated\n\tPacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error\n)\n\n// Deprecated\n//\n//nolint:staticcheck\nfunc NewUpstreamHandler(\n\tmetadata InboundContext,\n\tconnectionHandler ConnectionHandlerFunc,\n\tpacketHandler PacketConnectionHandlerFunc,\n\terrorHandler E.Handler,\n) UpstreamHandlerAdapter {\n\treturn &myUpstreamHandlerWrapper{\n\t\tmetadata:          metadata,\n\t\tconnectionHandler: connectionHandler,\n\t\tpacketHandler:     packetHandler,\n\t\terrorHandler:      errorHandler,\n\t}\n}\n\nvar _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)\n\n// Deprecated: use myUpstreamHandlerWrapperEx instead.\n//\n//nolint:staticcheck\ntype myUpstreamHandlerWrapper struct {\n\tmetadata          InboundContext\n\tconnectionHandler ConnectionHandlerFunc\n\tpacketHandler     PacketConnectionHandlerFunc\n\terrorHandler      E.Handler\n}\n\n// Deprecated: use myUpstreamHandlerWrapperEx instead.\nfunc (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {\n\tmyMetadata := w.metadata\n\tif metadata.Source.IsValid() {\n\t\tmyMetadata.Source = metadata.Source\n\t}\n\tif metadata.Destination.IsValid() {\n\t\tmyMetadata.Destination = metadata.Destination\n\t}\n\treturn w.connectionHandler(ctx, conn, myMetadata)\n}\n\n// Deprecated: use myUpstreamHandlerWrapperEx instead.\nfunc (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {\n\tmyMetadata := w.metadata\n\tif metadata.Source.IsValid() {\n\t\tmyMetadata.Source = metadata.Source\n\t}\n\tif metadata.Destination.IsValid() {\n\t\tmyMetadata.Destination = metadata.Destination\n\t}\n\treturn w.packetHandler(ctx, conn, myMetadata)\n}\n\n// Deprecated: use myUpstreamHandlerWrapperEx instead.\nfunc (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {\n\tw.errorHandler.NewError(ctx, err)\n}\n\n// Deprecated: removed\nfunc UpstreamMetadata(metadata InboundContext) M.Metadata {\n\treturn M.Metadata{\n\t\tSource:      metadata.Source.Unwrap(),\n\t\tDestination: metadata.Destination.Unwrap(),\n\t}\n}\n\n// Deprecated: Use NewUpstreamContextHandlerEx instead.\ntype myUpstreamContextHandlerWrapper struct {\n\tconnectionHandler ConnectionHandlerFunc\n\tpacketHandler     PacketConnectionHandlerFunc\n\terrorHandler      E.Handler\n}\n\n// Deprecated: Use NewUpstreamContextHandlerEx instead.\nfunc NewUpstreamContextHandler(\n\tconnectionHandler ConnectionHandlerFunc,\n\tpacketHandler PacketConnectionHandlerFunc,\n\terrorHandler E.Handler,\n) UpstreamHandlerAdapter {\n\treturn &myUpstreamContextHandlerWrapper{\n\t\tconnectionHandler: connectionHandler,\n\t\tpacketHandler:     packetHandler,\n\t\terrorHandler:      errorHandler,\n\t}\n}\n\n// Deprecated: Use NewUpstreamContextHandlerEx instead.\nfunc (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {\n\tmyMetadata := ContextFrom(ctx)\n\tif metadata.Source.IsValid() {\n\t\tmyMetadata.Source = metadata.Source\n\t}\n\tif metadata.Destination.IsValid() {\n\t\tmyMetadata.Destination = metadata.Destination\n\t}\n\treturn w.connectionHandler(ctx, conn, *myMetadata)\n}\n\n// Deprecated: Use NewUpstreamContextHandlerEx instead.\nfunc (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {\n\tmyMetadata := ContextFrom(ctx)\n\tif metadata.Source.IsValid() {\n\t\tmyMetadata.Source = metadata.Source\n\t}\n\tif metadata.Destination.IsValid() {\n\t\tmyMetadata.Destination = metadata.Destination\n\t}\n\treturn w.packetHandler(ctx, conn, *myMetadata)\n}\n\n// Deprecated: Use NewUpstreamContextHandlerEx instead.\nfunc (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {\n\tw.errorHandler.NewError(ctx, err)\n}\n\n// Deprecated: Use ConnectionRouterEx instead.\nfunc NewRouteHandler(\n\tmetadata InboundContext,\n\trouter ConnectionRouter,\n\tlogger logger.ContextLogger,\n) UpstreamHandlerAdapter {\n\treturn &routeHandlerWrapper{\n\t\tmetadata: metadata,\n\t\trouter:   router,\n\t\tlogger:   logger,\n\t}\n}\n\n// Deprecated: Use ConnectionRouterEx instead.\nfunc NewRouteContextHandler(\n\trouter ConnectionRouter,\n\tlogger logger.ContextLogger,\n) UpstreamHandlerAdapter {\n\treturn &routeContextHandlerWrapper{\n\t\trouter: router,\n\t\tlogger: logger,\n\t}\n}\n\nvar _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)\n\n// Deprecated: Use ConnectionRouterEx instead.\n//\n//nolint:staticcheck\ntype routeHandlerWrapper struct {\n\tmetadata InboundContext\n\trouter   ConnectionRouter\n\tlogger   logger.ContextLogger\n}\n\n// Deprecated: Use ConnectionRouterEx instead.\nfunc (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {\n\tmyMetadata := w.metadata\n\tif metadata.Source.IsValid() {\n\t\tmyMetadata.Source = metadata.Source\n\t}\n\tif metadata.Destination.IsValid() {\n\t\tmyMetadata.Destination = metadata.Destination\n\t}\n\treturn w.router.RouteConnection(ctx, conn, myMetadata)\n}\n\n// Deprecated: Use ConnectionRouterEx instead.\nfunc (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {\n\tmyMetadata := w.metadata\n\tif metadata.Source.IsValid() {\n\t\tmyMetadata.Source = metadata.Source\n\t}\n\tif metadata.Destination.IsValid() {\n\t\tmyMetadata.Destination = metadata.Destination\n\t}\n\treturn w.router.RoutePacketConnection(ctx, conn, myMetadata)\n}\n\n// Deprecated: Use ConnectionRouterEx instead.\nfunc (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {\n\tw.logger.ErrorContext(ctx, err)\n}\n\nvar _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)\n\n// Deprecated: Use ConnectionRouterEx instead.\ntype routeContextHandlerWrapper struct {\n\trouter ConnectionRouter\n\tlogger logger.ContextLogger\n}\n\n// Deprecated: Use ConnectionRouterEx instead.\nfunc (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {\n\tmyMetadata := ContextFrom(ctx)\n\tif metadata.Source.IsValid() {\n\t\tmyMetadata.Source = metadata.Source\n\t}\n\tif metadata.Destination.IsValid() {\n\t\tmyMetadata.Destination = metadata.Destination\n\t}\n\treturn w.router.RouteConnection(ctx, conn, *myMetadata)\n}\n\n// Deprecated: Use ConnectionRouterEx instead.\nfunc (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {\n\tmyMetadata := ContextFrom(ctx)\n\tif metadata.Source.IsValid() {\n\t\tmyMetadata.Source = metadata.Source\n\t}\n\tif metadata.Destination.IsValid() {\n\t\tmyMetadata.Destination = metadata.Destination\n\t}\n\treturn w.router.RoutePacketConnection(ctx, conn, *myMetadata)\n}\n\n// Deprecated: Use ConnectionRouterEx instead.\nfunc (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {\n\tw.logger.ErrorContext(ctx, err)\n}\n"
  },
  {
    "path": "adapter/v2ray.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype V2RayServerTransport interface {\n\tNetwork() []string\n\tServe(listener net.Listener) error\n\tServePacket(listener net.PacketConn) error\n\tClose() error\n}\n\ntype V2RayServerTransportHandler interface {\n\tN.TCPConnectionHandlerEx\n}\n\ntype V2RayClientTransport interface {\n\tDialContext(ctx context.Context) (net.Conn, error)\n\tClose() error\n}\n"
  },
  {
    "path": "box.go",
    "content": "package box\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/endpoint\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\tboxService \"github.com/sagernet/sing-box/adapter/service\"\n\t\"github.com/sagernet/sing-box/common/certificate\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/taskmonitor\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport/local\"\n\t\"github.com/sagernet/sing-box/experimental\"\n\t\"github.com/sagernet/sing-box/experimental/cachefile\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/protocol/direct\"\n\t\"github.com/sagernet/sing-box/route\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/pause\"\n)\n\nvar _ adapter.SimpleLifecycle = (*Box)(nil)\n\ntype Box struct {\n\tcreatedAt       time.Time\n\tlogFactory      log.Factory\n\tlogger          log.ContextLogger\n\tnetwork         *route.NetworkManager\n\tendpoint        *endpoint.Manager\n\tinbound         *inbound.Manager\n\toutbound        *outbound.Manager\n\tservice         *boxService.Manager\n\tdnsTransport    *dns.TransportManager\n\tdnsRouter       *dns.Router\n\tconnection      *route.ConnectionManager\n\trouter          *route.Router\n\tinternalService []adapter.LifecycleService\n\tdone            chan struct{}\n}\n\ntype Options struct {\n\toption.Options\n\tContext           context.Context\n\tPlatformLogWriter log.PlatformWriter\n}\n\nfunc Context(\n\tctx context.Context,\n\tinboundRegistry adapter.InboundRegistry,\n\toutboundRegistry adapter.OutboundRegistry,\n\tendpointRegistry adapter.EndpointRegistry,\n\tdnsTransportRegistry adapter.DNSTransportRegistry,\n\tserviceRegistry adapter.ServiceRegistry,\n) context.Context {\n\tif service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||\n\t\tservice.FromContext[adapter.InboundRegistry](ctx) == nil {\n\t\tctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)\n\t\tctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)\n\t}\n\tif service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||\n\t\tservice.FromContext[adapter.OutboundRegistry](ctx) == nil {\n\t\tctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)\n\t\tctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)\n\t}\n\tif service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||\n\t\tservice.FromContext[adapter.EndpointRegistry](ctx) == nil {\n\t\tctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)\n\t\tctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)\n\t}\n\tif service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {\n\t\tctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)\n\t\tctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)\n\t}\n\tif service.FromContext[adapter.ServiceRegistry](ctx) == nil {\n\t\tctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)\n\t\tctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)\n\t}\n\treturn ctx\n}\n\nfunc New(options Options) (*Box, error) {\n\tcreatedAt := time.Now()\n\tctx := options.Context\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\tctx = service.ContextWithDefaultRegistry(ctx)\n\n\tendpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)\n\tinboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)\n\toutboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)\n\tdnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)\n\tserviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)\n\n\tif endpointRegistry == nil {\n\t\treturn nil, E.New(\"missing endpoint registry in context\")\n\t}\n\tif inboundRegistry == nil {\n\t\treturn nil, E.New(\"missing inbound registry in context\")\n\t}\n\tif outboundRegistry == nil {\n\t\treturn nil, E.New(\"missing outbound registry in context\")\n\t}\n\tif dnsTransportRegistry == nil {\n\t\treturn nil, E.New(\"missing DNS transport registry in context\")\n\t}\n\tif serviceRegistry == nil {\n\t\treturn nil, E.New(\"missing service registry in context\")\n\t}\n\n\tctx = pause.WithDefaultManager(ctx)\n\texperimentalOptions := common.PtrValueOrDefault(options.Experimental)\n\terr := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar needCacheFile bool\n\tvar needClashAPI bool\n\tvar needV2RayAPI bool\n\tif experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil {\n\t\tneedCacheFile = true\n\t}\n\tif experimentalOptions.ClashAPI != nil || options.PlatformLogWriter != nil {\n\t\tneedClashAPI = true\n\t}\n\tif experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != \"\" {\n\t\tneedV2RayAPI = true\n\t}\n\tplatformInterface := service.FromContext[adapter.PlatformInterface](ctx)\n\tvar defaultLogWriter io.Writer\n\tif platformInterface != nil {\n\t\tdefaultLogWriter = io.Discard\n\t}\n\tlogFactory, err := log.New(log.Options{\n\t\tContext:        ctx,\n\t\tOptions:        common.PtrValueOrDefault(options.Log),\n\t\tObservable:     needClashAPI,\n\t\tDefaultWriter:  defaultLogWriter,\n\t\tBaseTime:       createdAt,\n\t\tPlatformWriter: options.PlatformLogWriter,\n\t})\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"create log factory\")\n\t}\n\n\tvar internalServices []adapter.LifecycleService\n\tcertificateOptions := common.PtrValueOrDefault(options.Certificate)\n\tif C.IsAndroid || certificateOptions.Store != \"\" && certificateOptions.Store != C.CertificateStoreSystem ||\n\t\tlen(certificateOptions.Certificate) > 0 ||\n\t\tlen(certificateOptions.CertificatePath) > 0 ||\n\t\tlen(certificateOptions.CertificateDirectoryPath) > 0 {\n\t\tcertificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger(\"certificate\"), certificateOptions)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tservice.MustRegister[adapter.CertificateStore](ctx, certificateStore)\n\t\tinternalServices = append(internalServices, certificateStore)\n\t}\n\n\trouteOptions := common.PtrValueOrDefault(options.Route)\n\tdnsOptions := common.PtrValueOrDefault(options.DNS)\n\tendpointManager := endpoint.NewManager(logFactory.NewLogger(\"endpoint\"), endpointRegistry)\n\tinboundManager := inbound.NewManager(logFactory.NewLogger(\"inbound\"), inboundRegistry, endpointManager)\n\toutboundManager := outbound.NewManager(logFactory.NewLogger(\"outbound\"), outboundRegistry, endpointManager, routeOptions.Final)\n\tdnsTransportManager := dns.NewTransportManager(logFactory.NewLogger(\"dns/transport\"), dnsTransportRegistry, outboundManager, dnsOptions.Final)\n\tserviceManager := boxService.NewManager(logFactory.NewLogger(\"service\"), serviceRegistry)\n\tservice.MustRegister[adapter.EndpointManager](ctx, endpointManager)\n\tservice.MustRegister[adapter.InboundManager](ctx, inboundManager)\n\tservice.MustRegister[adapter.OutboundManager](ctx, outboundManager)\n\tservice.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)\n\tservice.MustRegister[adapter.ServiceManager](ctx, serviceManager)\n\tdnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)\n\tservice.MustRegister[adapter.DNSRouter](ctx, dnsRouter)\n\tnetworkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger(\"network\"), routeOptions, dnsOptions)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"initialize network manager\")\n\t}\n\tservice.MustRegister[adapter.NetworkManager](ctx, networkManager)\n\tconnectionManager := route.NewConnectionManager(logFactory.NewLogger(\"connection\"))\n\tservice.MustRegister[adapter.ConnectionManager](ctx, connectionManager)\n\trouter := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)\n\tservice.MustRegister[adapter.Router](ctx, router)\n\terr = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"initialize router\")\n\t}\n\tntpOptions := common.PtrValueOrDefault(options.NTP)\n\tvar timeService *tls.TimeServiceWrapper\n\tif ntpOptions.Enabled {\n\t\ttimeService = new(tls.TimeServiceWrapper)\n\t\tservice.MustRegister[ntp.TimeService](ctx, timeService)\n\t}\n\tfor i, transportOptions := range dnsOptions.Servers {\n\t\tvar tag string\n\t\tif transportOptions.Tag != \"\" {\n\t\t\ttag = transportOptions.Tag\n\t\t} else {\n\t\t\ttag = F.ToString(i)\n\t\t}\n\t\terr = dnsTransportManager.Create(\n\t\t\tctx,\n\t\t\tlogFactory.NewLogger(F.ToString(\"dns/\", transportOptions.Type, \"[\", tag, \"]\")),\n\t\t\ttag,\n\t\t\ttransportOptions.Type,\n\t\t\ttransportOptions.Options,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"initialize DNS server[\", i, \"]\")\n\t\t}\n\t}\n\terr = dnsRouter.Initialize(dnsOptions.Rules)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"initialize dns router\")\n\t}\n\tfor i, endpointOptions := range options.Endpoints {\n\t\tvar tag string\n\t\tif endpointOptions.Tag != \"\" {\n\t\t\ttag = endpointOptions.Tag\n\t\t} else {\n\t\t\ttag = F.ToString(i)\n\t\t}\n\t\tendpointCtx := ctx\n\t\tif tag != \"\" {\n\t\t\t// TODO: remove this\n\t\t\tendpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{\n\t\t\t\tOutbound: tag,\n\t\t\t})\n\t\t}\n\t\terr = endpointManager.Create(\n\t\t\tendpointCtx,\n\t\t\trouter,\n\t\t\tlogFactory.NewLogger(F.ToString(\"endpoint/\", endpointOptions.Type, \"[\", tag, \"]\")),\n\t\t\ttag,\n\t\t\tendpointOptions.Type,\n\t\t\tendpointOptions.Options,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"initialize endpoint[\", i, \"]\")\n\t\t}\n\t}\n\tfor i, inboundOptions := range options.Inbounds {\n\t\tvar tag string\n\t\tif inboundOptions.Tag != \"\" {\n\t\t\ttag = inboundOptions.Tag\n\t\t} else {\n\t\t\ttag = F.ToString(i)\n\t\t}\n\t\terr = inboundManager.Create(\n\t\t\tctx,\n\t\t\trouter,\n\t\t\tlogFactory.NewLogger(F.ToString(\"inbound/\", inboundOptions.Type, \"[\", tag, \"]\")),\n\t\t\ttag,\n\t\t\tinboundOptions.Type,\n\t\t\tinboundOptions.Options,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"initialize inbound[\", i, \"]\")\n\t\t}\n\t}\n\tfor i, outboundOptions := range options.Outbounds {\n\t\tvar tag string\n\t\tif outboundOptions.Tag != \"\" {\n\t\t\ttag = outboundOptions.Tag\n\t\t} else {\n\t\t\ttag = F.ToString(i)\n\t\t}\n\t\toutboundCtx := ctx\n\t\tif tag != \"\" {\n\t\t\t// TODO: remove this\n\t\t\toutboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{\n\t\t\t\tOutbound: tag,\n\t\t\t})\n\t\t}\n\t\terr = outboundManager.Create(\n\t\t\toutboundCtx,\n\t\t\trouter,\n\t\t\tlogFactory.NewLogger(F.ToString(\"outbound/\", outboundOptions.Type, \"[\", tag, \"]\")),\n\t\t\ttag,\n\t\t\toutboundOptions.Type,\n\t\t\toutboundOptions.Options,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"initialize outbound[\", i, \"]\")\n\t\t}\n\t}\n\tfor i, serviceOptions := range options.Services {\n\t\tvar tag string\n\t\tif serviceOptions.Tag != \"\" {\n\t\t\ttag = serviceOptions.Tag\n\t\t} else {\n\t\t\ttag = F.ToString(i)\n\t\t}\n\t\terr = serviceManager.Create(\n\t\t\tctx,\n\t\t\tlogFactory.NewLogger(F.ToString(\"service/\", serviceOptions.Type, \"[\", tag, \"]\")),\n\t\t\ttag,\n\t\t\tserviceOptions.Type,\n\t\t\tserviceOptions.Options,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"initialize service[\", i, \"]\")\n\t\t}\n\t}\n\toutboundManager.Initialize(func() (adapter.Outbound, error) {\n\t\treturn direct.NewOutbound(\n\t\t\tctx,\n\t\t\trouter,\n\t\t\tlogFactory.NewLogger(\"outbound/direct\"),\n\t\t\t\"direct\",\n\t\t\toption.DirectOutboundOptions{},\n\t\t)\n\t})\n\tdnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {\n\t\treturn local.NewTransport(\n\t\t\tctx,\n\t\t\tlogFactory.NewLogger(\"dns/local\"),\n\t\t\t\"local\",\n\t\t\toption.LocalDNSServerOptions{},\n\t\t)\n\t})\n\tif platformInterface != nil {\n\t\terr = platformInterface.Initialize(networkManager)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"initialize platform interface\")\n\t\t}\n\t}\n\tif needCacheFile {\n\t\tcacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))\n\t\tservice.MustRegister[adapter.CacheFile](ctx, cacheFile)\n\t\tinternalServices = append(internalServices, cacheFile)\n\t}\n\tif needClashAPI {\n\t\tclashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)\n\t\tclashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)\n\t\tclashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"create clash-server\")\n\t\t}\n\t\trouter.AppendTracker(clashServer)\n\t\tservice.MustRegister[adapter.ClashServer](ctx, clashServer)\n\t\tinternalServices = append(internalServices, clashServer)\n\t}\n\tif needV2RayAPI {\n\t\tv2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger(\"v2ray-api\"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"create v2ray-server\")\n\t\t}\n\t\tif v2rayServer.StatsService() != nil {\n\t\t\trouter.AppendTracker(v2rayServer.StatsService())\n\t\t\tinternalServices = append(internalServices, v2rayServer)\n\t\t\tservice.MustRegister[adapter.V2RayServer](ctx, v2rayServer)\n\t\t}\n\t}\n\tif ntpOptions.Enabled {\n\t\tntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain())\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"create NTP service\")\n\t\t}\n\t\tntpService := ntp.NewService(ntp.Options{\n\t\t\tContext:       ctx,\n\t\t\tDialer:        ntpDialer,\n\t\t\tLogger:        logFactory.NewLogger(\"ntp\"),\n\t\t\tServer:        ntpOptions.ServerOptions.Build(),\n\t\t\tInterval:      time.Duration(ntpOptions.Interval),\n\t\t\tWriteToSystem: ntpOptions.WriteToSystem,\n\t\t})\n\t\ttimeService.TimeService = ntpService\n\t\tinternalServices = append(internalServices, adapter.NewLifecycleService(ntpService, \"ntp service\"))\n\t}\n\treturn &Box{\n\t\tnetwork:         networkManager,\n\t\tendpoint:        endpointManager,\n\t\tinbound:         inboundManager,\n\t\toutbound:        outboundManager,\n\t\tdnsTransport:    dnsTransportManager,\n\t\tservice:         serviceManager,\n\t\tdnsRouter:       dnsRouter,\n\t\tconnection:      connectionManager,\n\t\trouter:          router,\n\t\tcreatedAt:       createdAt,\n\t\tlogFactory:      logFactory,\n\t\tlogger:          logFactory.Logger(),\n\t\tinternalService: internalServices,\n\t\tdone:            make(chan struct{}),\n\t}, nil\n}\n\nfunc (s *Box) PreStart() error {\n\terr := s.preStart()\n\tif err != nil {\n\t\t// TODO: remove catch error\n\t\tdefer func() {\n\t\t\tv := recover()\n\t\t\tif v != nil {\n\t\t\t\tprintln(err.Error())\n\t\t\t\tdebug.PrintStack()\n\t\t\t\tpanic(\"panic on early close: \" + fmt.Sprint(v))\n\t\t\t}\n\t\t}()\n\t\ts.Close()\n\t\treturn err\n\t}\n\ts.logger.Info(\"sing-box pre-started (\", F.Seconds(time.Since(s.createdAt).Seconds()), \"s)\")\n\treturn nil\n}\n\nfunc (s *Box) Start() error {\n\terr := s.start()\n\tif err != nil {\n\t\t// TODO: remove catch error\n\t\tdefer func() {\n\t\t\tv := recover()\n\t\t\tif v != nil {\n\t\t\t\tprintln(err.Error())\n\t\t\t\tdebug.PrintStack()\n\t\t\t\tprintln(\"panic on early start: \" + fmt.Sprint(v))\n\t\t\t}\n\t\t}()\n\t\ts.Close()\n\t\treturn err\n\t}\n\ts.logger.Info(\"sing-box started (\", F.Seconds(time.Since(s.createdAt).Seconds()), \"s)\")\n\treturn nil\n}\n\nfunc (s *Box) preStart() error {\n\tmonitor := taskmonitor.New(s.logger, C.StartTimeout)\n\tmonitor.Start(\"start logger\")\n\terr := s.logFactory.Start()\n\tmonitor.Finish()\n\tif err != nil {\n\t\treturn E.Cause(err, \"start logger\")\n\t}\n\terr = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *Box) start() error {\n\terr := s.preStart()\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = adapter.StartNamed(s.logger, adapter.StartStateStart, s.internalService)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = adapter.StartNamed(s.logger, adapter.StartStatePostStart, s.internalService)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = adapter.StartNamed(s.logger, adapter.StartStateStarted, s.internalService)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *Box) Close() error {\n\tselect {\n\tcase <-s.done:\n\t\treturn os.ErrClosed\n\tdefault:\n\t\tclose(s.done)\n\t}\n\tvar err error\n\tfor _, closeItem := range []struct {\n\t\tname    string\n\t\tservice adapter.Lifecycle\n\t}{\n\t\t{\"service\", s.service},\n\t\t{\"endpoint\", s.endpoint},\n\t\t{\"inbound\", s.inbound},\n\t\t{\"outbound\", s.outbound},\n\t\t{\"router\", s.router},\n\t\t{\"connection\", s.connection},\n\t\t{\"dns-router\", s.dnsRouter},\n\t\t{\"dns-transport\", s.dnsTransport},\n\t\t{\"network\", s.network},\n\t} {\n\t\ts.logger.Trace(\"close \", closeItem.name)\n\t\tstartTime := time.Now()\n\t\terr = E.Append(err, closeItem.service.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close \", closeItem.name)\n\t\t})\n\t\ts.logger.Trace(\"close \", closeItem.name, \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t}\n\tfor _, lifecycleService := range s.internalService {\n\t\ts.logger.Trace(\"close \", lifecycleService.Name())\n\t\tstartTime := time.Now()\n\t\terr = E.Append(err, lifecycleService.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close \", lifecycleService.Name())\n\t\t})\n\t\ts.logger.Trace(\"close \", lifecycleService.Name(), \" completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\t}\n\ts.logger.Trace(\"close logger\")\n\tstartTime := time.Now()\n\terr = E.Append(err, s.logFactory.Close(), func(err error) error {\n\t\treturn E.Cause(err, \"close logger\")\n\t})\n\ts.logger.Trace(\"close logger completed (\", F.Seconds(time.Since(startTime).Seconds()), \"s)\")\n\treturn err\n}\n\nfunc (s *Box) Network() adapter.NetworkManager {\n\treturn s.network\n}\n\nfunc (s *Box) Router() adapter.Router {\n\treturn s.router\n}\n\nfunc (s *Box) Inbound() adapter.InboundManager {\n\treturn s.inbound\n}\n\nfunc (s *Box) Outbound() adapter.OutboundManager {\n\treturn s.outbound\n}\n\nfunc (s *Box) LogFactory() log.Factory {\n\treturn s.logFactory\n}\n"
  },
  {
    "path": "cmd/internal/app_store_connect/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/asc-go/asc\"\n\t\"github.com/sagernet/sing-box/cmd/internal/build_shared\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\tswitch os.Args[1] {\n\tcase \"next_macos_project_version\":\n\t\terr := fetchMacOSVersion(ctx)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\tcase \"publish_testflight\":\n\t\terr := publishTestflight(ctx)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\tcase \"cancel_app_store\":\n\t\terr := cancelAppStore(ctx, os.Args[2])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\tcase \"prepare_app_store\":\n\t\terr := prepareAppStore(ctx)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\tcase \"publish_app_store\":\n\t\terr := publishAppStore(ctx)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\tdefault:\n\t\tlog.Fatal(\"unknown action: \", os.Args[1])\n\t}\n}\n\nconst (\n\tappID   = \"6673731168\"\n\tgroupID = \"5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda\"\n)\n\nfunc createClient(expireDuration time.Duration) *asc.Client {\n\tprivateKey, err := os.ReadFile(os.Getenv(\"ASC_KEY_PATH\"))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\ttokenConfig, err := asc.NewTokenConfig(os.Getenv(\"ASC_KEY_ID\"), os.Getenv(\"ASC_KEY_ISSUER_ID\"), expireDuration, privateKey)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\treturn asc.NewClient(tokenConfig.Client())\n}\n\nfunc fetchMacOSVersion(ctx context.Context) error {\n\tclient := createClient(time.Minute)\n\tversions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{\n\t\tFilterPlatform: []string{\"MAC_OS\"},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar versionID string\nfindVersion:\n\tfor _, version := range versions.Data {\n\t\tswitch *version.Attributes.AppStoreState {\n\t\tcase asc.AppStoreVersionStateReadyForSale,\n\t\t\tasc.AppStoreVersionStatePendingDeveloperRelease:\n\t\t\tversionID = version.ID\n\t\t\tbreak findVersion\n\t\t}\n\t}\n\tif versionID == \"\" {\n\t\treturn E.New(\"no version found\")\n\t}\n\tlatestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tversionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)\n\tif err != nil {\n\t\treturn E.Cause(err, \"parse version code\")\n\t}\n\tos.Stdout.WriteString(F.ToString(versionInt+1, \"\\n\"))\n\treturn nil\n}\n\nfunc publishTestflight(ctx context.Context) error {\n\tif len(os.Args) < 3 {\n\t\treturn E.New(\"platform required: ios, macos, or tvos\")\n\t}\n\tvar platform asc.Platform\n\tswitch os.Args[2] {\n\tcase \"ios\":\n\t\tplatform = asc.PlatformIOS\n\tcase \"macos\":\n\t\tplatform = asc.PlatformMACOS\n\tcase \"tvos\":\n\t\tplatform = asc.PlatformTVOS\n\tdefault:\n\t\treturn E.New(\"unknown platform: \", os.Args[2])\n\t}\n\n\ttagVersion, err := build_shared.ReadTagVersion()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttag := tagVersion.VersionString()\n\n\treleaseNotes := F.ToString(\"sing-box \", tagVersion.String())\n\tif len(os.Args) >= 4 {\n\t\treleaseNotes = strings.Join(os.Args[3:], \" \")\n\t}\n\n\tclient := createClient(20 * time.Minute)\n\n\tlog.Info(tag, \" list build IDs\")\n\tbuildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {\n\t\treturn it.ID\n\t})\n\n\twaitingForProcess := false\n\tlog.Info(string(platform), \" list builds\")\n\tfor {\n\t\tbuilds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{\n\t\t\tFilterApp:                       []string{appID},\n\t\t\tFilterPreReleaseVersionPlatform: []string{string(platform)},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbuild := builds.Data[0]\n\t\tlog.Info(string(platform), \" \", tag, \" found build: \", build.ID, \" (\", *build.Attributes.Version, \")\")\n\t\tif !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {\n\t\t\tlog.Info(string(platform), \" \", tag, \" waiting for process\")\n\t\t\ttime.Sleep(15 * time.Second)\n\t\t\tcontinue\n\t\t}\n\t\tif *build.Attributes.ProcessingState != \"VALID\" {\n\t\t\twaitingForProcess = true\n\t\t\tlog.Info(string(platform), \" \", tag, \" waiting for process: \", *build.Attributes.ProcessingState)\n\t\t\ttime.Sleep(15 * time.Second)\n\t\t\tcontinue\n\t\t}\n\t\tlog.Info(string(platform), \" \", tag, \" list localizations\")\n\t\tlocalizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlocalization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {\n\t\t\treturn *it.Attributes.Locale == \"en-US\"\n\t\t})\n\t\tif localization.ID == \"\" {\n\t\t\tlog.Fatal(string(platform), \" \", tag, \" no en-US localization found\")\n\t\t}\n\t\tif localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == \"\" {\n\t\t\tlog.Info(string(platform), \" \", tag, \" update localization\")\n\t\t\t_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(releaseNotes))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tlog.Info(string(platform), \" \", tag, \" publish\")\n\t\tresponse, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})\n\t\tif response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {\n\t\t\tlog.Info(\"waiting for process\")\n\t\t\ttime.Sleep(15 * time.Second)\n\t\t\tcontinue\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlog.Info(string(platform), \" \", tag, \" list submissions\")\n\t\tbetaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{\n\t\t\tFilterBuild: []string{build.ID},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(betaSubmissions.Data) == 0 {\n\t\t\tlog.Info(string(platform), \" \", tag, \" create submission\")\n\t\t\t_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)\n\t\t\tif err != nil {\n\t\t\t\tif strings.Contains(err.Error(), \"ANOTHER_BUILD_IN_REVIEW\") {\n\t\t\t\t\tlog.Error(err)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tbreak\n\t}\n\treturn nil\n}\n\nfunc cancelAppStore(ctx context.Context, platform string) error {\n\tswitch platform {\n\tcase \"ios\":\n\t\tplatform = string(asc.PlatformIOS)\n\tcase \"macos\":\n\t\tplatform = string(asc.PlatformMACOS)\n\tcase \"tvos\":\n\t\tplatform = string(asc.PlatformTVOS)\n\t}\n\ttag, err := build_shared.ReadTag()\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient := createClient(time.Minute)\n\tfor {\n\t\tlog.Info(platform, \" list versions\")\n\t\tversions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{\n\t\t\tFilterPlatform: []string{string(platform)},\n\t\t})\n\t\tif isRetryable(response) {\n\t\t\tcontinue\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\t\tversion := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {\n\t\t\treturn *it.Attributes.VersionString == tag\n\t\t})\n\t\tif version.ID == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\tlog.Info(platform, \" \", tag, \" get submission\")\n\t\tsubmission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)\n\t\tif response != nil && response.StatusCode == http.StatusNotFound {\n\t\t\treturn nil\n\t\t}\n\t\tif isRetryable(response) {\n\t\t\tcontinue\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlog.Info(platform, \" \", tag, \" delete submission\")\n\t\t_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc prepareAppStore(ctx context.Context) error {\n\ttag, err := build_shared.ReadTag()\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient := createClient(time.Minute)\n\tfor _, platform := range []asc.Platform{\n\t\tasc.PlatformIOS,\n\t\tasc.PlatformMACOS,\n\t\tasc.PlatformTVOS,\n\t} {\n\t\tlog.Info(string(platform), \" list versions\")\n\t\tversions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{\n\t\t\tFilterPlatform: []string{string(platform)},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tversion := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {\n\t\t\treturn *it.Attributes.VersionString == tag\n\t\t})\n\t\tlog.Info(string(platform), \" \", tag, \" list builds\")\n\t\tbuilds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{\n\t\t\tFilterApp:                       []string{appID},\n\t\t\tFilterPreReleaseVersionPlatform: []string{string(platform)},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(builds.Data) == 0 {\n\t\t\tlog.Fatal(platform, \" \", tag, \" no build found\")\n\t\t}\n\t\tbuildID := common.Ptr(builds.Data[0].ID)\n\t\tif version.ID == \"\" {\n\t\t\tlog.Info(string(platform), \" \", tag, \" create version\")\n\t\t\tnewVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{\n\t\t\t\tPlatform:      platform,\n\t\t\t\tVersionString: tag,\n\t\t\t}, appID, buildID)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tversion = newVersion.Data\n\n\t\t} else {\n\t\t\tlog.Info(string(platform), \" \", tag, \" check build\")\n\t\t\tcurrentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID {\n\t\t\t\tswitch *version.Attributes.AppStoreState {\n\t\t\t\tcase asc.AppStoreVersionStatePrepareForSubmission,\n\t\t\t\t\tasc.AppStoreVersionStateRejected,\n\t\t\t\t\tasc.AppStoreVersionStateDeveloperRejected:\n\t\t\t\tcase asc.AppStoreVersionStateWaitingForReview,\n\t\t\t\t\tasc.AppStoreVersionStateInReview,\n\t\t\t\t\tasc.AppStoreVersionStatePendingDeveloperRelease:\n\t\t\t\t\tsubmission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tif submission != nil {\n\t\t\t\t\t\tlog.Info(string(platform), \" \", tag, \" delete submission\")\n\t\t\t\t\t\t_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttime.Sleep(5 * time.Second)\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tlog.Fatal(string(platform), \" \", tag, \" unknown state \", string(*version.Attributes.AppStoreState))\n\t\t\t\t}\n\t\t\t\tlog.Info(string(platform), \" \", tag, \" update build\")\n\t\t\t\tresponse, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif response.StatusCode != http.StatusNoContent {\n\t\t\t\t\tresponse.Write(os.Stderr)\n\t\t\t\t\tlog.Fatal(string(platform), \" \", tag, \" unexpected response: \", response.Status)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tswitch *version.Attributes.AppStoreState {\n\t\t\t\tcase asc.AppStoreVersionStatePrepareForSubmission,\n\t\t\t\t\tasc.AppStoreVersionStateRejected,\n\t\t\t\t\tasc.AppStoreVersionStateDeveloperRejected:\n\t\t\t\tcase asc.AppStoreVersionStateWaitingForReview,\n\t\t\t\t\tasc.AppStoreVersionStateInReview,\n\t\t\t\t\tasc.AppStoreVersionStatePendingDeveloperRelease:\n\t\t\t\t\tcontinue\n\t\t\t\tdefault:\n\t\t\t\t\tlog.Fatal(string(platform), \" \", tag, \" unknown state \", string(*version.Attributes.AppStoreState))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlog.Info(string(platform), \" \", tag, \" list localization\")\n\t\tlocalizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlocalization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool {\n\t\t\treturn *it.Attributes.Locale == \"en-US\"\n\t\t})\n\t\tif localization.ID == \"\" {\n\t\t\tlog.Info(string(platform), \" \", tag, \" no en-US localization found\")\n\t\t}\n\t\tif localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == \"\" {\n\t\t\tlog.Info(string(platform), \" \", tag, \" update localization\")\n\t\t\t_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{\n\t\t\t\tPromotionalText: common.Ptr(\"Yet another distribution for sing-box, the universal proxy platform.\"),\n\t\t\t\tWhatsNew:        common.Ptr(F.ToString(\"sing-box \", tag, \": Fixes and improvements.\")),\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tlog.Info(string(platform), \" \", tag, \" create submission\")\n\tfixSubmit:\n\t\tfor {\n\t\t\t_, response, err := client.Submission.CreateSubmission(ctx, version.ID)\n\t\t\tif err != nil {\n\t\t\t\tswitch response.StatusCode {\n\t\t\t\tcase http.StatusInternalServerError:\n\t\t\t\t\tcontinue\n\t\t\t\tdefault:\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tswitch response.StatusCode {\n\t\t\tcase http.StatusCreated:\n\t\t\t\tbreak fixSubmit\n\t\t\tdefault:\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc publishAppStore(ctx context.Context) error {\n\ttag, err := build_shared.ReadTag()\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient := createClient(time.Minute)\n\tfor _, platform := range []asc.Platform{\n\t\tasc.PlatformIOS,\n\t\tasc.PlatformMACOS,\n\t\tasc.PlatformTVOS,\n\t} {\n\t\tlog.Info(string(platform), \" list versions\")\n\t\tversions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{\n\t\t\tFilterPlatform: []string{string(platform)},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tversion := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {\n\t\t\treturn *it.Attributes.VersionString == tag\n\t\t})\n\t\tswitch *version.Attributes.AppStoreState {\n\t\tcase asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected:\n\t\t\tlog.Fatal(string(platform), \" \", tag, \" not submitted\")\n\t\tcase asc.AppStoreVersionStateWaitingForReview,\n\t\t\tasc.AppStoreVersionStateInReview:\n\t\t\tlog.Warn(string(platform), \" \", tag, \" waiting for review\")\n\t\t\tcontinue\n\t\tcase asc.AppStoreVersionStatePendingDeveloperRelease:\n\t\tdefault:\n\t\t\tlog.Fatal(string(platform), \" \", tag, \" unknown state \", string(*version.Attributes.AppStoreState))\n\t\t}\n\t\t_, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc isRetryable(response *asc.Response) bool {\n\tif response == nil {\n\t\treturn false\n\t}\n\tswitch response.StatusCode {\n\tcase http.StatusInternalServerError, http.StatusUnprocessableEntity:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n"
  },
  {
    "path": "cmd/internal/build/main.go",
    "content": "package main\n\nimport (\n\t\"go/build\"\n\t\"os\"\n\t\"os/exec\"\n\n\t\"github.com/sagernet/sing-box/cmd/internal/build_shared\"\n\t\"github.com/sagernet/sing-box/log\"\n)\n\nfunc main() {\n\tbuild_shared.FindSDK()\n\n\tif os.Getenv(\"GOPATH\") == \"\" {\n\t\tos.Setenv(\"GOPATH\", build.Default.GOPATH)\n\t}\n\n\tcommand := exec.Command(os.Args[1], os.Args[2:]...)\n\tcommand.Stdout = os.Stdout\n\tcommand.Stderr = os.Stderr\n\terr := command.Run()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "cmd/internal/build_libbox/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t_ \"github.com/sagernet/gomobile\"\n\t\"github.com/sagernet/sing-box/cmd/internal/build_shared\"\n\t\"github.com/sagernet/sing-box/log\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/rw\"\n\t\"github.com/sagernet/sing/common/shell\"\n)\n\nvar (\n\tdebugEnabled bool\n\ttarget       string\n\tplatform     string\n\t// withTailscale bool\n)\n\nfunc init() {\n\tflag.BoolVar(&debugEnabled, \"debug\", false, \"enable debug\")\n\tflag.StringVar(&target, \"target\", \"android\", \"target platform\")\n\tflag.StringVar(&platform, \"platform\", \"\", \"specify platform\")\n\t// flag.BoolVar(&withTailscale, \"with-tailscale\", false, \"build tailscale for iOS and tvOS\")\n}\n\nfunc main() {\n\tflag.Parse()\n\n\tbuild_shared.FindMobile()\n\n\tswitch target {\n\tcase \"android\":\n\t\tbuildAndroid()\n\tcase \"apple\":\n\t\tbuildApple()\n\t}\n}\n\nvar (\n\tsharedFlags []string\n\tdebugFlags  []string\n\tsharedTags  []string\n\tdarwinTags  []string\n\t// memcTags    []string\n\tnotMemcTags []string\n\tdebugTags   []string\n)\n\nfunc init() {\n\tsharedFlags = append(sharedFlags, \"-trimpath\")\n\tsharedFlags = append(sharedFlags, \"-buildvcs=false\")\n\tcurrentTag, err := build_shared.ReadTag()\n\tif err != nil {\n\t\tcurrentTag = \"unknown\"\n\t}\n\tsharedFlags = append(sharedFlags, \"-ldflags\", \"-X github.com/sagernet/sing-box/constant.Version=\"+currentTag+\" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid=  -checklinkname=0\")\n\tdebugFlags = append(debugFlags, \"-ldflags\", \"-X github.com/sagernet/sing-box/constant.Version=\"+currentTag+\" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0\")\n\n\tsharedTags = append(sharedTags, \"with_gvisor\", \"with_quic\", \"with_wireguard\", \"with_utls\", \"with_naive_outbound\", \"with_clash_api\", \"badlinkname\", \"tfogo_checklinkname0\")\n\tdarwinTags = append(darwinTags, \"with_dhcp\", \"grpcnotrace\")\n\t// memcTags = append(memcTags, \"with_tailscale\")\n\tsharedTags = append(sharedTags, \"with_tailscale\", \"ts_omit_logtail\", \"ts_omit_ssh\", \"ts_omit_drive\", \"ts_omit_taildrop\", \"ts_omit_webclient\", \"ts_omit_doctor\", \"ts_omit_capture\", \"ts_omit_kube\", \"ts_omit_aws\", \"ts_omit_synology\", \"ts_omit_bird\")\n\tnotMemcTags = append(notMemcTags, \"with_low_memory\")\n\tdebugTags = append(debugTags, \"debug\")\n}\n\ntype AndroidBuildConfig struct {\n\tAndroidAPI int\n\tOutputName string\n\tTags       []string\n}\n\nfunc filterTags(tags []string, exclude ...string) []string {\n\texcludeMap := make(map[string]bool)\n\tfor _, tag := range exclude {\n\t\texcludeMap[tag] = true\n\t}\n\tvar result []string\n\tfor _, tag := range tags {\n\t\tif !excludeMap[tag] {\n\t\t\tresult = append(result, tag)\n\t\t}\n\t}\n\treturn result\n}\n\nfunc checkJavaVersion() {\n\tvar javaPath string\n\tjavaHome := os.Getenv(\"JAVA_HOME\")\n\tif javaHome == \"\" {\n\t\tjavaPath = \"java\"\n\t} else {\n\t\tjavaPath = filepath.Join(javaHome, \"bin\", \"java\")\n\t}\n\n\tjavaVersion, err := shell.Exec(javaPath, \"--version\").ReadOutput()\n\tif err != nil {\n\t\tlog.Fatal(E.Cause(err, \"check java version\"))\n\t}\n\tif !strings.Contains(javaVersion, \"openjdk 17\") {\n\t\tlog.Fatal(\"java version should be openjdk 17\")\n\t}\n}\n\nfunc getAndroidBindTarget() string {\n\tif platform != \"\" {\n\t\treturn platform\n\t} else if debugEnabled {\n\t\treturn \"android/arm64\"\n\t}\n\treturn \"android\"\n}\n\nfunc buildAndroidVariant(config AndroidBuildConfig, bindTarget string) {\n\targs := []string{\n\t\t\"bind\",\n\t\t\"-v\",\n\t\t\"-o\", config.OutputName,\n\t\t\"-target\", bindTarget,\n\t\t\"-androidapi\", strconv.Itoa(config.AndroidAPI),\n\t\t\"-javapkg=io.nekohasekai\",\n\t\t\"-libname=box\",\n\t}\n\n\tif !debugEnabled {\n\t\targs = append(args, sharedFlags...)\n\t} else {\n\t\targs = append(args, debugFlags...)\n\t}\n\n\targs = append(args, \"-tags\", strings.Join(config.Tags, \",\"))\n\targs = append(args, \"./experimental/libbox\")\n\n\tcommand := exec.Command(build_shared.GoBinPath+\"/gomobile\", args...)\n\tcommand.Stdout = os.Stdout\n\tcommand.Stderr = os.Stderr\n\terr := command.Run()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tcopyPath := filepath.Join(\"..\", \"sing-box-for-android\", \"app\", \"libs\")\n\tif rw.IsDir(copyPath) {\n\t\tcopyPath, _ = filepath.Abs(copyPath)\n\t\terr = rw.CopyFile(config.OutputName, filepath.Join(copyPath, config.OutputName))\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tlog.Info(\"copied \", config.OutputName, \" to \", copyPath)\n\t}\n}\n\nfunc buildAndroid() {\n\tbuild_shared.FindSDK()\n\tcheckJavaVersion()\n\n\tbindTarget := getAndroidBindTarget()\n\n\t// Build main variant (SDK 23)\n\tmainTags := append([]string{}, sharedTags...)\n\t// mainTags = append(mainTags, memcTags...)\n\tif debugEnabled {\n\t\tmainTags = append(mainTags, debugTags...)\n\t}\n\tbuildAndroidVariant(AndroidBuildConfig{\n\t\tAndroidAPI: 23,\n\t\tOutputName: \"libbox.aar\",\n\t\tTags:       mainTags,\n\t}, bindTarget)\n\n\t// Build legacy variant (SDK 21, no naive outbound)\n\tlegacyTags := filterTags(sharedTags, \"with_naive_outbound\")\n\t// legacyTags = append(legacyTags, memcTags...)\n\tif debugEnabled {\n\t\tlegacyTags = append(legacyTags, debugTags...)\n\t}\n\tbuildAndroidVariant(AndroidBuildConfig{\n\t\tAndroidAPI: 21,\n\t\tOutputName: \"libbox-legacy.aar\",\n\t\tTags:       legacyTags,\n\t}, bindTarget)\n}\n\nfunc buildApple() {\n\tvar bindTarget string\n\tif platform != \"\" {\n\t\tbindTarget = platform\n\t} else if debugEnabled {\n\t\tbindTarget = \"ios\"\n\t} else {\n\t\tbindTarget = \"ios,iossimulator,tvos,tvossimulator,macos\"\n\t}\n\n\targs := []string{\n\t\t\"bind\",\n\t\t\"-v\",\n\t\t\"-target\", bindTarget,\n\t\t\"-libname=box\",\n\t\t\"-tags-not-macos=with_low_memory\",\n\t}\n\t//if !withTailscale {\n\t//\targs = append(args, \"-tags-macos=\"+strings.Join(memcTags, \",\"))\n\t//}\n\n\tif !debugEnabled {\n\t\targs = append(args, sharedFlags...)\n\t} else {\n\t\targs = append(args, debugFlags...)\n\t}\n\n\ttags := append(sharedTags, darwinTags...)\n\t//if withTailscale {\n\t//\ttags = append(tags, memcTags...)\n\t//}\n\tif debugEnabled {\n\t\ttags = append(tags, debugTags...)\n\t}\n\n\targs = append(args, \"-tags\", strings.Join(tags, \",\"))\n\targs = append(args, \"./experimental/libbox\")\n\n\tcommand := exec.Command(build_shared.GoBinPath+\"/gomobile\", args...)\n\tcommand.Stdout = os.Stdout\n\tcommand.Stderr = os.Stderr\n\terr := command.Run()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tcopyPath := filepath.Join(\"..\", \"sing-box-for-apple\")\n\tif rw.IsDir(copyPath) {\n\t\ttargetDir := filepath.Join(copyPath, \"Libbox.xcframework\")\n\t\ttargetDir, _ = filepath.Abs(targetDir)\n\t\tos.RemoveAll(targetDir)\n\t\tos.Rename(\"Libbox.xcframework\", targetDir)\n\t\tlog.Info(\"copied to \", targetDir)\n\t}\n}\n"
  },
  {
    "path": "cmd/internal/build_shared/sdk.go",
    "content": "package build_shared\n\nimport (\n\t\"go/build\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/rw\"\n)\n\nvar (\n\tandroidSDKPath string\n\tandroidNDKPath string\n)\n\nfunc FindSDK() {\n\tsearchPath := []string{\n\t\t\"$ANDROID_HOME\",\n\t\t\"$HOME/Android/Sdk\",\n\t\t\"$HOME/.local/lib/android/sdk\",\n\t\t\"$HOME/Library/Android/sdk\",\n\t}\n\tfor _, path := range searchPath {\n\t\tpath = os.ExpandEnv(path)\n\t\tif rw.IsFile(filepath.Join(path, \"licenses\", \"android-sdk-license\")) {\n\t\t\tandroidSDKPath = path\n\t\t\tbreak\n\t\t}\n\t}\n\tif androidSDKPath == \"\" {\n\t\tlog.Fatal(\"android SDK not found\")\n\t}\n\tif !findNDK() {\n\t\tlog.Fatal(\"android NDK not found\")\n\t}\n\n\tos.Setenv(\"ANDROID_HOME\", androidSDKPath)\n\tos.Setenv(\"ANDROID_SDK_HOME\", androidSDKPath)\n\tos.Setenv(\"ANDROID_NDK_HOME\", androidNDKPath)\n\tos.Setenv(\"NDK\", androidNDKPath)\n\tos.Setenv(\"PATH\", os.Getenv(\"PATH\")+\":\"+filepath.Join(androidNDKPath, \"toolchains\", \"llvm\", \"prebuilt\", runtime.GOOS+\"-x86_64\", \"bin\"))\n}\n\nfunc findNDK() bool {\n\tconst fixedVersion = \"28.0.13004108\"\n\tconst versionFile = \"source.properties\"\n\tif fixedPath := filepath.Join(androidSDKPath, \"ndk\", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {\n\t\tandroidNDKPath = fixedPath\n\t\treturn true\n\t}\n\tif ndkHomeEnv := os.Getenv(\"ANDROID_NDK_HOME\"); rw.IsFile(filepath.Join(ndkHomeEnv, versionFile)) {\n\t\tandroidNDKPath = ndkHomeEnv\n\t\treturn true\n\t}\n\tndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, \"ndk\"))\n\tif err != nil {\n\t\treturn false\n\t}\n\tversionNames := common.Map(ndkVersions, os.DirEntry.Name)\n\tif len(versionNames) == 0 {\n\t\treturn false\n\t}\n\tsort.Slice(versionNames, func(i, j int) bool {\n\t\tiVersions := strings.Split(versionNames[i], \".\")\n\t\tjVersions := strings.Split(versionNames[j], \".\")\n\t\tfor k := 0; k < len(iVersions) && k < len(jVersions); k++ {\n\t\t\tiVersion, _ := strconv.Atoi(iVersions[k])\n\t\t\tjVersion, _ := strconv.Atoi(jVersions[k])\n\t\t\tif iVersion != jVersion {\n\t\t\t\treturn iVersion > jVersion\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\tfor _, versionName := range versionNames {\n\t\tcurrentNDKPath := filepath.Join(androidSDKPath, \"ndk\", versionName)\n\t\tif rw.IsFile(filepath.Join(currentNDKPath, versionFile)) {\n\t\t\tandroidNDKPath = currentNDKPath\n\t\t\tlog.Warn(\"reproducibility warning: using NDK version \" + versionName + \" instead of \" + fixedVersion)\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar GoBinPath string\n\nfunc FindMobile() {\n\tgoBin := filepath.Join(build.Default.GOPATH, \"bin\")\n\tif runtime.GOOS == \"windows\" {\n\t\tif !rw.IsFile(filepath.Join(goBin, \"gobind.exe\")) {\n\t\t\tlog.Fatal(\"missing gomobile installation\")\n\t\t}\n\t} else {\n\t\tif !rw.IsFile(filepath.Join(goBin, \"gobind\")) {\n\t\t\tlog.Fatal(\"missing gomobile installation\")\n\t\t}\n\t}\n\tGoBinPath = goBin\n}\n"
  },
  {
    "path": "cmd/internal/build_shared/tag.go",
    "content": "package build_shared\n\nimport (\n\t\"github.com/sagernet/sing-box/common/badversion\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/shell\"\n)\n\nfunc ReadTag() (string, error) {\n\tcurrentTag, err := shell.Exec(\"git\", \"describe\", \"--tags\").ReadOutput()\n\tif err != nil {\n\t\treturn currentTag, err\n\t}\n\tcurrentTagRev, _ := shell.Exec(\"git\", \"describe\", \"--tags\", \"--abbrev=0\").ReadOutput()\n\tif currentTagRev == currentTag {\n\t\treturn currentTag[1:], nil\n\t}\n\tshortCommit, _ := shell.Exec(\"git\", \"rev-parse\", \"--short\", \"HEAD\").ReadOutput()\n\tversion := badversion.Parse(currentTagRev[1:])\n\treturn version.String() + \"-\" + shortCommit, nil\n}\n\nfunc ReadTagVersionRev() (badversion.Version, error) {\n\tcurrentTagRev := common.Must1(shell.Exec(\"git\", \"describe\", \"--tags\", \"--abbrev=0\").ReadOutput())\n\treturn badversion.Parse(currentTagRev[1:]), nil\n}\n\nfunc ReadTagVersion() (badversion.Version, error) {\n\tcurrentTag := common.Must1(shell.Exec(\"git\", \"describe\", \"--tags\").ReadOutput())\n\tcurrentTagRev := common.Must1(shell.Exec(\"git\", \"describe\", \"--tags\", \"--abbrev=0\").ReadOutput())\n\tversion := badversion.Parse(currentTagRev[1:])\n\tif currentTagRev != currentTag {\n\t\tif version.PreReleaseIdentifier == \"\" {\n\t\t\tversion.Patch++\n\t\t}\n\t}\n\treturn version, nil\n}\n"
  },
  {
    "path": "cmd/internal/format_docs/main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/log\"\n)\n\nfunc main() {\n\terr := filepath.Walk(\"docs\", func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif info.IsDir() {\n\t\t\treturn nil\n\t\t}\n\t\tif !strings.HasSuffix(path, \".md\") {\n\t\t\treturn nil\n\t\t}\n\t\treturn processFile(path)\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc processFile(path string) error {\n\tcontent, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlines := strings.Split(string(content), \"\\n\")\n\tmodified := false\n\tresult := make([]string, 0, len(lines))\n\n\tinQuoteBlock := false\n\tmaterialLines := []int{} // indices of :material- lines in the block\n\n\tfor _, line := range lines {\n\t\t// Check for quote block start\n\t\tif strings.HasPrefix(line, \"!!! quote \\\"\") && strings.Contains(line, \"sing-box\") {\n\t\t\tinQuoteBlock = true\n\t\t\tmaterialLines = nil\n\t\t\tresult = append(result, line)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Inside a quote block\n\t\tif inQuoteBlock {\n\t\t\ttrimmed := strings.TrimPrefix(line, \"    \")\n\t\t\tisMaterialLine := strings.HasPrefix(trimmed, \":material-\")\n\t\t\tisEmpty := strings.TrimSpace(line) == \"\"\n\t\t\tisIndented := strings.HasPrefix(line, \"    \")\n\n\t\t\tif isMaterialLine {\n\t\t\t\tmaterialLines = append(materialLines, len(result))\n\t\t\t\tresult = append(result, line)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Block ends when:\n\t\t\t// - Empty line AFTER we've seen material lines, OR\n\t\t\t// - Non-indented, non-empty line\n\t\t\tblockEnds := (isEmpty && len(materialLines) > 0) || (!isEmpty && !isIndented)\n\t\t\tif blockEnds {\n\t\t\t\t// Process collected material lines\n\t\t\t\tif len(materialLines) > 0 {\n\t\t\t\t\tfor j, idx := range materialLines {\n\t\t\t\t\t\tisLast := j == len(materialLines)-1\n\t\t\t\t\t\tresultLine := strings.TrimRight(result[idx], \" \")\n\t\t\t\t\t\tif !isLast {\n\t\t\t\t\t\t\t// Add trailing two spaces for non-last lines\n\t\t\t\t\t\t\tresultLine += \"  \"\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif result[idx] != resultLine {\n\t\t\t\t\t\t\tmodified = true\n\t\t\t\t\t\t\tresult[idx] = resultLine\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tinQuoteBlock = false\n\t\t\t\tmaterialLines = nil\n\t\t\t}\n\t\t}\n\n\t\tresult = append(result, line)\n\t}\n\n\t// Handle case where file ends while still in a block\n\tif inQuoteBlock && len(materialLines) > 0 {\n\t\tfor j, idx := range materialLines {\n\t\t\tisLast := j == len(materialLines)-1\n\t\t\tresultLine := strings.TrimRight(result[idx], \" \")\n\t\t\tif !isLast {\n\t\t\t\tresultLine += \"  \"\n\t\t\t}\n\t\t\tif result[idx] != resultLine {\n\t\t\t\tmodified = true\n\t\t\t\tresult[idx] = resultLine\n\t\t\t}\n\t\t}\n\t}\n\n\tif modified {\n\t\tnewContent := strings.Join(result, \"\\n\")\n\t\tif !bytes.Equal(content, []byte(newContent)) {\n\t\t\tlog.Info(\"formatted: \", path)\n\t\t\treturn os.WriteFile(path, []byte(newContent), 0o644)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/internal/protogen/main.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"go/build\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n)\n\n// envFile returns the name of the Go environment configuration file.\n// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166\nfunc envFile() (string, error) {\n\tif file := os.Getenv(\"GOENV\"); file != \"\" {\n\t\tif file == \"off\" {\n\t\t\treturn \"\", fmt.Errorf(\"GOENV=off\")\n\t\t}\n\t\treturn file, nil\n\t}\n\tdir, err := os.UserConfigDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif dir == \"\" {\n\t\treturn \"\", fmt.Errorf(\"missing user-config dir\")\n\t}\n\treturn filepath.Join(dir, \"go\", \"env\"), nil\n}\n\n// GetRuntimeEnv returns the value of runtime environment variable,\n// that is set by running following command: `go env -w key=value`.\nfunc GetRuntimeEnv(key string) (string, error) {\n\tfile, err := envFile()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif file == \"\" {\n\t\treturn \"\", fmt.Errorf(\"missing runtime env file\")\n\t}\n\tvar data []byte\n\tvar runtimeEnv string\n\tdata, readErr := os.ReadFile(file)\n\tif readErr != nil {\n\t\treturn \"\", readErr\n\t}\n\tenvStrings := strings.Split(string(data), \"\\n\")\n\tfor _, envItem := range envStrings {\n\t\tenvItem = strings.TrimSuffix(envItem, \"\\r\")\n\t\tenvKeyValue := strings.Split(envItem, \"=\")\n\t\tif strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {\n\t\t\truntimeEnv = strings.TrimSpace(envKeyValue[1])\n\t\t}\n\t}\n\treturn runtimeEnv, nil\n}\n\n// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.\nfunc GetGOBIN() string {\n\t// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`\n\tGOBIN := os.Getenv(\"GOBIN\")\n\tif GOBIN == \"\" {\n\t\tvar err error\n\t\t// The one set by user by running `go env -w GOBIN=/path`\n\t\tGOBIN, err = GetRuntimeEnv(\"GOBIN\")\n\t\tif err != nil {\n\t\t\t// The default one that Golang uses\n\t\t\treturn filepath.Join(build.Default.GOPATH, \"bin\")\n\t\t}\n\t\tif GOBIN == \"\" {\n\t\t\treturn filepath.Join(build.Default.GOPATH, \"bin\")\n\t\t}\n\t\treturn GOBIN\n\t}\n\treturn GOBIN\n}\n\nfunc main() {\n\tpwd, err := os.Getwd()\n\tif err != nil {\n\t\tfmt.Println(\"Can not get current working directory.\")\n\t\tos.Exit(1)\n\t}\n\n\tGOBIN := GetGOBIN()\n\tbinPath := os.Getenv(\"PATH\")\n\tpathSlice := []string{pwd, GOBIN, binPath}\n\tbinPath = strings.Join(pathSlice, string(os.PathListSeparator))\n\tos.Setenv(\"PATH\", binPath)\n\n\tsuffix := \"\"\n\tif runtime.GOOS == \"windows\" {\n\t\tsuffix = \".exe\"\n\t}\n\n\tprotoc := \"protoc\"\n\n\tif linkPath, err := os.Readlink(protoc); err == nil {\n\t\tprotoc = linkPath\n\t}\n\n\tprotoFilesMap := make(map[string][]string)\n\twalkErr := filepath.Walk(\"./\", func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\treturn err\n\t\t}\n\n\t\tif info.IsDir() {\n\t\t\treturn nil\n\t\t}\n\n\t\tdir := filepath.Dir(path)\n\t\tfilename := filepath.Base(path)\n\t\tif strings.HasSuffix(filename, \".proto\") &&\n\t\t\tfilename != \"typed_message.proto\" &&\n\t\t\tfilename != \"descriptor.proto\" {\n\t\t\tprotoFilesMap[dir] = append(protoFilesMap[dir], path)\n\t\t}\n\n\t\treturn nil\n\t})\n\tif walkErr != nil {\n\t\tfmt.Println(walkErr)\n\t\tos.Exit(1)\n\t}\n\n\tfor _, files := range protoFilesMap {\n\t\tfor _, relProtoFile := range files {\n\t\t\targs := []string{\n\t\t\t\t\"-I\", \".\",\n\t\t\t\t\"--go_out\", pwd,\n\t\t\t\t\"--go_opt\", \"paths=source_relative\",\n\t\t\t\t\"--go-grpc_out\", pwd,\n\t\t\t\t\"--go-grpc_opt\", \"paths=source_relative\",\n\t\t\t\t\"--plugin\", \"protoc-gen-go=\" + filepath.Join(GOBIN, \"protoc-gen-go\"+suffix),\n\t\t\t\t\"--plugin\", \"protoc-gen-go-grpc=\" + filepath.Join(GOBIN, \"protoc-gen-go-grpc\"+suffix),\n\t\t\t}\n\t\t\targs = append(args, relProtoFile)\n\t\t\tcmd := exec.Command(protoc, args...)\n\t\t\tcmd.Env = append(cmd.Env, os.Environ()...)\n\t\t\toutput, cmdErr := cmd.CombinedOutput()\n\t\t\tif len(output) > 0 {\n\t\t\t\tfmt.Println(string(output))\n\t\t\t}\n\t\t\tif cmdErr != nil {\n\t\t\t\tfmt.Println(cmdErr)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}\n\t}\n\n\tnormalizeWalkErr := filepath.Walk(\"./\", func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\treturn err\n\t\t}\n\n\t\tif info.IsDir() {\n\t\t\treturn nil\n\t\t}\n\n\t\tfilename := filepath.Base(path)\n\t\tif strings.HasSuffix(filename, \".pb.go\") &&\n\t\t\tpath != \"config.pb.go\" {\n\t\t\tif err := NormalizeGeneratedProtoFile(path); err != nil {\n\t\t\t\tfmt.Println(err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n\tif normalizeWalkErr != nil {\n\t\tfmt.Println(normalizeWalkErr)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc NormalizeGeneratedProtoFile(path string) error {\n\tfd, err := os.OpenFile(path, os.O_RDWR, 0o644)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = fd.Seek(0, io.SeekStart)\n\tif err != nil {\n\t\treturn err\n\t}\n\tout := bytes.NewBuffer(nil)\n\tscanner := bufio.NewScanner(fd)\n\tvalid := false\n\tfor scanner.Scan() {\n\t\tif !valid && !strings.HasPrefix(scanner.Text(), \"package \") {\n\t\t\tcontinue\n\t\t}\n\t\tvalid = true\n\t\tout.Write(scanner.Bytes())\n\t\tout.Write([]byte(\"\\n\"))\n\t}\n\t_, err = fd.Seek(0, io.SeekStart)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = fd.Truncate(0)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = io.Copy(fd, bytes.NewReader(out.Bytes()))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/internal/read_tag/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/cmd/internal/build_shared\"\n\t\"github.com/sagernet/sing-box/common/badversion\"\n\t\"github.com/sagernet/sing-box/log\"\n)\n\nvar (\n\tflagRunInCI    bool\n\tflagRunNightly bool\n)\n\nfunc init() {\n\tflag.BoolVar(&flagRunInCI, \"ci\", false, \"Run in CI\")\n\tflag.BoolVar(&flagRunNightly, \"nightly\", false, \"Run nightly\")\n}\n\nfunc main() {\n\tflag.Parse()\n\tvar (\n\t\tversionStr string\n\t\terr        error\n\t)\n\tif flagRunNightly {\n\t\tvar version badversion.Version\n\t\tversion, err = build_shared.ReadTagVersion()\n\t\tif err == nil {\n\t\t\tversionStr = version.String()\n\t\t}\n\t} else {\n\t\tversionStr, err = build_shared.ReadTag()\n\t}\n\tif flagRunInCI {\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\terr = setGitHubEnv(\"version\", versionStr)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t} else {\n\t\tif err != nil {\n\t\t\tlog.Error(err)\n\t\t\tos.Stdout.WriteString(\"unknown\\n\")\n\t\t} else {\n\t\t\tos.Stdout.WriteString(versionStr + \"\\n\")\n\t\t}\n\t}\n}\n\nfunc setGitHubEnv(name string, value string) error {\n\toutputFile, err := os.OpenFile(os.Getenv(\"GITHUB_ENV\"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = outputFile.WriteString(name + \"=\" + value + \"\\n\")\n\tif err != nil {\n\t\toutputFile.Close()\n\t\treturn err\n\t}\n\terr = outputFile.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\tos.Stderr.WriteString(name + \"=\" + value + \"\\n\")\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/internal/tun_bench/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/include\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/shell\"\n)\n\nvar iperf3Path string\n\nfunc main() {\n\terr := main0()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc main0() error {\n\terr := shell.Exec(\"sudo\", \"ls\").Run()\n\tif err != nil {\n\t\treturn err\n\t}\n\tresults, err := runTests()\n\tif err != nil {\n\t\treturn err\n\t}\n\tencoder := json.NewEncoder(os.Stdout)\n\tencoder.SetIndent(\"\", \"  \")\n\treturn encoder.Encode(results)\n}\n\nfunc runTests() ([]TestResult, error) {\n\tboxPaths := []string{\n\t\tos.ExpandEnv(\"$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box\"),\n\t\t//\"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box\",\n\t\t\"./sing-box\",\n\t}\n\tstacks := []string{\n\t\t\"gvisor\",\n\t\t\"system\",\n\t}\n\tmtus := []int{\n\t\t1500,\n\t\t4064,\n\t\t// 16384,\n\t\t// 32768,\n\t\t// 49152,\n\t\t65535,\n\t}\n\tflagList := [][]string{\n\t\t{},\n\t}\n\tvar results []TestResult\n\tfor _, boxPath := range boxPaths {\n\t\tfor _, stack := range stacks {\n\t\t\tfor _, mtu := range mtus {\n\t\t\t\tif strings.HasPrefix(boxPath, \".\") {\n\t\t\t\t\tfor _, flags := range flagList {\n\t\t\t\t\t\tresult, err := testOnce(boxPath, stack, mtu, false, flags)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresults = append(results, *result)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tresult, err := testOnce(boxPath, stack, mtu, false, nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tresults = append(results, *result)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn results, nil\n}\n\ntype TestResult struct {\n\tBoxPath       string   `json:\"box_path\"`\n\tStack         string   `json:\"stack\"`\n\tMTU           int      `json:\"mtu\"`\n\tFlags         []string `json:\"flags\"`\n\tMultiThread   bool     `json:\"multi_thread\"`\n\tUploadSpeed   string   `json:\"upload_speed\"`\n\tDownloadSpeed string   `json:\"download_speed\"`\n}\n\nfunc testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) {\n\ttestAddress := netip.MustParseAddr(\"1.1.1.1\")\n\ttestConfig := option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeTun,\n\t\t\t\tOptions: &option.TunInboundOptions{\n\t\t\t\t\tAddress:      []netip.Prefix{netip.MustParsePrefix(\"172.18.0.1/30\")},\n\t\t\t\t\tAutoRoute:    true,\n\t\t\t\t\tMTU:          uint32(mtu),\n\t\t\t\t\tStack:        stackName,\n\t\t\t\t\tRouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tIPCIDR: []string{testAddress.String()},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRouteOptions,\n\t\t\t\t\t\t\tRouteOptionsOptions: option.RouteOptionsActionOptions{\n\t\t\t\t\t\t\t\tOverrideAddress: \"127.0.0.1\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tAutoDetectInterface: true,\n\t\t},\n\t}\n\tctx := include.Context(context.Background())\n\ttempConfig, err := os.CreateTemp(\"\", \"tun-bench-*.json\")\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer os.Remove(tempConfig.Name())\n\tencoder := json.NewEncoderContext(ctx, tempConfig)\n\tencoder.SetIndent(\"\", \"  \")\n\terr = encoder.Encode(testConfig)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"encode test config\")\n\t}\n\ttempConfig.Close()\n\tvar sudoArgs []string\n\tif len(flags) > 0 {\n\t\tsudoArgs = append(sudoArgs, \"env\")\n\t\tsudoArgs = append(sudoArgs, flags...)\n\t}\n\tsudoArgs = append(sudoArgs, boxPath, \"run\", \"-c\", tempConfig.Name())\n\tboxProcess := shell.Exec(\"sudo\", sudoArgs...)\n\tboxProcess.Stdout = &stderrWriter{}\n\tboxProcess.Stderr = io.Discard\n\terr = boxProcess.Start()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif C.IsDarwin {\n\t\tiperf3Path, err = exec.LookPath(\"iperf3-darwin\")\n\t} else {\n\t\tiperf3Path, err = exec.LookPath(\"iperf3\")\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\tserverProcess := shell.Exec(iperf3Path, \"-s\")\n\tserverProcess.Stdout = io.Discard\n\tserverProcess.Stderr = io.Discard\n\terr = serverProcess.Start()\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"start iperf3 server\")\n\t}\n\n\ttime.Sleep(time.Second)\n\n\targs := []string{\"-c\", testAddress.String()}\n\tif multiThread {\n\t\targs = append(args, \"-P\", \"10\")\n\t}\n\n\tuploadProcess := shell.Exec(iperf3Path, args...)\n\toutput, err := uploadProcess.Read()\n\tif err != nil {\n\t\tboxProcess.Process.Signal(syscall.SIGKILL)\n\t\tserverProcess.Process.Signal(syscall.SIGKILL)\n\t\tprintln(output)\n\t\treturn\n\t}\n\n\tuploadResult := common.SubstringBeforeLast(output, \"iperf Done.\")\n\tuploadResult = common.SubstringBeforeLast(uploadResult, \"sender\")\n\tuploadResult = common.SubstringBeforeLast(uploadResult, \"bits/sec\")\n\tuploadResult = common.SubstringAfterLast(uploadResult, \"Bytes\")\n\tuploadResult = strings.ReplaceAll(uploadResult, \" \", \"\")\n\n\tresult = &TestResult{\n\t\tBoxPath:     boxPath,\n\t\tStack:       stackName,\n\t\tMTU:         mtu,\n\t\tFlags:       flags,\n\t\tMultiThread: multiThread,\n\t\tUploadSpeed: uploadResult,\n\t}\n\n\tdownloadProcess := shell.Exec(iperf3Path, append(args, \"-R\")...)\n\toutput, err = downloadProcess.Read()\n\tif err != nil {\n\t\tboxProcess.Process.Signal(syscall.SIGKILL)\n\t\tserverProcess.Process.Signal(syscall.SIGKILL)\n\t\tprintln(output)\n\t\treturn\n\t}\n\n\tdownloadResult := common.SubstringBeforeLast(output, \"iperf Done.\")\n\tdownloadResult = common.SubstringBeforeLast(downloadResult, \"receiver\")\n\tdownloadResult = common.SubstringBeforeLast(downloadResult, \"bits/sec\")\n\tdownloadResult = common.SubstringAfterLast(downloadResult, \"Bytes\")\n\tdownloadResult = strings.ReplaceAll(downloadResult, \" \", \"\")\n\n\tresult.DownloadSpeed = downloadResult\n\n\tprintArgs := []any{boxPath, stackName, mtu, \"upload\", uploadResult, \"download\", downloadResult}\n\tif len(flags) > 0 {\n\t\tprintArgs = append(printArgs, \"flags\", strings.Join(flags, \" \"))\n\t}\n\tif multiThread {\n\t\tprintArgs = append(printArgs, \"(-P 10)\")\n\t}\n\tfmt.Println(printArgs...)\n\terr = boxProcess.Process.Signal(syscall.SIGTERM)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = serverProcess.Process.Signal(syscall.SIGTERM)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tboxDone := make(chan struct{})\n\tgo func() {\n\t\tboxProcess.Cmd.Wait()\n\t\tclose(boxDone)\n\t}()\n\n\tserverDone := make(chan struct{})\n\tgo func() {\n\t\tserverProcess.Process.Wait()\n\t\tclose(serverDone)\n\t}()\n\n\tselect {\n\tcase <-boxDone:\n\tcase <-time.After(2 * time.Second):\n\t\tboxProcess.Process.Kill()\n\tcase <-time.After(4 * time.Second):\n\t\tprintln(\"box process did not close!\")\n\t\tos.Exit(1)\n\t}\n\n\tselect {\n\tcase <-serverDone:\n\tcase <-time.After(2 * time.Second):\n\t\tserverProcess.Process.Kill()\n\tcase <-time.After(4 * time.Second):\n\t\tprintln(\"server process did not close!\")\n\t\tos.Exit(1)\n\t}\n\n\treturn\n}\n\ntype stderrWriter struct{}\n\nfunc (w *stderrWriter) Write(p []byte) (n int, err error) {\n\treturn os.Stderr.Write(p)\n}\n"
  },
  {
    "path": "cmd/internal/update_android_version/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/cmd/internal/build_shared\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n)\n\nvar (\n\tflagRunInCI    bool\n\tflagRunNightly bool\n)\n\nfunc init() {\n\tflag.BoolVar(&flagRunInCI, \"ci\", false, \"Run in CI\")\n\tflag.BoolVar(&flagRunNightly, \"nightly\", false, \"Run nightly\")\n}\n\nfunc main() {\n\tflag.Parse()\n\tnewVersion := common.Must1(build_shared.ReadTag())\n\tvar androidPath string\n\tif flagRunInCI {\n\t\tandroidPath = \"clients/android\"\n\t} else {\n\t\tandroidPath = \"../sing-box-for-android\"\n\t}\n\tandroidPath, err := filepath.Abs(androidPath)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tcommon.Must(os.Chdir(androidPath))\n\tlocalProps := common.Must1(os.ReadFile(\"version.properties\"))\n\tvar propsList [][]string\n\tfor _, propLine := range strings.Split(string(localProps), \"\\n\") {\n\t\tpropsList = append(propsList, strings.Split(propLine, \"=\"))\n\t}\n\tvar (\n\t\tversionUpdated   bool\n\t\tgoVersionUpdated bool\n\t)\n\tfor _, propPair := range propsList {\n\t\tswitch propPair[0] {\n\t\tcase \"VERSION_NAME\":\n\t\t\tif propPair[1] != newVersion {\n\t\t\t\tlog.Info(\"updated version from \", propPair[1], \" to \", newVersion)\n\t\t\t\tversionUpdated = true\n\t\t\t\tpropPair[1] = newVersion\n\t\t\t}\n\t\tcase \"GO_VERSION\":\n\t\t\tif propPair[1] != runtime.Version() {\n\t\t\t\tlog.Info(\"updated Go version from \", propPair[1], \" to \", runtime.Version())\n\t\t\t\tgoVersionUpdated = true\n\t\t\t\tpropPair[1] = runtime.Version()\n\t\t\t}\n\t\t}\n\t}\n\tif !(versionUpdated || goVersionUpdated) {\n\t\tlog.Info(\"version not changed\")\n\t\treturn\n\t} else if flagRunInCI && !flagRunNightly {\n\t\tlog.Fatal(\"version changed, commit changes first.\")\n\t}\n\tfor _, propPair := range propsList {\n\t\tswitch propPair[0] {\n\t\tcase \"VERSION_CODE\":\n\t\t\tversionCode := common.Must1(strconv.ParseInt(propPair[1], 10, 64))\n\t\t\tpropPair[1] = strconv.Itoa(int(versionCode + 1))\n\t\t\tlog.Info(\"updated version code to \", propPair[1])\n\t\t}\n\t}\n\tvar newProps []string\n\tfor _, propPair := range propsList {\n\t\tnewProps = append(newProps, strings.Join(propPair, \"=\"))\n\t}\n\tcommon.Must(os.WriteFile(\"version.properties\", []byte(strings.Join(newProps, \"\\n\")), 0o644))\n}\n"
  },
  {
    "path": "cmd/internal/update_apple_version/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/cmd/internal/build_shared\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\n\t\"howett.net/plist\"\n)\n\nvar flagRunInCI bool\n\nfunc init() {\n\tflag.BoolVar(&flagRunInCI, \"ci\", false, \"Run in CI\")\n}\n\nfunc main() {\n\tflag.Parse()\n\tnewVersion := common.Must1(build_shared.ReadTagVersion())\n\tvar applePath string\n\tif flagRunInCI {\n\t\tapplePath = \"clients/apple\"\n\t} else {\n\t\tapplePath = \"../sing-box-for-apple\"\n\t}\n\tapplePath, err := filepath.Abs(applePath)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tcommon.Must(os.Chdir(applePath))\n\tprojectFile := common.Must1(os.Open(\"sing-box.xcodeproj/project.pbxproj\"))\n\tvar project map[string]any\n\tdecoder := plist.NewDecoder(projectFile)\n\tcommon.Must(decoder.Decode(&project))\n\tobjectsMap := project[\"objects\"].(map[string]any)\n\tprojectContent := string(common.Must1(os.ReadFile(\"sing-box.xcodeproj/project.pbxproj\")))\n\tnewContent, updated0 := findAndReplace(objectsMap, projectContent, []string{\"io.nekohasekai.sfavt\"}, newVersion.VersionString())\n\tnewContent, updated1 := findAndReplace(objectsMap, newContent, []string{\"io.nekohasekai.sfavt.standalone\", \"io.nekohasekai.sfavt.system\"}, newVersion.String())\n\tif updated0 || updated1 {\n\t\tlog.Info(\"updated version to \", newVersion.VersionString(), \" (\", newVersion.String(), \")\")\n\t}\n\tvar updated2 bool\n\tif macProjectVersion := os.Getenv(\"MACOS_PROJECT_VERSION\"); macProjectVersion != \"\" {\n\t\tnewContent, updated2 = findAndReplaceProjectVersion(objectsMap, newContent, []string{\"SFM\"}, macProjectVersion)\n\t\tif updated2 {\n\t\t\tlog.Info(\"updated macos project version to \", macProjectVersion)\n\t\t}\n\t}\n\tif updated0 || updated1 || updated2 {\n\t\tcommon.Must(os.WriteFile(\"sing-box.xcodeproj/project.pbxproj\", []byte(newContent), 0o644))\n\t}\n}\n\nfunc findAndReplace(objectsMap map[string]any, projectContent string, bundleIDList []string, newVersion string) (string, bool) {\n\tobjectKeyList := findObjectKey(objectsMap, bundleIDList)\n\tvar updated bool\n\tfor _, objectKey := range objectKeyList {\n\t\tmatchRegexp := common.Must1(regexp.Compile(objectKey + \".*= \\\\{\"))\n\t\tindexes := matchRegexp.FindStringIndex(projectContent)\n\t\tif len(indexes) < 2 {\n\t\t\tprintln(projectContent)\n\t\t\tlog.Fatal(\"failed to find object key \", objectKey, \": \", strings.Index(projectContent, objectKey))\n\t\t}\n\t\tindexStart := indexes[1]\n\t\tindexEnd := indexStart + strings.Index(projectContent[indexStart:], \"}\")\n\t\tversionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], \"MARKETING_VERSION = \") + 20\n\t\tversionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], \";\")\n\t\tversion := strings.Trim(projectContent[versionStart:versionEnd], \"\\\"\")\n\t\tif version == newVersion {\n\t\t\tcontinue\n\t\t}\n\t\tupdated = true\n\t\tprojectContent = projectContent[:versionStart] + \"\\\"\" + newVersion + \"\\\"\" + projectContent[versionEnd:]\n\t}\n\treturn projectContent, updated\n}\n\nfunc findAndReplaceProjectVersion(objectsMap map[string]any, projectContent string, directoryList []string, newVersion string) (string, bool) {\n\tobjectKeyList := findObjectKeyByDirectory(objectsMap, directoryList)\n\tvar updated bool\n\tfor _, objectKey := range objectKeyList {\n\t\tmatchRegexp := common.Must1(regexp.Compile(objectKey + \".*= \\\\{\"))\n\t\tindexes := matchRegexp.FindStringIndex(projectContent)\n\t\tif len(indexes) < 2 {\n\t\t\tprintln(projectContent)\n\t\t\tlog.Fatal(\"failed to find object key \", objectKey, \": \", strings.Index(projectContent, objectKey))\n\t\t}\n\t\tindexStart := indexes[1]\n\t\tindexEnd := indexStart + strings.Index(projectContent[indexStart:], \"}\")\n\t\tversionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], \"CURRENT_PROJECT_VERSION = \") + 26\n\t\tversionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], \";\")\n\t\tversion := projectContent[versionStart:versionEnd]\n\t\tif version == newVersion {\n\t\t\tcontinue\n\t\t}\n\t\tupdated = true\n\t\tprojectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:]\n\t}\n\treturn projectContent, updated\n}\n\nfunc findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {\n\tvar objectKeyList []string\n\tfor objectKey, object := range objectsMap {\n\t\tbuildSettings := object.(map[string]any)[\"buildSettings\"]\n\t\tif buildSettings == nil {\n\t\t\tcontinue\n\t\t}\n\t\tbundleIDObject := buildSettings.(map[string]any)[\"PRODUCT_BUNDLE_IDENTIFIER\"]\n\t\tif bundleIDObject == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif common.Contains(bundleIDList, bundleIDObject.(string)) {\n\t\t\tobjectKeyList = append(objectKeyList, objectKey)\n\t\t}\n\t}\n\treturn objectKeyList\n}\n\nfunc findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {\n\tvar objectKeyList []string\n\tfor objectKey, object := range objectsMap {\n\t\tbuildSettings := object.(map[string]any)[\"buildSettings\"]\n\t\tif buildSettings == nil {\n\t\t\tcontinue\n\t\t}\n\t\tinfoPListFile := buildSettings.(map[string]any)[\"INFOPLIST_FILE\"]\n\t\tif infoPListFile == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, searchDirectory := range directoryList {\n\t\t\tif strings.HasPrefix(infoPListFile.(string), searchDirectory+\"/\") {\n\t\t\t\tobjectKeyList = append(objectKeyList, objectKey)\n\t\t\t}\n\t\t}\n\n\t}\n\treturn objectKeyList\n}\n"
  },
  {
    "path": "cmd/internal/update_certificates/main.go",
    "content": "package main\n\nimport (\n\t\"encoding/csv\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\n\t\"golang.org/x/exp/slices\"\n)\n\nfunc main() {\n\terr := updateMozillaIncludedRootCAs()\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n\terr = updateChromeIncludedRootCAs()\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n}\n\nfunc updateMozillaIncludedRootCAs() error {\n\tresponse, err := http.Get(\"https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer response.Body.Close()\n\treader := csv.NewReader(response.Body)\n\theader, err := reader.Read()\n\tif err != nil {\n\t\treturn err\n\t}\n\tgeoIndex := slices.Index(header, \"Geographic Focus\")\n\tnameIndex := slices.Index(header, \"Common Name or Certificate Name\")\n\tcertIndex := slices.Index(header, \"PEM Info\")\n\n\tgenerated := strings.Builder{}\n\tgenerated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.\n\npackage certificate\n\nimport \"crypto/x509\"\n\nvar mozillaIncluded *x509.CertPool\n\nfunc init() {\n\tmozillaIncluded = x509.NewCertPool()\n`)\n\tfor {\n\t\trecord, err := reader.Read()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif record[geoIndex] == \"China\" {\n\t\t\tcontinue\n\t\t}\n\t\tgenerated.WriteString(\"\\n\t// \")\n\t\tgenerated.WriteString(record[nameIndex])\n\t\tgenerated.WriteString(\"\\n\")\n\t\tgenerated.WriteString(\"\tmozillaIncluded.AppendCertsFromPEM([]byte(`\")\n\t\tcert := record[certIndex]\n\t\t// Remove single quotes\n\t\tcert = cert[1 : len(cert)-1]\n\t\tgenerated.WriteString(cert)\n\t\tgenerated.WriteString(\"`))\\n\")\n\t}\n\tgenerated.WriteString(\"}\\n\")\n\treturn os.WriteFile(\"common/certificate/mozilla.go\", []byte(generated.String()), 0o644)\n}\n\nfunc fetchChinaFingerprints() (map[string]bool, error) {\n\tresponse, err := http.Get(\"https://ccadb.my.salesforce-sites.com/ccadb/AllCertificateRecordsCSVFormatv4\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer response.Body.Close()\n\treader := csv.NewReader(response.Body)\n\theader, err := reader.Read()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcountryIndex := slices.Index(header, \"Country\")\n\tfingerprintIndex := slices.Index(header, \"SHA-256 Fingerprint\")\n\n\tchinaFingerprints := make(map[string]bool)\n\tfor {\n\t\trecord, err := reader.Read()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif record[countryIndex] == \"China\" {\n\t\t\tchinaFingerprints[record[fingerprintIndex]] = true\n\t\t}\n\t}\n\treturn chinaFingerprints, nil\n}\n\nfunc updateChromeIncludedRootCAs() error {\n\tchinaFingerprints, err := fetchChinaFingerprints()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresponse, err := http.Get(\"https://ccadb.my.salesforce-sites.com/ccadb/RootCACertificatesIncludedByRSReportCSV\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer response.Body.Close()\n\treader := csv.NewReader(response.Body)\n\theader, err := reader.Read()\n\tif err != nil {\n\t\treturn err\n\t}\n\tsubjectIndex := slices.Index(header, \"Subject\")\n\tstatusIndex := slices.Index(header, \"Google Chrome Status\")\n\tcertIndex := slices.Index(header, \"X.509 Certificate (PEM)\")\n\tfingerprintIndex := slices.Index(header, \"SHA-256 Fingerprint\")\n\n\tgenerated := strings.Builder{}\n\tgenerated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.\n\npackage certificate\n\nimport \"crypto/x509\"\n\nvar chromeIncluded *x509.CertPool\n\nfunc init() {\n\tchromeIncluded = x509.NewCertPool()\n`)\n\tfor {\n\t\trecord, err := reader.Read()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif record[statusIndex] != \"Included\" {\n\t\t\tcontinue\n\t\t}\n\t\tif chinaFingerprints[record[fingerprintIndex]] {\n\t\t\tcontinue\n\t\t}\n\t\tgenerated.WriteString(\"\\n\t// \")\n\t\tgenerated.WriteString(record[subjectIndex])\n\t\tgenerated.WriteString(\"\\n\")\n\t\tgenerated.WriteString(\"\tchromeIncluded.AppendCertsFromPEM([]byte(`\")\n\t\tcert := record[certIndex]\n\t\t// Remove single quotes if present\n\t\tif len(cert) > 0 && cert[0] == '\\'' {\n\t\t\tcert = cert[1 : len(cert)-1]\n\t\t}\n\t\tgenerated.WriteString(cert)\n\t\tgenerated.WriteString(\"`))\\n\")\n\t}\n\tgenerated.WriteString(\"}\\n\")\n\treturn os.WriteFile(\"common/certificate/chrome.go\", []byte(generated.String()), 0o644)\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/user\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/experimental/deprecated\"\n\t\"github.com/sagernet/sing-box/include\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/filemanager\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tglobalCtx         context.Context\n\tconfigPaths       []string\n\tconfigDirectories []string\n\tworkingDir        string\n\tdisableColor      bool\n)\n\nvar mainCommand = &cobra.Command{\n\tUse:              \"sing-box\",\n\tPersistentPreRun: preRun,\n}\n\nfunc init() {\n\tmainCommand.PersistentFlags().StringArrayVarP(&configPaths, \"config\", \"c\", nil, \"set configuration file path\")\n\tmainCommand.PersistentFlags().StringArrayVarP(&configDirectories, \"config-directory\", \"C\", nil, \"set configuration directory path\")\n\tmainCommand.PersistentFlags().StringVarP(&workingDir, \"directory\", \"D\", \"\", \"set working directory\")\n\tmainCommand.PersistentFlags().BoolVarP(&disableColor, \"disable-color\", \"\", false, \"disable color output\")\n}\n\nfunc preRun(cmd *cobra.Command, args []string) {\n\tglobalCtx = context.Background()\n\tsudoUser := os.Getenv(\"SUDO_USER\")\n\tsudoUID, _ := strconv.Atoi(os.Getenv(\"SUDO_UID\"))\n\tsudoGID, _ := strconv.Atoi(os.Getenv(\"SUDO_GID\"))\n\tif sudoUID == 0 && sudoGID == 0 && sudoUser != \"\" {\n\t\tsudoUserObject, _ := user.Lookup(sudoUser)\n\t\tif sudoUserObject != nil {\n\t\t\tsudoUID, _ = strconv.Atoi(sudoUserObject.Uid)\n\t\t\tsudoGID, _ = strconv.Atoi(sudoUserObject.Gid)\n\t\t}\n\t}\n\tif sudoUID > 0 && sudoGID > 0 {\n\t\tglobalCtx = filemanager.WithDefault(globalCtx, \"\", \"\", sudoUID, sudoGID)\n\t}\n\tif disableColor {\n\t\tlog.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, \"\", nil, false).Logger())\n\t}\n\tif workingDir != \"\" {\n\t\t_, err := os.Stat(workingDir)\n\t\tif err != nil {\n\t\t\tfilemanager.MkdirAll(globalCtx, workingDir, 0o777)\n\t\t}\n\t\terr = os.Chdir(workingDir)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\tif len(configPaths) == 0 && len(configDirectories) == 0 {\n\t\tconfigPaths = append(configPaths, \"config.json\")\n\t}\n\tglobalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_check.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box\"\n\t\"github.com/sagernet/sing-box/log\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandCheck = &cobra.Command{\n\tUse:   \"check\",\n\tShort: \"Check configuration\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := check()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n\tArgs: cobra.NoArgs,\n}\n\nfunc init() {\n\tmainCommand.AddCommand(commandCheck)\n}\n\nfunc check() error {\n\toptions, err := readConfigAndMerge()\n\tif err != nil {\n\t\treturn err\n\t}\n\tctx, cancel := context.WithCancel(globalCtx)\n\tinstance, err := box.New(box.Options{\n\t\tContext: ctx,\n\t\tOptions: options,\n\t})\n\tif err == nil {\n\t\tinstance.Close()\n\t}\n\tcancel()\n\treturn err\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_format.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandFormatFlagWrite bool\n\nvar commandFormat = &cobra.Command{\n\tUse:   \"format\",\n\tShort: \"Format configuration\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := format()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n\tArgs: cobra.NoArgs,\n}\n\nfunc init() {\n\tcommandFormat.Flags().BoolVarP(&commandFormatFlagWrite, \"write\", \"w\", false, \"write result to (source) file instead of stdout\")\n\tmainCommand.AddCommand(commandFormat)\n}\n\nfunc format() error {\n\toptionsList, err := readConfig()\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, optionsEntry := range optionsList {\n\t\toptionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbuffer := new(bytes.Buffer)\n\t\tencoder := json.NewEncoder(buffer)\n\t\tencoder.SetIndent(\"\", \"  \")\n\t\terr = encoder.Encode(optionsEntry.options)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"encode config\")\n\t\t}\n\t\toutputPath, _ := filepath.Abs(optionsEntry.path)\n\t\tif !commandFormatFlagWrite {\n\t\t\tif len(optionsList) > 1 {\n\t\t\t\tos.Stdout.WriteString(outputPath + \"\\n\")\n\t\t\t}\n\t\t\tos.Stdout.WriteString(buffer.String() + \"\\n\")\n\t\t\tcontinue\n\t\t}\n\t\tif bytes.Equal(optionsEntry.content, buffer.Bytes()) {\n\t\t\tcontinue\n\t\t}\n\t\toutput, err := os.Create(optionsEntry.path)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"open output\")\n\t\t}\n\t\t_, err = output.Write(buffer.Bytes())\n\t\toutput.Close()\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"write output\")\n\t\t}\n\t\tos.Stderr.WriteString(outputPath + \"\\n\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_generate.go",
    "content": "package main\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\n\t\"github.com/gofrs/uuid/v5\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandGenerate = &cobra.Command{\n\tUse:   \"generate\",\n\tShort: \"Generate things\",\n}\n\nfunc init() {\n\tcommandGenerate.AddCommand(commandGenerateUUID)\n\tcommandGenerate.AddCommand(commandGenerateRandom)\n\n\tmainCommand.AddCommand(commandGenerate)\n}\n\nvar (\n\toutputBase64 bool\n\toutputHex    bool\n)\n\nvar commandGenerateRandom = &cobra.Command{\n\tUse:   \"rand <length>\",\n\tShort: \"Generate random bytes\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := generateRandom(args)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGenerateRandom.Flags().BoolVar(&outputBase64, \"base64\", false, \"Generate base64 string\")\n\tcommandGenerateRandom.Flags().BoolVar(&outputHex, \"hex\", false, \"Generate hex string\")\n}\n\nfunc generateRandom(args []string) error {\n\tlength, err := strconv.Atoi(args[0])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trandomBytes := make([]byte, length)\n\t_, err = rand.Read(randomBytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif outputBase64 {\n\t\t_, err = os.Stdout.WriteString(base64.StdEncoding.EncodeToString(randomBytes) + \"\\n\")\n\t} else if outputHex {\n\t\t_, err = os.Stdout.WriteString(hex.EncodeToString(randomBytes) + \"\\n\")\n\t} else {\n\t\t_, err = os.Stdout.Write(randomBytes)\n\t}\n\n\treturn err\n}\n\nvar commandGenerateUUID = &cobra.Command{\n\tUse:   \"uuid\",\n\tShort: \"Generate UUID string\",\n\tArgs:  cobra.NoArgs,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := generateUUID()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc generateUUID() error {\n\tnewUUID, err := uuid.NewV4()\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = os.Stdout.WriteString(newUUID.String() + \"\\n\")\n\treturn err\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_generate_ech.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/log\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandGenerateECHKeyPair = &cobra.Command{\n\tUse:   \"ech-keypair <plain_server_name>\",\n\tShort: \"Generate TLS ECH key pair\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := generateECHKeyPair(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGenerate.AddCommand(commandGenerateECHKeyPair)\n}\n\nfunc generateECHKeyPair(serverName string) error {\n\tconfigPem, keyPem, err := tls.ECHKeygenDefault(serverName)\n\tif err != nil {\n\t\treturn err\n\t}\n\tos.Stdout.WriteString(configPem)\n\tos.Stdout.WriteString(keyPem)\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_generate_tls.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/log\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar flagGenerateTLSKeyPairMonths int\n\nvar commandGenerateTLSKeyPair = &cobra.Command{\n\tUse:   \"tls-keypair <server_name>\",\n\tShort: \"Generate TLS self sign key pair\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := generateTLSKeyPair(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGenerateTLSKeyPair.Flags().IntVarP(&flagGenerateTLSKeyPairMonths, \"months\", \"m\", 1, \"Valid months\")\n\tcommandGenerate.AddCommand(commandGenerateTLSKeyPair)\n}\n\nfunc generateTLSKeyPair(serverName string) error {\n\tprivateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))\n\tif err != nil {\n\t\treturn err\n\t}\n\tos.Stdout.WriteString(string(privateKeyPem) + \"\\n\")\n\tos.Stdout.WriteString(string(publicKeyPem) + \"\\n\")\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_generate_vapid.go",
    "content": "//go:build go1.20\n\npackage main\n\nimport (\n\t\"crypto/ecdh\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandGenerateVAPIDKeyPair = &cobra.Command{\n\tUse:   \"vapid-keypair\",\n\tShort: \"Generate VAPID key pair\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := generateVAPIDKeyPair()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGenerate.AddCommand(commandGenerateVAPIDKeyPair)\n}\n\nfunc generateVAPIDKeyPair() error {\n\tprivateKey, err := ecdh.P256().GenerateKey(rand.Reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpublicKey := privateKey.PublicKey()\n\tos.Stdout.WriteString(\"PrivateKey: \" + base64.RawURLEncoding.EncodeToString(privateKey.Bytes()) + \"\\n\")\n\tos.Stdout.WriteString(\"PublicKey: \" + base64.RawURLEncoding.EncodeToString(publicKey.Bytes()) + \"\\n\")\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_generate_wireguard.go",
    "content": "package main\n\nimport (\n\t\"encoding/base64\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.zx2c4.com/wireguard/wgctrl/wgtypes\"\n)\n\nfunc init() {\n\tcommandGenerate.AddCommand(commandGenerateWireGuardKeyPair)\n\tcommandGenerate.AddCommand(commandGenerateRealityKeyPair)\n}\n\nvar commandGenerateWireGuardKeyPair = &cobra.Command{\n\tUse:   \"wg-keypair\",\n\tShort: \"Generate WireGuard key pair\",\n\tArgs:  cobra.NoArgs,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := generateWireGuardKey()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc generateWireGuardKey() error {\n\tprivateKey, err := wgtypes.GeneratePrivateKey()\n\tif err != nil {\n\t\treturn err\n\t}\n\tos.Stdout.WriteString(\"PrivateKey: \" + privateKey.String() + \"\\n\")\n\tos.Stdout.WriteString(\"PublicKey: \" + privateKey.PublicKey().String() + \"\\n\")\n\treturn nil\n}\n\nvar commandGenerateRealityKeyPair = &cobra.Command{\n\tUse:   \"reality-keypair\",\n\tShort: \"Generate reality key pair\",\n\tArgs:  cobra.NoArgs,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := generateRealityKey()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc generateRealityKey() error {\n\tprivateKey, err := wgtypes.GeneratePrivateKey()\n\tif err != nil {\n\t\treturn err\n\t}\n\tpublicKey := privateKey.PublicKey()\n\tos.Stdout.WriteString(\"PrivateKey: \" + base64.RawURLEncoding.EncodeToString(privateKey[:]) + \"\\n\")\n\tos.Stdout.WriteString(\"PublicKey: \" + base64.RawURLEncoding.EncodeToString(publicKey[:]) + \"\\n\")\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_geoip.go",
    "content": "package main\n\nimport (\n\t\"github.com/sagernet/sing-box/log\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"github.com/oschwald/maxminddb-golang\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tgeoipReader          *maxminddb.Reader\n\tcommandGeoIPFlagFile string\n)\n\nvar commandGeoip = &cobra.Command{\n\tUse:   \"geoip\",\n\tShort: \"GeoIP tools\",\n\tPersistentPreRun: func(cmd *cobra.Command, args []string) {\n\t\terr := geoipPreRun()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGeoip.PersistentFlags().StringVarP(&commandGeoIPFlagFile, \"file\", \"f\", \"geoip.db\", \"geoip file\")\n\tmainCommand.AddCommand(commandGeoip)\n}\n\nfunc geoipPreRun() error {\n\treader, err := maxminddb.Open(commandGeoIPFlagFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif reader.Metadata.DatabaseType != \"sing-geoip\" {\n\t\treader.Close()\n\t\treturn E.New(\"incorrect database type, expected sing-geoip, got \", reader.Metadata.DatabaseType)\n\t}\n\tgeoipReader = reader\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_geoip_export.go",
    "content": "package main\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\n\t\"github.com/oschwald/maxminddb-golang\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar flagGeoipExportOutput string\n\nconst flagGeoipExportDefaultOutput = \"geoip-<country>.srs\"\n\nvar commandGeoipExport = &cobra.Command{\n\tUse:   \"export <country>\",\n\tShort: \"Export geoip country as rule-set\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := geoipExport(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGeoipExport.Flags().StringVarP(&flagGeoipExportOutput, \"output\", \"o\", flagGeoipExportDefaultOutput, \"Output path\")\n\tcommandGeoip.AddCommand(commandGeoipExport)\n}\n\nfunc geoipExport(countryCode string) error {\n\tnetworks := geoipReader.Networks(maxminddb.SkipAliasedNetworks)\n\tcountryMap := make(map[string][]*net.IPNet)\n\tvar (\n\t\tipNet           *net.IPNet\n\t\tnextCountryCode string\n\t\terr             error\n\t)\n\tfor networks.Next() {\n\t\tipNet, err = networks.Network(&nextCountryCode)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcountryMap[nextCountryCode] = append(countryMap[nextCountryCode], ipNet)\n\t}\n\tipNets := countryMap[strings.ToLower(countryCode)]\n\tif len(ipNets) == 0 {\n\t\treturn E.New(\"country code not found: \", countryCode)\n\t}\n\n\tvar (\n\t\toutputFile   *os.File\n\t\toutputWriter io.Writer\n\t)\n\tif flagGeoipExportOutput == \"stdout\" {\n\t\toutputWriter = os.Stdout\n\t} else if flagGeoipExportOutput == flagGeoipExportDefaultOutput {\n\t\toutputFile, err = os.Create(\"geoip-\" + countryCode + \".json\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer outputFile.Close()\n\t\toutputWriter = outputFile\n\t} else {\n\t\toutputFile, err = os.Create(flagGeoipExportOutput)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer outputFile.Close()\n\t\toutputWriter = outputFile\n\t}\n\n\tencoder := json.NewEncoder(outputWriter)\n\tencoder.SetIndent(\"\", \"  \")\n\tvar headlessRule option.DefaultHeadlessRule\n\theadlessRule.IPCIDR = make([]string, 0, len(ipNets))\n\tfor _, cidr := range ipNets {\n\t\theadlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String())\n\t}\n\tvar plainRuleSet option.PlainRuleSetCompat\n\tplainRuleSet.Version = C.RuleSetVersion2\n\tplainRuleSet.Options.Rules = []option.HeadlessRule{\n\t\t{\n\t\t\tType:           C.RuleTypeDefault,\n\t\t\tDefaultOptions: headlessRule,\n\t\t},\n\t}\n\treturn encoder.Encode(plainRuleSet)\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_geoip_list.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandGeoipList = &cobra.Command{\n\tUse:   \"list\",\n\tShort: \"List geoip country codes\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := listGeoip()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGeoip.AddCommand(commandGeoipList)\n}\n\nfunc listGeoip() error {\n\tfor _, code := range geoipReader.Metadata.Languages {\n\t\tos.Stdout.WriteString(code + \"\\n\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_geoip_lookup.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandGeoipLookup = &cobra.Command{\n\tUse:   \"lookup <address>\",\n\tShort: \"Lookup if an IP address is contained in the GeoIP database\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := geoipLookup(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGeoip.AddCommand(commandGeoipLookup)\n}\n\nfunc geoipLookup(address string) error {\n\taddr, err := netip.ParseAddr(address)\n\tif err != nil {\n\t\treturn E.Cause(err, \"parse address\")\n\t}\n\tif !N.IsPublicAddr(addr) {\n\t\tos.Stdout.WriteString(\"private\\n\")\n\t\treturn nil\n\t}\n\tvar code string\n\t_ = geoipReader.Lookup(addr.AsSlice(), &code)\n\tif code != \"\" {\n\t\tos.Stdout.WriteString(code + \"\\n\")\n\t\treturn nil\n\t}\n\tos.Stdout.WriteString(\"unknown\\n\")\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_geosite.go",
    "content": "package main\n\nimport (\n\t\"github.com/sagernet/sing-box/common/geosite\"\n\t\"github.com/sagernet/sing-box/log\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tcommandGeoSiteFlagFile string\n\tgeositeReader          *geosite.Reader\n\tgeositeCodeList        []string\n)\n\nvar commandGeoSite = &cobra.Command{\n\tUse:   \"geosite\",\n\tShort: \"Geosite tools\",\n\tPersistentPreRun: func(cmd *cobra.Command, args []string) {\n\t\terr := geositePreRun()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGeoSite.PersistentFlags().StringVarP(&commandGeoSiteFlagFile, \"file\", \"f\", \"geosite.db\", \"geosite file\")\n\tmainCommand.AddCommand(commandGeoSite)\n}\n\nfunc geositePreRun() error {\n\treader, codeList, err := geosite.Open(commandGeoSiteFlagFile)\n\tif err != nil {\n\t\treturn E.Cause(err, \"open geosite file\")\n\t}\n\tgeositeReader = reader\n\tgeositeCodeList = codeList\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_geosite_export.go",
    "content": "package main\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/common/geosite\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/json\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandGeositeExportOutput string\n\nconst commandGeositeExportDefaultOutput = \"geosite-<category>.json\"\n\nvar commandGeositeExport = &cobra.Command{\n\tUse:   \"export <category>\",\n\tShort: \"Export geosite category as rule-set\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := geositeExport(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGeositeExport.Flags().StringVarP(&commandGeositeExportOutput, \"output\", \"o\", commandGeositeExportDefaultOutput, \"Output path\")\n\tcommandGeoSite.AddCommand(commandGeositeExport)\n}\n\nfunc geositeExport(category string) error {\n\tsourceSet, err := geositeReader.Read(category)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar (\n\t\toutputFile   *os.File\n\t\toutputWriter io.Writer\n\t)\n\tif commandGeositeExportOutput == \"stdout\" {\n\t\toutputWriter = os.Stdout\n\t} else if commandGeositeExportOutput == commandGeositeExportDefaultOutput {\n\t\toutputFile, err = os.Create(\"geosite-\" + category + \".json\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer outputFile.Close()\n\t\toutputWriter = outputFile\n\t} else {\n\t\toutputFile, err = os.Create(commandGeositeExportOutput)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer outputFile.Close()\n\t\toutputWriter = outputFile\n\t}\n\n\tencoder := json.NewEncoder(outputWriter)\n\tencoder.SetIndent(\"\", \"  \")\n\tvar headlessRule option.DefaultHeadlessRule\n\tdefaultRule := geosite.Compile(sourceSet)\n\theadlessRule.Domain = defaultRule.Domain\n\theadlessRule.DomainSuffix = defaultRule.DomainSuffix\n\theadlessRule.DomainKeyword = defaultRule.DomainKeyword\n\theadlessRule.DomainRegex = defaultRule.DomainRegex\n\tvar plainRuleSet option.PlainRuleSetCompat\n\tplainRuleSet.Version = C.RuleSetVersion2\n\tplainRuleSet.Options.Rules = []option.HeadlessRule{\n\t\t{\n\t\t\tType:           C.RuleTypeDefault,\n\t\t\tDefaultOptions: headlessRule,\n\t\t},\n\t}\n\treturn encoder.Encode(plainRuleSet)\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_geosite_list.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"sort\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\tF \"github.com/sagernet/sing/common/format\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandGeositeList = &cobra.Command{\n\tUse:   \"list <category>\",\n\tShort: \"List geosite categories\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := geositeList()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGeoSite.AddCommand(commandGeositeList)\n}\n\nfunc geositeList() error {\n\tvar geositeEntry []struct {\n\t\tcategory string\n\t\titems    int\n\t}\n\tfor _, category := range geositeCodeList {\n\t\tsourceSet, err := geositeReader.Read(category)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgeositeEntry = append(geositeEntry, struct {\n\t\t\tcategory string\n\t\t\titems    int\n\t\t}{category, len(sourceSet)})\n\t}\n\tsort.SliceStable(geositeEntry, func(i, j int) bool {\n\t\treturn geositeEntry[i].items < geositeEntry[j].items\n\t})\n\tfor _, entry := range geositeEntry {\n\t\tos.Stdout.WriteString(F.ToString(entry.category, \" (\", entry.items, \")\\n\"))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_geosite_lookup.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"sort\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandGeositeLookup = &cobra.Command{\n\tUse:   \"lookup [category] <domain>\",\n\tShort: \"Check if a domain is in the geosite\",\n\tArgs:  cobra.RangeArgs(1, 2),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tvar (\n\t\t\tsource string\n\t\t\ttarget string\n\t\t)\n\t\tswitch len(args) {\n\t\tcase 1:\n\t\t\ttarget = args[0]\n\t\tcase 2:\n\t\t\tsource = args[0]\n\t\t\ttarget = args[1]\n\t\t}\n\t\terr := geositeLookup(source, target)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandGeoSite.AddCommand(commandGeositeLookup)\n}\n\nfunc geositeLookup(source string, target string) error {\n\tvar sourceMatcherList []struct {\n\t\tcode    string\n\t\tmatcher *searchGeositeMatcher\n\t}\n\tif source != \"\" {\n\t\tsourceSet, err := geositeReader.Read(source)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsourceMatcher, err := newSearchGeositeMatcher(sourceSet)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"compile code: \"+source)\n\t\t}\n\t\tsourceMatcherList = []struct {\n\t\t\tcode    string\n\t\t\tmatcher *searchGeositeMatcher\n\t\t}{\n\t\t\t{\n\t\t\t\tcode:    source,\n\t\t\t\tmatcher: sourceMatcher,\n\t\t\t},\n\t\t}\n\n\t} else {\n\t\tfor _, code := range geositeCodeList {\n\t\t\tsourceSet, err := geositeReader.Read(code)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tsourceMatcher, err := newSearchGeositeMatcher(sourceSet)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"compile code: \"+code)\n\t\t\t}\n\t\t\tsourceMatcherList = append(sourceMatcherList, struct {\n\t\t\t\tcode    string\n\t\t\t\tmatcher *searchGeositeMatcher\n\t\t\t}{\n\t\t\t\tcode:    code,\n\t\t\t\tmatcher: sourceMatcher,\n\t\t\t})\n\t\t}\n\t}\n\tsort.SliceStable(sourceMatcherList, func(i, j int) bool {\n\t\treturn sourceMatcherList[i].code < sourceMatcherList[j].code\n\t})\n\n\tfor _, matcherItem := range sourceMatcherList {\n\t\tif matchRule := matcherItem.matcher.Match(target); matchRule != \"\" {\n\t\t\tos.Stdout.WriteString(\"Match code (\")\n\t\t\tos.Stdout.WriteString(matcherItem.code)\n\t\t\tos.Stdout.WriteString(\") \")\n\t\t\tos.Stdout.WriteString(matchRule)\n\t\t\tos.Stdout.WriteString(\"\\n\")\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_geosite_matcher.go",
    "content": "package main\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/common/geosite\"\n)\n\ntype searchGeositeMatcher struct {\n\tdomainMap   map[string]bool\n\tsuffixList  []string\n\tkeywordList []string\n\tregexList   []string\n}\n\nfunc newSearchGeositeMatcher(items []geosite.Item) (*searchGeositeMatcher, error) {\n\toptions := geosite.Compile(items)\n\tdomainMap := make(map[string]bool)\n\tfor _, domain := range options.Domain {\n\t\tdomainMap[domain] = true\n\t}\n\trule := &searchGeositeMatcher{\n\t\tdomainMap:   domainMap,\n\t\tsuffixList:  options.DomainSuffix,\n\t\tkeywordList: options.DomainKeyword,\n\t\tregexList:   options.DomainRegex,\n\t}\n\treturn rule, nil\n}\n\nfunc (r *searchGeositeMatcher) Match(domain string) string {\n\tif r.domainMap[domain] {\n\t\treturn \"domain=\" + domain\n\t}\n\tfor _, suffix := range r.suffixList {\n\t\tif strings.HasSuffix(domain, suffix) {\n\t\t\treturn \"domain_suffix=\" + suffix\n\t\t}\n\t}\n\tfor _, keyword := range r.keywordList {\n\t\tif strings.Contains(domain, keyword) {\n\t\t\treturn \"domain_keyword=\" + keyword\n\t\t}\n\t}\n\tfor _, regexStr := range r.regexList {\n\t\tregex, err := regexp.Compile(regexStr)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif regex.MatchString(domain) {\n\t\t\treturn \"domain_regex=\" + regexStr\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_merge.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/rw\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandMerge = &cobra.Command{\n\tUse:   \"merge <output-path>\",\n\tShort: \"Merge configurations\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := merge(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n\tArgs: cobra.ExactArgs(1),\n}\n\nfunc init() {\n\tmainCommand.AddCommand(commandMerge)\n}\n\nfunc merge(outputPath string) error {\n\tmergedOptions, err := readConfigAndMerge()\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = mergePathResources(&mergedOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuffer := new(bytes.Buffer)\n\tencoder := json.NewEncoder(buffer)\n\tencoder.SetIndent(\"\", \"  \")\n\terr = encoder.Encode(mergedOptions)\n\tif err != nil {\n\t\treturn E.Cause(err, \"encode config\")\n\t}\n\tif existsContent, err := os.ReadFile(outputPath); err != nil {\n\t\tif string(existsContent) == buffer.String() {\n\t\t\treturn nil\n\t\t}\n\t}\n\terr = rw.MkdirParent(outputPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.WriteFile(outputPath, buffer.Bytes(), 0o644)\n\tif err != nil {\n\t\treturn err\n\t}\n\toutputPath, _ = filepath.Abs(outputPath)\n\tos.Stderr.WriteString(outputPath + \"\\n\")\n\treturn nil\n}\n\nfunc mergePathResources(options *option.Options) error {\n\tfor _, inbound := range options.Inbounds {\n\t\tif tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions {\n\t\t\ttlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))\n\t\t}\n\t}\n\tfor _, outbound := range options.Outbounds {\n\t\tswitch outbound.Type {\n\t\tcase C.TypeSSH:\n\t\t\tmergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions))\n\t\t}\n\t\tif tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions {\n\t\t\ttlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc mergeTLSInboundOptions(options *option.InboundTLSOptions) *option.InboundTLSOptions {\n\tif options == nil {\n\t\treturn nil\n\t}\n\tif options.CertificatePath != \"\" {\n\t\tif content, err := os.ReadFile(options.CertificatePath); err == nil {\n\t\t\toptions.Certificate = trimStringArray(strings.Split(string(content), \"\\n\"))\n\t\t}\n\t}\n\tif options.KeyPath != \"\" {\n\t\tif content, err := os.ReadFile(options.KeyPath); err == nil {\n\t\t\toptions.Key = trimStringArray(strings.Split(string(content), \"\\n\"))\n\t\t}\n\t}\n\tif options.ECH != nil {\n\t\tif options.ECH.KeyPath != \"\" {\n\t\t\tif content, err := os.ReadFile(options.ECH.KeyPath); err == nil {\n\t\t\t\toptions.ECH.Key = trimStringArray(strings.Split(string(content), \"\\n\"))\n\t\t\t}\n\t\t}\n\t}\n\treturn options\n}\n\nfunc mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.OutboundTLSOptions {\n\tif options == nil {\n\t\treturn nil\n\t}\n\tif options.CertificatePath != \"\" {\n\t\tif content, err := os.ReadFile(options.CertificatePath); err == nil {\n\t\t\toptions.Certificate = trimStringArray(strings.Split(string(content), \"\\n\"))\n\t\t}\n\t}\n\tif options.ECH != nil {\n\t\tif options.ECH.ConfigPath != \"\" {\n\t\t\tif content, err := os.ReadFile(options.ECH.ConfigPath); err == nil {\n\t\t\t\toptions.ECH.Config = trimStringArray(strings.Split(string(content), \"\\n\"))\n\t\t\t}\n\t\t}\n\t}\n\treturn options\n}\n\nfunc mergeSSHOutboundOptions(options *option.SSHOutboundOptions) {\n\tif options.PrivateKeyPath != \"\" {\n\t\tif content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {\n\t\t\toptions.PrivateKey = trimStringArray(strings.Split(string(content), \"\\n\"))\n\t\t}\n\t}\n}\n\nfunc trimStringArray(array []string) []string {\n\treturn common.Filter(array, func(it string) bool {\n\t\treturn strings.TrimSpace(it) != \"\"\n\t})\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_rule_set.go",
    "content": "package main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandRuleSet = &cobra.Command{\n\tUse:   \"rule-set\",\n\tShort: \"Manage rule-sets\",\n}\n\nfunc init() {\n\tmainCommand.AddCommand(commandRuleSet)\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_rule_set_compile.go",
    "content": "package main\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/common/srs\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/route/rule\"\n\t\"github.com/sagernet/sing/common/json\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar flagRuleSetCompileOutput string\n\nconst flagRuleSetCompileDefaultOutput = \"<file_name>.srs\"\n\nvar commandRuleSetCompile = &cobra.Command{\n\tUse:   \"compile [source-path]\",\n\tShort: \"Compile rule-set json to binary\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := compileRuleSet(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandRuleSet.AddCommand(commandRuleSetCompile)\n\tcommandRuleSetCompile.Flags().StringVarP(&flagRuleSetCompileOutput, \"output\", \"o\", flagRuleSetCompileDefaultOutput, \"Output file\")\n}\n\nfunc compileRuleSet(sourcePath string) error {\n\tvar (\n\t\treader io.Reader\n\t\terr    error\n\t)\n\tif sourcePath == \"stdin\" {\n\t\treader = os.Stdin\n\t} else {\n\t\treader, err = os.Open(sourcePath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tcontent, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\tplainRuleSet, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar outputPath string\n\tif flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {\n\t\tif strings.HasSuffix(sourcePath, \".json\") {\n\t\t\toutputPath = sourcePath[:len(sourcePath)-5] + \".srs\"\n\t\t} else {\n\t\t\toutputPath = sourcePath + \".srs\"\n\t\t}\n\t} else {\n\t\toutputPath = flagRuleSetCompileOutput\n\t}\n\toutputFile, err := os.Create(outputPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = srs.Write(outputFile, plainRuleSet.Options, downgradeRuleSetVersion(plainRuleSet.Version, plainRuleSet.Options))\n\tif err != nil {\n\t\toutputFile.Close()\n\t\tos.Remove(outputPath)\n\t\treturn err\n\t}\n\toutputFile.Close()\n\treturn nil\n}\n\nfunc downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {\n\tif version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {\n\t\treturn rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||\n\t\t\tlen(rule.DefaultInterfaceAddress) > 0\n\t}) {\n\t\tversion = C.RuleSetVersion3\n\t}\n\tif version == C.RuleSetVersion3 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {\n\t\treturn len(rule.NetworkType) > 0 || rule.NetworkIsExpensive || rule.NetworkIsConstrained\n\t}) {\n\t\tversion = C.RuleSetVersion2\n\t}\n\treturn version\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_rule_set_convert.go",
    "content": "package main\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/common/convertor/adguard\"\n\t\"github.com/sagernet/sing-box/common/srs\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tflagRuleSetConvertType   string\n\tflagRuleSetConvertOutput string\n)\n\nvar commandRuleSetConvert = &cobra.Command{\n\tUse:   \"convert [source-path]\",\n\tShort: \"Convert adguard DNS filter to rule-set\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := convertRuleSet(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandRuleSet.AddCommand(commandRuleSetConvert)\n\tcommandRuleSetConvert.Flags().StringVarP(&flagRuleSetConvertType, \"type\", \"t\", \"\", \"Source type, available: adguard\")\n\tcommandRuleSetConvert.Flags().StringVarP(&flagRuleSetConvertOutput, \"output\", \"o\", flagRuleSetCompileDefaultOutput, \"Output file\")\n}\n\nfunc convertRuleSet(sourcePath string) error {\n\tvar (\n\t\treader io.Reader\n\t\terr    error\n\t)\n\tif sourcePath == \"stdin\" {\n\t\treader = os.Stdin\n\t} else {\n\t\treader, err = os.Open(sourcePath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tvar rules []option.HeadlessRule\n\tswitch flagRuleSetConvertType {\n\tcase \"adguard\":\n\t\trules, err = adguard.ToOptions(reader, log.StdLogger())\n\tcase \"\":\n\t\treturn E.New(\"source type is required\")\n\tdefault:\n\t\treturn E.New(\"unsupported source type: \", flagRuleSetConvertType)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar outputPath string\n\tif flagRuleSetConvertOutput == flagRuleSetCompileDefaultOutput {\n\t\tif strings.HasSuffix(sourcePath, \".txt\") {\n\t\t\toutputPath = sourcePath[:len(sourcePath)-4] + \".srs\"\n\t\t} else {\n\t\t\toutputPath = sourcePath + \".srs\"\n\t\t}\n\t} else {\n\t\toutputPath = flagRuleSetConvertOutput\n\t}\n\toutputFile, err := os.Create(outputPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer outputFile.Close()\n\terr = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, C.RuleSetVersion2)\n\tif err != nil {\n\t\toutputFile.Close()\n\t\tos.Remove(outputPath)\n\t\treturn err\n\t}\n\toutputFile.Close()\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_rule_set_decompile.go",
    "content": "package main\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/common/srs\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar flagRuleSetDecompileOutput string\n\nconst flagRuleSetDecompileDefaultOutput = \"<file_name>.json\"\n\nvar commandRuleSetDecompile = &cobra.Command{\n\tUse:   \"decompile [binary-path]\",\n\tShort: \"Decompile rule-set binary to json\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := decompileRuleSet(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandRuleSet.AddCommand(commandRuleSetDecompile)\n\tcommandRuleSetDecompile.Flags().StringVarP(&flagRuleSetDecompileOutput, \"output\", \"o\", flagRuleSetDecompileDefaultOutput, \"Output file\")\n}\n\nfunc decompileRuleSet(sourcePath string) error {\n\tvar (\n\t\treader io.Reader\n\t\terr    error\n\t)\n\tif sourcePath == \"stdin\" {\n\t\treader = os.Stdin\n\t} else {\n\t\treader, err = os.Open(sourcePath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\truleSet, err := srs.Read(reader, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {\n\t\treturn len(rule.AdGuardDomain) > 0\n\t}) {\n\t\treturn E.New(\"unable to decompile binary AdGuard rules to rule-set.\")\n\t}\n\tvar outputPath string\n\tif flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {\n\t\tif strings.HasSuffix(sourcePath, \".srs\") {\n\t\t\toutputPath = sourcePath[:len(sourcePath)-4] + \".json\"\n\t\t} else {\n\t\t\toutputPath = sourcePath + \".json\"\n\t\t}\n\t} else {\n\t\toutputPath = flagRuleSetDecompileOutput\n\t}\n\toutputFile, err := os.Create(outputPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tencoder := json.NewEncoder(outputFile)\n\tencoder.SetIndent(\"\", \"  \")\n\terr = encoder.Encode(ruleSet)\n\tif err != nil {\n\t\toutputFile.Close()\n\t\tos.Remove(outputPath)\n\t\treturn err\n\t}\n\toutputFile.Close()\n\treturn nil\n}\n\nfunc hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {\n\tfor _, rule := range rules {\n\t\tswitch rule.Type {\n\t\tcase C.RuleTypeDefault:\n\t\t\tif cond(rule.DefaultOptions) {\n\t\t\t\treturn true\n\t\t\t}\n\t\tcase C.RuleTypeLogical:\n\t\t\tif hasRule(rule.LogicalOptions.Rules, cond) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_rule_set_format.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandRuleSetFormatFlagWrite bool\n\nvar commandRuleSetFormat = &cobra.Command{\n\tUse:   \"format <source-path>\",\n\tShort: \"Format rule-set json\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := formatRuleSet(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandRuleSetFormat.Flags().BoolVarP(&commandRuleSetFormatFlagWrite, \"write\", \"w\", false, \"write result to (source) file instead of stdout\")\n\tcommandRuleSet.AddCommand(commandRuleSetFormat)\n}\n\nfunc formatRuleSet(sourcePath string) error {\n\tvar (\n\t\treader io.Reader\n\t\terr    error\n\t)\n\tif sourcePath == \"stdin\" {\n\t\treader = os.Stdin\n\t} else {\n\t\treader, err = os.Open(sourcePath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tcontent, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\tplainRuleSet, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuffer := new(bytes.Buffer)\n\tencoder := json.NewEncoder(buffer)\n\tencoder.SetIndent(\"\", \"  \")\n\terr = encoder.Encode(plainRuleSet)\n\tif err != nil {\n\t\treturn E.Cause(err, \"encode config\")\n\t}\n\toutputPath, _ := filepath.Abs(sourcePath)\n\tif !commandRuleSetFormatFlagWrite || sourcePath == \"stdin\" {\n\t\tos.Stdout.WriteString(buffer.String() + \"\\n\")\n\t\treturn nil\n\t}\n\tif bytes.Equal(content, buffer.Bytes()) {\n\t\treturn nil\n\t}\n\toutput, err := os.Create(sourcePath)\n\tif err != nil {\n\t\treturn E.Cause(err, \"open output\")\n\t}\n\t_, err = output.Write(buffer.Bytes())\n\toutput.Close()\n\tif err != nil {\n\t\treturn E.Cause(err, \"write output\")\n\t}\n\tos.Stderr.WriteString(outputPath + \"\\n\")\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_rule_set_match.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/srs\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/route/rule\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar flagRuleSetMatchFormat string\n\nvar commandRuleSetMatch = &cobra.Command{\n\tUse:   \"match <rule-set path> <IP address/domain>\",\n\tShort: \"Check if an IP address or a domain matches the rule-set\",\n\tArgs:  cobra.ExactArgs(2),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := ruleSetMatch(args[0], args[1])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandRuleSetMatch.Flags().StringVarP(&flagRuleSetMatchFormat, \"format\", \"f\", \"source\", \"rule-set format\")\n\tcommandRuleSet.AddCommand(commandRuleSetMatch)\n}\n\nfunc ruleSetMatch(sourcePath string, domain string) error {\n\tvar (\n\t\treader io.Reader\n\t\terr    error\n\t)\n\tif sourcePath == \"stdin\" {\n\t\treader = os.Stdin\n\t} else {\n\t\treader, err = os.Open(sourcePath)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"read rule-set\")\n\t\t}\n\t}\n\tcontent, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn E.Cause(err, \"read rule-set\")\n\t}\n\tif flagRuleSetMatchFormat == \"\" {\n\t\tswitch filepath.Ext(sourcePath) {\n\t\tcase \".json\":\n\t\t\tflagRuleSetMatchFormat = C.RuleSetFormatSource\n\t\tcase \".srs\":\n\t\t\tflagRuleSetMatchFormat = C.RuleSetFormatBinary\n\t\t}\n\t}\n\tvar ruleSet option.PlainRuleSetCompat\n\tswitch flagRuleSetMatchFormat {\n\tcase C.RuleSetFormatSource:\n\t\truleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\tcase C.RuleSetFormatBinary:\n\t\truleSet, err = srs.Read(bytes.NewReader(content), false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\tdefault:\n\t\treturn E.New(\"unknown rule-set format: \", flagRuleSetMatchFormat)\n\t}\n\tplainRuleSet, err := ruleSet.Upgrade()\n\tif err != nil {\n\t\treturn err\n\t}\n\tipAddress := M.ParseAddr(domain)\n\tvar metadata adapter.InboundContext\n\tif ipAddress.IsValid() {\n\t\tmetadata.Destination = M.SocksaddrFrom(ipAddress, 0)\n\t} else {\n\t\tmetadata.Domain = domain\n\t}\n\tfor i, ruleOptions := range plainRuleSet.Rules {\n\t\tvar currentRule adapter.HeadlessRule\n\t\tcurrentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"parse rule_set.rules.[\", i, \"]\")\n\t\t}\n\t\tif currentRule.Match(&metadata) {\n\t\t\tprintln(F.ToString(\"match rules.[\", i, \"]: \", currentRule))\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_rule_set_merge.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/rw\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\truleSetPaths       []string\n\truleSetDirectories []string\n)\n\nvar commandRuleSetMerge = &cobra.Command{\n\tUse:   \"merge <output-path>\",\n\tShort: \"Merge rule-set source files\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := mergeRuleSet(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n\tArgs: cobra.ExactArgs(1),\n}\n\nfunc init() {\n\tcommandRuleSetMerge.Flags().StringArrayVarP(&ruleSetPaths, \"config\", \"c\", nil, \"set input rule-set file path\")\n\tcommandRuleSetMerge.Flags().StringArrayVarP(&ruleSetDirectories, \"config-directory\", \"C\", nil, \"set input rule-set directory path\")\n\tcommandRuleSet.AddCommand(commandRuleSetMerge)\n}\n\ntype RuleSetEntry struct {\n\tcontent []byte\n\tpath    string\n\toptions option.PlainRuleSetCompat\n}\n\nfunc readRuleSetAt(path string) (*RuleSetEntry, error) {\n\tvar (\n\t\tconfigContent []byte\n\t\terr           error\n\t)\n\tif path == \"stdin\" {\n\t\tconfigContent, err = io.ReadAll(os.Stdin)\n\t} else {\n\t\tconfigContent, err = os.ReadFile(path)\n\t}\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"read config at \", path)\n\t}\n\toptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, configContent)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"decode config at \", path)\n\t}\n\treturn &RuleSetEntry{\n\t\tcontent: configContent,\n\t\tpath:    path,\n\t\toptions: options,\n\t}, nil\n}\n\nfunc readRuleSet() ([]*RuleSetEntry, error) {\n\tvar optionsList []*RuleSetEntry\n\tfor _, path := range ruleSetPaths {\n\t\toptionsEntry, err := readRuleSetAt(path)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\toptionsList = append(optionsList, optionsEntry)\n\t}\n\tfor _, directory := range ruleSetDirectories {\n\t\tentries, err := os.ReadDir(directory)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"read rule-set directory at \", directory)\n\t\t}\n\t\tfor _, entry := range entries {\n\t\t\tif !strings.HasSuffix(entry.Name(), \".json\") || entry.IsDir() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\toptionsEntry, err := readRuleSetAt(filepath.Join(directory, entry.Name()))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\toptionsList = append(optionsList, optionsEntry)\n\t\t}\n\t}\n\tsort.Slice(optionsList, func(i, j int) bool {\n\t\treturn optionsList[i].path < optionsList[j].path\n\t})\n\treturn optionsList, nil\n}\n\nfunc readRuleSetAndMerge() (option.PlainRuleSetCompat, error) {\n\toptionsList, err := readRuleSet()\n\tif err != nil {\n\t\treturn option.PlainRuleSetCompat{}, err\n\t}\n\tif len(optionsList) == 1 {\n\t\treturn optionsList[0].options, nil\n\t}\n\tvar optionVersion uint8\n\tfor _, options := range optionsList {\n\t\tif optionVersion < options.options.Version {\n\t\t\toptionVersion = options.options.Version\n\t\t}\n\t}\n\tvar mergedMessage json.RawMessage\n\tfor _, options := range optionsList {\n\t\tmergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)\n\t\tif err != nil {\n\t\t\treturn option.PlainRuleSetCompat{}, E.Cause(err, \"merge config at \", options.path)\n\t\t}\n\t}\n\tmergedOptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, mergedMessage)\n\tif err != nil {\n\t\treturn option.PlainRuleSetCompat{}, E.Cause(err, \"unmarshal merged config\")\n\t}\n\tmergedOptions.Version = optionVersion\n\treturn mergedOptions, nil\n}\n\nfunc mergeRuleSet(outputPath string) error {\n\tmergedOptions, err := readRuleSetAndMerge()\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuffer := new(bytes.Buffer)\n\tencoder := json.NewEncoder(buffer)\n\tencoder.SetIndent(\"\", \"  \")\n\terr = encoder.Encode(mergedOptions)\n\tif err != nil {\n\t\treturn E.Cause(err, \"encode config\")\n\t}\n\tif existsContent, err := os.ReadFile(outputPath); err != nil {\n\t\tif string(existsContent) == buffer.String() {\n\t\t\treturn nil\n\t\t}\n\t}\n\terr = rw.MkdirParent(outputPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.WriteFile(outputPath, buffer.Bytes(), 0o644)\n\tif err != nil {\n\t\treturn err\n\t}\n\toutputPath, _ = filepath.Abs(outputPath)\n\tos.Stderr.WriteString(outputPath + \"\\n\")\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_rule_set_upgrade.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandRuleSetUpgradeFlagWrite bool\n\nvar commandRuleSetUpgrade = &cobra.Command{\n\tUse:   \"upgrade <source-path>\",\n\tShort: \"Upgrade rule-set json\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := upgradeRuleSet(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandRuleSetUpgrade.Flags().BoolVarP(&commandRuleSetUpgradeFlagWrite, \"write\", \"w\", false, \"write result to (source) file instead of stdout\")\n\tcommandRuleSet.AddCommand(commandRuleSetUpgrade)\n}\n\nfunc upgradeRuleSet(sourcePath string) error {\n\tvar (\n\t\treader io.Reader\n\t\terr    error\n\t)\n\tif sourcePath == \"stdin\" {\n\t\treader = os.Stdin\n\t} else {\n\t\treader, err = os.Open(sourcePath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tcontent, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\tplainRuleSetCompat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch plainRuleSetCompat.Version {\n\tcase C.RuleSetVersion1:\n\tdefault:\n\t\tlog.Info(\"already up-to-date\")\n\t\treturn nil\n\t}\n\tplainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade()\n\tif err != nil {\n\t\treturn err\n\t}\n\tplainRuleSetCompat.Version = C.RuleSetVersionCurrent\n\tbuffer := new(bytes.Buffer)\n\tencoder := json.NewEncoder(buffer)\n\tencoder.SetIndent(\"\", \"  \")\n\terr = encoder.Encode(plainRuleSetCompat)\n\tif err != nil {\n\t\treturn E.Cause(err, \"encode config\")\n\t}\n\toutputPath, _ := filepath.Abs(sourcePath)\n\tif !commandRuleSetUpgradeFlagWrite || sourcePath == \"stdin\" {\n\t\tos.Stdout.WriteString(buffer.String() + \"\\n\")\n\t\treturn nil\n\t}\n\tif bytes.Equal(content, buffer.Bytes()) {\n\t\treturn nil\n\t}\n\toutput, err := os.Create(sourcePath)\n\tif err != nil {\n\t\treturn E.Cause(err, \"open output\")\n\t}\n\t_, err = output.Write(buffer.Bytes())\n\toutput.Close()\n\tif err != nil {\n\t\treturn E.Cause(err, \"write output\")\n\t}\n\tos.Stderr.WriteString(outputPath + \"\\n\")\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_run.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\truntimeDebug \"runtime/debug\"\n\t\"sort\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandRun = &cobra.Command{\n\tUse:   \"run\",\n\tShort: \"Run service\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := run()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tmainCommand.AddCommand(commandRun)\n}\n\ntype OptionsEntry struct {\n\tcontent []byte\n\tpath    string\n\toptions option.Options\n}\n\nfunc readConfigAt(path string) (*OptionsEntry, error) {\n\tvar (\n\t\tconfigContent []byte\n\t\terr           error\n\t)\n\tif path == \"stdin\" {\n\t\tconfigContent, err = io.ReadAll(os.Stdin)\n\t} else {\n\t\tconfigContent, err = os.ReadFile(path)\n\t}\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"read config at \", path)\n\t}\n\toptions, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"decode config at \", path)\n\t}\n\treturn &OptionsEntry{\n\t\tcontent: configContent,\n\t\tpath:    path,\n\t\toptions: options,\n\t}, nil\n}\n\nfunc readConfig() ([]*OptionsEntry, error) {\n\tvar optionsList []*OptionsEntry\n\tfor _, path := range configPaths {\n\t\toptionsEntry, err := readConfigAt(path)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\toptionsList = append(optionsList, optionsEntry)\n\t}\n\tfor _, directory := range configDirectories {\n\t\tentries, err := os.ReadDir(directory)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"read config directory at \", directory)\n\t\t}\n\t\tfor _, entry := range entries {\n\t\t\tif !strings.HasSuffix(entry.Name(), \".json\") || entry.IsDir() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\toptionsEntry, err := readConfigAt(filepath.Join(directory, entry.Name()))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\toptionsList = append(optionsList, optionsEntry)\n\t\t}\n\t}\n\tsort.Slice(optionsList, func(i, j int) bool {\n\t\treturn optionsList[i].path < optionsList[j].path\n\t})\n\treturn optionsList, nil\n}\n\nfunc readConfigAndMerge() (option.Options, error) {\n\toptionsList, err := readConfig()\n\tif err != nil {\n\t\treturn option.Options{}, err\n\t}\n\tif len(optionsList) == 1 {\n\t\treturn optionsList[0].options, nil\n\t}\n\tvar mergedMessage json.RawMessage\n\tfor _, options := range optionsList {\n\t\tmergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)\n\t\tif err != nil {\n\t\t\treturn option.Options{}, E.Cause(err, \"merge config at \", options.path)\n\t\t}\n\t}\n\tvar mergedOptions option.Options\n\terr = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage)\n\tif err != nil {\n\t\treturn option.Options{}, E.Cause(err, \"unmarshal merged config\")\n\t}\n\treturn mergedOptions, nil\n}\n\nfunc create() (*box.Box, context.CancelFunc, error) {\n\toptions, err := readConfigAndMerge()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif disableColor {\n\t\tif options.Log == nil {\n\t\t\toptions.Log = &option.LogOptions{}\n\t\t}\n\t\toptions.Log.DisableColor = true\n\t}\n\tctx, cancel := context.WithCancel(globalCtx)\n\tinstance, err := box.New(box.Options{\n\t\tContext: ctx,\n\t\tOptions: options,\n\t})\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, nil, E.Cause(err, \"create service\")\n\t}\n\n\tosSignals := make(chan os.Signal, 1)\n\tsignal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)\n\tdefer func() {\n\t\tsignal.Stop(osSignals)\n\t\tclose(osSignals)\n\t}()\n\tstartCtx, finishStart := context.WithCancel(context.Background())\n\tgo func() {\n\t\t_, loaded := <-osSignals\n\t\tif loaded {\n\t\t\tcancel()\n\t\t\tcloseMonitor(startCtx)\n\t\t}\n\t}()\n\terr = instance.Start()\n\tfinishStart()\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, nil, E.Cause(err, \"start service\")\n\t}\n\treturn instance, cancel, nil\n}\n\nfunc run() error {\n\tosSignals := make(chan os.Signal, 1)\n\tsignal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)\n\tdefer signal.Stop(osSignals)\n\tfor {\n\t\tinstance, cancel, err := create()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\truntimeDebug.FreeOSMemory()\n\t\tfor {\n\t\t\tosSignal := <-osSignals\n\t\t\tif osSignal == syscall.SIGHUP {\n\t\t\t\terr = check()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Error(E.Cause(err, \"reload service\"))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tcancel()\n\t\t\tcloseCtx, closed := context.WithCancel(context.Background())\n\t\t\tgo closeMonitor(closeCtx)\n\t\t\terr = instance.Close()\n\t\t\tclosed()\n\t\t\tif osSignal != syscall.SIGHUP {\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Error(E.Cause(err, \"sing-box did not closed properly\"))\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc closeMonitor(ctx context.Context) {\n\ttime.Sleep(C.FatalStopTimeout)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\tlog.Fatal(\"sing-box did not close!\")\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_tools.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandToolsFlagOutbound string\n\nvar commandTools = &cobra.Command{\n\tUse:   \"tools\",\n\tShort: \"Experimental tools\",\n}\n\nfunc init() {\n\tcommandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, \"outbound\", \"o\", \"\", \"Use specified tag instead of default outbound\")\n\tmainCommand.AddCommand(commandTools)\n}\n\nfunc createPreStartedClient() (*box.Box, error) {\n\toptions, err := readConfigAndMerge()\n\tif err != nil {\n\t\tif !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != \"config.json\" {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tinstance, err := box.New(box.Options{Context: globalCtx, Options: options})\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"create service\")\n\t}\n\terr = instance.PreStart()\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"start service\")\n\t}\n\treturn instance, nil\n}\n\nfunc createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) {\n\tif outboundTag == \"\" {\n\t\treturn instance.Outbound().Default(), nil\n\t} else {\n\t\toutbound, loaded := instance.Outbound().Outbound(outboundTag)\n\t\tif !loaded {\n\t\t\treturn nil, E.New(\"outbound not found: \", outboundTag)\n\t\t}\n\t\treturn outbound, nil\n\t}\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_tools_connect.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/task\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandConnectFlagNetwork string\n\nvar commandConnect = &cobra.Command{\n\tUse:   \"connect <address>\",\n\tShort: \"Connect to an address\",\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := connect(args[0])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandConnect.Flags().StringVarP(&commandConnectFlagNetwork, \"network\", \"n\", \"tcp\", \"network type\")\n\tcommandTools.AddCommand(commandConnect)\n}\n\nfunc connect(address string) error {\n\tswitch N.NetworkName(commandConnectFlagNetwork) {\n\tcase N.NetworkTCP, N.NetworkUDP:\n\tdefault:\n\t\treturn E.Cause(N.ErrUnknownNetwork, commandConnectFlagNetwork)\n\t}\n\tinstance, err := createPreStartedClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer instance.Close()\n\tdialer, err := createDialer(instance, commandToolsFlagOutbound)\n\tif err != nil {\n\t\treturn err\n\t}\n\tconn, err := dialer.DialContext(context.Background(), commandConnectFlagNetwork, M.ParseSocksaddr(address))\n\tif err != nil {\n\t\treturn E.Cause(err, \"connect to server\")\n\t}\n\tvar group task.Group\n\tgroup.Append(\"upload\", func(ctx context.Context) error {\n\t\treturn common.Error(bufio.Copy(conn, os.Stdin))\n\t})\n\tgroup.Append(\"download\", func(ctx context.Context) error {\n\t\treturn common.Error(bufio.Copy(os.Stdout, conn))\n\t})\n\tgroup.Cleanup(func() {\n\t\tconn.Close()\n\t})\n\terr = group.Run(context.Background())\n\tif E.IsClosed(err) {\n\t\tlog.Info(err)\n\t} else {\n\t\tlog.Error(err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_tools_fetch.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandFetch = &cobra.Command{\n\tUse:   \"fetch\",\n\tShort: \"Fetch an URL\",\n\tArgs:  cobra.MinimumNArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := fetch(args)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandTools.AddCommand(commandFetch)\n}\n\nvar (\n\thttpClient  *http.Client\n\thttp3Client *http.Client\n)\n\nfunc fetch(args []string) error {\n\tinstance, err := createPreStartedClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer instance.Close()\n\thttpClient = &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\tdialer, err := createDialer(instance, commandToolsFlagOutbound)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treturn dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t\t\t},\n\t\t\tForceAttemptHTTP2: true,\n\t\t},\n\t}\n\tdefer httpClient.CloseIdleConnections()\n\tif C.WithQUIC {\n\t\terr = initializeHTTP3Client(instance)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer http3Client.CloseIdleConnections()\n\t}\n\tfor _, urlString := range args {\n\t\tvar parsedURL *url.URL\n\t\tparsedURL, err = url.Parse(urlString)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tswitch parsedURL.Scheme {\n\t\tcase \"\":\n\t\t\tparsedURL.Scheme = \"http\"\n\t\t\tfallthrough\n\t\tcase \"http\", \"https\":\n\t\t\terr = fetchHTTP(httpClient, parsedURL)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"http3\":\n\t\t\tif !C.WithQUIC {\n\t\t\t\treturn C.ErrQUICNotIncluded\n\t\t\t}\n\t\t\tparsedURL.Scheme = \"https\"\n\t\t\terr = fetchHTTP(http3Client, parsedURL)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tdefault:\n\t\t\treturn E.New(\"unsupported scheme: \", parsedURL.Scheme)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error {\n\trequest, err := http.NewRequest(\"GET\", parsedURL.String(), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\trequest.Header.Add(\"User-Agent\", \"curl/7.88.0\")\n\tresponse, err := httpClient.Do(request)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer response.Body.Close()\n\t_, err = bufio.Copy(os.Stdout, response.Body)\n\tif errors.Is(err, io.EOF) {\n\t\treturn nil\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_tools_fetch_http3.go",
    "content": "//go:build with_quic\n\npackage main\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net/http\"\n\n\t\"github.com/sagernet/quic-go\"\n\t\"github.com/sagernet/quic-go/http3\"\n\tbox \"github.com/sagernet/sing-box\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc initializeHTTP3Client(instance *box.Box) error {\n\tdialer, err := createDialer(instance, commandToolsFlagOutbound)\n\tif err != nil {\n\t\treturn err\n\t}\n\thttp3Client = &http.Client{\n\t\tTransport: &http3.Transport{\n\t\t\tDial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {\n\t\t\t\tdestination := M.ParseSocksaddr(addr)\n\t\t\t\tudpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)\n\t\t\t\tif dErr != nil {\n\t\t\t\t\treturn nil, dErr\n\t\t\t\t}\n\t\t\t\treturn quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg)\n\t\t\t},\n\t\t},\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_tools_fetch_http3_stub.go",
    "content": "//go:build !with_quic\n\npackage main\n\nimport (\n\t\"net/url\"\n\t\"os\"\n\n\tbox \"github.com/sagernet/sing-box\"\n)\n\nfunc initializeHTTP3Client(instance *box.Box) error {\n\treturn os.ErrInvalid\n}\n\nfunc fetchHTTP3(parsedURL *url.URL) error {\n\treturn os.ErrInvalid\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_tools_synctime.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tcommandSyncTimeFlagServer   string\n\tcommandSyncTimeOutputFormat string\n\tcommandSyncTimeWrite        bool\n)\n\nvar commandSyncTime = &cobra.Command{\n\tUse:   \"synctime\",\n\tShort: \"Sync time using the NTP protocol\",\n\tArgs:  cobra.NoArgs,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := syncTime()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tcommandSyncTime.Flags().StringVarP(&commandSyncTimeFlagServer, \"server\", \"s\", \"time.apple.com\", \"Set NTP server\")\n\tcommandSyncTime.Flags().StringVarP(&commandSyncTimeOutputFormat, \"format\", \"f\", C.TimeLayout, \"Set output format\")\n\tcommandSyncTime.Flags().BoolVarP(&commandSyncTimeWrite, \"write\", \"w\", false, \"Write time to system\")\n\tcommandTools.AddCommand(commandSyncTime)\n}\n\nfunc syncTime() error {\n\tinstance, err := createPreStartedClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdialer, err := createDialer(instance, commandToolsFlagOutbound)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer instance.Close()\n\tserverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)\n\tif serverAddress.Port == 0 {\n\t\tserverAddress.Port = 123\n\t}\n\tresponse, err := ntp.Exchange(context.Background(), dialer, serverAddress)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif commandSyncTimeWrite {\n\t\terr = ntp.SetSystemTime(response.Time)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"write time to system\")\n\t\t}\n\t}\n\tos.Stdout.WriteString(response.Time.Local().Format(commandSyncTimeOutputFormat))\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/cmd_version.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar commandVersion = &cobra.Command{\n\tUse:   \"version\",\n\tShort: \"Print current version of sing-box\",\n\tRun:   printVersion,\n\tArgs:  cobra.NoArgs,\n}\n\nvar nameOnly bool\n\nfunc init() {\n\tcommandVersion.Flags().BoolVarP(&nameOnly, \"name\", \"n\", false, \"print version name only\")\n\tmainCommand.AddCommand(commandVersion)\n}\n\nfunc printVersion(cmd *cobra.Command, args []string) {\n\tif nameOnly {\n\t\tos.Stdout.WriteString(C.Version + \"\\n\")\n\t\treturn\n\t}\n\tversion := \"sing-box version \" + C.Version + \"\\n\\n\"\n\tversion += \"Environment: \" + runtime.Version() + \" \" + runtime.GOOS + \"/\" + runtime.GOARCH + \"\\n\"\n\n\tvar tags string\n\tvar revision string\n\n\tdebugInfo, loaded := debug.ReadBuildInfo()\n\tif loaded {\n\t\tfor _, setting := range debugInfo.Settings {\n\t\t\tswitch setting.Key {\n\t\t\tcase \"-tags\":\n\t\t\t\ttags = setting.Value\n\t\t\tcase \"vcs.revision\":\n\t\t\t\trevision = setting.Value\n\t\t\t}\n\t\t}\n\t}\n\n\tif tags != \"\" {\n\t\tversion += \"Tags: \" + tags + \"\\n\"\n\t}\n\tif revision != \"\" {\n\t\tversion += \"Revision: \" + revision + \"\\n\"\n\t}\n\n\tif C.CGO_ENABLED {\n\t\tversion += \"CGO: enabled\\n\"\n\t} else {\n\t\tversion += \"CGO: disabled\\n\"\n\t}\n\n\tos.Stdout.WriteString(version)\n}\n"
  },
  {
    "path": "cmd/sing-box/generate_completions.go",
    "content": "//go:build generate && generate_completions\n\npackage main\n\nimport \"github.com/sagernet/sing-box/log\"\n\nfunc main() {\n\terr := generateCompletions()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc generateCompletions() error {\n\terr := mainCommand.GenBashCompletionFile(\"release/completions/sing-box.bash\")\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = mainCommand.GenFishCompletionFile(\"release/completions/sing-box.fish\", true)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = mainCommand.GenZshCompletionFile(\"release/completions/sing-box.zsh\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/sing-box/main.go",
    "content": "//go:build !generate\n\npackage main\n\nimport \"github.com/sagernet/sing-box/log\"\n\nfunc main() {\n\tif err := mainCommand.Execute(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "common/badtls/raw_conn.go",
    "content": "//go:build go1.25 && badlinkname\n\npackage badtls\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"reflect\"\n\t\"sync/atomic\"\n\t\"unsafe\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/tls\"\n)\n\ntype RawConn struct {\n\tpointer unsafe.Pointer\n\tmethods *Methods\n\n\tIsClient            *bool\n\tIsHandshakeComplete *atomic.Bool\n\tVers                *uint16\n\tCipherSuite         *uint16\n\n\tRawInput *bytes.Buffer\n\tInput    *bytes.Reader\n\tHand     *bytes.Buffer\n\n\tCloseNotifySent *bool\n\tCloseNotifyErr  *error\n\n\tIn  *RawHalfConn\n\tOut *RawHalfConn\n\n\tBytesSent   *int64\n\tPacketsSent *int64\n\n\tActiveCall *atomic.Int32\n\tTmp        *[16]byte\n}\n\nfunc NewRawConn(rawTLSConn tls.Conn) (*RawConn, error) {\n\tvar (\n\t\tpointer unsafe.Pointer\n\t\tmethods *Methods\n\t\tloaded  bool\n\t)\n\tfor _, tlsCreator := range methodRegistry {\n\t\tpointer, methods, loaded = tlsCreator(rawTLSConn)\n\t\tif loaded {\n\t\t\tbreak\n\t\t}\n\t}\n\tif !loaded {\n\t\treturn nil, os.ErrInvalid\n\t}\n\n\tconn := &RawConn{\n\t\tpointer: pointer,\n\t\tmethods: methods,\n\t}\n\n\trawConn := reflect.Indirect(reflect.ValueOf(rawTLSConn))\n\n\trawIsClient := rawConn.FieldByName(\"isClient\")\n\tif !rawIsClient.IsValid() || rawIsClient.Kind() != reflect.Bool {\n\t\treturn nil, E.New(\"invalid Conn.isClient\")\n\t}\n\tconn.IsClient = (*bool)(unsafe.Pointer(rawIsClient.UnsafeAddr()))\n\n\trawIsHandshakeComplete := rawConn.FieldByName(\"isHandshakeComplete\")\n\tif !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {\n\t\treturn nil, E.New(\"invalid Conn.isHandshakeComplete\")\n\t}\n\tconn.IsHandshakeComplete = (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))\n\n\trawVers := rawConn.FieldByName(\"vers\")\n\tif !rawVers.IsValid() || rawVers.Kind() != reflect.Uint16 {\n\t\treturn nil, E.New(\"invalid Conn.vers\")\n\t}\n\tconn.Vers = (*uint16)(unsafe.Pointer(rawVers.UnsafeAddr()))\n\n\trawCipherSuite := rawConn.FieldByName(\"cipherSuite\")\n\tif !rawCipherSuite.IsValid() || rawCipherSuite.Kind() != reflect.Uint16 {\n\t\treturn nil, E.New(\"invalid Conn.cipherSuite\")\n\t}\n\tconn.CipherSuite = (*uint16)(unsafe.Pointer(rawCipherSuite.UnsafeAddr()))\n\n\trawRawInput := rawConn.FieldByName(\"rawInput\")\n\tif !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {\n\t\treturn nil, E.New(\"invalid Conn.rawInput\")\n\t}\n\tconn.RawInput = (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))\n\n\trawInput := rawConn.FieldByName(\"input\")\n\tif !rawInput.IsValid() || rawInput.Kind() != reflect.Struct {\n\t\treturn nil, E.New(\"invalid Conn.input\")\n\t}\n\tconn.Input = (*bytes.Reader)(unsafe.Pointer(rawInput.UnsafeAddr()))\n\n\trawHand := rawConn.FieldByName(\"hand\")\n\tif !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {\n\t\treturn nil, E.New(\"invalid Conn.hand\")\n\t}\n\tconn.Hand = (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))\n\n\trawCloseNotifySent := rawConn.FieldByName(\"closeNotifySent\")\n\tif !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {\n\t\treturn nil, E.New(\"invalid Conn.closeNotifySent\")\n\t}\n\tconn.CloseNotifySent = (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))\n\n\trawCloseNotifyErr := rawConn.FieldByName(\"closeNotifyErr\")\n\tif !rawCloseNotifyErr.IsValid() || rawCloseNotifyErr.Kind() != reflect.Interface {\n\t\treturn nil, E.New(\"invalid Conn.closeNotifyErr\")\n\t}\n\tconn.CloseNotifyErr = (*error)(unsafe.Pointer(rawCloseNotifyErr.UnsafeAddr()))\n\n\trawIn := rawConn.FieldByName(\"in\")\n\tif !rawIn.IsValid() || rawIn.Kind() != reflect.Struct {\n\t\treturn nil, E.New(\"invalid Conn.in\")\n\t}\n\thalfIn, err := NewRawHalfConn(rawIn, methods)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"invalid Conn.in\")\n\t}\n\tconn.In = halfIn\n\n\trawOut := rawConn.FieldByName(\"out\")\n\tif !rawOut.IsValid() || rawOut.Kind() != reflect.Struct {\n\t\treturn nil, E.New(\"invalid Conn.out\")\n\t}\n\thalfOut, err := NewRawHalfConn(rawOut, methods)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"invalid Conn.out\")\n\t}\n\tconn.Out = halfOut\n\n\trawBytesSent := rawConn.FieldByName(\"bytesSent\")\n\tif !rawBytesSent.IsValid() || rawBytesSent.Kind() != reflect.Int64 {\n\t\treturn nil, E.New(\"invalid Conn.bytesSent\")\n\t}\n\tconn.BytesSent = (*int64)(unsafe.Pointer(rawBytesSent.UnsafeAddr()))\n\n\trawPacketsSent := rawConn.FieldByName(\"packetsSent\")\n\tif !rawPacketsSent.IsValid() || rawPacketsSent.Kind() != reflect.Int64 {\n\t\treturn nil, E.New(\"invalid Conn.packetsSent\")\n\t}\n\tconn.PacketsSent = (*int64)(unsafe.Pointer(rawPacketsSent.UnsafeAddr()))\n\n\trawActiveCall := rawConn.FieldByName(\"activeCall\")\n\tif !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {\n\t\treturn nil, E.New(\"invalid Conn.activeCall\")\n\t}\n\tconn.ActiveCall = (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))\n\n\trawTmp := rawConn.FieldByName(\"tmp\")\n\tif !rawTmp.IsValid() || rawTmp.Kind() != reflect.Array || rawTmp.Len() != 16 || rawTmp.Type().Elem().Kind() != reflect.Uint8 {\n\t\treturn nil, E.New(\"invalid Conn.tmp\")\n\t}\n\tconn.Tmp = (*[16]byte)(unsafe.Pointer(rawTmp.UnsafeAddr()))\n\n\treturn conn, nil\n}\n\nfunc (c *RawConn) ReadRecord() error {\n\treturn c.methods.readRecord(c.pointer)\n}\n\nfunc (c *RawConn) HandlePostHandshakeMessage() error {\n\treturn c.methods.handlePostHandshakeMessage(c.pointer)\n}\n\nfunc (c *RawConn) WriteRecordLocked(typ uint16, data []byte) (int, error) {\n\treturn c.methods.writeRecordLocked(c.pointer, typ, data)\n}\n"
  },
  {
    "path": "common/badtls/raw_half_conn.go",
    "content": "//go:build go1.25 && badlinkname\n\npackage badtls\n\nimport (\n\t\"hash\"\n\t\"reflect\"\n\t\"sync\"\n\t\"unsafe\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype RawHalfConn struct {\n\tpointer unsafe.Pointer\n\tmethods *Methods\n\t*sync.Mutex\n\tErr           *error\n\tVersion       *uint16\n\tCipher        *any\n\tSeq           *[8]byte\n\tScratchBuf    *[13]byte\n\tTrafficSecret *[]byte\n\tMac           *hash.Hash\n\tRawKey        *[]byte\n\tRawIV         *[]byte\n\tRawMac        *[]byte\n}\n\nfunc NewRawHalfConn(rawHalfConn reflect.Value, methods *Methods) (*RawHalfConn, error) {\n\thalfConn := &RawHalfConn{\n\t\tpointer: (unsafe.Pointer)(rawHalfConn.UnsafeAddr()),\n\t\tmethods: methods,\n\t}\n\n\trawMutex := rawHalfConn.FieldByName(\"Mutex\")\n\tif !rawMutex.IsValid() || rawMutex.Kind() != reflect.Struct {\n\t\treturn nil, E.New(\"badtls: invalid halfConn.Mutex\")\n\t}\n\thalfConn.Mutex = (*sync.Mutex)(unsafe.Pointer(rawMutex.UnsafeAddr()))\n\n\trawErr := rawHalfConn.FieldByName(\"err\")\n\tif !rawErr.IsValid() || rawErr.Kind() != reflect.Interface {\n\t\treturn nil, E.New(\"badtls: invalid halfConn.err\")\n\t}\n\thalfConn.Err = (*error)(unsafe.Pointer(rawErr.UnsafeAddr()))\n\n\trawVersion := rawHalfConn.FieldByName(\"version\")\n\tif !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {\n\t\treturn nil, E.New(\"badtls: invalid halfConn.version\")\n\t}\n\thalfConn.Version = (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))\n\n\trawCipher := rawHalfConn.FieldByName(\"cipher\")\n\tif !rawCipher.IsValid() || rawCipher.Kind() != reflect.Interface {\n\t\treturn nil, E.New(\"badtls: invalid halfConn.cipher\")\n\t}\n\thalfConn.Cipher = (*any)(unsafe.Pointer(rawCipher.UnsafeAddr()))\n\n\trawSeq := rawHalfConn.FieldByName(\"seq\")\n\tif !rawSeq.IsValid() || rawSeq.Kind() != reflect.Array || rawSeq.Len() != 8 || rawSeq.Type().Elem().Kind() != reflect.Uint8 {\n\t\treturn nil, E.New(\"badtls: invalid halfConn.seq\")\n\t}\n\thalfConn.Seq = (*[8]byte)(unsafe.Pointer(rawSeq.UnsafeAddr()))\n\n\trawScratchBuf := rawHalfConn.FieldByName(\"scratchBuf\")\n\tif !rawScratchBuf.IsValid() || rawScratchBuf.Kind() != reflect.Array || rawScratchBuf.Len() != 13 || rawScratchBuf.Type().Elem().Kind() != reflect.Uint8 {\n\t\treturn nil, E.New(\"badtls: invalid halfConn.scratchBuf\")\n\t}\n\thalfConn.ScratchBuf = (*[13]byte)(unsafe.Pointer(rawScratchBuf.UnsafeAddr()))\n\n\trawTrafficSecret := rawHalfConn.FieldByName(\"trafficSecret\")\n\tif !rawTrafficSecret.IsValid() || rawTrafficSecret.Kind() != reflect.Slice || rawTrafficSecret.Type().Elem().Kind() != reflect.Uint8 {\n\t\treturn nil, E.New(\"badtls: invalid halfConn.trafficSecret\")\n\t}\n\thalfConn.TrafficSecret = (*[]byte)(unsafe.Pointer(rawTrafficSecret.UnsafeAddr()))\n\n\trawMac := rawHalfConn.FieldByName(\"mac\")\n\tif !rawMac.IsValid() || rawMac.Kind() != reflect.Interface {\n\t\treturn nil, E.New(\"badtls: invalid halfConn.mac\")\n\t}\n\thalfConn.Mac = (*hash.Hash)(unsafe.Pointer(rawMac.UnsafeAddr()))\n\n\trawKey := rawHalfConn.FieldByName(\"rawKey\")\n\tif rawKey.IsValid() {\n\t\tif /*!rawKey.IsValid() || */ rawKey.Kind() != reflect.Slice || rawKey.Type().Elem().Kind() != reflect.Uint8 {\n\t\t\treturn nil, E.New(\"badtls: invalid halfConn.rawKey\")\n\t\t}\n\t\thalfConn.RawKey = (*[]byte)(unsafe.Pointer(rawKey.UnsafeAddr()))\n\n\t\trawIV := rawHalfConn.FieldByName(\"rawIV\")\n\t\tif !rawIV.IsValid() || rawIV.Kind() != reflect.Slice || rawIV.Type().Elem().Kind() != reflect.Uint8 {\n\t\t\treturn nil, E.New(\"badtls: invalid halfConn.rawIV\")\n\t\t}\n\t\thalfConn.RawIV = (*[]byte)(unsafe.Pointer(rawIV.UnsafeAddr()))\n\n\t\trawMAC := rawHalfConn.FieldByName(\"rawMac\")\n\t\tif !rawMAC.IsValid() || rawMAC.Kind() != reflect.Slice || rawMAC.Type().Elem().Kind() != reflect.Uint8 {\n\t\t\treturn nil, E.New(\"badtls: invalid halfConn.rawMac\")\n\t\t}\n\t\thalfConn.RawMac = (*[]byte)(unsafe.Pointer(rawMAC.UnsafeAddr()))\n\t}\n\n\treturn halfConn, nil\n}\n\nfunc (hc *RawHalfConn) Decrypt(record []byte) ([]byte, uint8, error) {\n\treturn hc.methods.decrypt(hc.pointer, record)\n}\n\nfunc (hc *RawHalfConn) SetErrorLocked(err error) error {\n\treturn hc.methods.setErrorLocked(hc.pointer, err)\n}\n\nfunc (hc *RawHalfConn) SetTrafficSecret(suite unsafe.Pointer, level int, secret []byte) {\n\thc.methods.setTrafficSecret(hc.pointer, suite, level, secret)\n}\n\nfunc (hc *RawHalfConn) ExplicitNonceLen() int {\n\treturn hc.methods.explicitNonceLen(hc.pointer)\n}\n"
  },
  {
    "path": "common/badtls/read_wait.go",
    "content": "//go:build go1.25 && badlinkname\n\npackage badtls\n\nimport (\n\t\"github.com/sagernet/sing/common/buf\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/tls\"\n)\n\nvar _ N.ReadWaiter = (*ReadWaitConn)(nil)\n\ntype ReadWaitConn struct {\n\ttls.Conn\n\trawConn         *RawConn\n\treadWaitOptions N.ReadWaitOptions\n}\n\nfunc NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {\n\tif _, isReadWaitConn := conn.(N.ReadWaiter); isReadWaitConn {\n\t\treturn conn, nil\n\t}\n\trawConn, err := NewRawConn(conn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ReadWaitConn{\n\t\tConn:    conn,\n\t\trawConn: rawConn,\n\t}, nil\n}\n\nfunc (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {\n\tc.readWaitOptions = options\n\treturn false\n}\n\nfunc (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {\n\t//err = c.HandshakeContext(context.Background())\n\t//if err != nil {\n\t//\treturn\n\t//}\n\tc.rawConn.In.Lock()\n\tdefer c.rawConn.In.Unlock()\n\tfor c.rawConn.Input.Len() == 0 {\n\t\terr = c.rawConn.ReadRecord()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tfor c.rawConn.Hand.Len() > 0 {\n\t\t\terr = c.rawConn.HandlePostHandshakeMessage()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\tbuffer = c.readWaitOptions.NewBuffer()\n\tn, err := c.rawConn.Input.Read(buffer.FreeBytes())\n\tif err != nil {\n\t\tbuffer.Release()\n\t\treturn\n\t}\n\tbuffer.Truncate(n)\n\n\tif n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.Input.Len() > 0 &&\n\t\t// recordType(c.RawInput.Bytes()[0]) == recordTypeAlert {\n\t\tc.rawConn.RawInput.Bytes()[0] == 21 {\n\t\t_ = c.rawConn.ReadRecord()\n\t\t// return n, err // will be io.EOF on closeNotify\n\t}\n\n\tc.readWaitOptions.PostReturn(buffer)\n\treturn\n}\n\nfunc (c *ReadWaitConn) Upstream() any {\n\treturn c.Conn\n}\n\nfunc (c *ReadWaitConn) ReaderReplaceable() bool {\n\treturn true\n}\n"
  },
  {
    "path": "common/badtls/read_wait_stub.go",
    "content": "//go:build !go1.25 || !badlinkname\n\npackage badtls\n\nimport (\n\t\"os\"\n\n\t\"github.com/sagernet/sing/common/tls\"\n)\n\nfunc NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {\n\treturn nil, os.ErrInvalid\n}\n"
  },
  {
    "path": "common/badtls/registry.go",
    "content": "//go:build go1.25 && badlinkname\n\npackage badtls\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n\t\"unsafe\"\n)\n\ntype Methods struct {\n\treadRecord                 func(c unsafe.Pointer) error\n\thandlePostHandshakeMessage func(c unsafe.Pointer) error\n\twriteRecordLocked          func(c unsafe.Pointer, typ uint16, data []byte) (int, error)\n\n\tsetErrorLocked   func(hc unsafe.Pointer, err error) error\n\tdecrypt          func(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)\n\tsetTrafficSecret func(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)\n\texplicitNonceLen func(hc unsafe.Pointer) int\n}\n\nvar methodRegistry []func(conn net.Conn) (unsafe.Pointer, *Methods, bool)\n\nfunc init() {\n\tmethodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) {\n\t\ttlsConn, loaded := conn.(*tls.Conn)\n\t\tif !loaded {\n\t\t\treturn nil, nil, false\n\t\t}\n\t\treturn unsafe.Pointer(tlsConn), &Methods{\n\t\t\treadRecord:                 stdTLSReadRecord,\n\t\t\thandlePostHandshakeMessage: stdTLSHandlePostHandshakeMessage,\n\t\t\twriteRecordLocked:          stdWriteRecordLocked,\n\n\t\t\tsetErrorLocked:   stdSetErrorLocked,\n\t\t\tdecrypt:          stdDecrypt,\n\t\t\tsetTrafficSecret: stdSetTrafficSecret,\n\t\t\texplicitNonceLen: stdExplicitNonceLen,\n\t\t}, true\n\t})\n}\n\n//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord\nfunc stdTLSReadRecord(c unsafe.Pointer) error\n\n//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage\nfunc stdTLSHandlePostHandshakeMessage(c unsafe.Pointer) error\n\n//go:linkname stdWriteRecordLocked crypto/tls.(*Conn).writeRecordLocked\nfunc stdWriteRecordLocked(c unsafe.Pointer, typ uint16, data []byte) (int, error)\n\n//go:linkname stdSetErrorLocked crypto/tls.(*halfConn).setErrorLocked\nfunc stdSetErrorLocked(hc unsafe.Pointer, err error) error\n\n//go:linkname stdDecrypt crypto/tls.(*halfConn).decrypt\nfunc stdDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)\n\n//go:linkname stdSetTrafficSecret crypto/tls.(*halfConn).setTrafficSecret\nfunc stdSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)\n\n//go:linkname stdExplicitNonceLen crypto/tls.(*halfConn).explicitNonceLen\nfunc stdExplicitNonceLen(hc unsafe.Pointer) int\n"
  },
  {
    "path": "common/badtls/registry_utls.go",
    "content": "//go:build go1.25 && badlinkname\n\npackage badtls\n\nimport (\n\t\"net\"\n\t\"unsafe\"\n\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"github.com/metacubex/utls\"\n)\n\nfunc init() {\n\tmethodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) {\n\t\tvar pointer unsafe.Pointer\n\t\tif uConn, loaded := N.CastReader[*tls.Conn](conn); loaded {\n\t\t\tpointer = unsafe.Pointer(uConn)\n\t\t} else if uConn, loaded := N.CastReader[*tls.UConn](conn); loaded {\n\t\t\tpointer = unsafe.Pointer(uConn.Conn)\n\t\t} else {\n\t\t\treturn nil, nil, false\n\t\t}\n\t\treturn pointer, &Methods{\n\t\t\treadRecord:                 utlsReadRecord,\n\t\t\thandlePostHandshakeMessage: utlsHandlePostHandshakeMessage,\n\t\t\twriteRecordLocked:          utlsWriteRecordLocked,\n\n\t\t\tsetErrorLocked:   utlsSetErrorLocked,\n\t\t\tdecrypt:          utlsDecrypt,\n\t\t\tsetTrafficSecret: utlsSetTrafficSecret,\n\t\t\texplicitNonceLen: utlsExplicitNonceLen,\n\t\t}, true\n\t})\n}\n\n//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord\nfunc utlsReadRecord(c unsafe.Pointer) error\n\n//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage\nfunc utlsHandlePostHandshakeMessage(c unsafe.Pointer) error\n\n//go:linkname utlsWriteRecordLocked github.com/metacubex/utls.(*Conn).writeRecordLocked\nfunc utlsWriteRecordLocked(hc unsafe.Pointer, typ uint16, data []byte) (int, error)\n\n//go:linkname utlsSetErrorLocked github.com/metacubex/utls.(*halfConn).setErrorLocked\nfunc utlsSetErrorLocked(hc unsafe.Pointer, err error) error\n\n//go:linkname utlsDecrypt github.com/metacubex/utls.(*halfConn).decrypt\nfunc utlsDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)\n\n//go:linkname utlsSetTrafficSecret github.com/metacubex/utls.(*halfConn).setTrafficSecret\nfunc utlsSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)\n\n//go:linkname utlsExplicitNonceLen github.com/metacubex/utls.(*halfConn).explicitNonceLen\nfunc utlsExplicitNonceLen(hc unsafe.Pointer) int\n"
  },
  {
    "path": "common/badversion/version.go",
    "content": "package badversion\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\tF \"github.com/sagernet/sing/common/format\"\n\n\t\"golang.org/x/mod/semver\"\n)\n\ntype Version struct {\n\tMajor                int\n\tMinor                int\n\tPatch                int\n\tCommit               string\n\tPreReleaseIdentifier string\n\tPreReleaseVersion    int\n}\n\nfunc (v Version) LessThan(anotherVersion Version) bool {\n\treturn !v.GreaterThanOrEqual(anotherVersion)\n}\n\nfunc (v Version) LessThanOrEqual(anotherVersion Version) bool {\n\treturn v == anotherVersion || anotherVersion.GreaterThan(v)\n}\n\nfunc (v Version) GreaterThanOrEqual(anotherVersion Version) bool {\n\treturn v == anotherVersion || v.GreaterThan(anotherVersion)\n}\n\nfunc (v Version) GreaterThan(anotherVersion Version) bool {\n\tif v.Major > anotherVersion.Major {\n\t\treturn true\n\t} else if v.Major < anotherVersion.Major {\n\t\treturn false\n\t}\n\tif v.Minor > anotherVersion.Minor {\n\t\treturn true\n\t} else if v.Minor < anotherVersion.Minor {\n\t\treturn false\n\t}\n\tif v.Patch > anotherVersion.Patch {\n\t\treturn true\n\t} else if v.Patch < anotherVersion.Patch {\n\t\treturn false\n\t}\n\tif v.PreReleaseIdentifier == \"\" && anotherVersion.PreReleaseIdentifier != \"\" {\n\t\treturn true\n\t} else if v.PreReleaseIdentifier != \"\" && anotherVersion.PreReleaseIdentifier == \"\" {\n\t\treturn false\n\t}\n\tif v.PreReleaseIdentifier != \"\" && anotherVersion.PreReleaseIdentifier != \"\" {\n\t\tif v.PreReleaseIdentifier == anotherVersion.PreReleaseIdentifier {\n\t\t\tif v.PreReleaseVersion > anotherVersion.PreReleaseVersion {\n\t\t\t\treturn true\n\t\t\t} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\tpreReleaseIdentifier := parsePreReleaseIdentifier(v.PreReleaseIdentifier)\n\t\tanotherPreReleaseIdentifier := parsePreReleaseIdentifier(anotherVersion.PreReleaseIdentifier)\n\t\tif preReleaseIdentifier < anotherPreReleaseIdentifier {\n\t\t\treturn true\n\t\t} else if preReleaseIdentifier > anotherPreReleaseIdentifier {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\nfunc parsePreReleaseIdentifier(identifier string) int {\n\tif strings.HasPrefix(identifier, \"rc\") {\n\t\treturn 1\n\t} else if strings.HasPrefix(identifier, \"beta\") {\n\t\treturn 2\n\t} else if strings.HasPrefix(identifier, \"alpha\") {\n\t\treturn 3\n\t}\n\treturn 0\n}\n\nfunc (v Version) VersionString() string {\n\treturn F.ToString(v.Major, \".\", v.Minor, \".\", v.Patch)\n}\n\nfunc (v Version) String() string {\n\tversion := F.ToString(v.Major, \".\", v.Minor, \".\", v.Patch)\n\tif v.PreReleaseIdentifier != \"\" {\n\t\tversion = F.ToString(version, \"-\", v.PreReleaseIdentifier, \".\", v.PreReleaseVersion)\n\t}\n\treturn version\n}\n\nfunc (v Version) BadString() string {\n\tversion := F.ToString(v.Major, \".\", v.Minor)\n\tif v.Patch > 0 {\n\t\tversion = F.ToString(version, \".\", v.Patch)\n\t}\n\tif v.PreReleaseIdentifier != \"\" {\n\t\tversion = F.ToString(version, \"-\", v.PreReleaseIdentifier)\n\t\tif v.PreReleaseVersion > 0 {\n\t\t\tversion = F.ToString(version, v.PreReleaseVersion)\n\t\t}\n\t}\n\treturn version\n}\n\nfunc IsValid(versionName string) bool {\n\treturn semver.IsValid(\"v\" + versionName)\n}\n\nfunc Parse(versionName string) (version Version) {\n\tif strings.HasPrefix(versionName, \"v\") {\n\t\tversionName = versionName[1:]\n\t}\n\tif strings.Contains(versionName, \"-\") {\n\t\tparts := strings.Split(versionName, \"-\")\n\t\tversionName = parts[0]\n\t\tidentifier := parts[1]\n\t\tif strings.Contains(identifier, \".\") {\n\t\t\tidentifierParts := strings.Split(identifier, \".\")\n\t\t\tversion.PreReleaseIdentifier = identifierParts[0]\n\t\t\tif len(identifierParts) >= 2 {\n\t\t\t\tversion.PreReleaseVersion, _ = strconv.Atoi(identifierParts[1])\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(identifier, \"alpha\") {\n\t\t\t\tversion.PreReleaseIdentifier = \"alpha\"\n\t\t\t\tversion.PreReleaseVersion, _ = strconv.Atoi(identifier[5:])\n\t\t\t} else if strings.HasPrefix(identifier, \"beta\") {\n\t\t\t\tversion.PreReleaseIdentifier = \"beta\"\n\t\t\t\tversion.PreReleaseVersion, _ = strconv.Atoi(identifier[4:])\n\t\t\t} else {\n\t\t\t\tversion.Commit = identifier\n\t\t\t}\n\t\t}\n\t}\n\tversionElements := strings.Split(versionName, \".\")\n\tversionLen := len(versionElements)\n\tif versionLen >= 1 {\n\t\tversion.Major, _ = strconv.Atoi(versionElements[0])\n\t}\n\tif versionLen >= 2 {\n\t\tversion.Minor, _ = strconv.Atoi(versionElements[1])\n\t}\n\tif versionLen >= 3 {\n\t\tversion.Patch, _ = strconv.Atoi(versionElements[2])\n\t}\n\treturn\n}\n"
  },
  {
    "path": "common/badversion/version_json.go",
    "content": "package badversion\n\nimport \"github.com/sagernet/sing/common/json\"\n\nfunc (v Version) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(v.String())\n}\n\nfunc (v *Version) UnmarshalJSON(data []byte) error {\n\tvar version string\n\terr := json.Unmarshal(data, &version)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*v = Parse(version)\n\treturn nil\n}\n"
  },
  {
    "path": "common/badversion/version_test.go",
    "content": "package badversion\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCompareVersion(t *testing.T) {\n\tt.Parallel()\n\trequire.Equal(t, \"1.3.0-beta.1\", Parse(\"v1.3.0-beta1\").String())\n\trequire.Equal(t, \"1.3-beta1\", Parse(\"v1.3.0-beta.1\").BadString())\n\trequire.True(t, Parse(\"1.3.0\").GreaterThan(Parse(\"1.3-beta1\")))\n\trequire.True(t, Parse(\"1.3.0\").GreaterThan(Parse(\"1.3.0-beta1\")))\n\trequire.True(t, Parse(\"1.3.0-beta1\").GreaterThan(Parse(\"1.3.0-alpha1\")))\n\trequire.True(t, Parse(\"1.3.1\").GreaterThan(Parse(\"1.3.0\")))\n\trequire.True(t, Parse(\"1.4\").GreaterThan(Parse(\"1.3\")))\n}\n"
  },
  {
    "path": "common/certificate/chrome.go",
    "content": "// Code generated by 'make update_certificates'. DO NOT EDIT.\n\npackage certificate\n\nimport \"crypto/x509\"\n\nvar chromeIncluded *x509.CertPool\n\nfunc init() {\n\tchromeIncluded = x509.NewCertPool()\n\n\t// CN=Actalis Authentication Root CA; O=Actalis S.p.A./03358520967; L=Milan; C=IT\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE\nBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w\nMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290\nIENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC\nSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1\nODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv\nUTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX\n4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9\nKK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/\ngCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb\nrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ\n51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F\nbe8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe\nKF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F\nv6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn\nfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7\njPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz\nezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt\nifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL\ne3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70\njsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz\nWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V\nSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j\npwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX\nX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok\nfcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R\nK4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU\nZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU\nLysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT\nLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==\n-----END CERTIFICATE-----`))\n\n\t// CN=TunTrust Root CA; O=Agence Nationale de Certification Electronique; C=TN\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL\nBQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg\nQ2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv\nb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG\nEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u\nIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ\nKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ\nn56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd\n2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF\nVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ\nGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF\nli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU\nr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2\neY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb\nMlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg\njwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB\n7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW\n5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE\nITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0\n90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z\nxiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu\nQEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4\nFstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH\n22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP\nxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn\ndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5\nXc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b\nnV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ\nCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH\nu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj\nd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=\n-----END CERTIFICATE-----`))\n\n\t// CN=Amazon Root CA 4; O=Amazon; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5\nMQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\nUm9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\nA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\nQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi\n9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk\nM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB\n/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB\nMAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw\nCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW\n1KyLa2tJElMzrdfkviT8tQp21KW8EA==\n-----END CERTIFICATE-----`))\n\n\t// CN=Amazon Root CA 1; O=Amazon; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\nADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\nb24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\nMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\nb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\nca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\nIFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\nVOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\njgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\nAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\nA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\nU5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\nN+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\no/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\nrqXRfboQnoZsG4q5WTP468SQvvG5\n-----END CERTIFICATE-----`))\n\n\t// CN=Amazon Root CA 2; O=Amazon; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF\nADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\nb24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL\nMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\nb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK\ngXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ\nW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg\n1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K\n8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r\n2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me\nz/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR\n8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj\nmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz\n7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6\n+XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI\n0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm\nUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2\nLIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY\n+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS\nk5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl\n7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm\nbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl\nurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+\nfUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63\nn749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE\n76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H\n9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT\n4PsJYGw=\n-----END CERTIFICATE-----`))\n\n\t// CN=Amazon Root CA 3; O=Amazon; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5\nMQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\nUm9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\nA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\nQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl\nui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j\nQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr\nttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr\nBqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM\nYyRIHN8wfdVoOw==\n-----END CERTIFICATE-----`))\n\n\t// CN=Certum Trusted Network CA; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM\nMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D\nZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU\ncnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3\nWjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg\nUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw\nIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B\nAQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH\nUV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM\nTXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU\nBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM\nkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x\nAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV\nHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y\nsHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL\nI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8\nJ9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY\nVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI\n03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=\n-----END CERTIFICATE-----`))\n\n\t// CN=Certum EC-384 CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw\nCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw\nJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT\nEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0\nWjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT\nLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX\nBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE\nKI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm\nFy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj\nQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8\nEF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J\nUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn\nnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=\n-----END CERTIFICATE-----`))\n\n\t// CN=Certum Trusted Root CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6\nMQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu\nMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV\nBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw\nMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg\nU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo\nb3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ\nn0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q\np1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq\nNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF\n8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3\nHAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa\nmqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi\n7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF\nytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P\nqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ\nv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6\nTsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1\nvALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD\nggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4\nWxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo\nzMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR\n5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ\nGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf\n5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq\n0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D\nP78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM\nqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP\n0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf\nE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb\n-----END CERTIFICATE-----`))\n\n\t// CN=Certum Trusted Network CA 2; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB\ngDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu\nQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG\nA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz\nOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ\nVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp\nZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3\nb3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA\nDGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn\n0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB\nOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE\nfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E\nSv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m\no130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i\nsx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW\nOZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez\nTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS\nadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n\n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD\nAQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC\nAQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ\nF/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf\nCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29\nXN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm\ndjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/\nWjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb\nAoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq\nP/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko\nb7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj\nXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P\n5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi\nDrW5viSP\n-----END CERTIFICATE-----`))\n\n\t// CN=Autoridad de Certificacion Firmaprofesional CIF A62634068; C=ES\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE\nBhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h\ncHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1\nMDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg\nQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9\nthDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM\ncas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG\nL9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i\nNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h\nX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b\nm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy\nZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja\nEbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T\nKI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF\n6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh\nOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc\ntHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd\nIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j\nb20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC\nAG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw\nADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m\niWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF\nSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ\nhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P\nVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE\nEAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV\n1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t\nCsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR\n5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw\nf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9\nivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK\nGbqEZycPvEJdvSRUDewdcAZfpLz6IHxV\n-----END CERTIFICATE-----`))\n\n\t// CN=ANF Secure Server Root CA; OU=ANF CA Raiz; O=ANF Autoridad de Certificacion; C=ES; SerialNumber=G63287510\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV\nBAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk\nYWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV\nBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN\nMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF\nUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD\nVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v\ndCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj\ncqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q\nyGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH\n2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX\nH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL\nzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR\np1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz\nW7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/\nSiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn\nLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3\nn5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B\nu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj\no1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC\nAgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L\n9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej\nrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK\npFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0\nvPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq\nOknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ\n/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9\n2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI\n+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2\nMjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo\ntt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=\n-----END CERTIFICATE-----`))\n\n\t// CN=Buypass Class 2 Root CA; O=Buypass AS-983163327; C=NO\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd\nMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg\nQ2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow\nTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw\nHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB\nBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr\n6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV\nL4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91\n1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx\nMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ\nQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB\narcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr\nUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi\nFRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS\nP/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN\n9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP\nAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz\nuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h\n9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s\nA20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t\nOluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo\n+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7\nKcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2\nDISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us\nH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ\nI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7\n5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h\n3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz\nY11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=\n-----END CERTIFICATE-----`))\n\n\t// CN=Buypass Class 3 Root CA; O=Buypass AS-983163327; C=NO\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd\nMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg\nQ2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow\nTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw\nHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB\nBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y\nZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E\nN3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9\ntznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX\n0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c\n/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X\nKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY\nzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS\nO1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D\n34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP\nK9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3\nAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv\nTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj\nQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV\ncSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS\nIGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2\nHJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa\nO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv\n033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u\ndmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE\nkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41\n3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD\nu79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq\n4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=\n-----END CERTIFICATE-----`))\n\n\t// CN=Certainly Root R1; O=Certainly; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw\nPTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy\ndGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9\nMQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0\nYWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2\n1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT\nvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed\naFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0\n1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5\nr3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5\ncBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ\nwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ\n6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA\n2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH\nWyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR\neiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB\n/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u\nd0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr\nPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d\n8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi\n1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd\nrRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di\ntaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7\nlcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj\nyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn\nKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy\nyCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n\nwXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6\nOV+KmalBWQewLK8=\n-----END CERTIFICATE-----`))\n\n\t// CN=Certainly Root E1; O=Certainly; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw\nCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu\nbHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ\nBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s\neSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK\n+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2\nQNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E\nBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4\nhevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm\nut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG\nBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR\n-----END CERTIFICATE-----`))\n\n\t// CN=Certigna; O=Dhimyotis; C=FR\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV\nBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X\nDTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ\nBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4\nQCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny\ngQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw\nzBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q\n130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2\nJsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw\nDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw\nZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT\nAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj\nAQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG\n9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h\nbV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc\nfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu\nHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w\nt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw\nWyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==\n-----END CERTIFICATE-----`))\n\n\t// CN=Certigna Root CA; OU=0002 48146308100036; O=Dhimyotis; C=FR\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw\nWjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw\nMiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x\nMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD\nVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX\nBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw\nggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO\nty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M\nCiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu\nI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm\nTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh\nC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf\nePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz\nIoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT\nCo/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k\nJWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5\nhwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB\nGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE\nFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of\n1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov\nL3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo\ndHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr\naHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq\nhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L\n6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG\nHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6\n0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB\nlA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi\no2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1\ngPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v\nfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63\nNwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh\njWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw\n3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=\n-----END CERTIFICATE-----`))\n\n\t// OU=certSIGN ROOT CA; O=certSIGN; C=RO\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT\nAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD\nQTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP\nMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do\n0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ\nUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d\nRdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ\nOA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv\nJoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C\nAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O\nBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ\nLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY\nMnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ\n44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I\nJd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw\ni/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN\n9u6wWk5JRFRYX0KD\n-----END CERTIFICATE-----`))\n\n\t// OU=certSIGN ROOT CA G2; O=CERTSIGN SA; C=RO\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV\nBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g\nUk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ\nBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ\nR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF\ndRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw\nvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ\nuIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp\nn+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs\ncpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW\nxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P\nrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF\nDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx\nDTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy\nLcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C\neWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB\n/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ\nd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq\nkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC\nb6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl\nqiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0\nOJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c\nNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk\nltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO\npwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj\n03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk\nPuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE\n1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX\nQRBdJ3NghVdJIgc=\n-----END CERTIFICATE-----`))\n\n\t// CN=HiPKI Root CA - G1; O=Chunghwa Telecom Co., Ltd.; C=TW\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP\nMQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0\nZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa\nFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3\nYSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw\nqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv\nVcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6\nlZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz\nQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ\nKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK\nFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj\nHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr\ny+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ\n/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM\na/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6\nfsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\nHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG\nSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi\n7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc\nSE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza\nZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc\nXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg\niLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho\nL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF\nNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr\nkkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+\nvhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU\nYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==\n-----END CERTIFICATE-----`))\n\n\t// OU=ePKI Root Certification Authority; O=Chunghwa Telecom Co., Ltd.; C=TW\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe\nMQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0\nZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe\nFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw\nIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL\nSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH\nSyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh\nijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X\nDZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1\nTBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ\nfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA\nsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU\nWH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS\nnT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH\ndmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip\nNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC\nAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF\nMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH\nClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB\nuvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl\nPwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP\nJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/\ngpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2\nj6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6\n5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB\no2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS\n/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z\nGp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE\nW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D\nhNQ+IIX3Sj0rnP0qCglN6oH4EZw=\n-----END CERTIFICATE-----`))\n\n\t// CN=D-TRUST BR Root CA 1 2020; O=D-Trust GmbH; C=DE\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw\nCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS\nVVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5\nNDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG\nA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB\nBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS\nzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0\nQVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/\nVbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g\nPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf\nY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l\ndC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1\nc3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO\nPQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW\nwKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV\ndWNbFJWcHwHP2NVypw87\n-----END CERTIFICATE-----`))\n\n\t// CN=D-TRUST EV Root CA 1 2020; O=D-Trust GmbH; C=DE\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw\nCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS\nVVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5\nNTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG\nA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB\nBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC\n/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD\nwpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3\nOqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g\nPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf\nY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l\ndC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1\nc3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO\nPQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA\ny/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb\ngfM0agPnIjhQW+0ZT0MW\n-----END CERTIFICATE-----`))\n\n\t// CN=D-TRUST Root Class 3 CA 2 EV 2009; O=D-Trust GmbH; C=DE\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF\nMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD\nbGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw\nNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV\nBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn\nljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0\n3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z\nqQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR\np75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8\nHgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw\nggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea\nHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw\nOi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh\nc3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E\nRT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt\ndHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku\nY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp\n3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05\nnsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF\nCSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na\nxpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX\nKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1\n-----END CERTIFICATE-----`))\n\n\t// CN=D-TRUST Root Class 3 CA 2 2009; O=D-Trust GmbH; C=DE\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF\nMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD\nbGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha\nME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM\nHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03\nUAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42\ntSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R\nySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM\nlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp\n/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G\nA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G\nA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj\ndG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy\nMENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl\ncmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js\nL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL\nBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni\nacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0\no3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K\nzCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8\nPIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y\nJohw1+qRzT65ysCQblrGXnRl11z+o+I=\n-----END CERTIFICATE-----`))\n\n\t// CN=T-TeleSec GlobalRoot Class 3; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx\nKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd\nBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl\nYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1\nOTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy\naXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50\nZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G\nCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN\n8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/\nRLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4\nhqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5\nZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM\nEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj\nQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1\nA/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy\nWL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ\n1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30\n6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT\n91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml\ne9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p\nTpPDpFQUWw==\n-----END CERTIFICATE-----`))\n\n\t// CN=T-TeleSec GlobalRoot Class 2; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx\nKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd\nBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl\nYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1\nOTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy\naXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50\nZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G\nCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd\nAqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC\nFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi\n1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq\njnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ\nwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj\nQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/\nWSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy\nNsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC\nuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw\nIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6\ng1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN\n9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP\nBSeOE6Fuwg==\n-----END CERTIFICATE-----`))\n\n\t// CN=DigiCert TLS RSA4096 Root G5; O=DigiCert, Inc.; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN\nMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT\nHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN\nNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs\nIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+\najWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0\n2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp\nwgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM\npG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD\nnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po\nsMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx\nZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd\nLvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX\nKyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe\nXoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL\ntgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv\nTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN\nAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw\nGXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H\nPNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF\nO4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ\nREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik\nAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv\n/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+\np6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw\nMUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF\nqUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK\novfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+\n-----END CERTIFICATE-----`))\n\n\t// CN=DigiCert TLS ECC P384 Root G5; O=DigiCert, Inc.; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw\nCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp\nZ2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2\nMDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ\nbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG\nByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS\n7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp\n0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS\nB4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49\nBAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ\nLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4\nDXZDjC5Ty3zfDBeWUA==\n-----END CERTIFICATE-----`))\n\n\t// CN=DigiCert Assured ID Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv\nb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG\nEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl\ncnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c\nJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP\nmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+\nwRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4\nVYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/\nAUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB\nAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\nBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun\npyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC\ndWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf\nfwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm\nNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx\nH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe\n+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==\n-----END CERTIFICATE-----`))\n\n\t// CN=DigiCert Assured ID Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv\nb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG\nEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl\ncnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA\nn61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc\nbiJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp\nEgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA\nbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu\nYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB\nAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW\nBBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI\nQW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I\n0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni\nlmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9\nB5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv\nON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo\nIhNzbM8m9Yop5w==\n-----END CERTIFICATE-----`))\n\n\t// CN=DigiCert Assured ID Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw\nCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu\nZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg\nRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV\nUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\nY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq\nhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf\nZn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q\nRSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/\nBAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD\nAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY\nJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv\n6pZjamVFkpUBtA==\n-----END CERTIFICATE-----`))\n\n\t// CN=DigiCert Global Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----`))\n\n\t// CN=DigiCert Global Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\nMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\nq2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\ntCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\nvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\nBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\nNeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\nFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\npLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\nMrY=\n-----END CERTIFICATE-----`))\n\n\t// CN=DigiCert Global Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw\nCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu\nZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe\nFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw\nEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x\nIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF\nK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG\nfp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO\nZ9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd\nBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx\nAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/\noAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8\nsycX\n-----END CERTIFICATE-----`))\n\n\t// CN=DigiCert High Assurance EV Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\nZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\nMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\nLmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\nRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\nPNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\nxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\nIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\nhzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\nEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\nMAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\nFLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\nnzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\neM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\nhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\nYzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\nvEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n+OkuE6N36B9K\n-----END CERTIFICATE-----`))\n\n\t// CN=DigiCert Trusted Root G4; OU=www.digicert.com; O=DigiCert Inc; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg\nRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV\nUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\nY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y\nithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If\nxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV\nySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO\nDCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ\njdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/\nCNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi\nEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM\nfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY\nuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK\nchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t\n9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\nhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD\nggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2\nSV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd\n+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc\nfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa\nsjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N\ncCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N\n0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie\n4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI\nr/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1\n/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm\ngKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+\n-----END CERTIFICATE-----`))\n\n\t// CN=QuoVadis Root CA 2; O=QuoVadis Limited; C=BM\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x\nGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv\nb3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV\nBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W\nYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa\nGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg\nFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J\nWpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB\nrrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp\n+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1\nksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i\nUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz\nPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og\n/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH\noycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI\nyV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud\nEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2\nA8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL\nMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT\nElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f\nBluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn\ng/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl\nfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K\nWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha\nB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc\nhLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR\nTUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD\nmbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z\nohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y\n4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza\n8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u\n-----END CERTIFICATE-----`))\n\n\t// CN=QuoVadis Root CA 2 G3; O=QuoVadis Limited; C=BM\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL\nBQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc\nBgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00\nMjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM\naW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf\nqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW\nn4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym\nc5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+\nO7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1\no9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j\nIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq\nIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz\n8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh\nvNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l\n7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG\ncC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\nBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD\nggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66\nAarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC\nroijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga\nW/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n\nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE\n+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV\ncsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd\ndbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg\nKCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM\nHVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4\nWSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M\n-----END CERTIFICATE-----`))\n\n\t// CN=QuoVadis Root CA 3 G3; O=QuoVadis Limited; C=BM\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL\nBQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc\nBgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00\nMjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM\naW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR\n/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu\nFoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR\nU7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c\nra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR\nFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k\nA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw\neyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl\nsSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp\nVzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q\nA4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+\nydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\nBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD\nggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px\nKGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI\nFUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv\noxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg\nu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP\n0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf\n3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl\n8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+\nDhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN\nPlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/\nywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0\n-----END CERTIFICATE-----`))\n\n\t// CN=CA Disig Root R2; O=Disig a.s.; L=Bratislava; C=SK\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV\nBAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu\nMRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy\nMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx\nEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw\nggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe\nNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH\nPWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I\nx2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe\nQTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR\nyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO\nQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912\nH9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ\nQfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD\ni/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs\nnLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1\nrqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud\nDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI\nhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM\ntCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf\nGopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb\nlvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka\n+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal\nTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i\nnSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3\ngzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr\nG5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os\nzMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x\nL4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL\n-----END CERTIFICATE-----`))\n\n\t// CN=emSign ECC Root CA - G3; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG\nEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo\nbm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g\nRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ\nTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s\nb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw\ndjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0\nWXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS\nfvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB\nzhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq\nhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB\nCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD\n+JbNR6iC8hZVdyR+EhCVBCyj\n-----END CERTIFICATE-----`))\n\n\t// CN=emSign Root CA - G1; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD\nVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU\nZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH\nMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO\nMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv\nZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz\nf2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO\n8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq\nd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM\ntTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt\nOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB\no0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD\nAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x\nPaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM\nwiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d\nGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH\n6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby\nRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx\niN66zB+Afko=\n-----END CERTIFICATE-----`))\n\n\t// CN=AffirmTrust Commercial; O=AffirmTrust; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE\nBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz\ndCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL\nMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp\ncm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP\nHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr\nba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL\nMeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1\nyHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr\nVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/\nnx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ\nKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG\nXUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj\nvbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt\nZ8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g\nN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC\nnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=\n-----END CERTIFICATE-----`))\n\n\t// CN=Atos TrustedRoot 2011; O=Atos; C=DE\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE\nAwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG\nEwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM\nFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC\nREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp\nNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM\nVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+\nSZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ\n4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L\ncp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi\neowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV\nHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG\nA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3\nDQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j\nvZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP\nDpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc\nmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D\nlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv\nKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed\n-----END CERTIFICATE-----`))\n\n\t// CN=Atos TrustedRoot Root CA ECC TLS 2021; O=Atos; C=DE\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w\nLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w\nCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0\nMTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF\nQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI\nzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X\ntXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4\nAjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2\nKCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD\naAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu\nCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo\n9H1/IISpQuQo\n-----END CERTIFICATE-----`))\n\n\t// CN=Atos TrustedRoot Root CA RSA TLS 2021; O=Atos; C=DE\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM\nMS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx\nMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00\nMTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD\nQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN\nBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z\n4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv\nYe+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ\nkmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs\nGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln\nnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh\n3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD\n0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy\ngeBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8\nANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB\nc6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI\npw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU\ndEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB\nDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS\n4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs\no0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ\nqM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw\nxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM\nrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4\nAXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR\n0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY\no7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5\ndDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE\noji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ==\n-----END CERTIFICATE-----`))\n\n\t// CN=GlobalSign; OU=GlobalSign Root CA - R6; O=GlobalSign\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg\nMB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh\nbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx\nMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET\nMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ\nKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI\nxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k\nZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD\naNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw\nLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw\n1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX\nk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2\nSXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h\nbguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n\nWUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY\nrZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce\nMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD\nAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu\nbAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN\nnsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt\nIxg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61\n55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj\nvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf\ncDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz\noHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp\nnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs\npA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v\nJJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R\n8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4\n5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=\n-----END CERTIFICATE-----`))\n\n\t// CN=GlobalSign Root E46; O=GlobalSign nv-sa; C=BE\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx\nCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD\nExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw\nMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex\nHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA\nIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq\nR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd\nyXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ\n7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8\n+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A=\n-----END CERTIFICATE-----`))\n\n\t// CN=GlobalSign Root R46; O=GlobalSign nv-sa; C=BE\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA\nMEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD\nVQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy\nMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt\nc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ\nOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG\nvGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud\n316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo\n0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE\ny132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF\nzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE\n+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN\nI/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs\nx2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa\nByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC\n4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\nHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4\n7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg\nJuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti\n2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk\npnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF\nFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt\nrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk\nZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5\nu+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP\n4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6\nN3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3\nvouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6\n-----END CERTIFICATE-----`))\n\n\t// CN=GlobalSign; OU=GlobalSign ECC Root CA - R5; O=GlobalSign\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk\nMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH\nbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX\nDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD\nQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc\n8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke\nhOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD\nVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI\nKoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg\n515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO\nxwy8p2Fp8fc74SrL+SvzZpA3\n-----END CERTIFICATE-----`))\n\n\t// CN=GlobalSign; OU=GlobalSign Root CA - R3; O=GlobalSign\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G\nA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp\nZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4\nMTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG\nA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8\nRgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT\ngHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm\nKPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd\nQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ\nXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw\nDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o\nLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU\nRUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp\njjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK\n6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX\nmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs\nMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH\nWD9f\n-----END CERTIFICATE-----`))\n\n\t// CN=Starfield Root Certificate Authority - G2; O=Starfield Technologies, Inc.; L=Scottsdale; ST=Arizona; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx\nEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT\nHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs\nZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw\nMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6\nb25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj\naG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp\nY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg\nnLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1\nHOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N\nHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN\ndloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0\nHZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO\nBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G\nCSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU\nsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3\n4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg\n8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K\npL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1\nmMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0\n-----END CERTIFICATE-----`))\n\n\t// CN=Go Daddy Root Certificate Authority - G2; O=GoDaddy.com, Inc.; L=Scottsdale; ST=Arizona; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx\nEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT\nEUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp\nZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz\nNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH\nEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE\nAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD\nE6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH\n/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy\nDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh\nGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR\ntDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA\nAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE\nFDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX\nWWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu\n9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr\ngIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo\n2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO\nLPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI\n4uJEvlz36hz1\n-----END CERTIFICATE-----`))\n\n\t// CN=GlobalSign; OU=GlobalSign ECC Root CA - R4; O=GlobalSign\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD\nVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh\nbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw\nMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g\nUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx\nuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV\nHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/\n+wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147\nbmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm\n-----END CERTIFICATE-----`))\n\n\t// CN=GTS Root R4; O=Google Trust Services LLC; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD\nVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG\nA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw\nWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz\nIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\nAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi\nQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR\nHYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\nBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D\n9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8\np/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD\n-----END CERTIFICATE-----`))\n\n\t// CN=GTS Root R2; O=Google Trust Services LLC; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw\nCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\nMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\nMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\nY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA\nA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt\nnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY\n6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu\nMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k\nRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg\nf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV\n+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo\ndDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW\nIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa\nG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq\ngc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID\nAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\nFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H\nvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8\n0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC\nB19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u\nNmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg\nyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev\nHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6\nxLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR\nTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg\nJPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV\n7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl\n6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL\n-----END CERTIFICATE-----`))\n\n\t// CN=GTS Root R1; O=Google Trust Services LLC; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw\nCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\nMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\nMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\nY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA\nA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo\n27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w\nCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw\nTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl\nqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH\nszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8\nY/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk\nMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92\nwO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p\naDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN\nVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID\nAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\nFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb\nC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe\nQkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy\nh6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4\n7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J\nZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef\nMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/\nZ6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT\n6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ\n0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm\n2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb\nbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c\n-----END CERTIFICATE-----`))\n\n\t// CN=GTS Root R3; O=Google Trust Services LLC; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD\nVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG\nA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw\nWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz\nIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\nAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G\njOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2\n4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\nBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7\nVKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm\nZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X\n-----END CERTIFICATE-----`))\n\n\t// CN=ACCVRAIZ1; OU=PKIACCV; O=ACCV; C=ES\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE\nAwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw\nCQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ\nBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND\nVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb\nqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY\nHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo\nG2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA\nlHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr\nIA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/\n0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH\nk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47\n4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO\nm3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa\ncXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl\nuUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI\nKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls\nZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG\nAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2\nVuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT\nVfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG\nCCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA\ncgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA\nQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA\n7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA\ncgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA\nQwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA\nczAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu\naHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt\naW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud\nDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF\nBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp\nD70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU\nJyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m\nAM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD\nvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms\ntn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH\n7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h\nI6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA\nh1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF\nd3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H\npPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7\n-----END CERTIFICATE-----`))\n\n\t// OU=AC RAIZ FNMT-RCM; O=FNMT-RCM; C=ES\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx\nCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ\nWiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ\nBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG\nTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/\nyBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf\nBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz\nWHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF\ntBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z\n374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC\nIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL\nmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7\nwk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS\nMKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2\nZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet\nUqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw\nAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H\nYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3\nLmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD\nnFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1\nRXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM\nLVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf\n77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N\nJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm\nfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp\n6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp\n1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B\n9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok\nRqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv\nuu8wd+RU4riEmViAqhOLUTpPSPaLtrM=\n-----END CERTIFICATE-----`))\n\n\t// CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS; OU=Ceres; O=FNMT-RCM; C=ES; OrganizationIdentifier=VATES-Q2826004J\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw\nCQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw\nFgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S\nQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5\nMzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL\nDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS\nQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB\nBAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH\nsbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK\nUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD\nVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu\nSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC\nMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy\nv+c=\n-----END CERTIFICATE-----`))\n\n\t// CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1; OU=Kamu Sertifikasyon Merkezi - Kamu SM; O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK; L=Gebze - Kocaeli; C=TR\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx\nGDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp\nbXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w\nKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0\nBgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy\ndW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG\nEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll\nIEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU\nQUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT\nTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg\nLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7\na9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr\nLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr\nN3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X\nYacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/\niSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f\nAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH\nV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL\nBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh\nAHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf\nIPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4\nlzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c\n8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf\nlo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=\n-----END CERTIFICATE-----`))\n\n\t// CN=HARICA TLS RSA Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs\nMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl\nc2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg\nUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL\nMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl\nYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv\nb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l\nmwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE\n4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv\na9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M\npbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw\nKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b\nLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY\nAuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB\nAGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq\nE613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr\nW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ\nCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF\nMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE\nAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU\nX15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3\nf5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja\nH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP\nJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P\nzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt\njBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0\n/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT\nBGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79\naPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW\nxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU\n63ZTGI0RmLo=\n-----END CERTIFICATE-----`))\n\n\t// CN=HARICA TLS ECC Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw\nCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh\ncmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v\ndCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG\nA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj\naCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg\nQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7\nKKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y\nSTHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw\nAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD\nAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw\nSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN\nnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps\n-----END CERTIFICATE-----`))\n\n\t// CN=IdenTrust Commercial Root CA 1; O=IdenTrust; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK\nMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu\nVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw\nMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw\nJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT\n3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU\n+ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp\nS0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1\nbVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi\nT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL\nvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK\nVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK\ndHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT\nc+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv\nl7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N\niGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB\n/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD\nggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH\n6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt\nLRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93\nnAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3\n+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK\nW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT\nAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq\nl1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG\n4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ\nmUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A\n7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H\n-----END CERTIFICATE-----`))\n\n\t// CN=ISRG Root X1; O=Internet Security Research Group; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\nWhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\nZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\nMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\nh77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\nA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\nT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\nB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\nB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\nKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\nOlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\njh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\nqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\nrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\nhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\nubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\nNFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\nORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\nTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\njNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\noyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\nmRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\nemyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n-----END CERTIFICATE-----`))\n\n\t// CN=ISRG Root X2; O=Internet Security Research Group; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw\nCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg\nR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00\nMDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT\nZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw\nEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW\n+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9\nItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T\nAQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI\nzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW\ntL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1\n/q4AaOeMSQ+2b1tbFfLn\n-----END CERTIFICATE-----`))\n\n\t// CN=Izenpe.com; O=IZENPE S.A.; C=ES\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4\nMQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6\nZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD\nVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j\nb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq\nscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO\nxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H\nLmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX\nuaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD\nyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+\nJrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q\nrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN\nBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L\nhij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB\nQFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+\nHMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu\nZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg\nQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB\nBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx\nMCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\nAQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA\nA4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb\nlaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56\nawmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo\nJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw\nLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT\nVyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk\nLhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb\nUjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/\nQnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+\nnaM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls\nQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==\n-----END CERTIFICATE-----`))\n\n\t// CN=SZAFIR ROOT CA2; O=Krajowa Izba Rozliczeniowa S.A.; C=PL\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL\nBQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6\nZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw\nNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L\ncmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg\nUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN\nQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT\n3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw\n3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6\n3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5\nBSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN\nXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD\nAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF\nAAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw\n8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG\nnXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP\noky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy\nd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg\nLvWpCz/UXeHPhJ/iGcJfitYgHuNztw==\n-----END CERTIFICATE-----`))\n\n\t// CN=e-Szigno Root CA 2017; O=Microsec Ltd.; L=Budapest; C=HU; OrganizationIdentifier=VATHU-23584497\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV\nBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk\nLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv\nb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ\nBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg\nTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v\nIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv\nxie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H\nWyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G\nA1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB\neAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo\njbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ\n+efcMQ==\n-----END CERTIFICATE-----`))\n\n\t// CN=Microsec e-Szigno Root CA 2009; O=Microsec Ltd.; L=Budapest; C=HU; EmailAddress=info@e-szigno.hu\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD\nVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0\nZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G\nCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y\nOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx\nFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp\nZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o\ndTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP\nkd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc\ncbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U\nfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7\nN4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC\nxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1\n+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G\nA1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM\nPcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG\nSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h\nmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk\nddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775\ntyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c\n2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t\nHMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW\n-----END CERTIFICATE-----`))\n\n\t// CN=Microsoft ECC Root Certificate Authority 2017; O=Microsoft Corporation; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw\nCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD\nVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw\nMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV\nUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy\nb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq\nhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR\nogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb\nhGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3\nFQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV\nL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB\niudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=\n-----END CERTIFICATE-----`))\n\n\t// CN=Microsoft RSA Root Certificate Authority 2017; O=Microsoft Corporation; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl\nMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw\nNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5\nIDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG\nEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N\naWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ\nNt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0\nZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1\nHLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm\ngGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ\njEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc\naDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG\nYaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6\nW6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K\nUGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH\n+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q\nW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/\nBAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC\nNxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC\nLgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC\ngMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6\ntZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh\nSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2\nTaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3\npvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR\nxpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp\nGWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9\ndOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN\nAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB\nRA+GsCyRxj3qrg+E\n-----END CERTIFICATE-----`))\n\n\t// CN=NAVER Global Root Certification Authority; O=NAVER BUSINESS PLATFORM Corp.; C=KR\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM\nBQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG\nT1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0\naW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx\nCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD\nb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB\ndXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA\niQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH\n38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE\nHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz\nkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP\nszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq\nvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf\nnZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG\nYQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo\n0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a\nCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K\nAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I\n36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB\nAf8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN\nqo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj\ncu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm\n+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL\nhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe\nlHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7\np/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8\npiKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR\nLBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX\n5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO\ndh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul\n9XXeifdy\n-----END CERTIFICATE-----`))\n\n\t// CN=NetLock Arany (Class Gold) Főtanúsítvány; OU=Tanúsítványkiadók (Certification Services); O=NetLock Kft.; L=Budapest; C=HU\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG\nEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3\nMDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl\ncnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR\ndGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB\npzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM\nb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm\naWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz\nIEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT\nlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz\nAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5\nVA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG\nILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2\nBJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG\nAQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M\nU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh\nbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C\n+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC\nbLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F\nuLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2\nXjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=\n-----END CERTIFICATE-----`))\n\n\t// CN=OISTE WISeKey Global Root GC CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw\nCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91\nbmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg\nUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ\nBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu\nZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS\nb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni\neUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W\np2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E\nBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T\nrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV\n57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg\nMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9\n-----END CERTIFICATE-----`))\n\n\t// CN=OISTE WISeKey Global Root GB CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt\nMQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg\nRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i\nYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x\nCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG\nb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh\nbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3\nHEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx\nWuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX\n1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk\nu7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P\n99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r\nM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw\nAwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB\nBAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh\ncViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5\ngSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO\nZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf\naPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic\nNc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=\n-----END CERTIFICATE-----`))\n\n\t// CN=Security Communication ECC RootCA1; O=SECOM Trust Systems CO.,LTD.; C=JP\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT\nAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD\nVQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx\nNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT\nHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5\nIENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\nAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl\ndB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK\nULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E\nBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu\n9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O\nbe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k=\n-----END CERTIFICATE-----`))\n\n\t// OU=Security Communication RootCA2; O=SECOM Trust Systems CO.,LTD.; C=JP\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl\nMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe\nU2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX\nDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy\ndXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj\nYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV\nOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr\nzbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM\nVAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ\nhNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO\nojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw\nawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs\nOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3\nDQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF\ncoJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc\nokgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8\nt/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy\n1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/\nSjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03\n-----END CERTIFICATE-----`))\n\n\t// CN=Entrust Root Certification Authority; OU=www.entrust.net/CPS is incorporated by reference, (c) 2006 Entrust, Inc.; O=Entrust, Inc.; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC\nVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0\nLm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW\nKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl\ncnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw\nNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw\nNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy\nZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV\nBAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ\nKoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo\nNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4\n4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9\nKlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI\nrb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi\n94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB\nsDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi\ngA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo\nkORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE\nvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA\nA4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t\nO1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua\nAGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP\n9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/\neu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m\n0vdXcDazv/wor3ElhVsT/h5/WrQ8\n-----END CERTIFICATE-----`))\n\n\t// CN=Sectigo Public Server Authentication Root E46; O=Sectigo Limited; C=GB\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw\nCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T\nZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN\nMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG\nA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT\nZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA\nIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC\nWvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+\n6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B\nAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa\nqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q\n4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw==\n-----END CERTIFICATE-----`))\n\n\t// CN=COMODO ECC Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL\nMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE\nBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT\nIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw\nMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy\nZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N\nT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv\nbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR\nFtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J\ncfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW\nBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/\nBAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm\nfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv\nGDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=\n-----END CERTIFICATE-----`))\n\n\t// CN=COMODO Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIID0DCCArigAwIBAgIQIKTEf93f4cdTYwcTiHdgEjANBgkqhkiG9w0BAQUFADCB\ngTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\nA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV\nBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xMTAxMDEwMDAw\nMDBaFw0zMDEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl\nYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P\nRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0\naG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3\nUcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI\n2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8\nQ5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp\n+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+\nDT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O\nnKVIrLsm9wIDAQABo0IwQDAdBgNVHQ4EFgQUC1jli8ZMFTekQKkwqSG+RzZaVv8w\nDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD\nggEBAC/JxBwHO89hAgCx2SFRdXIDMLDEFh9sAIsQrK/xR9SuEDwMGvjUk2ysEDd8\nt6aDZK3N3w6HM503sMZ7OHKx8xoOo/lVem0DZgMXlUrxsXrfViEGQo+x06iF3u6X\nHWLrp+cxEmbDD6ZLLkGC9/3JG6gbr+48zuOcrigHoSybJMIPIyaDMouGDx8rEkYl\nFo92kANr3ryqImhrjKGsKxE5pttwwn1y6TPn/CbxdFqR5p2ErPioBhlG5qfpqjQi\npKGfeq23sqSaM4hxAjwu1nqyH6LKwN0vEJT9s4yEIHlG1QXUEOTS22RPuFvuG8Ug\nR1uUq27UlTMdphVx8fiUylQ5PsE=\n-----END CERTIFICATE-----`))\n\n\t// CN=COMODO RSA Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB\nhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\nA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV\nBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5\nMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT\nEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR\nQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh\ndGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR\n6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X\npz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC\n9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV\n/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf\nZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z\n+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w\nqP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah\nSL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC\nu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf\nFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq\ncrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E\nFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl\nwFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM\n4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV\n2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna\nFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ\nCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK\nboHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke\njkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL\nS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb\nQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl\n0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB\nNVOFBkpdn627G190\n-----END CERTIFICATE-----`))\n\n\t// CN=USERTrust RSA Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB\niDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl\ncnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV\nBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw\nMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV\nBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU\naGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy\ndGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\nAoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B\n3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY\ntJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/\nFp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2\nVN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT\n79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6\nc0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT\nYo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l\nc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee\nUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE\nHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd\nBgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G\nA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF\nUp/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO\nVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3\nATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs\n8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR\niQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze\nSf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ\nXHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/\nqS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB\nVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB\nL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG\njjxDah2nGN59PRbxYvnKkKj9\n-----END CERTIFICATE-----`))\n\n\t// CN=USERTrust ECC Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\neSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\nJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\nMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\nCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\nVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\naWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\nI+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\no4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\nA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\nVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\nzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\nRNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n-----END CERTIFICATE-----`))\n\n\t// CN=Sectigo Public Server Authentication Root R46; O=Sectigo Limited; C=GB\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf\nMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD\nEy1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw\nHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY\nMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp\nYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa\nef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz\nSDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf\niOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X\nME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3\nIuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS\nVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE\nSJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu\n+Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt\n8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L\nHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt\nzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P\nAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c\nmTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ\nYKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52\ngDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA\nFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB\nJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX\nDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui\nTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5\ndHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65\nLvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp\n0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY\nQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL\n-----END CERTIFICATE-----`))\n\n\t// CN=Entrust Root Certification Authority - G2; OU=See www.entrust.net/legal-terms, (c) 2009 Entrust, Inc. - for authorized use only; O=Entrust, Inc.; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC\nVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50\ncnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs\nIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz\ndCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy\nNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu\ndHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt\ndGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0\naG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj\nYXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T\nRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN\ncCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW\nwcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1\nU1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0\njaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP\nBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN\nBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/\njTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ\nRkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v\n1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R\nnAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH\nVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==\n-----END CERTIFICATE-----`))\n\n\t// CN=Entrust Root Certification Authority - EC1; OU=See www.entrust.net/legal-terms, (c) 2012 Entrust, Inc. - for authorized use only; O=Entrust, Inc.; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG\nA1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3\nd3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu\ndHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq\nRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy\nMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD\nVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0\nL2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g\nZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD\nZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi\nA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt\nByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH\nBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O\nBBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC\nR98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX\nhTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G\n-----END CERTIFICATE-----`))\n\n\t// CN=SSL.com Root Certification Authority RSA; O=SSL Corporation; L=Houston; ST=Texas; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE\nBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK\nDA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp\nY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz\nOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv\ndXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv\nbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN\nAQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R\nxFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX\nqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC\nC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3\n6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh\n/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF\nYD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E\nJNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc\nUS4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8\nZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm\n+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi\nM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV\nHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G\nA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV\ncpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc\nHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs\nPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/\nq5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0\ncuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr\na6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I\nH37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y\nK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu\nnLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf\noYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY\nIc2wBlX7Jz9TkHCpBB5XJ7k=\n-----END CERTIFICATE-----`))\n\n\t// CN=SSL.com TLS ECC Root CA 2022; O=SSL Corporation; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw\nCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT\nU0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2\nMDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh\ndGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG\nByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm\nacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN\nSeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME\nGDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW\nuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp\n15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN\nb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g==\n-----END CERTIFICATE-----`))\n\n\t// CN=SSL.com TLS RSA Root CA 2022; O=SSL Corporation; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO\nMQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD\nDBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX\nDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw\nb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC\nAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP\nL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY\nt6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins\nS657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3\nPnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO\nL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3\nR2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w\ndr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS\n+YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS\nd66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG\nAtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f\ngTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j\nBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z\nNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt\nhEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM\nQtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf\nR4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ\nDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW\nP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy\nlrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq\nbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w\nAgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q\nr5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji\nMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU\n98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA=\n-----END CERTIFICATE-----`))\n\n\t// CN=SSL.com Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC\nVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T\nU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0\naW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz\nWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0\nb24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS\nb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB\nBAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI\n7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg\nCemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud\nEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD\nVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T\nkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+\ngA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl\n-----END CERTIFICATE-----`))\n\n\t// CN=SSL.com EV Root Certification Authority RSA R2; O=SSL Corporation; L=Houston; ST=Texas; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV\nBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE\nCgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy\ndGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy\nMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G\nA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD\nDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq\nM0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf\nOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa\n4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9\nHSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR\naZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA\nb9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ\nGp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV\nPWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO\npgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu\nUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY\nMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV\nHSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4\n9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW\ns47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5\nSm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg\ncLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM\n79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz\n/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt\nll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm\nKf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK\nQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ\nw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi\nS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07\nmKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==\n-----END CERTIFICATE-----`))\n\n\t// CN=SSL.com EV Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC\nVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T\nU0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp\nY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx\nNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv\ndXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv\nbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49\nAgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA\nVIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku\nWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP\nMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX\n5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ\nytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg\nh5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==\n-----END CERTIFICATE-----`))\n\n\t// CN=SwissSign Gold CA - G2; O=SwissSign AG; C=CH\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV\nBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln\nbiBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF\nMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT\nd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC\nCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8\n76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+\nbbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c\n6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE\nemA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd\nMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt\nMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y\nMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y\nFGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi\naG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM\ngI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB\nqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7\nlqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn\n8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov\nL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6\n45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO\nUYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5\nO1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC\nbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv\nGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a\n77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC\nhdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3\n92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp\nLd6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w\nZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt\nQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ\n-----END CERTIFICATE-----`))\n\n\t// CN=TWCA CYBER Root CA; OU=Root CA; O=TAIWAN-CA; C=TW\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ\nMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290\nIENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5\nWhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO\nLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg\nQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P\n40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF\navcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/\n34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i\nJkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu\nj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf\nXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP\n2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA\nS9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA\noS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC\nkHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW\n5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD\nVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd\nBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB\nAGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t\ntGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn\n68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn\nTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t\nRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx\nf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI\nQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz\n8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4\nNxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX\nxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6\nt5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X\n-----END CERTIFICATE-----`))\n\n\t// CN=TWCA Global Root CA; OU=Root CA; O=TAIWAN-CA; C=TW\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx\nEjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT\nVFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5\nNTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT\nB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF\n10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz\n0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh\nMBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH\nzIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc\n46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2\nyKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi\nlaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP\noA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA\nBDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE\nqYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm\n4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB\n/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL\n1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn\nLhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF\nH6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo\nRI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+\nnile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh\n15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW\n6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW\nnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j\nwa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz\naGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy\nKwbQBM0=\n-----END CERTIFICATE-----`))\n\n\t// CN=TeliaSonera Root CA v1; O=TeliaSonera\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw\nNzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv\nb3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD\nVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2\nMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F\nVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1\n7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X\nZ75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+\n/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs\n81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm\ndtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe\nOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu\nsDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4\npgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs\nslESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ\narMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD\nVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG\n9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl\ndxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx\n0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj\nTQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed\nY2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7\nQ4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI\nOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7\nvVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW\nt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn\nHL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx\nSK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=\n-----END CERTIFICATE-----`))\n\n\t// CN=Telia Root CA v2; O=Telia Finland Oyj; C=FI\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx\nCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE\nAwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1\nNTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ\nMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP\nADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq\nAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9\nvVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9\nlRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD\nn3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT\n7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o\n6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC\nTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6\nWT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R\nDolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI\npEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj\nYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy\nrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw\nAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ\n8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi\n0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM\nA8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS\nSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K\nTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF\n6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er\n3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt\nTy3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT\nVmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW\nysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA\nrBPuUBQemMc=\n-----END CERTIFICATE-----`))\n\n\t// CN=Trustwave Global ECC P384 Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD\nVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf\nBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3\nYXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x\nNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G\nA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0\nd2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF\nQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB\nBAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ\nj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF\n1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G\nA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3\nAZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC\nMGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu\nSw==\n-----END CERTIFICATE-----`))\n\n\t// CN=Trustwave Global ECC P256 Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD\nVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf\nBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3\nYXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x\nNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G\nA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0\nd2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF\nQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG\nSM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN\nFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w\nDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw\nCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh\nDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7\n-----END CERTIFICATE-----`))\n\n\t// CN=SecureTrust CA; O=SecureTrust Corporation; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI\nMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x\nFzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz\nMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv\ncnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz\nZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO\n0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao\nwW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj\n7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS\n8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT\nBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB\n/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg\nJYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC\nNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3\n6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/\n3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm\nD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS\nCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR\n3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=\n-----END CERTIFICATE-----`))\n\n\t// CN=Trustwave Global Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US\n\tchromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw\nCQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x\nITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1\nc3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx\nOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI\nSWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI\nb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp\nY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\nALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn\nswuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu\n7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8\n1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW\n80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP\nJqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l\nRtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw\nhI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10\ncoos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc\nBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n\ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud\nEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud\nDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W\n0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe\nuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q\nlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB\naCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE\nsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT\nMaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe\nqu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh\nVicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8\nh6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9\nEEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK\nyeC2nOnOcXHebD8WpHk=\n-----END CERTIFICATE-----`))\n}\n"
  },
  {
    "path": "common/certificate/mozilla.go",
    "content": "// Code generated by 'make update_certificates'. DO NOT EDIT.\n\npackage certificate\n\nimport \"crypto/x509\"\n\nvar mozillaIncluded *x509.CertPool\n\nfunc init() {\n\tmozillaIncluded = x509.NewCertPool()\n\n\t// Actalis Authentication Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE\nBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w\nMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290\nIENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC\nSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1\nODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv\nUTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX\n4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9\nKK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/\ngCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb\nrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ\n51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F\nbe8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe\nKF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F\nv6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn\nfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7\njPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz\nezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt\nifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL\ne3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70\njsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz\nWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V\nSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j\npwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX\nX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok\nfcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R\nK4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU\nZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU\nLysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT\nLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==\n-----END CERTIFICATE-----`))\n\n\t// TunTrust Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL\nBQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg\nQ2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv\nb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG\nEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u\nIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ\nKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ\nn56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd\n2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF\nVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ\nGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF\nli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU\nr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2\neY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb\nMlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg\njwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB\n7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW\n5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE\nITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0\n90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z\nxiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu\nQEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4\nFstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH\n22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP\nxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn\ndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5\nXc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b\nnV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ\nCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH\nu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj\nd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=\n-----END CERTIFICATE-----`))\n\n\t// Amazon Root CA 1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\nADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\nb24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\nMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\nb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\nca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\nIFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\nVOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\njgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\nAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\nA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\nU5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\nN+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\no/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\nrqXRfboQnoZsG4q5WTP468SQvvG5\n-----END CERTIFICATE-----`))\n\n\t// Amazon Root CA 2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF\nADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\nb24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL\nMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\nb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK\ngXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ\nW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg\n1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K\n8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r\n2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me\nz/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR\n8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj\nmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz\n7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6\n+XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI\n0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm\nUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2\nLIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY\n+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS\nk5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl\n7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm\nbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl\nurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+\nfUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63\nn749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE\n76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H\n9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT\n4PsJYGw=\n-----END CERTIFICATE-----`))\n\n\t// Amazon Root CA 3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5\nMQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\nUm9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\nA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\nQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl\nui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j\nQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr\nttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr\nBqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM\nYyRIHN8wfdVoOw==\n-----END CERTIFICATE-----`))\n\n\t// Amazon Root CA 4\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5\nMQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\nUm9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\nA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\nQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi\n9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk\nM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB\n/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB\nMAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw\nCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW\n1KyLa2tJElMzrdfkviT8tQp21KW8EA==\n-----END CERTIFICATE-----`))\n\n\t// Starfield Services Root Certificate Authority - G2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx\nEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT\nHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs\nZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5\nMDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD\nVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy\nZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy\ndmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p\nOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2\n8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K\nTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe\nhRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk\n6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw\nDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q\nAdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI\nbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB\nve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z\nqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd\niEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn\n0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN\nsSi6\n-----END CERTIFICATE-----`))\n\n\t// Certum CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM\nMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD\nQTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM\nMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD\nQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E\njG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo\nePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI\nULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu\nOb7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg\nAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7\nHVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA\nuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa\nTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg\nxSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q\nCjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x\nO/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs\n6GAqm4VKQPNriiTsBhYscw==\n-----END CERTIFICATE-----`))\n\n\t// Certum EC-384 CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw\nCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw\nJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT\nEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0\nWjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT\nLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX\nBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE\nKI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm\nFy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj\nQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8\nEF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J\nUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn\nnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=\n-----END CERTIFICATE-----`))\n\n\t// Certum Trusted Network CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM\nMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D\nZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU\ncnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3\nWjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg\nUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw\nIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B\nAQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH\nUV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM\nTXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU\nBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM\nkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x\nAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV\nHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y\nsHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL\nI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8\nJ9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY\nVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI\n03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=\n-----END CERTIFICATE-----`))\n\n\t// Certum Trusted Network CA 2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB\ngDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu\nQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG\nA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz\nOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ\nVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp\nZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3\nb3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA\nDGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn\n0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB\nOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE\nfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E\nSv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m\no130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i\nsx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW\nOZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez\nTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS\nadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n\n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD\nAQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC\nAQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ\nF/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf\nCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29\nXN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm\ndjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/\nWjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb\nAoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq\nP/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko\nb7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj\nXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P\n5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi\nDrW5viSP\n-----END CERTIFICATE-----`))\n\n\t// Certum Trusted Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6\nMQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu\nMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV\nBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw\nMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg\nU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo\nb3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ\nn0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q\np1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq\nNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF\n8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3\nHAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa\nmqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi\n7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF\nytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P\nqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ\nv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6\nTsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1\nvALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD\nggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4\nWxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo\nzMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR\n5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ\nGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf\n5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq\n0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D\nP78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM\nqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP\n0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf\nE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb\n-----END CERTIFICATE-----`))\n\n\t// Autoridad de Certificacion Firmaprofesional CIF A62634068\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE\nBhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h\ncHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1\nMDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg\nQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9\nthDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM\ncas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG\nL9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i\nNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h\nX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b\nm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy\nZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja\nEbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T\nKI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF\n6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh\nOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc\ntHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd\nIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j\nb20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC\nAG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw\nADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m\niWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF\nSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ\nhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P\nVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE\nEAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV\n1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t\nCsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR\n5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw\nf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9\nivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK\nGbqEZycPvEJdvSRUDewdcAZfpLz6IHxV\n-----END CERTIFICATE-----`))\n\n\t// FIRMAPROFESIONAL CA ROOT-A WEB\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw\nCQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE\nYQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB\nIFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2WhcNNDcwMzMxMDkwMTM2WjBuMQsw\nCQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE\nYQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB\nIFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zf\ne9MEkVz6iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6C\ncyvHZpsKjECcfIr28jlgst7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB\n/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FDY1w8ndYn81LsF7Kpryz3dvgwHQYDVR0O\nBBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO\nPQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw\nhVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG\nXSaQpYXFuXqUPoeovQA=\n-----END CERTIFICATE-----`))\n\n\t// ANF Secure Server Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV\nBAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk\nYWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV\nBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN\nMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF\nUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD\nVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v\ndCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj\ncqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q\nyGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH\n2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX\nH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL\nzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR\np1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz\nW7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/\nSiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn\nLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3\nn5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B\nu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj\no1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC\nAgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L\n9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej\nrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK\npFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0\nvPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq\nOknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ\n/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9\n2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI\n+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2\nMjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo\ntt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=\n-----END CERTIFICATE-----`))\n\n\t// Buypass Class 2 Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd\nMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg\nQ2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow\nTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw\nHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB\nBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr\n6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV\nL4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91\n1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx\nMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ\nQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB\narcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr\nUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi\nFRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS\nP/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN\n9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP\nAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz\nuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h\n9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s\nA20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t\nOluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo\n+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7\nKcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2\nDISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us\nH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ\nI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7\n5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h\n3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz\nY11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=\n-----END CERTIFICATE-----`))\n\n\t// Buypass Class 3 Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd\nMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg\nQ2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow\nTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw\nHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB\nBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y\nZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E\nN3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9\ntznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX\n0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c\n/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X\nKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY\nzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS\nO1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D\n34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP\nK9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3\nAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv\nTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj\nQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV\ncSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS\nIGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2\nHJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa\nO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv\n033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u\ndmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE\nkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41\n3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD\nu79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq\n4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=\n-----END CERTIFICATE-----`))\n\n\t// Certainly Root E1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw\nCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu\nbHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ\nBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s\neSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK\n+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2\nQNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E\nBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4\nhevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm\nut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG\nBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR\n-----END CERTIFICATE-----`))\n\n\t// Certainly Root R1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw\nPTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy\ndGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9\nMQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0\nYWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2\n1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT\nvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed\naFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0\n1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5\nr3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5\ncBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ\nwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ\n6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA\n2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH\nWyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR\neiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB\n/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u\nd0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr\nPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d\n8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi\n1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd\nrRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di\ntaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7\nlcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj\nyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn\nKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy\nyCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n\nwXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6\nOV+KmalBWQewLK8=\n-----END CERTIFICATE-----`))\n\n\t// Certigna\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV\nBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X\nDTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ\nBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4\nQCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny\ngQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw\nzBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q\n130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2\nJsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw\nDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw\nZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT\nAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj\nAQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG\n9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h\nbV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc\nfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu\nHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w\nt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw\nWyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==\n-----END CERTIFICATE-----`))\n\n\t// Certigna Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw\nWjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw\nMiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x\nMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD\nVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX\nBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw\nggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO\nty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M\nCiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu\nI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm\nTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh\nC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf\nePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz\nIoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT\nCo/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k\nJWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5\nhwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB\nGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE\nFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of\n1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov\nL3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo\ndHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr\naHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq\nhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L\n6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG\nHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6\n0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB\nlA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi\no2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1\ngPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v\nfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63\nNwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh\njWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw\n3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=\n-----END CERTIFICATE-----`))\n\n\t// certSIGN ROOT CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT\nAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD\nQTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP\nMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do\n0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ\nUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d\nRdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ\nOA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv\nJoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C\nAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O\nBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ\nLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY\nMnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ\n44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I\nJd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw\ni/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN\n9u6wWk5JRFRYX0KD\n-----END CERTIFICATE-----`))\n\n\t// certSIGN ROOT CA G2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV\nBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g\nUk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ\nBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ\nR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF\ndRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw\nvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ\nuIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp\nn+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs\ncpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW\nxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P\nrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF\nDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx\nDTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy\nLcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C\neWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB\n/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ\nd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq\nkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC\nb6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl\nqiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0\nOJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c\nNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk\nltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO\npwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj\n03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk\nPuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE\n1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX\nQRBdJ3NghVdJIgc=\n-----END CERTIFICATE-----`))\n\n\t// ePKI Root Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe\nMQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0\nZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe\nFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw\nIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL\nSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH\nSyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh\nijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X\nDZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1\nTBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ\nfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA\nsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU\nWH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS\nnT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH\ndmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip\nNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC\nAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF\nMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH\nClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB\nuvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl\nPwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP\nJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/\ngpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2\nj6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6\n5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB\no2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS\n/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z\nGp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE\nW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D\nhNQ+IIX3Sj0rnP0qCglN6oH4EZw=\n-----END CERTIFICATE-----`))\n\n\t// HiPKI Root CA - G1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP\nMQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0\nZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa\nFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3\nYSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw\nqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv\nVcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6\nlZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz\nQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ\nKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK\nFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj\nHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr\ny+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ\n/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM\na/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6\nfsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\nHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG\nSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi\n7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc\nSE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza\nZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc\nXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg\niLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho\nL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF\nNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr\nkkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+\nvhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU\nYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==\n-----END CERTIFICATE-----`))\n\n\t// SecureSign Root CA12\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL\nBQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u\nLCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw\nNTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD\neWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS\nb290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF\nKxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt\np7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd\nJ1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur\nFzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J\nhscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K\nh9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD\nAgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF\nAAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld\nmmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ\nmBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA\n8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV\n55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/\nyOPiZwud9AzqVN/Ssq+xIvEg37xEHA==\n-----END CERTIFICATE-----`))\n\n\t// SecureSign Root CA14\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM\nBQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u\nLCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw\nNzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD\neWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS\nb290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/\nFjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg\nvlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy\n6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo\n/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J\nkdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ\n0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib\ny8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac\n18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs\n0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB\nSMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL\nApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD\nAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk\n86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E\nrX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib\ned87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT\nzfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS\nDCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4\n2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo\nFlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy\nK4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6\ndB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl\nLor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB\n365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c\nJRNItX+S\n-----END CERTIFICATE-----`))\n\n\t// SecureSign Root CA15\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw\nUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM\ndGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy\nNTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl\ncnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290\nIENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4\nwCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR\nZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT\n9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp\n4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6\nbkU6iYAZezKYVWOr62Nuk22rGwlgMU4=\n-----END CERTIFICATE-----`))\n\n\t// D-TRUST BR Root CA 1 2020\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw\nCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS\nVVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5\nNDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG\nA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB\nBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS\nzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0\nQVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/\nVbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g\nPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf\nY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l\ndC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1\nc3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO\nPQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW\nwKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV\ndWNbFJWcHwHP2NVypw87\n-----END CERTIFICATE-----`))\n\n\t// D-TRUST BR Root CA 2 2023\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI\nMQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE\nLVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw\nOTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi\nMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN\nAQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr\ni3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE\ngXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8\nk12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT\nRphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl\n2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U\ncSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP\n/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS\nuREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+\n0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N\nDfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+\nXTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61\nGNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG\nOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y\nXzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI\nFoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n\nriv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR\nVFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc\nLmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn\n4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD\nhQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG\nkoHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46\nls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS\nEcr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80\nknCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ\nhJ65bvspmZDogNOfJA==\n-----END CERTIFICATE-----`))\n\n\t// D-TRUST EV Root CA 1 2020\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw\nCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS\nVVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5\nNTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG\nA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB\nBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC\n/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD\nwpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3\nOqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g\nPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf\nY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l\ndC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1\nc3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO\nPQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA\ny/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb\ngfM0agPnIjhQW+0ZT0MW\n-----END CERTIFICATE-----`))\n\n\t// D-TRUST EV Root CA 2 2023\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI\nMQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE\nLVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw\nOTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi\nMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN\nAQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK\nF8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE\n7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe\nEMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6\nlHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb\nRlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV\njTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc\njmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx\nTnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+\nARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk\nhbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF\nNXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH\nkV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG\nOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y\nXzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14\nQvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4\npZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q\n3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU\nt7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX\ncI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8\nifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT\n2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs\n7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP\ngofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst\nNl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh\nXBxvWHZks/wCuPWdCg==\n-----END CERTIFICATE-----`))\n\n\t// D-TRUST Root CA 3 2013\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEDjCCAvagAwIBAgIDD92sMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAkRF\nMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxHzAdBgNVBAMMFkQtVFJVU1QgUm9vdCBD\nQSAzIDIwMTMwHhcNMTMwOTIwMDgyNTUxWhcNMjgwOTIwMDgyNTUxWjBFMQswCQYD\nVQQGEwJERTEVMBMGA1UECgwMRC1UcnVzdCBHbWJIMR8wHQYDVQQDDBZELVRSVVNU\nIFJvb3QgQ0EgMyAyMDEzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\nxHtCkoIf7O1UmI4SwMoJ35NuOpNcG+QQd55OaYhs9uFp8vabomGxvQcgdJhl8Ywm\nCM2oNcqANtFjbehEeoLDbF7eu+g20sRoNoyfMr2EIuDcwu4QRjltr5M5rofmw7wJ\nySxrZ1vZm3Z1TAvgu8XXvD558l++0ZBX+a72Zl8xv9Ntj6e6SvMjZbu376Ml1wrq\nWLbviPr6ebJSWNXwrIyhUXQplapRO5AyA58ccnSQ3j3tYdLl4/1kR+W5t0qp9x+u\nloYErC/jpIF3t1oW/9gPP/a3eMykr/pbPBJbqFKJcu+I89VEgYaVI5973bzZNO98\nlDyqwEHC451QGsDkGSL8swIDAQABo4IBBTCCAQEwDwYDVR0TAQH/BAUwAwEB/zAd\nBgNVHQ4EFgQUP5DIfccVb/Mkj6nDL0uiDyGyL+cwDgYDVR0PAQH/BAQDAgEGMIG+\nBgNVHR8EgbYwgbMwdKByoHCGbmxkYXA6Ly9kaXJlY3RvcnkuZC10cnVzdC5uZXQv\nQ049RC1UUlVTVCUyMFJvb3QlMjBDQSUyMDMlMjAyMDEzLE89RC1UcnVzdCUyMEdt\nYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MDugOaA3hjVodHRwOi8v\nY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2FfM18yMDEzLmNybDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlkOWOR0SCNEzzQhtZwUGq2aS7eziG1cqRdw8Cqf\njXv5e4X6xznoEAiwNStfzwLS05zICx7uBVSuN5MECX1sj8J0vPgclL4xAUAt8yQg\nt4RVLFzI9XRKEBmLo8ftNdYJSNMOwLo5qLBGArDbxohZwr78e7Erz35ih1WWzAFv\nm2chlTWL+BD8cRu3SzdppjvW7IvuwbDzJcmPkn2h6sPKRL8mpXSSnON065102ctN\nh9j8tGlsi6BDB2B4l+nZk3zCRrybN1Kj7Yo8E6l7U0tJmhEFLAtuVqwfLoJs4Gln\ntQ5tLdnkwBXxP/oYcuEVbSdbLTAoK59ImmQrme/ydUlfXA==\n-----END CERTIFICATE-----`))\n\n\t// D-TRUST Root Class 3 CA 2 2009\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF\nMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD\nbGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha\nME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM\nHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03\nUAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42\ntSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R\nySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM\nlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp\n/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G\nA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G\nA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj\ndG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy\nMENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl\ncmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js\nL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL\nBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni\nacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0\no3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K\nzCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8\nPIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y\nJohw1+qRzT65ysCQblrGXnRl11z+o+I=\n-----END CERTIFICATE-----`))\n\n\t// D-TRUST Root Class 3 CA 2 EV 2009\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF\nMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD\nbGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw\nNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV\nBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn\nljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0\n3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z\nqQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR\np75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8\nHgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw\nggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea\nHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw\nOi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh\nc3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E\nRT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt\ndHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku\nY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp\n3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05\nnsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF\nCSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na\nxpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX\nKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1\n-----END CERTIFICATE-----`))\n\n\t// D-Trust SBR Root CA 1 2022\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICXjCCAeOgAwIBAgIQUs/kjG2gSvc/gpcMgAmMlTAKBggqhkjOPQQDAzBJMQsw\nCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSMwIQYDVQQDExpELVRy\ndXN0IFNCUiBSb290IENBIDEgMjAyMjAeFw0yMjA3MDYxMTMwMDBaFw0zNzA3MDYx\nMTI5NTlaMEkxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgxIzAh\nBgNVBAMTGkQtVHJ1c3QgU0JSIFJvb3QgQ0EgMSAyMDIyMHYwEAYHKoZIzj0CAQYF\nK4EEACIDYgAEWZM59oxJZijXYQzIq38Moy3foqR8kito1S5+HkDLtGhJfxKhq39X\nnxkuYy5b/mZxDDMPud5rxIjDse/sOUDjlqvb5XuuH9z5r0aaakYGL8c3ZIsXYv6W\nw6LuhOCwlzm8o4GPMIGMMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPEpox4B\nEh09dVZNx1B8xRmqDxi3MA4GA1UdDwEB/wQEAwIBBjBKBgNVHR8EQzBBMD+gPaA7\nhjlodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Nicl9yb290X2Nh\nXzFfMjAyMi5jcmwwCgYIKoZIzj0EAwMDaQAwZgIxAJf53q5Lj5i1HkB/Mn1NVEPa\nic3CqpI80YIec8/6TJIg+2MnxfVzPQk996dhhozzagIxAOcvfLj1JYw7OR82q431\nhqIu4Xpk2mc5Av7+Mz/Zc7ZYWzr8sqTZYHh3zHmnpq5VvQ==\n-----END CERTIFICATE-----`))\n\n\t// D-Trust SBR Root CA 2 2022\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFrDCCA5SgAwIBAgIQVNWjlR49lbpyG5rQMSFKujANBgkqhkiG9w0BAQ0FADBJ\nMQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSMwIQYDVQQDExpE\nLVRydXN0IFNCUiBSb290IENBIDIgMjAyMjAeFw0yMjA3MDcwNzMwMDBaFw0zNzA3\nMDcwNzI5NTlaMEkxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgx\nIzAhBgNVBAMTGkQtVHJ1c3QgU0JSIFJvb3QgQ0EgMiAyMDIyMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEAryy8jjaM62SvUWrWbjxekTrqmsPKbPuqJ55k\nIqlA37koRVrsU2EWKJjCiqR1eFCE3fogSJIHZUE1ZlESdGGdBwaFOTFXeyg/1Zyl\n7FrpHEsnn84nBvM39VLYETMWQTof9WN4ZWOGyb/IAQQfbu7i7KwM7oKS4vYaDT85\n+Z1lk634uQXBPfg3gVbDoP4F7OCUFjojFgTapgqThXJtYTuhjUXW43++Fb02hAj2\nC4NrJqqiveCw56rgrmfE04KlDKmk8DN5DVA/8O+QPSS5f9IgbOqX87+c3EfeCWG9\nlHmVWgJ2NWDERyIN93ZjA9PG+4PGXaut7WklKwNbTSUAQeOMhxdSqOAFK0NNFBPK\n5z9DIrw3pHXx9r867zIeru5YhpByugSsQEjvXMR4p6mPJ1rLeuxY8sIIWJBtTQOF\neXEVBQ5OPvnfDwX3XxRIViENM5KxrIzlGP6/D+7gBKq9IfJYtlyJCosYCSIaszXG\nZsL1MxWZgOAI+ZYvE4zu2reIxOk3tddq1zqETatwjNNOFFWgohD8ZNpn6PHLM93J\nmoqPli9Ygdn4mgBDzJD7VXb7huM3ASgMb/TpWU0Vd1FCSsw0uIBDUIHvV6UT26eU\neQ9Lyn4Xfa+jIWTocVVWjwawR+xZD11wWywWQvCGnnXea01ImITiVxi2nIKZZTqL\ngHhXDEkCAwEAAaOBjzCBjDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRds4CU\nG+WGv2i6FDSk9u5t8t3f5zAOBgNVHQ8BAf8EBAMCAQYwSgYDVR0fBEMwQTA/oD2g\nO4Y5aHR0cDovL2NybC5kLXRydXN0Lm5ldC9jcmwvZC10cnVzdF9zYnJfcm9vdF9j\nYV8yXzIwMjIuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA0VC5YGFbNSr2X0/V9K9yv\nD1HhTbwhS5P0AEQTBxALJRg+SFmW96Hhk5B4Zho9I+siqwGmjgxRM+ZtjDHurKQB\ncDlI3sdmLGsNy3Ofh5LpPkcfuO8v7rdWjEiJ8DinFTmy7sA/F6RzAgicvAaKpMK3\nYWH5w9vE0Hp8Yd6xWJH13WVMLwv46z217Yq+dxy6WQISZnHlmCfODj2vUaJF+YL7\nWqWUcPeLhMNMZSWbe+IfMHCzQI467r3052jFnckpR3EOk8i1SE71ZrsHiHFpa3tI\njm/wEcS0yXAUmCC97afqAdpupZsS/j5EMLPw63VSwPTD+ncmpHeCLW/zKB5OlfAw\n94n4LKJQW/K+Mn5sVNtyySpa4By2C9hSmlmh47ABJ8WgFlBm3OuubfSbWz2EbVuH\n56mJu2644JtTicD/LkAaiUQuGENnOOR8cl/ZoyklQUE9HHcbZKjDVe5jcWZig/R/\nJpmgVDuhEm1wYs7T+bi9IvzUmtS74jgWL7d9OcKwqQPpnM9+GI123F8Ru+tC7FAJ\nPlzskDHYGnK6P2kH7pg0wjSk1toT1qmE8gCGwFS6HhGw4rnEB7SR56rmMVZvsUTE\nKmK8ybBlnDT8DBpT3yEXu8JtoQrm8bCqRAlQSTh6XXHiMS4ZsN+VQgR9hIjOCiNn\nazidFt4G/ihwOKVarvyD7Q==\n-----END CERTIFICATE-----`))\n\n\t// T-TeleSec GlobalRoot Class 2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx\nKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd\nBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl\nYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1\nOTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy\naXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50\nZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G\nCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd\nAqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC\nFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi\n1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq\njnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ\nwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj\nQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/\nWSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy\nNsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC\nuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw\nIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6\ng1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN\n9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP\nBSeOE6Fuwg==\n-----END CERTIFICATE-----`))\n\n\t// T-TeleSec GlobalRoot Class 3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx\nKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd\nBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl\nYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1\nOTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy\naXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50\nZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G\nCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN\n8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/\nRLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4\nhqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5\nZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM\nEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj\nQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1\nA/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy\nWL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ\n1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30\n6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT\n91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml\ne9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p\nTpPDpFQUWw==\n-----END CERTIFICATE-----`))\n\n\t// Telekom Security SMIME ECC Root 2021\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICRzCCAc2gAwIBAgIQFSrdFMkY0aRWQIamJa8HXzAKBggqhkjOPQQDAzBlMQsw\nCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH\nbWJIMS0wKwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIEVDQyBSb290IDIw\nMjEwHhcNMjEwMzE4MTEwODMwWhcNNDYwMzE3MjM1OTU5WjBlMQswCQYDVQQGEwJE\nRTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMS0wKwYD\nVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIEVDQyBSb290IDIwMjEwdjAQBgcq\nhkjOPQIBBgUrgQQAIgNiAASwGY+ia7XHzQ8wmTcMw2Bb8fEnIFU9wJKLq1ehb3OD\nIcJDEwxeiarHBTV5k2KQ1l0TH9F6oLyeEKdmfEYKsFdsv+ZUOTghbBJccczTWl9t\nt6eG37Pf7sLniUGWNfYvSrWjQjBAMB0GA1UdDgQWBBQrywEMY8NTEqWoV6/QnIP7\nvZA6SzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQD\nAwNoADBlAjEA1rxIkodHA8dwOyW2H65GZ3N0ACdL5KUEogPfXiitbl4DyN1onLa/\nlBBIlS8P/xiLAjABQDOel5dNBfJ0VAzNOf1qawnBJD9hjjiht+jXRBURYv8OYTdH\nS0B/Sl+yZ1pzdcI=\n-----END CERTIFICATE-----`))\n\n\t// Telekom Security SMIME RSA Root 2023\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFtzCCA5+gAwIBAgIQDH5i9XlzO51Djotj7ZGVuDANBgkqhkiG9w0BAQwFADBl\nMQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0\neSBHbWJIMS0wKwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIFJTQSBSb290\nIDIwMjMwHhcNMjMwMzI4MTIwOTIyWhcNNDgwMzI3MjM1OTU5WjBlMQswCQYDVQQG\nEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMS0w\nKwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIFJTQSBSb290IDIwMjMwggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDvxQ6LvjLSZ0f/Ckxnsyq/yMPF\nkeu1xx6R4WaoiItVIIAfUV53l54ZClzHazchfAM2AfSIJdmoLkGq/Ngm4JZAYnmu\nV54DOBocsncUPumhctDk4DfRF0btUFx6WMX4K/d1L8+BnlostzqsoFmYBFEM/0nF\nUP0e00eFSzNPoje1rwSaJzKdVtU/VWHji2+uUf6X/mkH+mJbJuYUeRWlEziuXze+\nlErWDYAWaaSRsjpJmHWdRhCKXHp/hKXorx7Hq7NaRrWjS/WmIzYARrHbBbYbzp56\nMlya1XLDnYZNK4TTHrWI2hB4nCLDOyO16xMHvW9T7Jvsm9Nl9QcJ412nmbV+ho7V\nAv+3hQnjRxTdlmYYNN4I1d/LGJliCyvsAF1SRNPGlvwyViWRz80ZO5U5PgKHmWO2\n1T40eg8RdYG8fQTKYLQoddcCUd1SAC7H/YnxXPPLpCcSOI+7+4nw5MQ4LL6CoHFh\nYpGPSAwvK6mw8csQBOd0vzeQ708qQzWXEsYqcA3eLFVHeWMp9cofagZSHK4tJCKD\nIq/QqjC3Kh//ZSNYZZPIjn1AEDGGeNlVyzww8N5RKgA20idFX9jooSE9fkZWOylF\n8R0FCc62QzDcRZAQMEyka4aLPz0vMZFx7ya59r6dsGzfEe5YP0N5hjmA8SYXB5jw\nmaowLENZFM7t4kAThQIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE\nFJrOrCrsAfplcN6XnfHSAIylo2S7MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw\nFoAUms6sKuwB+mVw3ped8dIAjKWjZLswDQYJKoZIhvcNAQEMBQADggIBAONQ/fVA\nFiIJljoNqe+B5y4y8KHxSV57iA0Ecte+Z6i6He5Qu3JuetG7DHIwRsjV1wISFplO\nHt9alu6Pkb6uhvgQd6XEbkdhwPIm2U9haAVIdQgVpaF71biziXnm7fHzYQCGey4x\n/qNc+Hk9tFuIe+Ajuw2hF/rLaA2Yd3EI4m1DdGvENsWUQaQA1lctmYqLIBIVAjIO\n0knsgUjFaidS17JzVVOWPJ5PTLWg0E9X0GcoSGS+xri67GTPyHvFaucq5llXttbU\n1sBnXNmeKAlAv/OpNTFlYAPLGWyClQMeXz/hvepJceVbtwtHFhsgiW2UmQx+iGwd\nDfS3IRpZl6zL6L4XH5V8U5uvUFKqjQsur1rXYPIqaSq57lRwGKq99aE/0t2hYxkA\n+KcM66N58nBZo/iiEgPsE//kAoY218HDpLXUpMI3RbaUcD3FveujFR3jNnoVaSpW\nNDnPpZo2qsjtebzP9s4EUwvaslAjfLw+Jq3wDkO7JsuuwkDeNx8KoFHNY522T9jG\nR3y82LTtnovzEeKotT7srnA+fiK7NUgXYGIUkTCjdj2mUTaLHw3dajEcpe3dlqNu\ncg8TTaqnqVx4+QMSGJM3RRKJPfi+yr3ZvgzZGGSnyEE+dYIhOH1l9KDUE0sHeCn5\nnX7Mhz/E2i6I3eML3FpRWunZEk+eAtv3BSVR\n-----END CERTIFICATE-----`))\n\n\t// Telekom Security TLS ECC Root 2020\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw\nCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH\nbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw\nMB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx\nJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE\nAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49\nAgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O\ntdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP\nf8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f\nMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA\nMGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di\nz6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn\n27iQ7t0l\n-----END CERTIFICATE-----`))\n\n\t// Telekom Security TLS RSA Root 2023\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj\nMQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0\neSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy\nMDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC\nREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG\nA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ\nKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9\ncUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV\ncp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA\nU6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6\nY+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug\nBTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy\n8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J\nco4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg\n8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8\nrFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12\nmAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg\n+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX\ngj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2\np5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ\npGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm\n9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw\nM807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd\nGGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+\nCqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t\nxKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+\nw6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK\nL4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj\nX273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q\nntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm\ndTdmQRCsu/WU48IxK63nI1bMNSWSs1A=\n-----END CERTIFICATE-----`))\n\n\t// DigiCert Assured ID Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv\nb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG\nEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl\ncnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c\nJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP\nmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+\nwRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4\nVYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/\nAUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB\nAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\nBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun\npyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC\ndWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf\nfwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm\nNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx\nH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe\n+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==\n-----END CERTIFICATE-----`))\n\n\t// DigiCert Assured ID Root G2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv\nb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG\nEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl\ncnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA\nn61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc\nbiJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp\nEgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA\nbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu\nYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB\nAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW\nBBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI\nQW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I\n0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni\nlmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9\nB5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv\nON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo\nIhNzbM8m9Yop5w==\n-----END CERTIFICATE-----`))\n\n\t// DigiCert Assured ID Root G3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw\nCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu\nZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg\nRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV\nUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\nY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq\nhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf\nZn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q\nRSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/\nBAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD\nAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY\nJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv\n6pZjamVFkpUBtA==\n-----END CERTIFICATE-----`))\n\n\t// DigiCert Global Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----`))\n\n\t// DigiCert Global Root G2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\nMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\nq2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\ntCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\nvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\nBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\nNeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\nFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\npLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\nMrY=\n-----END CERTIFICATE-----`))\n\n\t// DigiCert Global Root G3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw\nCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu\nZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe\nFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw\nEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x\nIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF\nK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG\nfp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO\nZ9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd\nBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx\nAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/\noAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8\nsycX\n-----END CERTIFICATE-----`))\n\n\t// DigiCert High Assurance EV Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\nZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\nMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\nLmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\nRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\nPNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\nxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\nIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\nhzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\nEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\nMAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\nFLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\nnzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\neM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\nhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\nYzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\nvEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n+OkuE6N36B9K\n-----END CERTIFICATE-----`))\n\n\t// DigiCert SMIME ECC P384 Root G5\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICHDCCAaOgAwIBAgIQBT9uoAYBcn3tP8OjtqPW7zAKBggqhkjOPQQDAzBQMQsw\nCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xKDAmBgNVBAMTH0Rp\nZ2lDZXJ0IFNNSU1FIEVDQyBQMzg0IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN\nNDYwMTE0MjM1OTU5WjBQMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs\nIEluYy4xKDAmBgNVBAMTH0RpZ2lDZXJ0IFNNSU1FIEVDQyBQMzg0IFJvb3QgRzUw\ndjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQWnVXlttT7+2drGtShqtJ3lT6I5QeftnBm\nICikiOxwNa+zMv83E0qevAED3oTBuMbmZUeJ8hNVv82lHghgf61/6GGSKc8JR14L\nHMAfpL/yW7yY75lMzHBrtrrQKB2/vgSjQjBAMB0GA1UdDgQWBBRzemuW20IHi1Jm\nwmQyF/7gZ5AurTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggq\nhkjOPQQDAwNnADBkAjA3RPUygONx6/Rtz3zMkZrDbnHY0iNdkk2CQm1cYZX2kfWn\nCPZql+mclC2YcP0ztgkCMAc8L7lYgl4Po2Kok2fwIMNpvwMsO1CnO69BOMlSSJHW\nDvu8YDB8ZD8SHkV/UT70pg==\n-----END CERTIFICATE-----`))\n\n\t// DigiCert SMIME RSA4096 Root G5\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFajCCA1KgAwIBAgIQBfa6BCODRst9XOa5W7ocVTANBgkqhkiG9w0BAQwFADBP\nMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJzAlBgNVBAMT\nHkRpZ2lDZXJ0IFNNSU1FIFJTQTQwOTYgUm9vdCBHNTAeFw0yMTAxMTUwMDAwMDBa\nFw00NjAxMTQyMzU5NTlaME8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2Vy\ndCwgSW5jLjEnMCUGA1UEAxMeRGlnaUNlcnQgU01JTUUgUlNBNDA5NiBSb290IEc1\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Gpb2fj5fey1e+9f3Vw0\n2Npd0ctldashfFsA1IJvRYVBiqkSAnIy8BT1A3W7Y5dJD0CZCxoeVqfS0OGr3eUE\nG+MfFBICiPWggAn2J5pQ8LrjouCsahSRtWs4EHqiMeGRG7e58CtbyHcJdrdRxDYK\nmVNURCW3CTWGFwVWkz1BtwLXYh+KkhGH6hFt6ggR3LF4SEmS9rRRgHgj2P7hVho6\nkBNWNInV4pWLX96yzPs/OLeF9+qevy6hLi9NfWoRLjag/xEIBJVV4Bs7Z5OplFXq\nMu0GOn/Cf+OtEyfRNEGzMMO/tIj4A4Kk3z6reHegWZNx593rAAR7zEg5KOAeoxVp\nyDayoQuX31XW75GcpPYW91EK7gMjkdwE/+DdOPYiAwDCB3EaEsnXRiqUG83Wuxvu\nv75NUFiwC80wdin1z+W2ai92sLBpatBtZRg1fpO8chfBVULNL8Ilu/T9HaFkIlRd\n4p5yQYRucZbqRQe2XnpKhp1zZHc4A9IPU6VVIMRN/2hvVanq3XHkT9mFo3xOKQKe\nCwnyGlPMAKbd0TT2DcEwsZwCZKw17aWwKbHSlTMP0iAzvewjS/IZ+dqYZOQsMR8u\n4Y0cBJUoTYxYzUvlc4KGjOyo1nlc+2S73AxMKPYXr+Jo1haGmNv8AdwxuvicDvko\nRkrh/ZYGRXkRaBdlXIsmh1sCAwEAAaNCMEAwHQYDVR0OBBYEFNGj1FcdT1XbdUxc\nQp5jFs60xjsfMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG\nSIb3DQEBDAUAA4ICAQAHpwreU7ua63C/sjaQzeSnuPEM5F1aHXhl/Mm4HiMRV3xp\nNW0B/1NQvwcOuscBP1gqlHUDqxwLI9wbih43PR1Yj3PZsypv3xCgWwynyrB/uSSi\nATUy5V5GQevYf3PnQumkUSZ3gQqo6w8KUJ1+iiBn/AuOOhHTxYxgGNlLsfzU8bRJ\nTq6H4dH7dqFf8wbPl5YM6Z51gVxTDSL8NuZJbnTbAIWNfCKgjvsQTNRiE1vvS3Im\ni/xOio/+lxBTxXiLQmQbX+CJ/bsJf1DgVIUmEWodZflJKdx8Nt/7PffSrO4yjW6m\nfTmcRcTKDfU7tHlTpS9Wx1HFikxkXZBDI45rTBd4zOi/9TvkqEjPrZsM3zJK09kS\njiN4DS2vn6+ePAnClwDtOmkccT8539OPxGb17zaUD/PdkraWX5Cm3XOqpiCUlCVq\nCQxy5BMjYEyjyhcue2cA29DN6nofOSZXiTB3y07llUVPX/s2XD35ILU6ECVPkzJa\n7sGW6OlWBLBJYU3seKidGMH/2OovVu+VK3sEXmfjVUDtOQT5C3n1aoxcD4makMfN\ni97bJjWhbs2zQvKiDzsMjpP/FM/895P35EEIbhlSEQ9TGXN4DM/YhYH4rVXIsJ5G\nY6+cUu5cv/DAWzceCSDSPiPGoRVKDjZ+MMV5arwiiNkMUkAf3U4PZyYW0q0XHA==\n-----END CERTIFICATE-----`))\n\n\t// DigiCert TLS ECC P384 Root G5\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw\nCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp\nZ2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2\nMDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ\nbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG\nByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS\n7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp\n0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS\nB4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49\nBAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ\nLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4\nDXZDjC5Ty3zfDBeWUA==\n-----END CERTIFICATE-----`))\n\n\t// DigiCert TLS RSA4096 Root G5\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN\nMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT\nHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN\nNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs\nIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+\najWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0\n2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp\nwgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM\npG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD\nnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po\nsMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx\nZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd\nLvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX\nKyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe\nXoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL\ntgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv\nTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN\nAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw\nGXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H\nPNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF\nO4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ\nREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik\nAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv\n/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+\np6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw\nMUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF\nqUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK\novfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+\n-----END CERTIFICATE-----`))\n\n\t// DigiCert Trusted Root G4\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg\nRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV\nUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\nY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y\nithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If\nxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV\nySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO\nDCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ\njdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/\nCNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi\nEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM\nfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY\nuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK\nchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t\n9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\nhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD\nggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2\nSV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd\n+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc\nfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa\nsjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N\ncCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N\n0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie\n4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI\nr/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1\n/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm\ngKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+\n-----END CERTIFICATE-----`))\n\n\t// QuoVadis Root CA 1 G3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL\nBQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc\nBgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00\nMjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM\naW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV\nwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe\nrNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341\n68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh\n4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp\nUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o\nabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc\n3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G\nKubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt\nhfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO\nTk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt\nzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\nBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD\nggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC\nMTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2\ncDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN\nqXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5\nYCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv\nb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2\n8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k\nNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj\nZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp\nq1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt\nnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD\n-----END CERTIFICATE-----`))\n\n\t// QuoVadis Root CA 2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x\nGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv\nb3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV\nBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W\nYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa\nGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg\nFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J\nWpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB\nrrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp\n+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1\nksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i\nUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz\nPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og\n/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH\noycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI\nyV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud\nEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2\nA8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL\nMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT\nElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f\nBluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn\ng/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl\nfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K\nWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha\nB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc\nhLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR\nTUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD\nmbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z\nohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y\n4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza\n8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u\n-----END CERTIFICATE-----`))\n\n\t// QuoVadis Root CA 2 G3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL\nBQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc\nBgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00\nMjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM\naW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf\nqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW\nn4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym\nc5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+\nO7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1\no9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j\nIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq\nIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz\n8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh\nvNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l\n7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG\ncC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\nBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD\nggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66\nAarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC\nroijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga\nW/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n\nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE\n+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV\ncsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd\ndbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg\nKCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM\nHVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4\nWSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M\n-----END CERTIFICATE-----`))\n\n\t// QuoVadis Root CA 3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x\nGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv\nb3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV\nBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W\nYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM\nV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB\n4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr\nH556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd\n8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv\nvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT\nmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe\nbtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc\nT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt\nWAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ\nc6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A\n4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD\nVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG\nCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0\naXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0\naWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu\ndC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw\nczALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G\nA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC\nTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg\nUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0\n7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem\nd1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd\n+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B\n4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN\nt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x\nDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57\nk8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s\nzHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j\nWy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT\nmJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK\n4SVhM7JZG+Ju1zdXtg2pEto=\n-----END CERTIFICATE-----`))\n\n\t// QuoVadis Root CA 3 G3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL\nBQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc\nBgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00\nMjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM\naW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR\n/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu\nFoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR\nU7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c\nra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR\nFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k\nA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw\neyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl\nsSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp\nVzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q\nA4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+\nydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\nBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD\nggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px\nKGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI\nFUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv\noxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg\nu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP\n0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf\n3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl\n8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+\nDhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN\nPlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/\nywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0\n-----END CERTIFICATE-----`))\n\n\t// DIGITALSIGN GLOBAL ROOT ECDSA CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICajCCAfCgAwIBAgIUNi2PcoiiKCfkAP8kxi3k6/qdtuEwCgYIKoZIzj0EAwMw\nZDELMAkGA1UEBhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmljYWRv\ncmEgRGlnaXRhbDEpMCcGA1UEAwwgRElHSVRBTFNJR04gR0xPQkFMIFJPT1QgRUNE\nU0EgQ0EwHhcNMjEwMTIxMTEwNzUwWhcNNDYwMTE1MTEwNzUwWjBkMQswCQYDVQQG\nEwJQVDEqMCgGA1UECgwhRGlnaXRhbFNpZ24gQ2VydGlmaWNhZG9yYSBEaWdpdGFs\nMSkwJwYDVQQDDCBESUdJVEFMU0lHTiBHTE9CQUwgUk9PVCBFQ0RTQSBDQTB2MBAG\nByqGSM49AgEGBSuBBAAiA2IABG4Lo6szTRzqSuj8BI0UoH3wCCxfg6uT0dJ7utdJ\nfY/sElBf1LnL5fD5M2MfyVfsQNgRC5foUhbMKY70BoYeONw9V8Tuqr3IVAQmWicT\nUUc9Hx8ajqiVpDPQzEfMbbj8SKNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME\nGDAWgBTOr0qLGnXi8TjnAvAWrV7qZNV7tDAdBgNVHQ4EFgQUzq9Kixp14vE45wLw\nFq1e6mTVe7QwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMAqIxHGc\nRANNjbTHvKiu2TAnNWprFmPX/OdZ4aeJG0wxmiNVRObzQyHVRydvbVcBqgIxAPuy\n6uKXf1G1n0jrvG81iahkcKtXds3AxhRgyn/iggBz98w16o4km+UIWccEjHN4/g==\n-----END CERTIFICATE-----`))\n\n\t// DIGITALSIGN GLOBAL ROOT RSA CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFtTCCA52gAwIBAgIUXVnIyqsJV/XmtdoplARq/8XUlYcwDQYJKoZIhvcNAQEN\nBQAwYjELMAkGA1UEBhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmlj\nYWRvcmEgRGlnaXRhbDEnMCUGA1UEAwweRElHSVRBTFNJR04gR0xPQkFMIFJPT1Qg\nUlNBIENBMB4XDTIxMDEyMTEwNTAzNFoXDTQ2MDExNTEwNTAzNFowYjELMAkGA1UE\nBhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmljYWRvcmEgRGlnaXRh\nbDEnMCUGA1UEAwweRElHSVRBTFNJR04gR0xPQkFMIFJPT1QgUlNBIENBMIICIjAN\nBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyIe2ONMc8N4S+IPHxIriibi0Inp4\n+AxmUWh2NwrVT8JaCLgWXPdyAQk3hIEqVGvXktBs+qinQxI06w7bNw8p/ooxUULo\nS5yQqMgsEdP9oCl+zt6U9oLgWLRORSXxIvI90w97VBrcMrbWUU5+QbRXuCzGuQ4u\nylfx1cjTWOel6UIRrtMgJZRp14/Kog3D058HaD8V0mcuU/12gpsLc6kpDZ4RkxQI\nmOyeVBJKVqIGFexrbC6SYC6GDa6CH1FN47IH1xAZVyL2qWlEhPPZPaAGv8yIfn/1\nzlulwipqdELqb6b/+Wix0F+9kdJVbzNXTB6d5OKLwYVloOBqnAAAiJLdWAgW8nAx\nqBzh3r1OcenWvn61oVrDTfe/m72UpP31qlOTRskmAQRwxKBxus4lZvuRflVw7kkK\nTWJ/wlCacvIYZ53pRag0hOj4gfbRWiIeB087s3/dEaVz3L6pGTppqW0bMuKJqqUn\nC1p+dOIPZDldfly5wRf8x41eyewk7dLyP3qERTcCvj5rWcTmWxZtwKqeqrVZLixw\nVZzMmZaYJFTRjtrKtBG0t3BDH2+QCyCgqHYTZdvbI1p1S6ELMXcK7n1oYRoTjOpR\nflxWo1dMXaHrE2W/VBTM8+7c1+w8l/J4Vrjfclxw/M4G3Z/SBzHv51KRns2618AY\nRAcxZUkyaRNK648CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAW\ngBS1Nrw8jBqrLPZZGS2DFNqTJRXWhjAdBgNVHQ4EFgQUtTa8PIwaqyz2WRktgxTa\nkyUV1oYwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDQUAA4ICAQAU+zElODH4\nygiyI3Y4rfjTWfXMtFcl4US+fvwW7K76Jp9PZxZKVvD97ccZATSOkFot1oBc7HHS\ngSWCHgBx35rR1R0iu9Gl82IPtOvcJHP+plbNmhTFBDUWMaIH66UA4rb4X3L9P2FJ\njt5+TTjXeh50N2xR3L4ABLg4FPMgwe2bpyP9DUKEHX/yc8PQeGPxn+zXW+nxvmyg\nSwOejWnhFNqIEIEjU//aVCsLxrmWlQQYRvN7qJfYW2ik5DgcDkXlmNMJrppe7LN5\nDTly8vSUnQ6eYCLmqPZMhc0HgjpoOc09X+M49LavO2tKn2BRRaJAAuWqDOM+0XjU\nonScJroFmihwSj6mC9AdSfC6+K5BEH6kBxK9qM8pPVe7x/FDRwA+rnAYWiB7Ccs6\nOnCA5UxgmMEVwR1K98jwm+FyreddaFgLBLGMvJ+3+26LWwRV++sjVdd4UNoly74n\nNrskGnkcUdH+E7v/eCzcpL4v9sVLU8+nTJlecKxZiASuZAS/e6Z6TdPod72hflAV\n8+9JMIVNIVeq2yx1l62BAYeisXCdHgZaA2CxP6ZtgizUFLGBpeg9iB20cixYN4qO\nOJS4c92p4Lj2d6KzfFjermk6tYulGrvy2HQGnP1icyAhdrF+cJ4Z1OsXYhk4mc02\nK0f+McvfueSsCNPYpuvUnn5LZKRVXSsXyQ==\n-----END CERTIFICATE-----`))\n\n\t// CA Disig Root R2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV\nBAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu\nMRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy\nMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx\nEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw\nggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe\nNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH\nPWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I\nx2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe\nQTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR\nyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO\nQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912\nH9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ\nQfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD\ni/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs\nnLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1\nrqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud\nDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI\nhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM\ntCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf\nGopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb\nlvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka\n+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal\nTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i\nnSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3\ngzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr\nG5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os\nzMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x\nL4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL\n-----END CERTIFICATE-----`))\n\n\t// GLOBALTRUST 2020\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG\nA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw\nFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx\nMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u\naXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq\nhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b\nRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z\nYybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3\nQWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw\nyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+\nBlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ\nSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH\nr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0\n4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me\ndKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw\nq7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2\nnKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\nAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu\nH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA\nVC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC\nXtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd\n6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf\n+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi\nkvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7\nwry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB\nTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C\nMUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn\n4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I\naFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy\nqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==\n-----END CERTIFICATE-----`))\n\n\t// emSign ECC Root CA - C3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG\nEwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx\nIDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw\nMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln\nbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND\nIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci\nMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti\nsIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O\nBBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\nAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c\n3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J\n0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==\n-----END CERTIFICATE-----`))\n\n\t// emSign ECC Root CA - G3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG\nEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo\nbm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g\nRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ\nTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s\nb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw\ndjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0\nWXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS\nfvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB\nzhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq\nhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB\nCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD\n+JbNR6iC8hZVdyR+EhCVBCyj\n-----END CERTIFICATE-----`))\n\n\t// emSign Root CA - C1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG\nA1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg\nSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw\nMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln\nbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v\ndCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ\nBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ\nHdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH\n3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH\nGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c\nxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1\naylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq\nTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL\nBQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87\n/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4\nkqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG\nYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT\n+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo\nWXzhriKi4gp6D/piq1JM4fHfyr6DDUI=\n-----END CERTIFICATE-----`))\n\n\t// emSign Root CA - G1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD\nVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU\nZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH\nMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO\nMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv\nZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz\nf2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO\n8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq\nd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM\ntTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt\nOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB\no0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD\nAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x\nPaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM\nwiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d\nGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH\n6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby\nRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx\niN66zB+Afko=\n-----END CERTIFICATE-----`))\n\n\t// AffirmTrust Commercial\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE\nBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz\ndCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL\nMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp\ncm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP\nHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr\nba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL\nMeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1\nyHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr\nVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/\nnx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ\nKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG\nXUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj\nvbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt\nZ8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g\nN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC\nnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=\n-----END CERTIFICATE-----`))\n\n\t// AffirmTrust Networking\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE\nBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz\ndCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL\nMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp\ncm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y\nYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua\nkCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL\nQESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp\n6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG\nyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i\nQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ\nKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO\ntDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu\nQY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ\nLgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u\nolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48\nx3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=\n-----END CERTIFICATE-----`))\n\n\t// AffirmTrust Premium\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE\nBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz\ndCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG\nA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U\ncnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf\nqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ\nJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ\n+jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS\ns8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5\nHMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7\n70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG\nV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S\nqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S\n5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia\nC1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX\nOwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE\nFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/\nBAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2\nKI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg\nNt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B\n8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ\nMKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc\n0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ\nu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF\nu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH\nYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8\nGKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO\nRtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e\nKeC2uAloGRwYQw==\n-----END CERTIFICATE-----`))\n\n\t// AffirmTrust Premium ECC\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC\nVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ\ncmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ\nBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt\nVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D\n0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9\nss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G\nA1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G\nA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs\naobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I\nflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==\n-----END CERTIFICATE-----`))\n\n\t// Atos TrustedRoot 2011\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE\nAwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG\nEwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM\nFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC\nREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp\nNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM\nVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+\nSZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ\n4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L\ncp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi\neowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV\nHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG\nA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3\nDQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j\nvZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP\nDpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc\nmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D\nlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv\nKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed\n-----END CERTIFICATE-----`))\n\n\t// Atos TrustedRoot Root CA ECC G2 2020\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICMTCCAbagAwIBAgIMC3MoERh0MBzvbwiEMAoGCCqGSM49BAMDMEsxCzAJBgNV\nBAYTAkRFMQ0wCwYDVQQKDARBdG9zMS0wKwYDVQQDDCRBdG9zIFRydXN0ZWRSb290\nIFJvb3QgQ0EgRUNDIEcyIDIwMjAwHhcNMjAxMjE1MDgzOTEwWhcNNDAxMjEwMDgz\nOTA5WjBLMQswCQYDVQQGEwJERTENMAsGA1UECgwEQXRvczEtMCsGA1UEAwwkQXRv\ncyBUcnVzdGVkUm9vdCBSb290IENBIEVDQyBHMiAyMDIwMHYwEAYHKoZIzj0CAQYF\nK4EEACIDYgAEyFyAyk7CKB9XvzjmYSP80KlblhYWwwxeFaWQCf84KLR6HgrWUyrB\nu5BAdDfpgeiNL2gBNXxSLtj0WLMRHFvZhxiTkS3sndpsnm2ESPzCiQXrmBMCAWxT\nHg5JY1hHsa/Co2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFFsfxHFs\nshufvlwfjP2ztvuzDgmHMB0GA1UdDgQWBBRbH8RxbLIbn75cH4z9s7b7sw4JhzAO\nBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAOzgmf3d5FTByx/oPijX\nFVlKgspTMOzrNqW5yM6TR1bIYabhbZJTlY/241VT8N165wIxALCH1RuzYPyRjYDK\nohtRSzhUy6oee9flRJUWLzxEeC4luuqQ5OxS7lfsA4TzXtsWDQ==\n-----END CERTIFICATE-----`))\n\n\t// Atos TrustedRoot Root CA ECC TLS 2021\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w\nLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w\nCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0\nMTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF\nQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI\nzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X\ntXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4\nAjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2\nKCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD\naAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu\nCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo\n9H1/IISpQuQo\n-----END CERTIFICATE-----`))\n\n\t// Atos TrustedRoot Root CA RSA G2 2020\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFfzCCA2egAwIBAgIMR7opRlU+FpKXsKtAMA0GCSqGSIb3DQEBDAUAMEsxCzAJ\nBgNVBAYTAkRFMQ0wCwYDVQQKDARBdG9zMS0wKwYDVQQDDCRBdG9zIFRydXN0ZWRS\nb290IFJvb3QgQ0EgUlNBIEcyIDIwMjAwHhcNMjAxMjE1MDg0MTIzWhcNNDAxMjEw\nMDg0MTIyWjBLMQswCQYDVQQGEwJERTENMAsGA1UECgwEQXRvczEtMCsGA1UEAwwk\nQXRvcyBUcnVzdGVkUm9vdCBSb290IENBIFJTQSBHMiAyMDIwMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEAljGFSqoPMv554UOHnPsjt45/DVS9x2KTd+Qc\nNQR2owOLIu7EhN2lk25uso4JA+tRFjEXqmkVGA5ndCNe6pp9tTk+PYKpa+H+qRyw\nrVpNTHiDQYvP8h1impgEnGPpq2X+SB0kZQdHPrmRLumdm38aNak0sLflcDPvSnJR\ntge/YD8qn51U3/PXlElRA1pAqWjdEVlc+HamvFBSEO2s7JXg1INrSdoKT5mD3jKD\nSINnlbJ+54GFPc2C98oC7W2IXQiNuDW/KmkwmbtL0UHbRaCTmVGBkDYIqoq26I+z\ny+7lRg1ydfVJbOGify+87YSmN+7ewk85Tvae8MnRmzCdSW3h2v8SEIzW5Zl7BbZ9\nsAnHpPiyHDmVOTP0Nc4lYnuwXyDzy234bFIUZESP08ipdgflr3GZLS0EJUh2r8Pn\nzEPyB7xKJCQ33fpulAlvTF4BtP5U7COWpV7dhv/pRirx6NzspT2vb6oOD7R1+j4I\nuSZFT2aGTLwZuOHVNe6ChMjTqxLnzXMzYnf0F8u9NHYqBc6V5Xh5S56wjfk8WDiR\n6l6HOMC3Qv2qTIcjrQQgsX52Qtq7tha6V8iOE/p11QhMrziRqu+P+p9JLlR8Clax\nevrETi/Uo/oWitCV5Zem/8P8fA5HWPN/B3sS3Fc/LeOhTVtSTDOHmagJe2x+DvLP\nVkKe6wUCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQgJfMH\n/adv8ZbukRBpzJrvfchoeDAdBgNVHQ4EFgQUICXzB/2nb/GW7pEQacya733IaHgw\nDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQAkK06Y8h0X7dl2JrYw\nM+hpRaFRS1LYejowtuQS6r+fTOAEpPY1xv6hMPdThZKtVAVXX5LlKt42J557E0fJ\nanWv/PM35wz1PQFztWlR+L1Z0boL+Lq6ZCdDs3yDlYrnnhOW129KlkFJiw4grRbG\n96aHW4gSiYuJyhLSVq8iASFG6auYP6eI3uTLKpp1Gfo5XgkF1wMyGrgXUQjHAEB9\n9L74DFn0aXZu06RYW14mc+RCVQZeeEAP0zif7yZRcHSR8XdiAejZy+uh3zkyHbtr\n/XH+68+l5hT9AIATxpoASLCZBemugEj7CT9RFLW552BNTcovgSHuUgxletz1iUlM\nMJI0WIAyWbEN/yRhD+cKQtB7vPiOJ0c/cJ0n2bYGPaW7y16Prg5Tx5xqbztMD6NA\ncKiaB87UblsHotLiVLa9bzNyY61RmOGPdvFqBzgl/vZizl/bY8Jume8G3LneGRro\nVD190nZ12V4+MkinjPKecgz4uFi4FyOlFId1WHoAgQciOWpMlKC1otunLMGw8aOb\nwEz3bXDqMZ/xrn0+cyjZod/6k/CbsPDizSUgde/ifTIFyZt27su9MR75lJhLJFhW\nSMDeBky9pjRd7RZhY3P7GeL6W9iXddRtnmA5XpSLAizrmc5gKm4bjKdLvP025pgf\nZfJ/8eOPTIBGNli2oWXLzhxEdQ==\n-----END CERTIFICATE-----`))\n\n\t// Atos TrustedRoot Root CA RSA TLS 2021\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM\nMS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx\nMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00\nMTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD\nQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN\nBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z\n4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv\nYe+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ\nkmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs\nGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln\nnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh\n3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD\n0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy\ngeBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8\nANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB\nc6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI\npw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU\ndEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB\nDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS\n4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs\no0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ\nqM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw\nxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM\nrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4\nAXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR\n0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY\no7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5\ndDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE\noji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ==\n-----END CERTIFICATE-----`))\n\n\t// GlobalSign\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg\nMB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh\nbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx\nMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET\nMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ\nKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI\nxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k\nZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD\naNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw\nLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw\n1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX\nk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2\nSXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h\nbguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n\nWUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY\nrZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce\nMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD\nAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu\nbAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN\nnsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt\nIxg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61\n55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj\nvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf\ncDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz\noHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp\nnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs\npA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v\nJJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R\n8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4\n5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=\n-----END CERTIFICATE-----`))\n\n\t// GlobalSign\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G\nA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp\nZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4\nMTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG\nA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8\nRgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT\ngHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm\nKPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd\nQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ\nXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw\nDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o\nLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU\nRUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp\njjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK\n6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX\nmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs\nMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH\nWD9f\n-----END CERTIFICATE-----`))\n\n\t// GlobalSign\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk\nMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH\nbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX\nDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD\nQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc\n8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke\nhOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD\nVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI\nKoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg\n515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO\nxwy8p2Fp8fc74SrL+SvzZpA3\n-----END CERTIFICATE-----`))\n\n\t// GlobalSign Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\nA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\nb3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\nMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\nYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\naWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\njc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\nxy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\n1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\nsnUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\nU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\n9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\nAQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\nyj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\n38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\nAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\nDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\nHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n-----END CERTIFICATE-----`))\n\n\t// GlobalSign Root E46\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx\nCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD\nExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw\nMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex\nHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA\nIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq\nR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd\nyXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ\n7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8\n+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A=\n-----END CERTIFICATE-----`))\n\n\t// GlobalSign Root R46\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA\nMEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD\nVQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy\nMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt\nc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ\nOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG\nvGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud\n316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo\n0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE\ny132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF\nzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE\n+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN\nI/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs\nx2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa\nByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC\n4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\nHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4\n7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg\nJuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti\n2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk\npnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF\nFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt\nrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk\nZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5\nu+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP\n4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6\nN3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3\nvouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6\n-----END CERTIFICATE-----`))\n\n\t// GlobalSign Secure Mail Root E45\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICITCCAaegAwIBAgIQdlP+qicdlUZd1vGe5biQCjAKBggqhkjOPQQDAzBSMQsw\nCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UEAxMf\nR2xvYmFsU2lnbiBTZWN1cmUgTWFpbCBSb290IEU0NTAeFw0yMDAzMTgwMDAwMDBa\nFw00NTAzMTgwMDAwMDBaMFIxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxT\naWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNlY3VyZSBNYWlsIFJvb3Qg\nRTQ1MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+XmLgUc3iZY/RUlQfxomC5Myfi7A\nwKcImsNuj5s+CyLsN1O3b4qwvCc3S22pRjvZH/+loUS7LXO/nkEHXFObUQg6Wrtv\nOMcWkXjCShNpHYLfWi8AiJaiLhx0+Z1+ZjeKo0IwQDAOBgNVHQ8BAf8EBAMCAYYw\nDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU3xNei1/CQAL9VreUTLYe1aaxFJYw\nCgYIKoZIzj0EAwMDaAAwZQIwE7C+13EgPuSrnM42En1fTB8qtWlFM1/TLVqy5IjH\n3go2QjJ5naZruuH5RCp7isMSAjEAoGYcToedh8ntmUwbCu4tYMM3xx3NtXKw2cbv\nvPL/P/BS3QjnqmR5w+RpV5EvpMt8\n-----END CERTIFICATE-----`))\n\n\t// GlobalSign Secure Mail Root R45\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFcDCCA1igAwIBAgIQdlP+qExQq5+NMrUdA49X3DANBgkqhkiG9w0BAQwFADBS\nMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UE\nAxMfR2xvYmFsU2lnbiBTZWN1cmUgTWFpbCBSb290IFI0NTAeFw0yMDAzMTgwMDAw\nMDBaFw00NTAzMTgwMDAwMDBaMFIxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\nYWxTaWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNlY3VyZSBNYWlsIFJv\nb3QgUjQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3HnMbQb5bbvg\nVgRsf+B1zC0FSehL3FTsW3eVcr9/Yp2FqYokUF9T5dt0b6QpWxMqCa2axS/C93Y7\noUVGqkPmJP4rsG8ycBlGWnkmL/w9fV9ky1fMYWGo2ZVu45Wgbn9HEhjW7wPJ+4r6\nmr2CFalVd0sRT1nga8Nx8wzYVNWBaD4TuRUuh4o8RCc2YiRu+CwFcjBhvUKRI8Sd\nJafZVJoUozGtgHkMp2NsmKOsV0czH2WW4dDSNdr5cfehpiW1QV3fPmDY0fafpfK4\nzBOqj/mybuGDLZPdPoUa3eixXCYBy0mF/PzS1H+FYoZ0+cvsNSKiDDCPO6t561by\n+kLz7fkfRYlAKa3qknTqUv1WtCvaou11wm6rzlKQS/be8EmPmkjUiBltRebMjLnd\nZGBgAkD4uc+8WOs9hbnGCtOcB2aPxxg5I0bhPB6jL1Bhkgs9K2zxo0c4V5GrDY/G\nnU0E0iZSXOWl/SotFioBaeepfeE2t7Eqxdmxjb25i87Mi6E+C0jNUJU0xNgIWdhr\nJvS+9dQiFwBXya6bBDAznwv731aiyW5Udtqxl2InWQ8RiiIbZJY/qPG3JEqNPFN8\nbYN2PbImSHP1RBYBLQkqjhaWUNBzBl27IkiCTApGWj+A/1zy8pqsLAjg1urwEjiB\nT6YQ7UarzBacC89kppkChURnRq39TecCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgGG\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKCTFShu7o8IsjXGnmJ5dKexDit7\nMA0GCSqGSIb3DQEBDAUAA4ICAQBFCvjRXKxigdAE17b/V1GJCwzL3iRlN/urnu1m\n9OoMGWmJuBmxMFa02fb3vsaul8tF9hGMOjBkTMGfWcBGQggGR2QXeOCVBwbWjKKs\nqdk/03tWT/zEhyjftisWI8CfH1vj1kReIk8jBIw1FrV5B4ZcL5fi9ghkptzbqIrj\npHt3DdEpkyggtFOjS05f3sH2dSP8Hzx4T3AxeC+iNVRxBKzIxG3D9pGx/s3uRG6B\n9kDFPioBv6tMsQM/DRHkD9Ik4yKIm59fRz1RSeAJN34XITF2t2dxSChLJdcQ6J9h\nWRbFPjJOHwzOo8wP5McRByIvOAjdW5frQmxZmpruetCd38XbCUMuCqoZPWvoajB6\nV+a/s2o5qY/j8U9laLa9nyiPoRZaCVA6Mi4dL0QRQqYA5jGY/y2hD+akYFbPedey\nTtew+m4MVyPHzh+lsUxtGUmeDn9wj3E/WCifdd1h4Dq3Obbul9Q1UfuLSWDIPGau\nl+6NJllXu3jwelAwCbBgqp9O3Mk+HjrcYpMzsDpUdG8sMUXRaxEyamh29j32ahNe\nJJjn6h2az3iCB2D3TRDTgZpFjZ6vm9yAx0OylWikww7oCkcVv1Qz3AHn1aYec9h6\nsr8vreNVMJ7fDkG84BH1oQyoIuHjAKNOcHyS4wTRekKKdZBZ45vRTKJkvXN5m2/y\ns8H2PA==\n-----END CERTIFICATE-----`))\n\n\t// Go Daddy Class 2 Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh\nMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE\nYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3\nMDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo\nZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg\nMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN\nADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA\nPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w\nwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi\nEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY\navx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+\nYihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE\nsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h\n/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5\nIEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj\nYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD\nggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy\nOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P\nTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ\nHmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER\ndEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf\nReYNnyicsbkqWletNw+vHX/bvZ8=\n-----END CERTIFICATE-----`))\n\n\t// Go Daddy Root Certificate Authority - G2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx\nEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT\nEUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp\nZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz\nNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH\nEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE\nAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD\nE6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH\n/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy\nDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh\nGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR\ntDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA\nAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE\nFDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX\nWWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu\n9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr\ngIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo\n2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO\nLPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI\n4uJEvlz36hz1\n-----END CERTIFICATE-----`))\n\n\t// Starfield Class 2 Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl\nMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp\nU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw\nNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE\nChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp\nZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3\nDQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf\n8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN\n+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0\nX9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa\nK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA\n1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G\nA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR\nzt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0\nYXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD\nbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w\nDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3\nL7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D\neruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl\nxy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp\nVSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY\nWQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=\n-----END CERTIFICATE-----`))\n\n\t// Starfield Root Certificate Authority - G2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx\nEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT\nHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs\nZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw\nMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6\nb25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj\naG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp\nY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg\nnLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1\nHOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N\nHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN\ndloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0\nHZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO\nBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G\nCSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU\nsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3\n4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg\n8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K\npL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1\nmMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0\n-----END CERTIFICATE-----`))\n\n\t// GlobalSign\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD\nVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh\nbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw\nMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g\nUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx\nuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV\nHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/\n+wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147\nbmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm\n-----END CERTIFICATE-----`))\n\n\t// GTS Root R1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw\nCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\nMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\nMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\nY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA\nA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo\n27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w\nCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw\nTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl\nqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH\nszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8\nY/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk\nMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92\nwO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p\naDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN\nVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID\nAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\nFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb\nC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe\nQkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy\nh6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4\n7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J\nZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef\nMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/\nZ6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT\n6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ\n0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm\n2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb\nbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c\n-----END CERTIFICATE-----`))\n\n\t// GTS Root R2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw\nCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\nMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\nMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\nY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA\nA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt\nnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY\n6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu\nMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k\nRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg\nf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV\n+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo\ndDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW\nIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa\nG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq\ngc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID\nAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\nFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H\nvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8\n0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC\nB19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u\nNmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg\nyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev\nHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6\nxLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR\nTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg\nJPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV\n7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl\n6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL\n-----END CERTIFICATE-----`))\n\n\t// GTS Root R3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD\nVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG\nA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw\nWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz\nIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\nAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G\njOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2\n4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\nBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7\nVKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm\nZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X\n-----END CERTIFICATE-----`))\n\n\t// GTS Root R4\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD\nVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG\nA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw\nWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz\nIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\nAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi\nQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR\nHYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\nBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D\n9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8\np/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD\n-----END CERTIFICATE-----`))\n\n\t// Hongkong Post Root CA 3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL\nBQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ\nSG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n\na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5\nNDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT\nCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u\nZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\nAoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO\ndem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI\nVoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV\n9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY\n2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY\nvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt\nbNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb\nx39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+\nl2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK\nTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj\nHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP\nBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e\ni9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw\nDQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG\n7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk\nMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr\ngZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk\nGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS\n3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm\nOzj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+\nl6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c\nJfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP\nL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa\nLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG\nmpv0\n-----END CERTIFICATE-----`))\n\n\t// ACCVRAIZ1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE\nAwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw\nCQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ\nBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND\nVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb\nqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY\nHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo\nG2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA\nlHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr\nIA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/\n0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH\nk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47\n4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO\nm3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa\ncXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl\nuUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI\nKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls\nZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG\nAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2\nVuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT\nVfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG\nCCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA\ncgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA\nQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA\n7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA\ncgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA\nQwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA\nczAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu\naHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt\naW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud\nDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF\nBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp\nD70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU\nJyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m\nAM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD\nvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms\ntn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH\n7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h\nI6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA\nh1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF\nd3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H\npPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7\n-----END CERTIFICATE-----`))\n\n\t// AC RAIZ FNMT-RCM\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx\nCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ\nWiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ\nBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG\nTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/\nyBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf\nBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz\nWHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF\ntBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z\n374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC\nIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL\nmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7\nwk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS\nMKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2\nZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet\nUqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw\nAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H\nYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3\nLmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD\nnFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1\nRXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM\nLVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf\n77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N\nJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm\nfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp\n6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp\n1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B\n9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok\nRqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv\nuu8wd+RU4riEmViAqhOLUTpPSPaLtrM=\n-----END CERTIFICATE-----`))\n\n\t// AC RAIZ FNMT-RCM SERVIDORES SEGUROS\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw\nCQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw\nFgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S\nQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5\nMzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL\nDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS\nQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB\nBAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH\nsbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK\nUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD\nVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu\nSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC\nMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy\nv+c=\n-----END CERTIFICATE-----`))\n\n\t// Staat der Nederlanden Root CA - G3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO\nTDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh\ndCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX\nDTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl\nciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv\nb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP\ncPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW\nIkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX\nxz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy\nKJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR\n9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az\n5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8\n6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7\nNgzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP\nbMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt\nBznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt\nXUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF\nMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd\nINyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD\nU5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp\nLiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8\nIpf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp\ngZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh\n/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw\n0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A\nfsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq\n4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR\n1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/\nQFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM\n94B7IWcnMFk=\n-----END CERTIFICATE-----`))\n\n\t// TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx\nGDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp\nbXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w\nKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0\nBgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy\ndW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG\nEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll\nIEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU\nQUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT\nTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg\nLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7\na9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr\nLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr\nN3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X\nYacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/\niSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f\nAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH\nV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL\nBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh\nAHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf\nIPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4\nlzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c\n8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf\nlo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=\n-----END CERTIFICATE-----`))\n\n\t// HARICA Client ECC Root CA 2021\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICWjCCAeGgAwIBAgIQMWjZ2OFiVx7SGUSI5hB98DAKBggqhkjOPQQDAzBvMQsw\nCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh\ncmNoIEluc3RpdHV0aW9ucyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBFQ0Mg\nUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDMzNFoXDTQ1MDIxMzExMDMzM1owbzEL\nMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl\nYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGllbnQgRUND\nIFJvb3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABAcYrZWWlNBcD4L3\nKkD6AsnJPTamowRqwW2VAYhgElRsXKIrbhM6iJUMHCaGNkqJGbcY3jvoqFAfyt9b\nv0mAFdvjMOEdWscqigEH/m0sNO8oKJe8wflXhpWLNc+eWtFolaNCMEAwDwYDVR0T\nAQH/BAUwAwEB/zAdBgNVHQ4EFgQUUgjSvjKBJf31GpfsTl8au1PNkK0wDgYDVR0P\nAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMEwxRUZPqOa+w3eyGhhLLYh7WOar\nlGtEA7AX/9+Cc0RRLP2THQZ7FNKJ7EAM7yEBLgIwL8kuWmwsHdmV4J6wuVxSfPb4\nOMou8dQd8qJJopX4wVheT/5zCu8xsKsjWBOMi947\n-----END CERTIFICATE-----`))\n\n\t// HARICA Client RSA Root CA 2021\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFqjCCA5KgAwIBAgIQVVL4HtsbJCyeu5YYzQIoPjANBgkqhkiG9w0BAQsFADBv\nMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl\nc2VhcmNoIEluc3RpdHV0aW9ucyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBS\nU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTg0NloXDTQ1MDIxMzEwNTg0NVow\nbzELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBS\nZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGllbnQg\nUlNBIFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\nAIHbV0KQLHQ19Pi4dBlNqwlad0WBc2KwNZ/40LczAIcTtparDlQSMAe8m7dI19EZ\ng66O2KnxqQCEsIxenugMj1Rpv/bUCE8mcP4YQWMaszKLQPgHq1cx8MYWdmeatN0v\n8tFrxdCShJFxbg8uY+kfU6TdUhPMCYMpgQzFU3VEsQ5nUxjQwx+IS5+UJLQpvLvo\nTv1v0hUdSdyNcPIRGiBRVRG6iG/E91B51qox4oQ9XjLIdypQceULL+m26u+rCjM5\nDv2PpWdDgo6YaQkJG0DNOGdH6snsl3ES3iT1cjzR90NMJveQsonpRUtVPTEFekHi\nlbpDwBfFtoU9GY1kcPNbrM2f0yl1h0uVZ2qm+NHdvJCGiUMpqTdb9V2wJlpTQnaQ\nK8+eVmwrVM9cmmXfW4tIYDh8+8ULz3YEYwIzKn31g2fn+sZD/SsP1CYvd6QywSTq\nZJ2/szhxMUTyR7iiZkGh+5t7vMdGanW/WqKM6GpEwbiWtcAyCC17dDVzssrG/q8R\nchj258jCz6Uq6nvWWeh8oLJqQAlpDqWW29EAufGIbjbwiLKd8VLyw3y/MIk8Cmn5\nIqRl4ZvgdMaxhZeWLK6Uj1CmORIfvkfygXjTdTaefVogl+JSrpmfxnybZvP+2M/u\nvZcGHS2F3D42U5Z7ILroyOGtlmI+EXyzAISep0xxq0o3AgMBAAGjQjBAMA8GA1Ud\nEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKDWBz1eJPd7oEQuJFINGaorBJGnMA4GA1Ud\nDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEADUf5CWYxUux57sKo8mg+7ZZF\nyzqmmGM/6itNTgPQHILhy9Pl1qtbZyi8nf4MmQqAVafOGyNhDbBX8P7gyr7mkNuD\nLL6DjvR5tv7QDUKnWB9p6oH1BaX+RmjrbHjJ4Orn5t4xxdLVLIJjKJ1dqBp+iObn\nK/Es1dAFntwtvTdm1ASip62/OsKoO63/jZ0z4LmahKGHH3b0gnTXDvkwSD5biD6q\nXGvWLwzojnPCGJGDObZmWtAfYCddTeP2Og1mUJx4e6vzExCuDy+r6GSzGCCdRjVk\nJXPqmxBcWDWJsUZIp/Ss1B2eW8yppRoTTyRQqtkbbbFA+53dWHTEwm8UcuzbNZ+4\nVHVFw6bIGig1Oq5l8qmYzq9byTiMMTt/zNyW/eJb1tBZ9Ha6C8tPgxDHQNAdYOkq\n5UhYdwxFab4ZcQQk4uMkH0rIwT6Z9ZaYOEgloRWwG9fihBhb9nE1mmh7QMwYXAwk\nndSV9ZmqRuqurL/0FBkk6Izs4/W8BmiKKgwFXwqXdafcfsD913oY3zDROEsfsJhw\nv8x8c/BuxDGlpJcdrL/ObCFKvicjZ/MGVoEKkY624QMFMyzaNAhNTlAjrR+lxdR6\n/uoJ7KcoYItGfLXqm91P+edrFcaIz0Pb5SfcBFZub0YV8VYt6FwMc8MjgTggy8kM\nac8sqzuEYDMZUv1pFDM=\n-----END CERTIFICATE-----`))\n\n\t// HARICA TLS ECC Root CA 2021\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw\nCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh\ncmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v\ndCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG\nA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj\naCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg\nQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7\nKKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y\nSTHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw\nAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD\nAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw\nSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN\nnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps\n-----END CERTIFICATE-----`))\n\n\t// HARICA TLS RSA Root CA 2021\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs\nMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl\nc2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg\nUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL\nMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl\nYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv\nb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l\nmwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE\n4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv\na9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M\npbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw\nKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b\nLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY\nAuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB\nAGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq\nE613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr\nW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ\nCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF\nMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE\nAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU\nX15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3\nf5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja\nH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP\nJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P\nzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt\njBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0\n/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT\nBGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79\naPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW\nxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU\n63ZTGI0RmLo=\n-----END CERTIFICATE-----`))\n\n\t// Hellenic Academic and Research Institutions ECC RootCA 2015\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN\nBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl\nc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl\nbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv\nb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ\nBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj\nYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5\nMUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0\ndXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg\nQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa\njq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC\nMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi\nC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep\nlSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof\nTUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR\n-----END CERTIFICATE-----`))\n\n\t// Hellenic Academic and Research Institutions RootCA 2015\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix\nDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k\nIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT\nN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v\ndENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG\nA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh\nZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx\nQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1\ndGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC\nAQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA\n4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0\nAoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10\n4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C\nojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV\n9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD\ngfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6\nY5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq\nNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko\nLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc\nBw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV\nHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd\nctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I\nXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI\nM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot\n9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V\nZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea\nj8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh\nX9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ\nl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf\nbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4\npcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK\ne7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0\nvm9qp/UsQu0yrbYhnr68\n-----END CERTIFICATE-----`))\n\n\t// IdenTrust Commercial Root CA 1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK\nMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu\nVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw\nMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw\nJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT\n3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU\n+ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp\nS0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1\nbVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi\nT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL\nvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK\nVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK\ndHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT\nc+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv\nl7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N\niGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB\n/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD\nggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH\n6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt\nLRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93\nnAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3\n+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK\nW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT\nAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq\nl1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG\n4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ\nmUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A\n7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H\n-----END CERTIFICATE-----`))\n\n\t// IdenTrust Public Sector Root CA 1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu\nVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN\nMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0\nMSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7\nekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy\nRBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS\nbdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF\n/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R\n3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw\nEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy\n9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V\nGxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ\n2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV\nWaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD\nW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/\nBAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN\nAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj\nt2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV\nDRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9\nTaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G\nlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW\nmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df\nWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5\n+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ\ntshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA\nGaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv\n8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c\n-----END CERTIFICATE-----`))\n\n\t// ISRG Root X1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\nWhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\nZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\nMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\nh77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\nA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\nT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\nB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\nB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\nKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\nOlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\njh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\nqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\nrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\nhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\nubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\nNFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\nORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\nTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\njNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\noyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\nmRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\nemyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n-----END CERTIFICATE-----`))\n\n\t// ISRG Root X2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw\nCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg\nR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00\nMDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT\nZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw\nEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW\n+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9\nItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T\nAQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI\nzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW\ntL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1\n/q4AaOeMSQ+2b1tbFfLn\n-----END CERTIFICATE-----`))\n\n\t// Izenpe.com\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4\nMQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6\nZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD\nVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j\nb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq\nscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO\nxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H\nLmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX\nuaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD\nyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+\nJrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q\nrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN\nBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L\nhij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB\nQFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+\nHMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu\nZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg\nQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB\nBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx\nMCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\nAQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA\nA4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb\nlaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56\nawmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo\nJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw\nLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT\nVyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk\nLhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb\nUjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/\nQnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+\nnaM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls\nQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==\n-----END CERTIFICATE-----`))\n\n\t// SZAFIR ROOT CA2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL\nBQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6\nZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw\nNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L\ncmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg\nUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN\nQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT\n3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw\n3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6\n3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5\nBSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN\nXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD\nAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF\nAAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw\n8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG\nnXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP\noky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy\nd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg\nLvWpCz/UXeHPhJ/iGcJfitYgHuNztw==\n-----END CERTIFICATE-----`))\n\n\t// LAWtrust Root CA2 (4096)\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFmDCCA4CgAwIBAgIEVRpusTANBgkqhkiG9w0BAQsFADBDMQswCQYDVQQGEwJa\nQTERMA8GA1UEChMITEFXdHJ1c3QxITAfBgNVBAMTGExBV3RydXN0IFJvb3QgQ0Ey\nICg0MDk2KTAgFw0yMzAyMTQwOTE5MzhaGA8yMDUzMDIxNDA5NDkzOFowQzELMAkG\nA1UEBhMCWkExETAPBgNVBAoTCExBV3RydXN0MSEwHwYDVQQDExhMQVd0cnVzdCBS\nb290IENBMiAoNDA5NikwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM\nF8srQ7ps+cmTimUNEkzsJxS3E3ng1NUtGFbx+eoqEBZObETHamVG85qJNdGH+DOJ\nL4gJGpIQkZDBa58Obn8mihNdGKxoAQ0QeGVw2I6PhFqXMBjQEQ5KjVIQpYErUSj1\nY8S27ECzAeWtd73lOO+8jbPdGaB7DY2022r7JTNa+pGvxHFFMPiIKXvLv9W6JwSO\n3bIA98pcmTUU6v11BhUIu8pXaPs/+7Q0c2PR1ePIOFppfWp6RAwNik7tkh0Qjzsi\nLLbf7cXG8Il5VGVeXxu9j33fubft6+TFB9FnPJU7kf5CelJAgATSOVdL9JJ9/5vv\n5Z3JCbKREjimKQg7ruvKzO1N504hAQf8bzLOaYyEUsZ36icwCt6lrzAraB+s1Owh\nrSJJds4PwvIHKvlqEoOaOwSuGXr+oYYk+kFeJXxArCe24yk2bzXiV9AZWN//ZPbD\nAUl22yu+vLlPFArVG1gh9hwuAHz4lLXLNxoU5DK5FtRg7AWqXzL6aiMSrNQQu9Ki\ngrRLDotwJ6rWB8FniPqEwwjJioTI0jdygQ+NFkrk1zVRpTgPjIRLlTbA9ded4F2P\nq5HuAAi5nVIf7PiZu3lWsUna0uXYYYtbr/CrN8V7Go6Gvn7FexUeYWjoC4eLc0mh\nF3N+KXiOyuBBL3VzdKKXOn/3LnQJuExgi0Y2GRAtnQIDAQABo4GRMIGOMA8GA1Ud\nEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMCsGA1UdEAQkMCKADzIwMjMwMjE0\nMDkxOTM4WoEPMjA1MzAyMTQwOTQ5MzhaMB8GA1UdIwQYMBaAFNfWVmJcPxeB5nNE\nKfVRBe8LYDesMB0GA1UdDgQWBBTX1lZiXD8XgeZzRCn1UQXvC2A3rDANBgkqhkiG\n9w0BAQsFAAOCAgEASZwp/j3snkV/qz48/iNvNz53p1P/eJ/8SUSAV2acbtp5/81F\nrUyTv7VZxukQt+X4jPuHxR6L2LM/ApYKu4qO79e0wIMgOJdZRWT89ncT8gnXocg4\ndAjq+UhM+h8EnLT/7G5WNnKTbJU+LF/eDwurycwVPhaPZvyyELih0bTewGMZzO9T\nqnU2IoslH7+byNfBX+ymNwmqe2K89iIt8dZY3Yy7UvQLp3apensajdytmoFiLoYF\nkHJHL6HJZ4SwDWywuJsWt9CZFC+cEpsjqI2mQx7p5S3leKcfZJRktneyqFz7Casp\n6x5tddH20MWlwx2fHvMaLbLIH+UoCm7zX/3a5iOhdpBcS5gBgizuRy0CGl9/NMVp\ntXKtPvPPnm34KegRJyvgWQsbYetKymmlpNXNURuUjnnN3/audF2xLBuGU/7RMAZB\nNAdigkz0fseHdA6wIR4JIIDBsxU9Rm3T8QaSP++glYocbncxtut4KQx77oKlT36k\nKV6eqi34jsDz/A0GhZtO3PfiCXzQFFEeerMjr/rRYSpltQHZuOMHyiR20vBKvu+G\nBIBCFXARaH7Xx7v+506bnJWlHEqkydAJjKrOSNIekpfXEentZsw33PXXG3SbpupC\nrF0y4Fj0gUf/0hLifhzcSXaWwx2fS8pcKjdbPYrROJsh2uO/RUPT4Fh3Hyg=\n-----END CERTIFICATE-----`))\n\n\t// e-Szigno Root CA 2017\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV\nBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk\nLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv\nb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ\nBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg\nTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v\nIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv\nxie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H\nWyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G\nA1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB\neAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo\njbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ\n+efcMQ==\n-----END CERTIFICATE-----`))\n\n\t// Microsec e-Szigno Root CA 2009\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD\nVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0\nZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G\nCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y\nOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx\nFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp\nZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o\ndTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP\nkd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc\ncbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U\nfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7\nN4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC\nxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1\n+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G\nA1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM\nPcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG\nSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h\nmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk\nddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775\ntyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c\n2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t\nHMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW\n-----END CERTIFICATE-----`))\n\n\t// Microsoft ECC Root Certificate Authority 2017\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw\nCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD\nVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw\nMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV\nUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy\nb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq\nhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR\nogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb\nhGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3\nFQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV\nL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB\niudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=\n-----END CERTIFICATE-----`))\n\n\t// Microsoft RSA Root Certificate Authority 2017\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl\nMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw\nNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5\nIDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG\nEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N\naWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ\nNt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0\nZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1\nHLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm\ngGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ\njEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc\naDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG\nYaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6\nW6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K\nUGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH\n+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q\nW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/\nBAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC\nNxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC\nLgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC\ngMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6\ntZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh\nSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2\nTaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3\npvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR\nxpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp\nGWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9\ndOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN\nAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB\nRA+GsCyRxj3qrg+E\n-----END CERTIFICATE-----`))\n\n\t// NAVER Global Root Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM\nBQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG\nT1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0\naW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx\nCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD\nb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB\ndXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA\niQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH\n38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE\nHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz\nkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP\nszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq\nvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf\nnZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG\nYQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo\n0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a\nCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K\nAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I\n36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB\nAf8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN\nqo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj\ncu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm\n+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL\nhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe\nlHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7\np/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8\npiKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR\nLBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX\n5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO\ndh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul\n9XXeifdy\n-----END CERTIFICATE-----`))\n\n\t// NetLock Arany (Class Gold) Főtanúsítvány\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG\nEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3\nMDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl\ncnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR\ndGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB\npzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM\nb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm\naWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz\nIEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT\nlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz\nAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5\nVA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG\nILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2\nBJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG\nAQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M\nU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh\nbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C\n+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC\nbLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F\nuLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2\nXjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=\n-----END CERTIFICATE-----`))\n\n\t// OISTE Client Root ECC G1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICNDCCAbqgAwIBAgIQVOyX1ou0xAshbg6y0FPIejAKBggqhkjOPQQDAzBLMQsw\nCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwY\nT0lTVEUgQ2xpZW50IFJvb3QgRUNDIEcxMB4XDTIzMDUzMTE0MzE0MFoXDTQ4MDUy\nNDE0MzEzOVowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRp\nb24xITAfBgNVBAMMGE9JU1RFIENsaWVudCBSb290IEVDQyBHMTB2MBAGByqGSM49\nAgEGBSuBBAAiA2IABIhOaB/Jnr46BFsVwzX0zFDFCK04bqg80gK6zKsl/XVA/WcZ\nnxsKXfbLFnv5XB6C3BVE1Jw8bWGTRfRPz2K53z5TjZrUSt6Iqgum8dRh1h501Riy\nxU1M74B77A3rgzlUlqNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSZ\nVzs5sS0AjCFmjJVpnG117Iw/+jAdBgNVHQ4EFgQUmVc7ObEtAIwhZoyVaZxtdeyM\nP/owDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMQCW/+SCThYiW6CF\nGDw9Oo8gBggl5/WRNhmte7TfW2YSN3Nw7c0FKAdeCM4NQl8ZkQICMGdJh64GQR0g\n0zGmqiY38SeKYQ3+mgZDpy6eJkejMhiL6F5QBfGwekh23tuhYkq6dw==\n-----END CERTIFICATE-----`))\n\n\t// OISTE Client Root RSA G1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFgzCCA2ugAwIBAgIQNBdvWQGIG6ql3chIu7Q7czANBgkqhkiG9w0BAQwFADBL\nMQswCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UE\nAwwYT0lTVEUgQ2xpZW50IFJvb3QgUlNBIEcxMB4XDTIzMDUzMTE0MjMyOVoXDTQ4\nMDUyNDE0MjMyOFowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5k\nYXRpb24xITAfBgNVBAMMGE9JU1RFIENsaWVudCBSb290IFJTQSBHMTCCAiIwDQYJ\nKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpP/v5UE7WEPLzg0zHxHW7cxFNx+uQ5\nUUN2fZIfgX8Aa0HC5trcGE1sF1lwCTNi7GmILbDdWflhYGBW8ba07+uH0BP+w89v\nj345WFGziQKOVJUeIl+rKAVDJ/hF9AlCJpT+vRN4u5HyEBCcDWd82mQg63owGrpI\nDXhUKpkxNKvLpmrnDGc5ZqQmqCco5/PmPHPkK8xvMS4TdGHLaObSM85SvH5lJFoh\ngTFDqrKc0RjnYTxSr4CJ6TRG3vlNmVptHb3GJdGTVY74J5JDOoyVRUDjiRinhsFZ\nmMrbJhwTwIyBuZiwrWmtbhjje2JB9a02/gu0eyBfn6lu+ZmCElLSisRUeLR890Gb\nA+cHXrPCuUlkZ5IWxGCQDrCCfTOt0Dbq0XZrfIhHmKwb+bRQjGGBadgx8436PvL1\nS6/Owx3vXygb6xjWoFhSMr5Cb81JlyLBcLnT42BP3oOCoE4wvXNTwr0X/aDAmI/q\nDhcH5kOVIE7bEaj549O4J0cMJ9sS64FVzHXbn9MXQ8T764oobemvRFBaQ/vxOeKT\nUM+Y/ESWWDilpe1Fw1JCBafv5TykrD3n1qlWBaqww6cZ5OU911dEbZQRH8pwyPy5\nTMxBWoN0U5B4z9bULk+xqk0u9dEIWzpk78inqHph7Oym1YhOtlTUWJHCJWSRvAoU\nPZIUmrULBukvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU\nKYIlNQo6vpIr5AkD5OyPjThyOcswHQYDVR0OBBYEFCmCJTUKOr6SK+QJA+Tsj404\ncjnLMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAbSOGwv/14MjA\nVYpgMcyXQ0dwQ9Pj7FL608Ke+4kyGspGk08Elyvb0JyEDZUHQlT+72kh35IDLo83\nISN3qXc3bKDErpynWDlKFZdiRoNRIO0/wqPxw2In0KwTHv48Uh2Q1WPxqV7qf+fn\n65ZaUezUqRvjDJRmrMuIkkm+c1yK4Gq8poHNs1zUI5LITfkgjHCUS2ht8o8ebDX3\n6F/U170gN1Jm/yu7SWa3cagsX3MPB5LnTl+lBtvJijyXxULqfQ+BG1frngwP/6Mn\nIElTprM6TMttMDXa8vCa/lDfbVwkPU13an2GX0zQ4aa0rgQTAZDxgGiEB5SCB4Pr\nkeWTDnWRrqMjIElk1Lo5lldw7lU0KHzWr8qpnubJAckHwdBEsYC0UVCqj/ac5Wdz\n0BvqgzUXL1DG3lbHu6MDy+KhGOj4zlEGo9IDQGEap2dXg/zRErkoqtpOa9Wc2IU3\n2r0i1zRZnBqmznjWlHgHBg+xkyGgSccQngquUXca+XGQw62YD4opamABqk+tIAMt\nao6jC2rW/ZMMimHLvSjxX3H9uDM51krx9rJoUj5lj0OdgSQk9ihMNaf9MwqleMEE\nH+xJasSu1UQWpqeNf9ohlj6ouhZn1Kmh58Ka+BDZO5ruaPYvAO7Lu2aNIjiG9L9f\neKnIoB1au3VQ+VILDx0CLBQa84dqd/M=\n-----END CERTIFICATE-----`))\n\n\t// OISTE Server Root ECC G1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQsw\nCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwY\nT0lTVEUgU2VydmVyIFJvb3QgRUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUy\nNDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRp\nb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBHMTB2MBAGByqGSM49\nAgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOujvqQy\ncvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N\n2xml4z+cKrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3\nTYhlz/w9itWj8UnATgwQb0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9C\ntJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCpKjAd0MKfkFFR\nQD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxgZzFDJe0CMQCSia7pXGKD\nYmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c=\n-----END CERTIFICATE-----`))\n\n\t// OISTE Server Root RSA G1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBL\nMQswCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UE\nAwwYT0lTVEUgU2VydmVyIFJvb3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4\nMDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5k\nYXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJTQSBHMTCCAiIwDQYJ\nKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxVYOPM\nvLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7b\nrEi56rAUjtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzk\nik/HEzxux9UTl7Ko2yRpg1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4z\nO8vbUZeUapU8zhhabkvG/AePLhq5SvdkNCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8R\ntOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY+m0o/DjH40ytas7ZTpOS\njswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+lKXHiHUh\nsd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+Ho\nmnqT8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu\n+zrkL8Fl47l6QGzwBrd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYR\ni3drVByjtdgQ8K4p92cIiBdcuJd5z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnT\nkCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU\n8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC77EUOSh+1sbM2\nzQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33\nI3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG\n5D1rd9QhEOP28yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8\nqyiWXmFcuCIzGEgWUOrKL+mlSdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dP\nAGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l8PjaV8GUgeV6Vg27Rn9vkf195hfk\ngSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+FKrDgHGdPY3ofRRs\nYWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNqqYY1\n9tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome\n/msVuduCmsuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3\nJ8tRd/iWkx7P8nd9H0aTolkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2\nwq1yVAb+axj5d9spLFKebXd7Yv0PTY6YMjAwcRLWJTXjn/hvnLXrahut6hDTlhZy\nBiElxky8j3C7DOReIoMt0r7+hVu05L0=\n-----END CERTIFICATE-----`))\n\n\t// OISTE WISeKey Global Root GA CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB\nijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly\naWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl\nZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w\nNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G\nA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD\nVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX\nSVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR\nVVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2\nw93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF\nmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg\n4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9\n4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw\nDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw\nEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx\nSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2\nftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8\nvPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa\nhNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi\nFj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ\n/L7fCg0=\n-----END CERTIFICATE-----`))\n\n\t// OISTE WISeKey Global Root GB CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt\nMQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg\nRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i\nYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x\nCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG\nb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh\nbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3\nHEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx\nWuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX\n1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk\nu7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P\n99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r\nM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw\nAwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB\nBAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh\ncViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5\ngSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO\nZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf\naPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic\nNc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=\n-----END CERTIFICATE-----`))\n\n\t// OISTE WISeKey Global Root GC CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw\nCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91\nbmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg\nUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ\nBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu\nZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS\nb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni\neUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W\np2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E\nBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T\nrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV\n57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg\nMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9\n-----END CERTIFICATE-----`))\n\n\t// Security Communication ECC RootCA1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT\nAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD\nVQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx\nNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT\nHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5\nIENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\nAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl\ndB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK\nULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E\nBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu\n9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O\nbe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k=\n-----END CERTIFICATE-----`))\n\n\t// Security Communication RootCA2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl\nMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe\nU2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX\nDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy\ndXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj\nYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV\nOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr\nzbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM\nVAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ\nhNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO\nojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw\nawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs\nOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3\nDQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF\ncoJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc\nokgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8\nt/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy\n1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/\nSjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03\n-----END CERTIFICATE-----`))\n\n\t// AAA Certificate Services\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb\nMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow\nGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj\nYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL\nMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE\nBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM\nGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP\nADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua\nBtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe\n3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4\nYgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR\nrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm\nez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU\noBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF\nMAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v\nQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t\nb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF\nAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q\nGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz\nRt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2\nG9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi\nl2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3\nsmPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==\n-----END CERTIFICATE-----`))\n\n\t// COMODO Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB\ngTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\nA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV\nBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw\nMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl\nYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P\nRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0\naG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3\nUcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI\n2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8\nQ5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp\n+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+\nDT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O\nnKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW\n/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g\nPKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u\nQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY\nSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv\nIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/\nRxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4\nzJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd\nBA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB\nZQ==\n-----END CERTIFICATE-----`))\n\n\t// COMODO ECC Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL\nMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE\nBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT\nIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw\nMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy\nZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N\nT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv\nbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR\nFtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J\ncfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW\nBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/\nBAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm\nfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv\nGDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=\n-----END CERTIFICATE-----`))\n\n\t// COMODO RSA Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB\nhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\nA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV\nBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5\nMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT\nEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR\nQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh\ndGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR\n6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X\npz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC\n9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV\n/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf\nZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z\n+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w\nqP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah\nSL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC\nu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf\nFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq\ncrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E\nFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl\nwFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM\n4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV\n2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna\nFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ\nCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK\nboHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke\njkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL\nS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb\nQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl\n0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB\nNVOFBkpdn627G190\n-----END CERTIFICATE-----`))\n\n\t// Entrust Root Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC\nVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0\nLm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW\nKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl\ncnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw\nNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw\nNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy\nZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV\nBAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ\nKoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo\nNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4\n4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9\nKlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI\nrb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi\n94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB\nsDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi\ngA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo\nkORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE\nvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA\nA4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t\nO1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua\nAGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP\n9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/\neu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m\n0vdXcDazv/wor3ElhVsT/h5/WrQ8\n-----END CERTIFICATE-----`))\n\n\t// Entrust Root Certification Authority - EC1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG\nA1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3\nd3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu\ndHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq\nRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy\nMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD\nVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0\nL2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g\nZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD\nZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi\nA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt\nByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH\nBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O\nBBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC\nR98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX\nhTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G\n-----END CERTIFICATE-----`))\n\n\t// Entrust Root Certification Authority - G2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC\nVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50\ncnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs\nIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz\ndCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy\nNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu\ndHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt\ndGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0\naG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj\nYXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T\nRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN\ncCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW\nwcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1\nU1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0\njaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP\nBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN\nBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/\njTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ\nRkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v\n1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R\nnAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH\nVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==\n-----END CERTIFICATE-----`))\n\n\t// Entrust Root Certification Authority - G4\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw\ngb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL\nEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg\nMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw\nBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0\nMB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT\nMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1\nc3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ\nbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg\nUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B\nAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ\n2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E\nT+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j\n5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM\nC1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T\nDtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX\nwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A\n2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm\nnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8\ndWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl\nN4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj\nc0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD\nVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS\n5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS\nGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr\nhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/\nB7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI\nAeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw\nH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+\nb7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk\n2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol\nIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk\n5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY\nn/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw==\n-----END CERTIFICATE-----`))\n\n\t// Entrust.net Certification Authority (2048)\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML\nRW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp\nbmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5\nIEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp\nZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3\nMjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3\nLmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp\nYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG\nA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq\nK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe\nsYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX\nMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT\nXTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/\nHoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH\n4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\nHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub\nj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo\nU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf\nzX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b\nu/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+\nbYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er\nfF6adulZkMV8gzURZVE=\n-----END CERTIFICATE-----`))\n\n\t// Sectigo Public Email Protection Root E46\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICMTCCAbegAwIBAgIQbvXTp0GOoFlApzBr0kBlVjAKBggqhkjOPQQDAzBaMQsw\nCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTEwLwYDVQQDEyhT\nZWN0aWdvIFB1YmxpYyBFbWFpbCBQcm90ZWN0aW9uIFJvb3QgRTQ2MB4XDTIxMDMy\nMjAwMDAwMFoXDTQ2MDMyMTIzNTk1OVowWjELMAkGA1UEBhMCR0IxGDAWBgNVBAoT\nD1NlY3RpZ28gTGltaXRlZDExMC8GA1UEAxMoU2VjdGlnbyBQdWJsaWMgRW1haWwg\nUHJvdGVjdGlvbiBSb290IEU0NjB2MBAGByqGSM49AgEGBSuBBAAiA2IABLinUpT1\nPgWwG/YfsdN+ueQFZlSAzmylaH3kU1LbgvrEht9DePfIrRa8P3gyy2vTSdZE5bN+\nn3umxizy4rbTibCaPEvOiUvGxss6SWAPRrxtTnqcyZuFewq2sEfCiOPU0aNCMEAw\nHQYDVR0OBBYEFC1OjKfCI7JXqQZrPmsrifPDXkfOMA4GA1UdDwEB/wQEAwIBhjAP\nBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCSnRpZY0VYjhsW5H16\nbDZIMB8rcueQMzT9JKLGBoxvOzJXWvj+xkkSU5rZELKZUXICMAUlKjMh/JPmIqLM\ncFUoNVaiB8QhhCMaTEyZUJmSFMtK3Fb79dOPaiz1cTr4izsDng==\n-----END CERTIFICATE-----`))\n\n\t// Sectigo Public Email Protection Root R46\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFgDCCA2igAwIBAgIQHUSeuQ2DkXSu3fLriLemozANBgkqhkiG9w0BAQwFADBa\nMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTEwLwYDVQQD\nEyhTZWN0aWdvIFB1YmxpYyBFbWFpbCBQcm90ZWN0aW9uIFJvb3QgUjQ2MB4XDTIx\nMDMyMjAwMDAwMFoXDTQ2MDMyMTIzNTk1OVowWjELMAkGA1UEBhMCR0IxGDAWBgNV\nBAoTD1NlY3RpZ28gTGltaXRlZDExMC8GA1UEAxMoU2VjdGlnbyBQdWJsaWMgRW1h\naWwgUHJvdGVjdGlvbiBSb290IFI0NjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC\nAgoCggIBAJHlG/qqbTcrdccuXxSl2yyXtixGj2nZ7JYt8x1avtMdI+ZoCf9KEXMa\nrmefdprS5+y42V8r+SZWUa92nan8F+8yCtAjPLosT0eD7J0FaEJeBuDV6CtoSJey\n+vOkcTV9NJsXi39NDdvcTwVMlGK/NfovyKccZtlxX+XmWlXKq/S4dxlFUEVOSqvb\nnmbBGbc3QshWpUAS+TPoOEU6xoSjAo4vJLDDQYUHSZzP3NHyJm/tMxwzZypFN9mF\nZSIasbUQUglrA8YfcD2RxH2QPe1m+JD/JeDtkqKLMSmtnBJmeGOdV+z7C96O3IvL\nOql39Lrl7DiMi+YTZqdpWMOCGhrN8Z/YU5JOSX2pRefxQyFatz5AzWOJz9m/x1AL\n4bzniJatntQX2l3P4JH9phDUuQOBm2ms+4SogTXrG+tobHxgPsPfybSudB1Ird1u\nEYbhKmo2Fq7IzrzbWPxAk0DYjlOXwqwiOOWIMbMuoe/s4EIN6v+TVkoGpJtMAmhk\nj1ZQwYEF/cvbxdcV8mu1dsOj+TLOyrVKqRt9Gdx/x2p+ley2uI39lUqcoytti/Fw\n5UcrAFzkuZ7U+NlYKdDL4ChibK6cYuLMvDaTQfXv/kZilbBXSnQsR1Ipnd2ioU9C\nwpLOLVBSXowKoffYncX4/TaHTlf9aKFfmYMc8LXd6JLTZUBVypaFAgMBAAGjQjBA\nMB0GA1UdDgQWBBSn15V360rDJ82TvjdMJoQhFH1dmDAOBgNVHQ8BAf8EBAMCAYYw\nDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQwFAAOCAgEANNLxFfOTAdRyi/Cr\nCB8TPHO0sKvoeNlsupqvJuwQgOUNUzHd4/qMUSIkMze4GH46+ljoNOWM4KEfCUHS\nNz/Mywk1Qojp/BHXz0KqpHC2ccFTvcV0r8QiJGPPYoJ9yctRwYiQbVtcvvuZqLq2\nhrDpZgvlG2uv6iuGp9+oI0yWP09XQhgVg0Pxhia3KgPOC53opWgejG+9heMbUY/n\nFy8r0NZ4wi3dcojUZZ76mdR+55cKkgGapamEOgwqdD0zGMiH9+ik9YZCOf1rdSn8\nAAasoqUaVI7pUEkXZq9LBC2blIClVKuMVxdEnw/WaGRytEseAcfZm5TZg5mvEgUR\no5gi0vJXyiT5ujgVEki6Yzv8i5V41nIHVszN/J0c0MVkO2M0zwSZircweXq28sbV\n2VR6hwt+TveE7BTziBYS8dWuChoJ7oat5av9rsMpeXTDAV8Rm991mcZK95uPbEns\nIS+0AlmzLdBykLoLFHR4S8/BX1VyjlQrE876WAzTuyzZqZFh+PjxtnvevKnMkgTM\nS2tfc4C2Ie1QT9d2h27O39K3vWKhfVhiaEVStj/eEtvtBGmedoiqAW3ahsdgG8NS\nrDfsUHGAciohRQpTRzwZ643SWQTeJbDrHzVvYH3Xtca7CyeN4E1U5c8dJgFuOzXI\nIBKJg/DS7Vg7NJ27MfUy/THzVho=\n-----END CERTIFICATE-----`))\n\n\t// Sectigo Public Server Authentication Root E46\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw\nCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T\nZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN\nMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG\nA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT\nZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA\nIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC\nWvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+\n6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B\nAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa\nqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q\n4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw==\n-----END CERTIFICATE-----`))\n\n\t// Sectigo Public Server Authentication Root R46\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf\nMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD\nEy1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw\nHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY\nMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp\nYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa\nef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz\nSDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf\niOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X\nME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3\nIuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS\nVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE\nSJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu\n+Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt\n8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L\nHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt\nzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P\nAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c\nmTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ\nYKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52\ngDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA\nFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB\nJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX\nDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui\nTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5\ndHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65\nLvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp\n0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY\nQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL\n-----END CERTIFICATE-----`))\n\n\t// USERTrust ECC Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\neSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\nJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\nMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\nCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\nVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\naWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\nI+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\no4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\nA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\nVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\nzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\nRNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n-----END CERTIFICATE-----`))\n\n\t// USERTrust RSA Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB\niDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl\ncnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV\nBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw\nMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV\nBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU\naGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy\ndGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\nAoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B\n3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY\ntJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/\nFp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2\nVN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT\n79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6\nc0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT\nYo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l\nc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee\nUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE\nHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd\nBgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G\nA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF\nUp/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO\nVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3\nATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs\n8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR\niQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze\nSf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ\nXHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/\nqS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB\nVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB\nL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG\njjxDah2nGN59PRbxYvnKkKj9\n-----END CERTIFICATE-----`))\n\n\t// SSL.com Client ECC Root CA 2022\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICQDCCAcagAwIBAgIQdvhIHq7wPHAf4D8lVAGD1TAKBggqhkjOPQQDAzBRMQsw\nCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSgwJgYDVQQDDB9T\nU0wuY29tIENsaWVudCBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzAzMloX\nDTQ2MDgxOTE2MzAzMVowUTELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw\nb3JhdGlvbjEoMCYGA1UEAwwfU1NMLmNvbSBDbGllbnQgRUNDIFJvb3QgQ0EgMjAy\nMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABC1Tfp+LPrM2ulDizOvcuiaK04wGP2cP\n7/UX5dSumkYqQQEHaedncfHCAzbG8CtSjs8UkmikPnBREmmNeKKCyikUwOSUIrJE\nkmBvyASkZ9Wi0PPQ1+qOPA+60kBHkDTufaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAf\nBgNVHSMEGDAWgBS3/i1ixYFTzVIaL11goMNd+7IcHDAdBgNVHQ4EFgQUt/4tYsWB\nU81SGi9dYKDDXfuyHBwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUC\nME0HES0R+7kmwyHdcuEX/MHPFOpJznGHjtZT3BHNXVSKr9kt9IxR6rxmR+J/lYNg\nZQIxAIwhTE+75bBQ35BiSebMkdv4P11xkQiOT5LJf6Zc6hN+7W3E6MMqb1wR4aXz\nalqaTQ==\n-----END CERTIFICATE-----`))\n\n\t// SSL.com Client RSA Root CA 2022\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFjzCCA3egAwIBAgIQdq/uiJMVRbZQU5uAnKTfmjANBgkqhkiG9w0BAQsFADBR\nMQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSgwJgYDVQQD\nDB9TU0wuY29tIENsaWVudCBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzEw\nN1oXDTQ2MDgxOTE2MzEwNlowUTELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBD\nb3Jwb3JhdGlvbjEoMCYGA1UEAwwfU1NMLmNvbSBDbGllbnQgUlNBIFJvb3QgQ0Eg\nMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALhY20Yw+8k/48jw\nATM04tpIqBjpIG6a1wHh1SmPMLQjauTLYrC+4p8gvT5UoDlox4Y3ZnQGBu90K9rc\nn4SpUi+Q0u5+fPulIq1vcEZnlj0p1KO7VnsUBFnBIWNEHrIfElyQh2UNiPYeiCLi\nY1S78zb41n/c2v8pNanGbg5pWz/YvoKHFXBdsMdcEg9jpjjNz3O5ww6JJjcbP2Ic\nMmnRm9n/VZAx3rFj3c/FdHf874ghU78AMRomLAAwpV9s4+T2AIrKmIecdAN6i2bs\nfv2jjzUlXHils6T7PW2pivBsiIKL/UrQb+TXo7SONEk4vs5F5dIcyl7CNxSLzWZW\nMzed5WvsQ5JkoELadW/AFez5ab00uYp7+hb7Vf5SIOgEBFZWZfU3RJjIikbpt6y4\n6L5ijlQ2W/c7cL9d7i26X95CGYbwf4vrCMvYvuoOQkKgNnNXF+0y6tCN6Acbm5no\nxJpiBA5I9zwSuvdYwZqM6cewIzZWNB3LbNq6B4Qd/dGsn+bCie/DuWwYs2mHV1+1\nDDhbpyEkKjunNJGetFTqKE/TwaOL5OYr1fKdv5thACLd1ktEHz9dVv7enHjMmVuq\n5L2620NLrUwmTKNNNIpsdDYT22L8m7IFgf+uPwzN9hui9DnnyvVMXPtUdzWAWsAS\noRMBM2c9nYGhqfWFJFiIeOf042hVAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8w\nHwYDVR0jBBgwFoAU8DhClDSpPAB/Uu45pfdLDbxqfSMwHQYDVR0OBBYEFPA4QpQ0\nqTwAf1LuOaX3Sw28an0jMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC\nAgEAmU/b8OrWEfoq/cirbeQOc2LSQp8V/nxwUj9kh4IxP0VALuEinwZmKfyW0y2N\ntjjH2fMnwVkpoIz2cyQPKCLXTmHdE93bnzJSk/tPzOo4PJhqA6sWryHRQq59RSvq\nxM+KWZ+CcHY6+GImyRCXWEAkpC25LymAJ+GJa3LKSQhxN1MF8YDO00IC0vzC0ZQG\n7gfi9oPif5/nu1bDW7/dlZMJHiTBzybNraSuwrRp56q17TeU6d3RY4VrmnpKVnbc\nGYUo1OTGpNi4lkF30LRZ8UYFh4cCH2m5ghjQQ9km2hpnqNZ1durybQ5C/4gmom6E\n/n5iG/DGPe3AHGrHkda4ADdJm7mEBaHNbjHWROpTi7pTmB2hkIrphfgb8pNYw8jc\nmiZPPiDPT0PzEIx/EGF6NsqqC33Mn0dEWa6llcaZU+MHaz1JELAY/10OhUMUS+dr\n00q1smBh3GlJAiNd6JJxw5yfRWd5HtwyhrqqVTxkbzK1EEAV3nJAeOBucLtu6wno\nOdmsupJ13UPKugGVrRqBKzrw48UvDBhNEMauwO3+BVJ/GQXLqa81CAw4IuT+VuVT\nPr/k1rPZCMM91TMygSTFqeFlEbgyMzBxGEkdGkXGmhSKWDkobvPLUblJJmR4A8eR\nEYOpuZA0tm+qBZ6FKFeZvn8nBkliTaH8CeErRglMFJtWj0U=\n-----END CERTIFICATE-----`))\n\n\t// SSL.com EV Root Certification Authority ECC\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC\nVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T\nU0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp\nY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx\nNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv\ndXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv\nbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49\nAgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA\nVIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku\nWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP\nMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX\n5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ\nytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg\nh5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==\n-----END CERTIFICATE-----`))\n\n\t// SSL.com EV Root Certification Authority RSA R2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV\nBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE\nCgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy\ndGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy\nMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G\nA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD\nDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq\nM0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf\nOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa\n4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9\nHSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR\naZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA\nb9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ\nGp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV\nPWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO\npgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu\nUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY\nMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV\nHSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4\n9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW\ns47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5\nSm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg\ncLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM\n79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz\n/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt\nll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm\nKf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK\nQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ\nw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi\nS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07\nmKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==\n-----END CERTIFICATE-----`))\n\n\t// SSL.com Root Certification Authority ECC\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC\nVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T\nU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0\naW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz\nWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0\nb24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS\nb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB\nBAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI\n7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg\nCemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud\nEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD\nVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T\nkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+\ngA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl\n-----END CERTIFICATE-----`))\n\n\t// SSL.com Root Certification Authority RSA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE\nBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK\nDA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp\nY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz\nOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv\ndXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv\nbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN\nAQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R\nxFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX\nqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC\nC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3\n6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh\n/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF\nYD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E\nJNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc\nUS4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8\nZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm\n+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi\nM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV\nHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G\nA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV\ncpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc\nHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs\nPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/\nq5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0\ncuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr\na6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I\nH37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y\nK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu\nnLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf\noYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY\nIc2wBlX7Jz9TkHCpBB5XJ7k=\n-----END CERTIFICATE-----`))\n\n\t// SSL.com TLS ECC Root CA 2022\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw\nCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT\nU0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2\nMDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh\ndGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG\nByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm\nacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN\nSeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME\nGDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW\nuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp\n15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN\nb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g==\n-----END CERTIFICATE-----`))\n\n\t// SSL.com TLS RSA Root CA 2022\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO\nMQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD\nDBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX\nDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw\nb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC\nAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP\nL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY\nt6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins\nS657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3\nPnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO\nL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3\nR2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w\ndr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS\n+YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS\nd66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG\nAtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f\ngTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j\nBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z\nNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt\nhEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM\nQtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf\nR4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ\nDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW\nP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy\nlrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq\nbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w\nAgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q\nr5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji\nMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU\n98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA=\n-----END CERTIFICATE-----`))\n\n\t// SwissSign Gold CA - G2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV\nBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln\nbiBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF\nMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT\nd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC\nCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8\n76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+\nbbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c\n6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE\nemA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd\nMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt\nMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y\nMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y\nFGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi\naG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM\ngI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB\nqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7\nlqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn\n8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov\nL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6\n45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO\nUYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5\nO1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC\nbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv\nGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a\n77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC\nhdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3\n92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp\nLd6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w\nZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt\nQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ\n-----END CERTIFICATE-----`))\n\n\t// SwissSign RSA SMIME Root CA 2022 - 1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFlzCCA3+gAwIBAgIURg7UAXGQoBqDLEpCECgV0mEbrTIwDQYJKoZIhvcNAQEL\nBQAwUzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEtMCsGA1UE\nAxMkU3dpc3NTaWduIFJTQSBTTUlNRSBSb290IENBIDIwMjIgLSAxMB4XDTIyMDYw\nODEwNTMxM1oXDTQ3MDYwODEwNTMxM1owUzELMAkGA1UEBhMCQ0gxFTATBgNVBAoT\nDFN3aXNzU2lnbiBBRzEtMCsGA1UEAxMkU3dpc3NTaWduIFJTQSBTTUlNRSBSb290\nIENBIDIwMjIgLSAxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1Pv6\nP4aimXAJOsnWoU4Bzka1LSRIDUXprMka1zKApObTytbyKTfsmizWgc7mG52xD0Hf\nWNNfqqB5WQuMrfnF+Rz7w+k1QHTDwQzLZ/ucXgwj+dAv+kyCRRy19R/4GW7ak7dO\naIN+Yi0djJUfcNnOWowhXai+CKlWbdn3uZCZxzvXvZ4uyWdXLiHO8DKD+wQB+beC\nRA2yy3oJoUg+T8ALahsb7M8dnn8GkKwoBQuo5lQ7oqcsOROZqPs06/XwvQHYiBHI\nrroZAkkC3IostL1hYOydeFxqiy8Xhl7yT5MAa13FsqmlGOrmbX5XBfsH/Lx8oUOx\nZhyoZ/urN/aqqrh6Qfc51YyfrnI2J+RixkOZ8aFB6f+Jnw9Jr8kUBhcnZDkNpbQq\nW+w8+5/FX8Y7XSYZ8oQpuJVECVL9bDDQYo8opYGWK5QvJnXkCYwK3zjzfl04joKa\njNyers4SQjoi8jWNT9IayEkzC/o2P/8sa2ogcUzNrRA/aTKEjlzuU4hE4t3MAzCS\nhnmQKkt1+1JixPRvTffbI6EY3UVTF5pjJEiJIs1+mwEcgCgDj1sr+h/jfBm95o+x\nQHag8sc3sjKUEDLNpxOX8TssejQie3Q6QOKvgvjBwXj8X+Q1f8D0TPBMsuqHA3Il\nWYMqCKRR3s/uqOfoQD+I8DarCU7YoKh/8+EJ27kCAwEAAaNjMGEwDwYDVR0TAQH/\nBAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAUzC6tiYyD40CjJWml\n6pJ90jc6x8YwHQYDVR0OBBYEFMwurYmMg+NAoyVppeqSfdI3OsfGMA0GCSqGSIb3\nDQEBCwUAA4ICAQAAB2YWpe3Hub+8yJGtWO1eEgWz9kabe+SEEC8HsVpeMm5tAPBe\nx5piOYdN5Dzzvva6alNshG0H1GHKZ2a+mz5FMJ1R0tdaQq6dkg4jq9AVlD6omsqb\n7cHCXyGjmYD8uaZhDlCAgCfH6H2g1JR6mAPn7kKL81JQXO++sHZaHAmhv4PAHnZl\n0CVBW2mRk3f5jEvwLNubBgAXg/palLSGie+8CgsS+AZN0nPikThduWpLT6ev2iYl\nkiMafB8nDZGE7xdy9kbrazs3qdTVmmO6XnmMKrWbojS1zJYn+XkIPH9t4P983MUm\nr8OhemkW3Yc1c8ZrMWtWAS1PmdnuyuHQg962hecW+NGuM0j7Gs9dX4qEYXQHbxmw\nUSGyoQSxe1OP76JFrR+Y3flqBGyqNsWvjOopSUrn/1ezxjwRSRgX5maF4egj8osO\nPJPEP3ZOfmKiKcsWMN4saa+Rp+JX5TNMv9iOB6J/oTVGaUqoICn/694glVmxrk0w\na9iatAMfwjjkINUO1howTGicjODtoQ+OQl3rgCoSeaYXF7SVKo40kae90ayoGkMh\ni97v4KxGJWUKxiuhmz4i6Bg4tSb2LMoIIN4w0a1U/dxIFZ/Np0HXNziFME8SiEM0\ng9cqTdQAV1zlyvDd4ZIoKxh1vUekQhPpVlqNSl7ODnU1gHMZDywpi7uVuA==\n-----END CERTIFICATE-----`))\n\n\t// SwissSign RSA TLS Root CA 2022 - 1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL\nBQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE\nAxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx\nMTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT\nd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg\nMjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX\nvDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7\nLCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX\n5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE\nEPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt\n/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x\n0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5\nKaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM\n0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd\nOxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta\nclXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK\nwP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD\nAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4\nDxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL\nBQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3\n10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz\nHqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ\niJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc\ngC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM\nZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF\nLhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp\nzv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td\nAo9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0\nrk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO\ngLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ\n-----END CERTIFICATE-----`))\n\n\t// TWCA CYBER Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ\nMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290\nIENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5\nWhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO\nLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg\nQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P\n40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF\navcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/\n34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i\nJkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu\nj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf\nXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP\n2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA\nS9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA\noS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC\nkHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW\n5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD\nVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd\nBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB\nAGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t\ntGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn\n68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn\nTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t\nRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx\nf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI\nQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz\n8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4\nNxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX\nxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6\nt5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X\n-----END CERTIFICATE-----`))\n\n\t// TWCA Global Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx\nEjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT\nVFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5\nNTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT\nB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG\nSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF\n10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz\n0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh\nMBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH\nzIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc\n46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2\nyKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi\nlaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP\noA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA\nBDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE\nqYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm\n4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB\n/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL\n1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn\nLhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF\nH6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo\nRI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+\nnile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh\n15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW\n6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW\nnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j\nwa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz\naGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy\nKwbQBM0=\n-----END CERTIFICATE-----`))\n\n\t// TWCA Global Root CA G2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFlTCCA32gAwIBAgIQQAE0jMIAAAAAAAAAAZdY9DANBgkqhkiG9w0BAQwFADBU\nMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290\nIENBMR8wHQYDVQQDExZUV0NBIEdsb2JhbCBSb290IENBIEcyMB4XDTIyMTEyMjA2\nNDIyMVoXDTQ3MTEyMjE1NTk1OVowVDELMAkGA1UEBhMCVFcxEjAQBgNVBAoTCVRB\nSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEfMB0GA1UEAxMWVFdDQSBHbG9iYWwg\nUm9vdCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKoO1SCS\nAa2C+QwIkTRrihbQRhb/A7jYjeqTNPv/K739bqrcm/KGgVX1iRzEjXVqWHiREx4C\nE3A9774K5wCPuDHldMUwvv991pnlwkKjzyHWswh/kdVh5qKVEA3vXpcLSTjVIrDX\ni1lvnzWbf9KRzHp/u6Cf3lUz9kuNCup9CcB53L1E4v4c52QhKM8ESuK0v4Z5KrsO\nk8mPXqwwOVKQB7nqnCZCFMRnRv7RGmihPlAZoyYKJymQwva063OaeB7hmPRlDDUh\nBvgL3mLlTcGzXdm5+mGXKuPqx0RVJJL+Eqc/xHfgLQKBB9X7feYQnjq0qO/s+1Dq\nNc/MfrtCuURsUum/KnIfP96bcOncWsU7u7/wWYWvL8GwFHkFrHWfJfURJwZgIcdt\nZb6oiZzlrEbf+F1EA41gvfexDcwv70FUL+5rlblOfDTfO/l3nX3NBz0cBjMSgOxy\nnPItgtrVO8TH+QTDZAJ89TVgp7RGKS4b76VYgC56iVE4Njz9oXe4gDDQit6NpzQm\n7CO7GFUYNkXu7QEGqk2/ZAzKmJcaMQJm+HhoW4jfCajnm/o0bXAcIa0Ii/Khtqx2\nar/xgCUAvjweTa65PLaVY71rfkcSkFVFEY3sFx/BvieBk1djaQAmd4vDWeV70Q1E\n8qjw94WaBffCLnCak4XYlZAxkFSm7AufN0UPAgMBAAGjYzBhMA4GA1UdDwEB/wQE\nAwIBBjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFJKM1DbRW0dTxHENhN1k\nKvU2ZEDnMB0GA1UdDgQWBBSSjNQ20VtHU8RxDYTdZCr1NmRA5zANBgkqhkiG9w0B\nAQwFAAOCAgEAJfxL2pC02nXnQTqB0ab+oGrzGHFiaiQIi6l6TclVzs8QKC4EGZYF\nz10CICo7s1U/Ac1CzbJ37f9183x325alz4xnBvSkm3L2IUkJmKMyXndaYwnvYkOX\nAji16jwYUGj8WVvZedTx5FZIE1bY03ELXniUOBFF+gUX9Q51HmJSYUa6LhmthrSI\nD7FQ5kAANBqVnZPgUfnUVUbplTwlhi6X1wExGETsHGDpfWmvMviXQCUkto0aVTzF\nt/e8BlI7cTBwPnEXfvFmBF5dvIoxQ6aSHXtU0qU2i2+N1l7a1MMuHd85VWCCMJ4n\n/46A3WNMplU12NAzqYBtPl6dzKhngGb6mVcMUsoZdbA4NVUqgcWMHlbXX5DyINja\n4GZx6bJ4q2e5JG5rNnL8b439f3I5KGdSkQUfV2XSo6cNYfqh59U1RpXJBof2MOwy\nUamsVsAhTqMUdAU6vOO/bT1OP16lpG0pv4RRdVOOhhr1UXAqDRxOQOH9o+OlK2eQ\nksdsroW/OpsXFcqcKpPUTTkNvCAIo42IbAkNjK5EIU3JcezYJtcXni0RGDyjIn24\nJ1S/aMg7QsyPXk7n3MLF+mpED41WiHrfiYRsoLM+PfFlAAmI6irrQM6zXawyF67B\nm+nQwfVJlN2nznxaB+uuIJwXMJJpk3Lzmltxm/5q33owaY6zLtsPLN0=\n-----END CERTIFICATE-----`))\n\n\t// TWCA Root Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES\nMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU\nV0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz\nWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO\nLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm\naWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\nAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE\nAcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH\nK3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX\nRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z\nrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx\n3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq\nhkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC\nMErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls\nXebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D\nlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn\naspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ\nYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==\n-----END CERTIFICATE-----`))\n\n\t// Telia Root CA v2\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx\nCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE\nAwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1\nNTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ\nMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP\nADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq\nAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9\nvVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9\nlRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD\nn3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT\n7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o\n6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC\nTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6\nWT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R\nDolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI\npEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj\nYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy\nrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw\nAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ\n8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi\n0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM\nA8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS\nSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K\nTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF\n6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er\n3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt\nTy3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT\nVmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW\nysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA\nrBPuUBQemMc=\n-----END CERTIFICATE-----`))\n\n\t// TeliaSonera Root CA v1\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw\nNzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv\nb3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD\nVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2\nMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F\nVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1\n7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X\nZ75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+\n/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs\n81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm\ndtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe\nOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu\nsDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4\npgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs\nslESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ\narMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD\nVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG\n9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl\ndxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx\n0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj\nTQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed\nY2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7\nQ4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI\nOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7\nvVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW\nt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn\nHL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx\nSK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=\n-----END CERTIFICATE-----`))\n\n\t// TrustAsia Global Root CA G3\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM\nBQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp\nZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe\nFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw\nIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU\ncnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC\nDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS\nT1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK\nAtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1\nnyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep\nqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA\nyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs\nhH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX\nzhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv\nkV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT\nf9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA\nuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB\no2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih\nMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E\nBAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4\nwM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2\nXFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1\nJKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j\nITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV\nVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx\nxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on\nAX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d\n7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj\ngKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV\n+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo\nFGWsJwt0ivKH\n-----END CERTIFICATE-----`))\n\n\t// TrustAsia Global Root CA G4\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw\nWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs\nIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y\nMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD\nVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz\ndEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx\ns8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw\nLxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij\nYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD\npm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE\nAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR\nUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj\n/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA==\n-----END CERTIFICATE-----`))\n\n\t// TrustAsia SMIME ECC Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICNjCCAbugAwIBAgIUWsL4KU/jfcVeHRhvO5MgH/97ui0wCgYIKoZIzj0EAwMw\nWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs\nIEluYy4xJDAiBgNVBAMTG1RydXN0QXNpYSBTTUlNRSBFQ0MgUm9vdCBDQTAeFw0y\nNDA1MTUwNTQxNTlaFw00NDA1MTUwNTQxNThaMFoxCzAJBgNVBAYTAkNOMSUwIwYD\nVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDExtUcnVz\ndEFzaWEgU01JTUUgRUNDIFJvb3QgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATN\n2fsnvWnshsmQQ7FwF5SnyXcjOj8jZdMcox0eQlQg69BCu1m5i6zyho1Ljh2qliIj\nOXZtkpvrIst6Q6Jz/XNLwiUPKrFpxv9F36k8lYC7qR5Kky/sHB2I9BGSN583mHKj\nQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFDFn5nKyDeYioKzPfiKnWTLj\nZiOlMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNpADBmAjEA3TpMjaTGf+29\npcZPPv0xSyjWilbfZRZ3h037ujIIgeCeM0iLn5SG7wErlOaM1tSOAjEAn4GcsCb9\nK9by9XGEnqjHiozWWBFStbgEy8xxdWPixhk42W1sGXGkFhkhk7oGRChs\n-----END CERTIFICATE-----`))\n\n\t// TrustAsia SMIME RSA Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFhDCCA2ygAwIBAgIUWu5x394MV4W1uzYi17h2RgJzyv8wDQYJKoZIhvcNAQEM\nBQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp\nZXMsIEluYy4xJDAiBgNVBAMTG1RydXN0QXNpYSBTTUlNRSBSU0EgUm9vdCBDQTAe\nFw0yNDA1MTUwNTQyMDFaFw00NDA1MTUwNTQyMDBaMFoxCzAJBgNVBAYTAkNOMSUw\nIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDExtU\ncnVzdEFzaWEgU01JTUUgUlNBIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC\nDwAwggIKAoICAQCYlZytPFlz05N2pkhUphyIckxN4YL/GhMfUN6M2ZBC0byZ0zej\n13E6yt1eG5BhQm6PQAFzfR8xutQdbgTSqpCESjMKRJ9aGR+0bi1o/K/An0oQEr8+\ngsKCsC/nkG+QZBCD7Ow2lAx8T+ACDT2HeUJNAOUwrnAfFf36z1IlNk15ILvxEJjg\nYIfJ9XgMIu0C5hFs8ZtakRF0htD+eJKWBMOY78Zwr6mQqhb2Iu3Y+kYoceLJCMBQ\nvHajui2W8hH5pL0QVvgnbStLZIjcF13PAAiKkq4azBLX3/AQKPPNOuo6Eowb52EJ\nQ2rkOOn+dDnbzQo7w09T1q5x1TiDhx/O50zzEVWH37ev9+sahhBtqO1I3TLQ26oq\nC3J3KXf9eug/eCAqaL7ebwjmtYVHgDf5cZaLpZhWl3wRZRaO1M7YJ9T5WsWnjbvR\nNw2lq2Vd2nSTiF7bdfZ/m8KasW0IAgyYSrvNMK92NQKFViNRCUAJBffwPR7CyHoa\nusVBFbkNdrS0pLhF/Y2jOz0DKs2zlX80e92hT9k6/yf1DcIBnP9ZdVoayefS/X9P\nD7X+DTzmoNb7tXZctDBNED/+4utaDrFPT1B+CDMCkVcySYmnQBBQF2ufY7qyslaY\ndvT/cukEnNSnTE/2Oh9aVDFvy7oyrfhtr0XHe2NE38L9eOhKirB0dRbejwIDAQAB\no0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSAGqpDwcl/ixqWRbw9u2tI\nUmxbqzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACp1gaGCIOp/\nVq4JMJcQePTZQBRSpO5qf/AJKNYQY+BOe8kxxwilF+uvhuKXB0+pDqKFzO2kgIEd\nWlMGPEwaqbeEhs989YUKcJnQ7TaRjed3Ls6EnCiGLSU1jEwB5n3bYV3id4TTAdFi\n3QyiCmSk/PDtOkjyOew11qF6F3Hs09LsuCb7rRVwVkrPZMC5YFv35s2gwgMr+bLl\n2rqlNxzYjdp5dCpn5KJ6xyyNpcFqgWzM9ak5aiJ9ouIIzemT27rLH3V3nveYrxTk\nO6BMp3LntV5TScz/klfxWSsJuulSk8APRQth1mxZcwvY+QEv2gNPNxz034NeC0Gg\nsXw5AKFs0Ni0kXIrGz+imtHE3yvVyJV9hM12G9zkJMY/FSI9hadCK+1+cVlhSMI9\nkWNAfCmzgBYKJfwYYA5TrQ4qzvxBOs2x5GprzDltyE1luKqTiHhuDwKL4JaOdB/Q\nfuF0t/aBauQjrI79jzUdmnEKTypVL/4YwQD3e0iKZa9vCB1D51q4H6ToA+v9TLW0\nk6gx3kOdEr3n6aTS32/8b0aj7zFOjRerG6ng+Kk0VqEO53TsqIeF2Hc1S40+bnJ8\nSMwfcrNxdNQkhrzIwON5FAHO2fqBxlyz+V0MOL7O8o6NXz0l4VE5I6jqAI4Es79y\noMK6g/vNpJd1IJq/p1Di3a0sH/Q/o8gx\n-----END CERTIFICATE-----`))\n\n\t// TrustAsia TLS ECC Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMw\nWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs\nIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQw\nNTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UE\nChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RB\nc2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/pVs/\nAT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDp\nguMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAw\nDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw\nDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01\nL18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqR\nOkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ==\n-----END CERTIFICATE-----`))\n\n\t// TrustAsia TLS RSA Root CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEM\nBQAwWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp\nZXMsIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcN\nMjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMG\nA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1\nc3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC\nAgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+\nNmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJ\nQ1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561\nHmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32\nft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8Tb\nxNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeX\ni9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQ\nUNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+j\nTnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DT\nbE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8\nS8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMBAAGjQjBA\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT\nMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3\nRz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4\niqME3mmL5Dw8veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt\n7DlK9RME7I10nYEKqG/odv6LTytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp\n2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHxtlotJnMnlvm5P1vQiJ3koP26TpUJ\ng3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp27RIGAAtvKLEiUUj\npQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87qqA8M\npugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongP\nXvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe\nSsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0\nly4wBOeY99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy\n323imttUQ/hHWKNddBWcwauwxzQ=\n-----END CERTIFICATE-----`))\n\n\t// Secure Global CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK\nMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x\nGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx\nMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg\nQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ\niQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa\n/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ\njnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI\nHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7\nsFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w\ngZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF\nMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw\nKaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG\nAQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L\nURYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO\nH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm\nI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY\niNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc\nf8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW\n-----END CERTIFICATE-----`))\n\n\t// SecureTrust CA\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI\nMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x\nFzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz\nMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv\ncnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz\nZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO\n0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao\nwW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj\n7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS\n8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT\nBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB\n/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg\nJYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC\nNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3\n6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/\n3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm\nD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS\nCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR\n3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=\n-----END CERTIFICATE-----`))\n\n\t// Trustwave Global Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw\nCQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x\nITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1\nc3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx\nOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI\nSWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI\nb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp\nY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\nALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn\nswuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu\n7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8\n1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW\n80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP\nJqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l\nRtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw\nhI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10\ncoos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc\nBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n\ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud\nEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud\nDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W\n0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe\nuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q\nlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB\naCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE\nsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT\nMaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe\nqu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh\nVicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8\nh6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9\nEEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK\nyeC2nOnOcXHebD8WpHk=\n-----END CERTIFICATE-----`))\n\n\t// Trustwave Global ECC P256 Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD\nVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf\nBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3\nYXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x\nNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G\nA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0\nd2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF\nQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG\nSM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN\nFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w\nDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw\nCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh\nDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7\n-----END CERTIFICATE-----`))\n\n\t// Trustwave Global ECC P384 Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD\nVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf\nBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3\nYXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x\nNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G\nA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0\nd2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF\nQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB\nBAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ\nj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF\n1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G\nA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3\nAZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC\nMGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu\nSw==\n-----END CERTIFICATE-----`))\n\n\t// XRamp Global Certification Authority\n\tmozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----\nMIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB\ngjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk\nMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY\nUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx\nNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3\ndy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy\ndmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB\ndXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6\n38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP\nKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q\nDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4\nqEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa\nJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi\nPvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P\nBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs\njVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0\neS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD\nggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR\nvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt\nqZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa\nIR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy\ni6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ\nO+7ETPTsJ3xCwnR8gooJybQDJbw=\n-----END CERTIFICATE-----`))\n}\n"
  },
  {
    "path": "common/certificate/store.go",
    "content": "package certificate\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/sagernet/fswatch\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nvar _ adapter.CertificateStore = (*Store)(nil)\n\ntype Store struct {\n\taccess                    sync.RWMutex\n\tsystemPool                *x509.CertPool\n\tcurrentPool               *x509.CertPool\n\tcertificate               string\n\tcertificatePaths          []string\n\tcertificateDirectoryPaths []string\n\twatcher                   *fswatch.Watcher\n}\n\nfunc NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {\n\tvar systemPool *x509.CertPool\n\tswitch options.Store {\n\tcase C.CertificateStoreSystem, \"\":\n\t\tsystemPool = x509.NewCertPool()\n\t\tplatformInterface := service.FromContext[adapter.PlatformInterface](ctx)\n\t\tvar systemValid bool\n\t\tif platformInterface != nil {\n\t\t\tfor _, cert := range platformInterface.SystemCertificates() {\n\t\t\t\tif systemPool.AppendCertsFromPEM([]byte(cert)) {\n\t\t\t\t\tsystemValid = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !systemValid {\n\t\t\tcertPool, err := x509.SystemCertPool()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tsystemPool = certPool\n\t\t}\n\tcase C.CertificateStoreMozilla:\n\t\tsystemPool = mozillaIncluded\n\tcase C.CertificateStoreChrome:\n\t\tsystemPool = chromeIncluded\n\tcase C.CertificateStoreNone:\n\t\tsystemPool = nil\n\tdefault:\n\t\treturn nil, E.New(\"unknown certificate store: \", options.Store)\n\t}\n\tstore := &Store{\n\t\tsystemPool:                systemPool,\n\t\tcertificate:               strings.Join(options.Certificate, \"\\n\"),\n\t\tcertificatePaths:          options.CertificatePath,\n\t\tcertificateDirectoryPaths: options.CertificateDirectoryPath,\n\t}\n\tvar watchPaths []string\n\tfor _, target := range options.CertificatePath {\n\t\twatchPaths = append(watchPaths, target)\n\t}\n\tfor _, target := range options.CertificateDirectoryPath {\n\t\twatchPaths = append(watchPaths, target)\n\t}\n\tif len(watchPaths) > 0 {\n\t\twatcher, err := fswatch.NewWatcher(fswatch.Options{\n\t\t\tPath:   watchPaths,\n\t\t\tLogger: logger,\n\t\t\tCallback: func(_ string) {\n\t\t\t\terr := store.update()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogger.Error(E.Cause(err, \"reload certificates\"))\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"fswatch: create fsnotify watcher\")\n\t\t}\n\t\tstore.watcher = watcher\n\t}\n\terr := store.update()\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"initializing certificate store\")\n\t}\n\treturn store, nil\n}\n\nfunc (s *Store) Name() string {\n\treturn \"certificate\"\n}\n\nfunc (s *Store) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif s.watcher != nil {\n\t\treturn s.watcher.Start()\n\t}\n\treturn nil\n}\n\nfunc (s *Store) Close() error {\n\tif s.watcher != nil {\n\t\treturn s.watcher.Close()\n\t}\n\treturn nil\n}\n\nfunc (s *Store) Pool() *x509.CertPool {\n\ts.access.RLock()\n\tdefer s.access.RUnlock()\n\treturn s.currentPool\n}\n\nfunc (s *Store) update() error {\n\ts.access.Lock()\n\tdefer s.access.Unlock()\n\tvar currentPool *x509.CertPool\n\tif s.systemPool == nil {\n\t\tcurrentPool = x509.NewCertPool()\n\t} else {\n\t\tcurrentPool = s.systemPool.Clone()\n\t}\n\tif s.certificate != \"\" {\n\t\tif !currentPool.AppendCertsFromPEM([]byte(s.certificate)) {\n\t\t\treturn E.New(\"invalid certificate PEM strings\")\n\t\t}\n\t}\n\tfor _, path := range s.certificatePaths {\n\t\tpemContent, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !currentPool.AppendCertsFromPEM(pemContent) {\n\t\t\treturn E.New(\"invalid certificate PEM file: \", path)\n\t\t}\n\t}\n\tvar firstErr error\n\tfor _, directoryPath := range s.certificateDirectoryPaths {\n\t\tdirectoryEntries, err := readUniqueDirectoryEntries(directoryPath)\n\t\tif err != nil {\n\t\t\tif firstErr == nil && !os.IsNotExist(err) {\n\t\t\t\tfirstErr = E.Cause(err, \"invalid certificate directory: \", directoryPath)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tfor _, directoryEntry := range directoryEntries {\n\t\t\tpemContent, err := os.ReadFile(filepath.Join(directoryPath, directoryEntry.Name()))\n\t\t\tif err == nil {\n\t\t\t\tcurrentPool.AppendCertsFromPEM(pemContent)\n\t\t\t}\n\t\t}\n\t}\n\tif firstErr != nil {\n\t\treturn firstErr\n\t}\n\ts.currentPool = currentPool\n\treturn nil\n}\n\nfunc readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {\n\tfiles, err := os.ReadDir(dir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tuniq := files[:0]\n\tfor _, f := range files {\n\t\tif !isSameDirSymlink(f, dir) {\n\t\t\tuniq = append(uniq, f)\n\t\t}\n\t}\n\treturn uniq, nil\n}\n\nfunc isSameDirSymlink(f fs.DirEntry, dir string) bool {\n\tif f.Type()&fs.ModeSymlink == 0 {\n\t\treturn false\n\t}\n\ttarget, err := os.Readlink(filepath.Join(dir, f.Name()))\n\treturn err == nil && !strings.Contains(target, \"/\")\n}\n"
  },
  {
    "path": "common/compatible/map.go",
    "content": "package compatible\n\nimport \"sync\"\n\n// Map is a generics sync.Map\ntype Map[K comparable, V any] struct {\n\tm sync.Map\n}\n\nfunc (m *Map[K, V]) Len() int {\n\tvar count int\n\tm.m.Range(func(key, value any) bool {\n\t\tcount++\n\t\treturn true\n\t})\n\treturn count\n}\n\nfunc (m *Map[K, V]) Load(key K) (V, bool) {\n\tv, ok := m.m.Load(key)\n\tif !ok {\n\t\treturn *new(V), false\n\t}\n\n\treturn v.(V), ok\n}\n\nfunc (m *Map[K, V]) Store(key K, value V) {\n\tm.m.Store(key, value)\n}\n\nfunc (m *Map[K, V]) Delete(key K) {\n\tm.m.Delete(key)\n}\n\nfunc (m *Map[K, V]) Range(f func(key K, value V) bool) {\n\tm.m.Range(func(key, value any) bool {\n\t\treturn f(key.(K), value.(V))\n\t})\n}\n\nfunc (m *Map[K, V]) LoadOrStore(key K, value V) (V, bool) {\n\tv, ok := m.m.LoadOrStore(key, value)\n\treturn v.(V), ok\n}\n\nfunc (m *Map[K, V]) LoadAndDelete(key K) (V, bool) {\n\tv, ok := m.m.LoadAndDelete(key)\n\tif !ok {\n\t\treturn *new(V), false\n\t}\n\n\treturn v.(V), ok\n}\n\nfunc New[K comparable, V any]() *Map[K, V] {\n\treturn &Map[K, V]{m: sync.Map{}}\n}\n"
  },
  {
    "path": "common/convertor/adguard/convertor.go",
    "content": "package adguard\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n)\n\ntype agdguardRuleLine struct {\n\truleLine    string\n\tisRawDomain bool\n\tisExclude   bool\n\tisSuffix    bool\n\thasStart    bool\n\thasEnd      bool\n\tisRegexp    bool\n\tisImportant bool\n}\n\nfunc ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) {\n\tscanner := bufio.NewScanner(reader)\n\tvar (\n\t\truleLines    []agdguardRuleLine\n\t\tignoredLines int\n\t)\nparseLine:\n\tfor scanner.Scan() {\n\t\truleLine := scanner.Text()\n\t\tif ruleLine == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(ruleLine, \"!\") || strings.HasPrefix(ruleLine, \"#\") {\n\t\t\tcontinue\n\t\t}\n\t\toriginRuleLine := ruleLine\n\t\tif M.IsDomainName(ruleLine) {\n\t\t\truleLines = append(ruleLines, agdguardRuleLine{\n\t\t\t\truleLine:    ruleLine,\n\t\t\t\tisRawDomain: true,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\t\thostLine, err := parseAdGuardHostLine(ruleLine)\n\t\tif err == nil {\n\t\t\tif hostLine != \"\" {\n\t\t\t\truleLines = append(ruleLines, agdguardRuleLine{\n\t\t\t\t\truleLine:    hostLine,\n\t\t\t\t\tisRawDomain: true,\n\t\t\t\t\thasStart:    true,\n\t\t\t\t\thasEnd:      true,\n\t\t\t\t})\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasSuffix(ruleLine, \"|\") {\n\t\t\truleLine = ruleLine[:len(ruleLine)-1]\n\t\t}\n\t\tvar (\n\t\t\tisExclude   bool\n\t\t\tisSuffix    bool\n\t\t\thasStart    bool\n\t\t\thasEnd      bool\n\t\t\tisRegexp    bool\n\t\t\tisImportant bool\n\t\t)\n\t\tif !strings.HasPrefix(ruleLine, \"/\") && strings.Contains(ruleLine, \"$\") {\n\t\t\tparams := common.SubstringAfter(ruleLine, \"$\")\n\t\t\tfor _, param := range strings.Split(params, \",\") {\n\t\t\t\tparamParts := strings.Split(param, \"=\")\n\t\t\t\tvar ignored bool\n\t\t\t\tif len(paramParts) > 0 && len(paramParts) <= 2 {\n\t\t\t\t\tswitch paramParts[0] {\n\t\t\t\t\tcase \"app\", \"network\":\n\t\t\t\t\t\t// maybe support by package_name/process_name\n\t\t\t\t\tcase \"dnstype\":\n\t\t\t\t\t\t// maybe support by query_type\n\t\t\t\t\tcase \"important\":\n\t\t\t\t\t\tignored = true\n\t\t\t\t\t\tisImportant = true\n\t\t\t\t\tcase \"dnsrewrite\":\n\t\t\t\t\t\tif len(paramParts) == 2 && M.ParseAddr(paramParts[1]).IsUnspecified() {\n\t\t\t\t\t\t\tignored = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !ignored {\n\t\t\t\t\tignoredLines++\n\t\t\t\t\tlogger.Debug(\"ignored unsupported rule with modifier: \", paramParts[0], \": \", originRuleLine)\n\t\t\t\t\tcontinue parseLine\n\t\t\t\t}\n\t\t\t}\n\t\t\truleLine = common.SubstringBefore(ruleLine, \"$\")\n\t\t}\n\t\tif strings.HasPrefix(ruleLine, \"@@\") {\n\t\t\truleLine = ruleLine[2:]\n\t\t\tisExclude = true\n\t\t}\n\t\tif strings.HasSuffix(ruleLine, \"|\") {\n\t\t\truleLine = ruleLine[:len(ruleLine)-1]\n\t\t}\n\t\tif strings.HasPrefix(ruleLine, \"||\") {\n\t\t\truleLine = ruleLine[2:]\n\t\t\tisSuffix = true\n\t\t} else if strings.HasPrefix(ruleLine, \"|\") {\n\t\t\truleLine = ruleLine[1:]\n\t\t\thasStart = true\n\t\t}\n\t\tif strings.HasSuffix(ruleLine, \"^\") {\n\t\t\truleLine = ruleLine[:len(ruleLine)-1]\n\t\t\thasEnd = true\n\t\t}\n\t\tif strings.HasPrefix(ruleLine, \"/\") && strings.HasSuffix(ruleLine, \"/\") {\n\t\t\truleLine = ruleLine[1 : len(ruleLine)-1]\n\t\t\tif ignoreIPCIDRRegexp(ruleLine) {\n\t\t\t\tignoredLines++\n\t\t\t\tlogger.Debug(\"ignored unsupported rule with IPCIDR regexp: \", originRuleLine)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tisRegexp = true\n\t\t} else {\n\t\t\tif strings.Contains(ruleLine, \"://\") {\n\t\t\t\truleLine = common.SubstringAfter(ruleLine, \"://\")\n\t\t\t\tisSuffix = true\n\t\t\t}\n\t\t\tif strings.Contains(ruleLine, \"/\") {\n\t\t\t\tignoredLines++\n\t\t\t\tlogger.Debug(\"ignored unsupported rule with path: \", originRuleLine)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif strings.Contains(ruleLine, \"?\") || strings.Contains(ruleLine, \"&\") {\n\t\t\t\tignoredLines++\n\t\t\t\tlogger.Debug(\"ignored unsupported rule with query: \", originRuleLine)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif strings.Contains(ruleLine, \"[\") || strings.Contains(ruleLine, \"]\") ||\n\t\t\t\tstrings.Contains(ruleLine, \"(\") || strings.Contains(ruleLine, \")\") ||\n\t\t\t\tstrings.Contains(ruleLine, \"!\") || strings.Contains(ruleLine, \"#\") {\n\t\t\t\tignoredLines++\n\t\t\t\tlogger.Debug(\"ignored unsupported cosmetic filter: \", originRuleLine)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif strings.Contains(ruleLine, \"~\") {\n\t\t\t\tignoredLines++\n\t\t\t\tlogger.Debug(\"ignored unsupported rule modifier: \", originRuleLine)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar domainCheck string\n\t\t\tif strings.HasPrefix(ruleLine, \".\") || strings.HasPrefix(ruleLine, \"-\") {\n\t\t\t\tdomainCheck = \"r\" + ruleLine\n\t\t\t} else {\n\t\t\t\tdomainCheck = ruleLine\n\t\t\t}\n\t\t\tif ruleLine == \"\" {\n\t\t\t\tignoredLines++\n\t\t\t\tlogger.Debug(\"ignored unsupported rule with empty domain\", originRuleLine)\n\t\t\t\tcontinue\n\t\t\t} else {\n\t\t\t\tdomainCheck = strings.ReplaceAll(domainCheck, \"*\", \"x\")\n\t\t\t\tif !M.IsDomainName(domainCheck) {\n\t\t\t\t\t_, ipErr := parseADGuardIPCIDRLine(ruleLine)\n\t\t\t\t\tif ipErr == nil {\n\t\t\t\t\t\tignoredLines++\n\t\t\t\t\t\tlogger.Debug(\"ignored unsupported rule with IPCIDR: \", originRuleLine)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif M.ParseSocksaddr(domainCheck).Port != 0 {\n\t\t\t\t\t\tlogger.Debug(\"ignored unsupported rule with port: \", originRuleLine)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger.Debug(\"ignored unsupported rule with invalid domain: \", originRuleLine)\n\t\t\t\t\t}\n\t\t\t\t\tignoredLines++\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\truleLines = append(ruleLines, agdguardRuleLine{\n\t\t\truleLine:    ruleLine,\n\t\t\tisExclude:   isExclude,\n\t\t\tisSuffix:    isSuffix,\n\t\t\thasStart:    hasStart,\n\t\t\thasEnd:      hasEnd,\n\t\t\tisRegexp:    isRegexp,\n\t\t\tisImportant: isImportant,\n\t\t})\n\t}\n\tif len(ruleLines) == 0 {\n\t\treturn nil, E.New(\"AdGuard rule-set is empty or all rules are unsupported\")\n\t}\n\tif common.All(ruleLines, func(it agdguardRuleLine) bool {\n\t\treturn it.isRawDomain\n\t}) {\n\t\treturn []option.HeadlessRule{\n\t\t\t{\n\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\tDefaultOptions: option.DefaultHeadlessRule{\n\t\t\t\t\tDomain: common.Map(ruleLines, func(it agdguardRuleLine) string {\n\t\t\t\t\t\treturn it.ruleLine\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\tmapDomain := func(it agdguardRuleLine) string {\n\t\truleLine := it.ruleLine\n\t\tif it.isSuffix {\n\t\t\truleLine = \"||\" + ruleLine\n\t\t} else if it.hasStart {\n\t\t\truleLine = \"|\" + ruleLine\n\t\t}\n\t\tif it.hasEnd {\n\t\t\truleLine += \"^\"\n\t\t}\n\t\treturn ruleLine\n\t}\n\n\timportantDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && !it.isRegexp && !it.isExclude }), mapDomain)\n\timportantDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && it.isRegexp && !it.isExclude }), mapDomain)\n\timportantExcludeDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && !it.isRegexp && it.isExclude }), mapDomain)\n\timportantExcludeDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && it.isRegexp && it.isExclude }), mapDomain)\n\tdomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && !it.isRegexp && !it.isExclude }), mapDomain)\n\tdomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && it.isRegexp && !it.isExclude }), mapDomain)\n\texcludeDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && !it.isRegexp && it.isExclude }), mapDomain)\n\texcludeDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && it.isRegexp && it.isExclude }), mapDomain)\n\tcurrentRule := option.HeadlessRule{\n\t\tType: C.RuleTypeDefault,\n\t\tDefaultOptions: option.DefaultHeadlessRule{\n\t\t\tAdGuardDomain: domain,\n\t\t\tDomainRegex:   domainRegex,\n\t\t},\n\t}\n\tif len(excludeDomain) > 0 || len(excludeDomainRegex) > 0 {\n\t\tcurrentRule = option.HeadlessRule{\n\t\t\tType: C.RuleTypeLogical,\n\t\t\tLogicalOptions: option.LogicalHeadlessRule{\n\t\t\t\tMode: C.LogicalTypeAnd,\n\t\t\t\tRules: []option.HeadlessRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\t\tDefaultOptions: option.DefaultHeadlessRule{\n\t\t\t\t\t\t\tAdGuardDomain: excludeDomain,\n\t\t\t\t\t\t\tDomainRegex:   excludeDomainRegex,\n\t\t\t\t\t\t\tInvert:        true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tcurrentRule,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\tif len(importantDomain) > 0 || len(importantDomainRegex) > 0 {\n\t\tcurrentRule = option.HeadlessRule{\n\t\t\tType: C.RuleTypeLogical,\n\t\t\tLogicalOptions: option.LogicalHeadlessRule{\n\t\t\t\tMode: C.LogicalTypeOr,\n\t\t\t\tRules: []option.HeadlessRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\t\tDefaultOptions: option.DefaultHeadlessRule{\n\t\t\t\t\t\t\tAdGuardDomain: importantDomain,\n\t\t\t\t\t\t\tDomainRegex:   importantDomainRegex,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tcurrentRule,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\tif len(importantExcludeDomain) > 0 || len(importantExcludeDomainRegex) > 0 {\n\t\tcurrentRule = option.HeadlessRule{\n\t\t\tType: C.RuleTypeLogical,\n\t\t\tLogicalOptions: option.LogicalHeadlessRule{\n\t\t\t\tMode: C.LogicalTypeAnd,\n\t\t\t\tRules: []option.HeadlessRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\t\tDefaultOptions: option.DefaultHeadlessRule{\n\t\t\t\t\t\t\tAdGuardDomain: importantExcludeDomain,\n\t\t\t\t\t\t\tDomainRegex:   importantExcludeDomainRegex,\n\t\t\t\t\t\t\tInvert:        true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tcurrentRule,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\tif ignoredLines > 0 {\n\t\tlogger.Info(\"parsed rules: \", len(ruleLines), \"/\", len(ruleLines)+ignoredLines)\n\t}\n\treturn []option.HeadlessRule{currentRule}, nil\n}\n\nvar ErrInvalid = E.New(\"invalid binary AdGuard rule-set\")\n\nfunc FromOptions(rules []option.HeadlessRule) ([]byte, error) {\n\tif len(rules) != 1 {\n\t\treturn nil, ErrInvalid\n\t}\n\trule := rules[0]\n\tvar (\n\t\timportantDomain             []string\n\t\timportantDomainRegex        []string\n\t\timportantExcludeDomain      []string\n\t\timportantExcludeDomainRegex []string\n\t\tdomain                      []string\n\t\tdomainRegex                 []string\n\t\texcludeDomain               []string\n\t\texcludeDomainRegex          []string\n\t)\nparse:\n\tfor {\n\t\tswitch rule.Type {\n\t\tcase C.RuleTypeLogical:\n\t\t\tif !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {\n\t\t\t\treturn nil, ErrInvalid\n\t\t\t}\n\t\t\tif rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {\n\t\t\t\tif len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {\n\t\t\t\t\timportantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain\n\t\t\t\t\timportantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex\n\t\t\t\t\tif len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {\n\t\t\t\t\t\treturn nil, ErrInvalid\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\texcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain\n\t\t\t\t\texcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex\n\t\t\t\t\tif len(excludeDomain)+len(excludeDomainRegex) == 0 {\n\t\t\t\t\t\treturn nil, ErrInvalid\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {\n\t\t\t\timportantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain\n\t\t\t\timportantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex\n\t\t\t\tif len(importantDomain)+len(importantDomainRegex) == 0 {\n\t\t\t\t\treturn nil, ErrInvalid\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn nil, ErrInvalid\n\t\t\t}\n\t\t\trule = rule.LogicalOptions.Rules[1]\n\t\tcase C.RuleTypeDefault:\n\t\t\tdomain = rule.DefaultOptions.AdGuardDomain\n\t\t\tdomainRegex = rule.DefaultOptions.DomainRegex\n\t\t\tif len(domain)+len(domainRegex) == 0 {\n\t\t\t\treturn nil, ErrInvalid\n\t\t\t}\n\t\t\tbreak parse\n\t\t}\n\t}\n\tvar output bytes.Buffer\n\tfor _, ruleLine := range importantDomain {\n\t\toutput.WriteString(ruleLine)\n\t\toutput.WriteString(\"$important\\n\")\n\t}\n\tfor _, ruleLine := range importantDomainRegex {\n\t\toutput.WriteString(\"/\")\n\t\toutput.WriteString(ruleLine)\n\t\toutput.WriteString(\"/$important\\n\")\n\n\t}\n\tfor _, ruleLine := range importantExcludeDomain {\n\t\toutput.WriteString(\"@@\")\n\t\toutput.WriteString(ruleLine)\n\t\toutput.WriteString(\"$important\\n\")\n\t}\n\tfor _, ruleLine := range importantExcludeDomainRegex {\n\t\toutput.WriteString(\"@@/\")\n\t\toutput.WriteString(ruleLine)\n\t\toutput.WriteString(\"/$important\\n\")\n\t}\n\tfor _, ruleLine := range domain {\n\t\toutput.WriteString(ruleLine)\n\t\toutput.WriteString(\"\\n\")\n\t}\n\tfor _, ruleLine := range domainRegex {\n\t\toutput.WriteString(\"/\")\n\t\toutput.WriteString(ruleLine)\n\t\toutput.WriteString(\"/\\n\")\n\t}\n\tfor _, ruleLine := range excludeDomain {\n\t\toutput.WriteString(\"@@\")\n\t\toutput.WriteString(ruleLine)\n\t\toutput.WriteString(\"\\n\")\n\t}\n\tfor _, ruleLine := range excludeDomainRegex {\n\t\toutput.WriteString(\"@@/\")\n\t\toutput.WriteString(ruleLine)\n\t\toutput.WriteString(\"/\\n\")\n\t}\n\treturn output.Bytes(), nil\n}\n\nfunc ignoreIPCIDRRegexp(ruleLine string) bool {\n\tif strings.HasPrefix(ruleLine, \"(http?:\\\\/\\\\/)\") {\n\t\truleLine = ruleLine[12:]\n\t} else if strings.HasPrefix(ruleLine, \"(https?:\\\\/\\\\/)\") {\n\t\truleLine = ruleLine[13:]\n\t} else if strings.HasPrefix(ruleLine, \"^\") {\n\t\truleLine = ruleLine[1:]\n\t}\n\treturn common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, \"\\\\.\"), 10, 8)) == nil ||\n\t\tcommon.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, \".\"), 10, 8)) == nil\n}\n\nfunc parseAdGuardHostLine(ruleLine string) (string, error) {\n\tidx := strings.Index(ruleLine, \" \")\n\tif idx == -1 {\n\t\treturn \"\", os.ErrInvalid\n\t}\n\taddress, err := netip.ParseAddr(ruleLine[:idx])\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !address.IsUnspecified() {\n\t\treturn \"\", nil\n\t}\n\tdomain := ruleLine[idx+1:]\n\tif !M.IsDomainName(domain) {\n\t\treturn \"\", E.New(\"invalid domain name: \", domain)\n\t}\n\treturn domain, nil\n}\n\nfunc parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) {\n\tvar isPrefix bool\n\tif strings.HasSuffix(ruleLine, \".\") {\n\t\tisPrefix = true\n\t\truleLine = ruleLine[:len(ruleLine)-1]\n\t}\n\truleStringParts := strings.Split(ruleLine, \".\")\n\tif len(ruleStringParts) > 4 || len(ruleStringParts) < 4 && !isPrefix {\n\t\treturn netip.Prefix{}, os.ErrInvalid\n\t}\n\truleParts := make([]uint8, 0, len(ruleStringParts))\n\tfor _, part := range ruleStringParts {\n\t\trulePart, err := strconv.ParseUint(part, 10, 8)\n\t\tif err != nil {\n\t\t\treturn netip.Prefix{}, err\n\t\t}\n\t\truleParts = append(ruleParts, uint8(rulePart))\n\t}\n\tbitLen := len(ruleParts) * 8\n\tfor len(ruleParts) < 4 {\n\t\truleParts = append(ruleParts, 0)\n\t}\n\treturn netip.PrefixFrom(netip.AddrFrom4([4]byte(ruleParts)), bitLen), nil\n}\n"
  },
  {
    "path": "common/convertor/adguard/convertor_test.go",
    "content": "package adguard\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/route/rule\"\n\t\"github.com/sagernet/sing/common/logger\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestConverter(t *testing.T) {\n\tt.Parallel()\n\truleString := `||sagernet.org^$important\n@@|sing-box.sagernet.org^$important\n||example.org^\n|example.com^\nexample.net^\n||example.edu\n||example.edu.tw^\n|example.gov\nexample.arpa\n@@|sagernet.example.org^\n`\n\trules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())\n\trequire.NoError(t, err)\n\trequire.Len(t, rules, 1)\n\trule, err := rule.NewHeadlessRule(context.Background(), rules[0])\n\trequire.NoError(t, err)\n\tmatchDomain := []string{\n\t\t\"example.org\",\n\t\t\"www.example.org\",\n\t\t\"example.com\",\n\t\t\"example.net\",\n\t\t\"isexample.net\",\n\t\t\"www.example.net\",\n\t\t\"example.edu\",\n\t\t\"example.edu.cn\",\n\t\t\"example.edu.tw\",\n\t\t\"www.example.edu\",\n\t\t\"www.example.edu.cn\",\n\t\t\"example.gov\",\n\t\t\"example.gov.cn\",\n\t\t\"example.arpa\",\n\t\t\"www.example.arpa\",\n\t\t\"isexample.arpa\",\n\t\t\"example.arpa.cn\",\n\t\t\"www.example.arpa.cn\",\n\t\t\"isexample.arpa.cn\",\n\t\t\"sagernet.org\",\n\t\t\"www.sagernet.org\",\n\t}\n\tnotMatchDomain := []string{\n\t\t\"example.org.cn\",\n\t\t\"notexample.org\",\n\t\t\"example.com.cn\",\n\t\t\"www.example.com.cn\",\n\t\t\"example.net.cn\",\n\t\t\"notexample.edu\",\n\t\t\"notexample.edu.cn\",\n\t\t\"www.example.gov\",\n\t\t\"notexample.gov\",\n\t\t\"sagernet.example.org\",\n\t\t\"sing-box.sagernet.org\",\n\t}\n\tfor _, domain := range matchDomain {\n\t\trequire.True(t, rule.Match(&adapter.InboundContext{\n\t\t\tDomain: domain,\n\t\t}), domain)\n\t}\n\tfor _, domain := range notMatchDomain {\n\t\trequire.False(t, rule.Match(&adapter.InboundContext{\n\t\t\tDomain: domain,\n\t\t}), domain)\n\t}\n\truleFromOptions, err := FromOptions(rules)\n\trequire.NoError(t, err)\n\trequire.Equal(t, ruleString, string(ruleFromOptions))\n}\n\nfunc TestHosts(t *testing.T) {\n\tt.Parallel()\n\trules, err := ToOptions(strings.NewReader(`\n127.0.0.1 localhost\n::1 localhost #[IPv6]\n0.0.0.0 google.com\n`), logger.NOP())\n\trequire.NoError(t, err)\n\trequire.Len(t, rules, 1)\n\trule, err := rule.NewHeadlessRule(context.Background(), rules[0])\n\trequire.NoError(t, err)\n\tmatchDomain := []string{\n\t\t\"google.com\",\n\t}\n\tnotMatchDomain := []string{\n\t\t\"www.google.com\",\n\t\t\"notgoogle.com\",\n\t\t\"localhost\",\n\t}\n\tfor _, domain := range matchDomain {\n\t\trequire.True(t, rule.Match(&adapter.InboundContext{\n\t\t\tDomain: domain,\n\t\t}), domain)\n\t}\n\tfor _, domain := range notMatchDomain {\n\t\trequire.False(t, rule.Match(&adapter.InboundContext{\n\t\t\tDomain: domain,\n\t\t}), domain)\n\t}\n}\n\nfunc TestSimpleHosts(t *testing.T) {\n\tt.Parallel()\n\trules, err := ToOptions(strings.NewReader(`\nexample.com\nwww.example.org\n`), logger.NOP())\n\trequire.NoError(t, err)\n\trequire.Len(t, rules, 1)\n\trule, err := rule.NewHeadlessRule(context.Background(), rules[0])\n\trequire.NoError(t, err)\n\tmatchDomain := []string{\n\t\t\"example.com\",\n\t\t\"www.example.org\",\n\t}\n\tnotMatchDomain := []string{\n\t\t\"example.com.cn\",\n\t\t\"www.example.com\",\n\t\t\"notexample.com\",\n\t\t\"example.org\",\n\t}\n\tfor _, domain := range matchDomain {\n\t\trequire.True(t, rule.Match(&adapter.InboundContext{\n\t\t\tDomain: domain,\n\t\t}), domain)\n\t}\n\tfor _, domain := range notMatchDomain {\n\t\trequire.False(t, rule.Match(&adapter.InboundContext{\n\t\t\tDomain: domain,\n\t\t}), domain)\n\t}\n}\n"
  },
  {
    "path": "common/dialer/default.go",
    "content": "package dialer\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/netip\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"github.com/database64128/tfo-go/v2\"\n)\n\nvar (\n\t_ ParallelInterfaceDialer = (*DefaultDialer)(nil)\n\t_ WireGuardListener       = (*DefaultDialer)(nil)\n)\n\ntype DefaultDialer struct {\n\tdialer4                tfo.Dialer\n\tdialer6                tfo.Dialer\n\tudpDialer4             net.Dialer\n\tudpDialer6             net.Dialer\n\tudpListener            net.ListenConfig\n\tudpAddr4               string\n\tudpAddr6               string\n\tnetns                  string\n\tconnectionManager      adapter.ConnectionManager\n\tnetworkManager         adapter.NetworkManager\n\tnetworkStrategy        *C.NetworkStrategy\n\tdefaultNetworkStrategy bool\n\tnetworkType            []C.InterfaceType\n\tfallbackNetworkType    []C.InterfaceType\n\tnetworkFallbackDelay   time.Duration\n\tnetworkLastFallback    common.TypedValue[time.Time]\n}\n\nfunc NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {\n\tconnectionManager := service.FromContext[adapter.ConnectionManager](ctx)\n\tnetworkManager := service.FromContext[adapter.NetworkManager](ctx)\n\tplatformInterface := service.FromContext[adapter.PlatformInterface](ctx)\n\n\tvar (\n\t\tdialer                 net.Dialer\n\t\tlistener               net.ListenConfig\n\t\tinterfaceFinder        control.InterfaceFinder\n\t\tnetworkStrategy        *C.NetworkStrategy\n\t\tdefaultNetworkStrategy bool\n\t\tnetworkType            []C.InterfaceType\n\t\tfallbackNetworkType    []C.InterfaceType\n\t\tnetworkFallbackDelay   time.Duration\n\t)\n\tif networkManager != nil {\n\t\tinterfaceFinder = networkManager.InterfaceFinder()\n\t} else {\n\t\tinterfaceFinder = control.NewDefaultInterfaceFinder()\n\t}\n\tif options.BindInterface != \"\" {\n\t\tif !(C.IsLinux || C.IsDarwin || C.IsWindows) {\n\t\t\treturn nil, E.New(\"`bind_interface` is only supported on Linux, macOS and Windows\")\n\t\t}\n\t\tbindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)\n\t\tdialer.Control = control.Append(dialer.Control, bindFunc)\n\t\tlistener.Control = control.Append(listener.Control, bindFunc)\n\t}\n\tif options.RoutingMark > 0 {\n\t\tif !C.IsLinux {\n\t\t\treturn nil, E.New(\"`routing_mark` is only supported on Linux\")\n\t\t}\n\t\tdialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))\n\t\tlistener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))\n\t}\n\tdisableDefaultBind := options.BindInterface != \"\" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil\n\tif disableDefaultBind || options.TCPFastOpen {\n\t\tif options.NetworkStrategy != nil || len(options.NetworkType) > 0 && options.FallbackNetworkType == nil && options.FallbackDelay == 0 {\n\t\t\treturn nil, E.New(\"`network_strategy` is conflict with `bind_interface`, `inet4_bind_address`, `inet6_bind_address` and `tcp_fast_open`\")\n\t\t}\n\t}\n\n\tif networkManager != nil {\n\t\tdefaultOptions := networkManager.DefaultOptions()\n\t\tif defaultOptions.BindInterface != \"\" && !disableDefaultBind {\n\t\t\tbindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)\n\t\t\tdialer.Control = control.Append(dialer.Control, bindFunc)\n\t\t\tlistener.Control = control.Append(listener.Control, bindFunc)\n\t\t} else if networkManager.AutoDetectInterface() && !disableDefaultBind {\n\t\t\tif platformInterface != nil {\n\t\t\t\tnetworkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)\n\t\t\t\tnetworkType = common.Map(options.NetworkType, option.InterfaceType.Build)\n\t\t\t\tfallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)\n\t\t\t\tif networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {\n\t\t\t\t\tnetworkStrategy = defaultOptions.NetworkStrategy\n\t\t\t\t\tnetworkType = defaultOptions.NetworkType\n\t\t\t\t\tfallbackNetworkType = defaultOptions.FallbackNetworkType\n\t\t\t\t}\n\t\t\t\tnetworkFallbackDelay = time.Duration(options.FallbackDelay)\n\t\t\t\tif networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {\n\t\t\t\t\tnetworkFallbackDelay = defaultOptions.FallbackDelay\n\t\t\t\t}\n\t\t\t\tif networkStrategy == nil {\n\t\t\t\t\tnetworkStrategy = common.Ptr(C.NetworkStrategyDefault)\n\t\t\t\t\tdefaultNetworkStrategy = true\n\t\t\t\t}\n\t\t\t\tbindFunc := networkManager.ProtectFunc()\n\t\t\t\tdialer.Control = control.Append(dialer.Control, bindFunc)\n\t\t\t\tlistener.Control = control.Append(listener.Control, bindFunc)\n\t\t\t} else {\n\t\t\t\tbindFunc := networkManager.AutoDetectInterfaceFunc()\n\t\t\t\tdialer.Control = control.Append(dialer.Control, bindFunc)\n\t\t\t\tlistener.Control = control.Append(listener.Control, bindFunc)\n\t\t\t}\n\t\t}\n\t\tif options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {\n\t\t\tdialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))\n\t\t\tlistener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))\n\t\t}\n\t}\n\tif networkManager != nil {\n\t\tmarkFunc := networkManager.AutoRedirectOutputMarkFunc()\n\t\tdialer.Control = control.Append(dialer.Control, markFunc)\n\t\tlistener.Control = control.Append(listener.Control, markFunc)\n\t}\n\tif options.ReuseAddr {\n\t\tlistener.Control = control.Append(listener.Control, control.ReuseAddr())\n\t}\n\tif options.ProtectPath != \"\" {\n\t\tdialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))\n\t\tlistener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))\n\t}\n\tif options.BindAddressNoPort {\n\t\tif !C.IsLinux {\n\t\t\treturn nil, E.New(\"`bind_address_no_port` is only supported on Linux\")\n\t\t}\n\t\tdialer.Control = control.Append(dialer.Control, control.BindAddressNoPort())\n\t}\n\tif options.ConnectTimeout != 0 {\n\t\tdialer.Timeout = time.Duration(options.ConnectTimeout)\n\t} else {\n\t\tdialer.Timeout = C.TCPConnectTimeout\n\t}\n\tif !options.DisableTCPKeepAlive {\n\t\tkeepIdle := time.Duration(options.TCPKeepAlive)\n\t\tif keepIdle == 0 {\n\t\t\tkeepIdle = C.TCPKeepAliveInitial\n\t\t}\n\t\tkeepInterval := time.Duration(options.TCPKeepAliveInterval)\n\t\tif keepInterval == 0 {\n\t\t\tkeepInterval = C.TCPKeepAliveInterval\n\t\t}\n\t\tdialer.KeepAliveConfig = net.KeepAliveConfig{\n\t\t\tEnable:   true,\n\t\t\tIdle:     keepIdle,\n\t\t\tInterval: keepInterval,\n\t\t}\n\t}\n\tvar udpFragment bool\n\tif options.UDPFragment != nil {\n\t\tudpFragment = *options.UDPFragment\n\t} else {\n\t\tudpFragment = options.UDPFragmentDefault\n\t}\n\tif !udpFragment {\n\t\tdialer.Control = control.Append(dialer.Control, control.DisableUDPFragment())\n\t\tlistener.Control = control.Append(listener.Control, control.DisableUDPFragment())\n\t}\n\tvar (\n\t\tdialer4    = dialer\n\t\tudpDialer4 = dialer\n\t\tudpAddr4   string\n\t)\n\tif options.Inet4BindAddress != nil {\n\t\tbindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified())\n\t\tdialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}\n\t\tudpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}\n\t\tudpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()\n\t}\n\tvar (\n\t\tdialer6    = dialer\n\t\tudpDialer6 = dialer\n\t\tudpAddr6   string\n\t)\n\tif options.Inet6BindAddress != nil {\n\t\tbindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified())\n\t\tdialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}\n\t\tudpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}\n\t\tudpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()\n\t}\n\tif options.TCPMultiPath {\n\t\tdialer4.SetMultipathTCP(true)\n\t}\n\ttcpDialer4 := tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen}\n\ttcpDialer6 := tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen}\n\treturn &DefaultDialer{\n\t\tdialer4:                tcpDialer4,\n\t\tdialer6:                tcpDialer6,\n\t\tudpDialer4:             udpDialer4,\n\t\tudpDialer6:             udpDialer6,\n\t\tudpListener:            listener,\n\t\tudpAddr4:               udpAddr4,\n\t\tudpAddr6:               udpAddr6,\n\t\tnetns:                  options.NetNs,\n\t\tconnectionManager:      connectionManager,\n\t\tnetworkManager:         networkManager,\n\t\tnetworkStrategy:        networkStrategy,\n\t\tdefaultNetworkStrategy: defaultNetworkStrategy,\n\t\tnetworkType:            networkType,\n\t\tfallbackNetworkType:    fallbackNetworkType,\n\t\tnetworkFallbackDelay:   networkFallbackDelay,\n\t}, nil\n}\n\nfunc setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefault bool) control.Func {\n\tif networkManager == nil {\n\t\treturn control.RoutingMark(mark)\n\t}\n\treturn func(network, address string, conn syscall.RawConn) error {\n\t\tif networkManager.AutoRedirectOutputMark() != 0 {\n\t\t\tif isDefault {\n\t\t\t\treturn E.New(\"`route.default_mark` is conflict with `tun.auto_redirect`\")\n\t\t\t} else {\n\t\t\t\treturn E.New(\"`routing_mark` is conflict with `tun.auto_redirect`\")\n\t\t\t}\n\t\t}\n\t\treturn control.RoutingMark(mark)(network, address, conn)\n\t}\n}\n\nfunc (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {\n\tif !address.IsValid() {\n\t\treturn nil, E.New(\"invalid address\")\n\t} else if address.IsDomain() {\n\t\treturn nil, E.New(\"domain not resolved\")\n\t}\n\tif d.networkStrategy == nil {\n\t\treturn d.trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) {\n\t\t\tswitch N.NetworkName(network) {\n\t\t\tcase N.NetworkUDP:\n\t\t\t\tif !address.IsIPv6() {\n\t\t\t\t\treturn d.udpDialer4.DialContext(ctx, network, address.String())\n\t\t\t\t} else {\n\t\t\t\t\treturn d.udpDialer6.DialContext(ctx, network, address.String())\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !address.IsIPv6() {\n\t\t\t\treturn DialSlowContext(&d.dialer4, ctx, network, address)\n\t\t\t} else {\n\t\t\t\treturn DialSlowContext(&d.dialer6, ctx, network, address)\n\t\t\t}\n\t\t}))\n\t} else {\n\t\treturn d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)\n\t}\n}\n\nfunc (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {\n\tif strategy == nil {\n\t\tstrategy = d.networkStrategy\n\t}\n\tif strategy == nil {\n\t\treturn d.DialContext(ctx, network, address)\n\t}\n\tif len(interfaceType) == 0 {\n\t\tinterfaceType = d.networkType\n\t}\n\tif len(fallbackInterfaceType) == 0 {\n\t\tfallbackInterfaceType = d.fallbackNetworkType\n\t}\n\tif fallbackDelay == 0 {\n\t\tfallbackDelay = d.networkFallbackDelay\n\t}\n\tvar dialer net.Dialer\n\tif N.NetworkName(network) == N.NetworkTCP {\n\t\tdialer = d.dialer4.Dialer\n\t} else {\n\t\tdialer = d.udpDialer4\n\t}\n\tfastFallback := time.Since(d.networkLastFallback.Load()) < C.TCPTimeout\n\tvar (\n\t\tconn      net.Conn\n\t\tisPrimary bool\n\t\terr       error\n\t)\n\tif !fastFallback {\n\t\tconn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)\n\t} else {\n\t\tconn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)\n\t}\n\tif err != nil {\n\t\t// bind interface failed on legacy xiaomi systems\n\t\tif d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {\n\t\t\td.networkStrategy = nil\n\t\t\treturn d.DialContext(ctx, network, address)\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif !fastFallback && !isPrimary {\n\t\td.networkLastFallback.Store(time.Now())\n\t}\n\treturn d.trackConn(conn, nil)\n}\n\nfunc (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tif d.networkStrategy == nil {\n\t\treturn d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {\n\t\t\tif destination.IsIPv6() {\n\t\t\t\treturn d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)\n\t\t\t} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {\n\t\t\t\treturn d.udpListener.ListenPacket(ctx, N.NetworkUDP+\"4\", d.udpAddr4)\n\t\t\t} else {\n\t\t\t\treturn d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)\n\t\t\t}\n\t\t}))\n\t} else {\n\t\treturn d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)\n\t}\n}\n\nfunc (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer {\n\tif !destination.Is6() {\n\t\treturn d.dialer6.Dialer\n\t} else {\n\t\treturn d.dialer4.Dialer\n\t}\n}\n\nfunc (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {\n\tif strategy == nil {\n\t\tstrategy = d.networkStrategy\n\t}\n\tif strategy == nil {\n\t\treturn d.ListenPacket(ctx, destination)\n\t}\n\tif len(interfaceType) == 0 {\n\t\tinterfaceType = d.networkType\n\t}\n\tif len(fallbackInterfaceType) == 0 {\n\t\tfallbackInterfaceType = d.fallbackNetworkType\n\t}\n\tif fallbackDelay == 0 {\n\t\tfallbackDelay = d.networkFallbackDelay\n\t}\n\tnetwork := N.NetworkUDP\n\tif destination.IsIPv4() && !destination.Addr.IsUnspecified() {\n\t\tnetwork += \"4\"\n\t}\n\tpacketConn, err := d.listenSerialInterfacePacket(ctx, d.udpListener, network, \"\", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)\n\tif err != nil {\n\t\t// bind interface failed on legacy xiaomi systems\n\t\tif d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {\n\t\t\td.networkStrategy = nil\n\t\t\treturn d.ListenPacket(ctx, destination)\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn d.trackPacketConn(packetConn, nil)\n}\n\nfunc (d *DefaultDialer) WireGuardControl() control.Func {\n\treturn d.udpListener.Control\n}\n\nfunc (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {\n\tif d.connectionManager == nil || err != nil {\n\t\treturn conn, err\n\t}\n\treturn d.connectionManager.TrackConn(conn), nil\n}\n\nfunc (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {\n\tif d.connectionManager == nil || err != nil {\n\t\treturn conn, err\n\t}\n\treturn d.connectionManager.TrackPacketConn(conn), nil\n}\n"
  },
  {
    "path": "common/dialer/default_parallel_interface.go",
    "content": "package dialer\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, bool, error) {\n\tprimaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)\n\tif len(primaryInterfaces)+len(fallbackInterfaces) == 0 {\n\t\treturn nil, false, E.New(\"no available network interface\")\n\t}\n\tdefaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()\n\tif fallbackDelay == 0 {\n\t\tfallbackDelay = N.DefaultFallbackDelay\n\t}\n\treturned := make(chan struct{})\n\tdefer close(returned)\n\ttype dialResult struct {\n\t\tnet.Conn\n\t\terror\n\t\tprimary bool\n\t}\n\tresults := make(chan dialResult) // unbuffered\n\tstartRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {\n\t\tperNetDialer := dialer\n\t\tif defaultInterface == nil || iif.Index != defaultInterface.Index {\n\t\t\tperNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))\n\t\t}\n\t\tconn, err := perNetDialer.DialContext(ctx, network, addr)\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase results <- dialResult{error: E.Cause(err, \"dial \", iif.Name, \" (\", iif.Index, \")\"), primary: primary}:\n\t\t\tcase <-returned:\n\t\t\t}\n\t\t} else {\n\t\t\tselect {\n\t\t\tcase results <- dialResult{Conn: conn, primary: primary}:\n\t\t\tcase <-returned:\n\t\t\t\tconn.Close()\n\t\t\t}\n\t\t}\n\t}\n\tprimaryCtx, primaryCancel := context.WithCancel(ctx)\n\tdefer primaryCancel()\n\tfor _, iif := range primaryInterfaces {\n\t\tgo startRacer(primaryCtx, true, iif)\n\t}\n\tvar (\n\t\tfallbackTimer *time.Timer\n\t\tfallbackChan  <-chan time.Time\n\t)\n\tif len(fallbackInterfaces) > 0 {\n\t\tfallbackTimer = time.NewTimer(fallbackDelay)\n\t\tdefer fallbackTimer.Stop()\n\t\tfallbackChan = fallbackTimer.C\n\t}\n\tvar errors []error\n\tfor {\n\t\tselect {\n\t\tcase <-fallbackChan:\n\t\t\tfallbackCtx, fallbackCancel := context.WithCancel(ctx)\n\t\t\tdefer fallbackCancel()\n\t\t\tfor _, iif := range fallbackInterfaces {\n\t\t\t\tgo startRacer(fallbackCtx, false, iif)\n\t\t\t}\n\t\tcase res := <-results:\n\t\t\tif res.error == nil {\n\t\t\t\treturn res.Conn, res.primary, nil\n\t\t\t}\n\t\t\terrors = append(errors, res.error)\n\t\t\tif len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {\n\t\t\t\treturn nil, false, E.Errors(errors...)\n\t\t\t}\n\t\t\tif res.primary && fallbackTimer != nil && fallbackTimer.Stop() {\n\t\t\t\tfallbackTimer.Reset(0)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) {\n\tprimaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)\n\tif len(primaryInterfaces)+len(fallbackInterfaces) == 0 {\n\t\treturn nil, false, E.New(\"no available network interface\")\n\t}\n\tdefaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()\n\tif fallbackDelay == 0 {\n\t\tfallbackDelay = N.DefaultFallbackDelay\n\t}\n\treturned := make(chan struct{})\n\tdefer close(returned)\n\ttype dialResult struct {\n\t\tnet.Conn\n\t\terror\n\t\tprimary bool\n\t}\n\tstartAt := time.Now()\n\tresults := make(chan dialResult) // unbuffered\n\tstartRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {\n\t\tperNetDialer := dialer\n\t\tif defaultInterface == nil || iif.Index != defaultInterface.Index {\n\t\t\tperNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))\n\t\t}\n\t\tconn, err := perNetDialer.DialContext(ctx, network, addr)\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase results <- dialResult{error: E.Cause(err, \"dial \", iif.Name, \" (\", iif.Index, \")\"), primary: primary}:\n\t\t\tcase <-returned:\n\t\t\t}\n\t\t} else {\n\t\t\tselect {\n\t\t\tcase results <- dialResult{Conn: conn, primary: primary}:\n\t\t\tcase <-returned:\n\t\t\t\tif primary && time.Since(startAt) <= fallbackDelay {\n\t\t\t\t\tresetFastFallback(time.Time{})\n\t\t\t\t}\n\t\t\t\tconn.Close()\n\t\t\t}\n\t\t}\n\t}\n\tfor _, iif := range primaryInterfaces {\n\t\tgo startRacer(ctx, true, iif)\n\t}\n\tfallbackCtx, fallbackCancel := context.WithCancel(ctx)\n\tdefer fallbackCancel()\n\tfor _, iif := range fallbackInterfaces {\n\t\tgo startRacer(fallbackCtx, false, iif)\n\t}\n\tvar errors []error\n\tfor {\n\t\tselect {\n\t\tcase res := <-results:\n\t\t\tif res.error == nil {\n\t\t\t\treturn res.Conn, res.primary, nil\n\t\t\t}\n\t\t\terrors = append(errors, res.error)\n\t\t\tif len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {\n\t\t\t\treturn nil, false, E.Errors(errors...)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {\n\tprimaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)\n\tif len(primaryInterfaces)+len(fallbackInterfaces) == 0 {\n\t\treturn nil, E.New(\"no available network interface\")\n\t}\n\tdefaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()\n\tvar errors []error\n\tfor _, primaryInterface := range primaryInterfaces {\n\t\tperNetListener := listener\n\t\tif defaultInterface == nil || primaryInterface.Index != defaultInterface.Index {\n\t\t\tperNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))\n\t\t}\n\t\tconn, err := perNetListener.ListenPacket(ctx, network, addr)\n\t\tif err == nil {\n\t\t\treturn conn, nil\n\t\t}\n\t\terrors = append(errors, E.Cause(err, \"listen \", primaryInterface.Name, \" (\", primaryInterface.Index, \")\"))\n\t}\n\tfor _, fallbackInterface := range fallbackInterfaces {\n\t\tperNetListener := listener\n\t\tif defaultInterface == nil || fallbackInterface.Index != defaultInterface.Index {\n\t\t\tperNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))\n\t\t}\n\t\tconn, err := perNetListener.ListenPacket(ctx, network, addr)\n\t\tif err == nil {\n\t\t\treturn conn, nil\n\t\t}\n\t\terrors = append(errors, E.Cause(err, \"listen \", fallbackInterface.Name, \" (\", fallbackInterface.Index, \")\"))\n\t}\n\treturn nil, E.Errors(errors...)\n}\n\nfunc selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {\n\tinterfaces := networkManager.NetworkInterfaces()\n\tswitch strategy {\n\tcase C.NetworkStrategyDefault:\n\t\tif len(interfaceType) == 0 {\n\t\t\tdefaultIf := networkManager.InterfaceMonitor().DefaultInterface()\n\t\t\tif defaultIf != nil {\n\t\t\t\tfor _, iif := range interfaces {\n\t\t\t\t\tif iif.Index == defaultIf.Index {\n\t\t\t\t\t\tprimaryInterfaces = append(primaryInterfaces, iif)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tprimaryInterfaces = interfaces\n\t\t\t}\n\t\t} else {\n\t\t\tprimaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {\n\t\t\t\treturn common.Contains(interfaceType, it.Type)\n\t\t\t})\n\t\t}\n\tcase C.NetworkStrategyHybrid:\n\t\tif len(interfaceType) == 0 {\n\t\t\tprimaryInterfaces = interfaces\n\t\t} else {\n\t\t\tprimaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {\n\t\t\t\treturn common.Contains(interfaceType, it.Type)\n\t\t\t})\n\t\t}\n\tcase C.NetworkStrategyFallback:\n\t\tif len(interfaceType) == 0 {\n\t\t\tdefaultIf := networkManager.InterfaceMonitor().DefaultInterface()\n\t\t\tif defaultIf != nil {\n\t\t\t\tfor _, iif := range interfaces {\n\t\t\t\t\tif iif.Index == defaultIf.Index {\n\t\t\t\t\t\tprimaryInterfaces = append(primaryInterfaces, iif)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tprimaryInterfaces = interfaces\n\t\t\t}\n\t\t} else {\n\t\t\tprimaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {\n\t\t\t\treturn common.Contains(interfaceType, it.Type)\n\t\t\t})\n\t\t}\n\t\tif len(fallbackInterfaceType) == 0 {\n\t\t\tfallbackInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {\n\t\t\t\treturn !common.Any(primaryInterfaces, func(iif adapter.NetworkInterface) bool {\n\t\t\t\t\treturn it.Index == iif.Index\n\t\t\t\t})\n\t\t\t})\n\t\t} else {\n\t\t\tfallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {\n\t\t\t\treturn common.Contains(fallbackInterfaceType, iif.Type)\n\t\t\t})\n\t\t}\n\t}\n\treturn primaryInterfaces, fallbackInterfaces\n}\n"
  },
  {
    "path": "common/dialer/default_parallel_network.go",
    "content": "package dialer\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"time\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {\n\tif len(destinationAddresses) == 0 {\n\t\tif !destination.IsIP() {\n\t\t\tpanic(\"invalid usage\")\n\t\t}\n\t\tdestinationAddresses = []netip.Addr{destination.Addr}\n\t}\n\tif parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {\n\t\treturn parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)\n\t}\n\tvar errors []error\n\tif parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {\n\t\tfor _, address := range destinationAddresses {\n\t\t\tconn, err := parallelDialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)\n\t\t\tif err == nil {\n\t\t\t\treturn conn, nil\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t} else {\n\t\tfor _, address := range destinationAddresses {\n\t\t\tconn, err := dialer.DialContext(ctx, network, M.SocksaddrFrom(address, destination.Port))\n\t\t\tif err == nil {\n\t\t\t\treturn conn, nil\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t}\n\treturn nil, E.Errors(errors...)\n}\n\nfunc DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {\n\tif len(destinationAddresses) == 0 {\n\t\tif !destination.IsIP() {\n\t\t\tpanic(\"invalid usage\")\n\t\t}\n\t\tdestinationAddresses = []netip.Addr{destination.Addr}\n\t}\n\n\tif fallbackDelay == 0 {\n\t\tfallbackDelay = N.DefaultFallbackDelay\n\t}\n\n\treturned := make(chan struct{})\n\tdefer close(returned)\n\n\taddresses4 := common.Filter(destinationAddresses, func(address netip.Addr) bool {\n\t\treturn address.Is4() || address.Is4In6()\n\t})\n\taddresses6 := common.Filter(destinationAddresses, func(address netip.Addr) bool {\n\t\treturn address.Is6() && !address.Is4In6()\n\t})\n\tif len(addresses4) == 0 || len(addresses6) == 0 {\n\t\treturn DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)\n\t}\n\tvar primaries, fallbacks []netip.Addr\n\tif preferIPv6 {\n\t\tprimaries = addresses6\n\t\tfallbacks = addresses4\n\t} else {\n\t\tprimaries = addresses4\n\t\tfallbacks = addresses6\n\t}\n\ttype dialResult struct {\n\t\tnet.Conn\n\t\terror\n\t\tprimary bool\n\t\tdone    bool\n\t}\n\tresults := make(chan dialResult) // unbuffered\n\tstartRacer := func(ctx context.Context, primary bool) {\n\t\tras := primaries\n\t\tif !primary {\n\t\t\tras = fallbacks\n\t\t}\n\t\tc, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)\n\t\tselect {\n\t\tcase results <- dialResult{Conn: c, error: err, primary: primary, done: true}:\n\t\tcase <-returned:\n\t\t\tif c != nil {\n\t\t\t\tc.Close()\n\t\t\t}\n\t\t}\n\t}\n\tvar primary, fallback dialResult\n\tprimaryCtx, primaryCancel := context.WithCancel(ctx)\n\tdefer primaryCancel()\n\tgo startRacer(primaryCtx, true)\n\tfallbackTimer := time.NewTimer(fallbackDelay)\n\tdefer fallbackTimer.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-fallbackTimer.C:\n\t\t\tfallbackCtx, fallbackCancel := context.WithCancel(ctx)\n\t\t\tdefer fallbackCancel()\n\t\t\tgo startRacer(fallbackCtx, false)\n\n\t\tcase res := <-results:\n\t\t\tif res.error == nil {\n\t\t\t\treturn res.Conn, nil\n\t\t\t}\n\t\t\tif res.primary {\n\t\t\t\tprimary = res\n\t\t\t} else {\n\t\t\t\tfallback = res\n\t\t\t}\n\t\t\tif primary.done && fallback.done {\n\t\t\t\treturn nil, primary.error\n\t\t\t}\n\t\t\tif res.primary && fallbackTimer.Stop() {\n\t\t\t\tfallbackTimer.Reset(0)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {\n\tif len(destinationAddresses) == 0 {\n\t\tif !destination.IsIP() {\n\t\t\tpanic(\"invalid usage\")\n\t\t}\n\t\tdestinationAddresses = []netip.Addr{destination.Addr}\n\t}\n\tif parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {\n\t\treturn parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)\n\t}\n\tvar errors []error\n\tif parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {\n\t\tfor _, address := range destinationAddresses {\n\t\t\tconn, err := parallelDialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)\n\t\t\tif err == nil {\n\t\t\t\treturn conn, address, nil\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t} else {\n\t\tfor _, address := range destinationAddresses {\n\t\t\tconn, err := dialer.ListenPacket(ctx, M.SocksaddrFrom(address, destination.Port))\n\t\t\tif err == nil {\n\t\t\t\treturn conn, address, nil\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t}\n\treturn nil, netip.Addr{}, E.Errors(errors...)\n}\n"
  },
  {
    "path": "common/dialer/detour.go",
    "content": "package dialer\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype DirectDialer interface {\n\tIsEmpty() bool\n}\n\ntype DetourDialer struct {\n\toutboundManager adapter.OutboundManager\n\tdetour          string\n\tlegacyDNSDialer bool\n\tdialer          N.Dialer\n\tinitOnce        sync.Once\n\tinitErr         error\n}\n\nfunc NewDetour(outboundManager adapter.OutboundManager, detour string, legacyDNSDialer bool) N.Dialer {\n\treturn &DetourDialer{\n\t\toutboundManager: outboundManager,\n\t\tdetour:          detour,\n\t\tlegacyDNSDialer: legacyDNSDialer,\n\t}\n}\n\nfunc InitializeDetour(dialer N.Dialer) error {\n\tdetourDialer, isDetour := common.Cast[*DetourDialer](dialer)\n\tif !isDetour {\n\t\treturn nil\n\t}\n\treturn common.Error(detourDialer.Dialer())\n}\n\nfunc (d *DetourDialer) Dialer() (N.Dialer, error) {\n\td.initOnce.Do(d.init)\n\treturn d.dialer, d.initErr\n}\n\nfunc (d *DetourDialer) init() {\n\tdialer, loaded := d.outboundManager.Outbound(d.detour)\n\tif !loaded {\n\t\td.initErr = E.New(\"outbound detour not found: \", d.detour)\n\t\treturn\n\t}\n\tif !d.legacyDNSDialer {\n\t\tif directDialer, isDirect := dialer.(DirectDialer); isDirect {\n\t\t\tif directDialer.IsEmpty() {\n\t\t\t\td.initErr = E.New(\"detour to an empty direct outbound makes no sense\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\td.dialer = dialer\n}\n\nfunc (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tdialer, err := d.Dialer()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dialer.DialContext(ctx, network, destination)\n}\n\nfunc (d *DetourDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tdialer, err := d.Dialer()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dialer.ListenPacket(ctx, destination)\n}\n\nfunc (d *DetourDialer) Upstream() any {\n\tdetour, _ := d.Dialer()\n\treturn detour\n}\n"
  },
  {
    "path": "common/dialer/dialer.go",
    "content": "package dialer\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/experimental/deprecated\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n)\n\ntype Options struct {\n\tContext          context.Context\n\tOptions          option.DialerOptions\n\tRemoteIsDomain   bool\n\tDirectResolver   bool\n\tResolverOnDetour bool\n\tNewDialer        bool\n\tLegacyDNSDialer  bool\n\tDirectOutbound   bool\n}\n\n// TODO: merge with NewWithOptions\nfunc New(ctx context.Context, options option.DialerOptions, remoteIsDomain bool) (N.Dialer, error) {\n\treturn NewWithOptions(Options{\n\t\tContext:        ctx,\n\t\tOptions:        options,\n\t\tRemoteIsDomain: remoteIsDomain,\n\t})\n}\n\nfunc NewWithOptions(options Options) (N.Dialer, error) {\n\tdialOptions := options.Options\n\tvar (\n\t\tdialer N.Dialer\n\t\terr    error\n\t)\n\tif dialOptions.Detour != \"\" {\n\t\toutboundManager := service.FromContext[adapter.OutboundManager](options.Context)\n\t\tif outboundManager == nil {\n\t\t\treturn nil, E.New(\"missing outbound manager\")\n\t\t}\n\t\tdialer = NewDetour(outboundManager, dialOptions.Detour, options.LegacyDNSDialer)\n\t} else {\n\t\tdialer, err = NewDefault(options.Context, dialOptions)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif options.RemoteIsDomain && (dialOptions.Detour == \"\" || options.ResolverOnDetour || dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != \"\") {\n\t\tnetworkManager := service.FromContext[adapter.NetworkManager](options.Context)\n\t\tdnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context)\n\t\tvar defaultOptions adapter.NetworkOptions\n\t\tif networkManager != nil {\n\t\t\tdefaultOptions = networkManager.DefaultOptions()\n\t\t}\n\t\tvar (\n\t\t\tserver               string\n\t\t\tdnsQueryOptions      adapter.DNSQueryOptions\n\t\t\tresolveFallbackDelay time.Duration\n\t\t)\n\t\tif dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != \"\" {\n\t\t\tvar transport adapter.DNSTransport\n\t\t\tif !options.DirectResolver {\n\t\t\t\tvar loaded bool\n\t\t\t\ttransport, loaded = dnsTransport.Transport(dialOptions.DomainResolver.Server)\n\t\t\t\tif !loaded {\n\t\t\t\t\treturn nil, E.New(\"domain resolver not found: \" + dialOptions.DomainResolver.Server)\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar strategy C.DomainStrategy\n\t\t\tif dialOptions.DomainResolver.Strategy != option.DomainStrategy(C.DomainStrategyAsIS) {\n\t\t\t\tstrategy = C.DomainStrategy(dialOptions.DomainResolver.Strategy)\n\t\t\t} else if\n\t\t\t//nolint:staticcheck\n\t\t\tdialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {\n\t\t\t\t//nolint:staticcheck\n\t\t\t\tstrategy = C.DomainStrategy(dialOptions.DomainStrategy)\n\t\t\t\tdeprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)\n\t\t\t}\n\t\t\tserver = dialOptions.DomainResolver.Server\n\t\t\tdnsQueryOptions = adapter.DNSQueryOptions{\n\t\t\t\tTransport:    transport,\n\t\t\t\tStrategy:     strategy,\n\t\t\t\tDisableCache: dialOptions.DomainResolver.DisableCache,\n\t\t\t\tRewriteTTL:   dialOptions.DomainResolver.RewriteTTL,\n\t\t\t\tClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),\n\t\t\t}\n\t\t\tresolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)\n\t\t} else if options.DirectResolver {\n\t\t\treturn nil, E.New(\"missing domain resolver for domain server address\")\n\t\t} else {\n\t\t\tif defaultOptions.DomainResolver != \"\" {\n\t\t\t\tdnsQueryOptions = defaultOptions.DomainResolveOptions\n\t\t\t\ttransport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)\n\t\t\t\tif !loaded {\n\t\t\t\t\treturn nil, E.New(\"default domain resolver not found: \" + defaultOptions.DomainResolver)\n\t\t\t\t}\n\t\t\t\tdnsQueryOptions.Transport = transport\n\t\t\t\tresolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)\n\t\t\t} else {\n\t\t\t\ttransports := dnsTransport.Transports()\n\t\t\t\tif len(transports) < 2 {\n\t\t\t\t\tdnsQueryOptions.Transport = dnsTransport.Default()\n\t\t\t\t} else if options.NewDialer {\n\t\t\t\t\treturn nil, E.New(\"missing domain resolver for domain server address\")\n\t\t\t\t} else {\n\t\t\t\t\tdeprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif\n\t\t\t//nolint:staticcheck\n\t\t\tdialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {\n\t\t\t\t//nolint:staticcheck\n\t\t\t\tdnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)\n\t\t\t\tdeprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)\n\t\t\t}\n\t\t}\n\t\tdialer = NewResolveDialer(\n\t\t\toptions.Context,\n\t\t\tdialer,\n\t\t\tdialOptions.Detour == \"\" && !dialOptions.TCPFastOpen,\n\t\t\tserver,\n\t\t\tdnsQueryOptions,\n\t\t\tresolveFallbackDelay,\n\t\t)\n\t}\n\treturn dialer, nil\n}\n\ntype ParallelInterfaceDialer interface {\n\tN.Dialer\n\tDialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)\n\tListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)\n}\n\ntype ParallelNetworkDialer interface {\n\tDialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)\n\tListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)\n}\n\ntype PacketDialerWithDestination interface {\n\tListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error)\n}\n"
  },
  {
    "path": "common/dialer/resolve.go",
    "content": "package dialer\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nvar (\n\t_ N.Dialer                = (*resolveDialer)(nil)\n\t_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)\n)\n\ntype ResolveDialer interface {\n\tN.Dialer\n\tQueryOptions() adapter.DNSQueryOptions\n}\n\ntype ParallelInterfaceResolveDialer interface {\n\tParallelInterfaceDialer\n\tQueryOptions() adapter.DNSQueryOptions\n}\n\ntype resolveDialer struct {\n\ttransport     adapter.DNSTransportManager\n\trouter        adapter.DNSRouter\n\tdialer        N.Dialer\n\tparallel      bool\n\tserver        string\n\tinitOnce      sync.Once\n\tinitErr       error\n\tqueryOptions  adapter.DNSQueryOptions\n\tfallbackDelay time.Duration\n}\n\nfunc NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer {\n\tif parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {\n\t\treturn &resolveParallelNetworkDialer{\n\t\t\tresolveDialer{\n\t\t\t\ttransport:     service.FromContext[adapter.DNSTransportManager](ctx),\n\t\t\t\trouter:        service.FromContext[adapter.DNSRouter](ctx),\n\t\t\t\tdialer:        dialer,\n\t\t\t\tparallel:      parallel,\n\t\t\t\tserver:        server,\n\t\t\t\tqueryOptions:  queryOptions,\n\t\t\t\tfallbackDelay: fallbackDelay,\n\t\t\t},\n\t\t\tparallelDialer,\n\t\t}\n\t}\n\treturn &resolveDialer{\n\t\ttransport:     service.FromContext[adapter.DNSTransportManager](ctx),\n\t\trouter:        service.FromContext[adapter.DNSRouter](ctx),\n\t\tdialer:        dialer,\n\t\tparallel:      parallel,\n\t\tserver:        server,\n\t\tqueryOptions:  queryOptions,\n\t\tfallbackDelay: fallbackDelay,\n\t}\n}\n\ntype resolveParallelNetworkDialer struct {\n\tresolveDialer\n\tdialer ParallelInterfaceDialer\n}\n\nfunc (d *resolveDialer) initialize() error {\n\td.initOnce.Do(d.initServer)\n\treturn d.initErr\n}\n\nfunc (d *resolveDialer) initServer() {\n\tif d.server == \"\" {\n\t\treturn\n\t}\n\ttransport, loaded := d.transport.Transport(d.server)\n\tif !loaded {\n\t\td.initErr = E.New(\"domain resolver not found: \" + d.server)\n\t\treturn\n\t}\n\td.queryOptions.Transport = transport\n}\n\nfunc (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\terr := d.initialize()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !destination.IsDomain() {\n\t\treturn d.dialer.DialContext(ctx, network, destination)\n\t}\n\tctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)\n\taddresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif d.parallel {\n\t\treturn N.DialParallel(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)\n\t} else {\n\t\treturn N.DialSerial(ctx, d.dialer, network, destination, addresses)\n\t}\n}\n\nfunc (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\terr := d.initialize()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !destination.IsDomain() {\n\t\treturn d.dialer.ListenPacket(ctx, destination)\n\t}\n\tctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)\n\taddresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconn, destinationAddress, err := N.ListenSerial(ctx, d.dialer, destination, addresses)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil\n}\n\nfunc (d *resolveDialer) QueryOptions() adapter.DNSQueryOptions {\n\treturn d.queryOptions\n}\n\nfunc (d *resolveDialer) Upstream() any {\n\treturn d.dialer\n}\n\nfunc (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {\n\terr := d.initialize()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !destination.IsDomain() {\n\t\treturn d.dialer.DialContext(ctx, network, destination)\n\t}\n\tctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)\n\taddresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif fallbackDelay == 0 {\n\t\tfallbackDelay = d.fallbackDelay\n\t}\n\tif d.parallel {\n\t\treturn DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)\n\t} else {\n\t\treturn DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)\n\t}\n}\n\nfunc (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {\n\terr := d.initialize()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !destination.IsDomain() {\n\t\treturn d.dialer.ListenPacket(ctx, destination)\n\t}\n\tctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)\n\taddresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif fallbackDelay == 0 {\n\t\tfallbackDelay = d.fallbackDelay\n\t}\n\tconn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil\n}\n\nfunc (d *resolveParallelNetworkDialer) QueryOptions() adapter.DNSQueryOptions {\n\treturn d.queryOptions\n}\n\nfunc (d *resolveParallelNetworkDialer) Upstream() any {\n\treturn d.dialer\n}\n"
  },
  {
    "path": "common/dialer/router.go",
    "content": "package dialer\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n)\n\ntype DefaultOutboundDialer struct {\n\toutbound adapter.OutboundManager\n}\n\nfunc NewDefaultOutbound(ctx context.Context) N.Dialer {\n\treturn &DefaultOutboundDialer{\n\t\toutbound: service.FromContext[adapter.OutboundManager](ctx),\n\t}\n}\n\nfunc (d *DefaultOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\treturn d.outbound.Default().DialContext(ctx, network, destination)\n}\n\nfunc (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn d.outbound.Default().ListenPacket(ctx, destination)\n}\n\nfunc (d *DefaultOutboundDialer) Upstream() any {\n\treturn d.outbound.Default()\n}\n"
  },
  {
    "path": "common/dialer/tfo.go",
    "content": "package dialer\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"github.com/database64128/tfo-go/v2\"\n)\n\ntype slowOpenConn struct {\n\tdialer      *tfo.Dialer\n\tctx         context.Context\n\tnetwork     string\n\tdestination M.Socksaddr\n\tconn        atomic.Pointer[net.TCPConn]\n\tcreate      chan struct{}\n\tdone        chan struct{}\n\taccess      sync.Mutex\n\tcloseOnce   sync.Once\n\terr         error\n}\n\nfunc DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tif dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {\n\t\tswitch N.NetworkName(network) {\n\t\tcase N.NetworkTCP, N.NetworkUDP:\n\t\t\treturn dialer.Dialer.DialContext(ctx, network, destination.String())\n\t\tdefault:\n\t\t\treturn dialer.Dialer.DialContext(ctx, network, destination.AddrString())\n\t\t}\n\t}\n\treturn &slowOpenConn{\n\t\tdialer:      dialer,\n\t\tctx:         ctx,\n\t\tnetwork:     network,\n\t\tdestination: destination,\n\t\tcreate:      make(chan struct{}),\n\t\tdone:        make(chan struct{}),\n\t}, nil\n}\n\nfunc (c *slowOpenConn) Read(b []byte) (n int, err error) {\n\tconn := c.conn.Load()\n\tif conn != nil {\n\t\treturn conn.Read(b)\n\t}\n\tselect {\n\tcase <-c.create:\n\t\tif c.err != nil {\n\t\t\treturn 0, c.err\n\t\t}\n\t\treturn c.conn.Load().Read(b)\n\tcase <-c.done:\n\t\treturn 0, os.ErrClosed\n\t}\n}\n\nfunc (c *slowOpenConn) Write(b []byte) (n int, err error) {\n\ttcpConn := c.conn.Load()\n\tif tcpConn != nil {\n\t\treturn tcpConn.Write(b)\n\t}\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tselect {\n\tcase <-c.create:\n\t\tif c.err != nil {\n\t\t\treturn 0, c.err\n\t\t}\n\t\treturn c.conn.Load().Write(b)\n\tcase <-c.done:\n\t\treturn 0, os.ErrClosed\n\tdefault:\n\t}\n\tconn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)\n\tif err != nil {\n\t\tc.err = err\n\t} else {\n\t\tc.conn.Store(conn.(*net.TCPConn))\n\t}\n\tn = len(b)\n\tclose(c.create)\n\treturn\n}\n\nfunc (c *slowOpenConn) Close() error {\n\tc.closeOnce.Do(func() {\n\t\tclose(c.done)\n\t\tconn := c.conn.Load()\n\t\tif conn != nil {\n\t\t\tconn.Close()\n\t\t}\n\t})\n\treturn nil\n}\n\nfunc (c *slowOpenConn) LocalAddr() net.Addr {\n\tconn := c.conn.Load()\n\tif conn == nil {\n\t\treturn M.Socksaddr{}\n\t}\n\treturn conn.LocalAddr()\n}\n\nfunc (c *slowOpenConn) RemoteAddr() net.Addr {\n\tconn := c.conn.Load()\n\tif conn == nil {\n\t\treturn M.Socksaddr{}\n\t}\n\treturn conn.RemoteAddr()\n}\n\nfunc (c *slowOpenConn) SetDeadline(t time.Time) error {\n\tconn := c.conn.Load()\n\tif conn == nil {\n\t\treturn os.ErrInvalid\n\t}\n\treturn conn.SetDeadline(t)\n}\n\nfunc (c *slowOpenConn) SetReadDeadline(t time.Time) error {\n\tconn := c.conn.Load()\n\tif conn == nil {\n\t\treturn os.ErrInvalid\n\t}\n\treturn conn.SetReadDeadline(t)\n}\n\nfunc (c *slowOpenConn) SetWriteDeadline(t time.Time) error {\n\tconn := c.conn.Load()\n\tif conn == nil {\n\t\treturn os.ErrInvalid\n\t}\n\treturn conn.SetWriteDeadline(t)\n}\n\nfunc (c *slowOpenConn) Upstream() any {\n\treturn common.PtrOrNil(c.conn.Load())\n}\n\nfunc (c *slowOpenConn) ReaderReplaceable() bool {\n\treturn c.conn.Load() != nil\n}\n\nfunc (c *slowOpenConn) WriterReplaceable() bool {\n\treturn c.conn.Load() != nil\n}\n\nfunc (c *slowOpenConn) LazyHeadroom() bool {\n\treturn c.conn.Load() == nil\n}\n\nfunc (c *slowOpenConn) NeedHandshake() bool {\n\treturn c.conn.Load() == nil\n}\n\nfunc (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {\n\tconn := c.conn.Load()\n\tif conn == nil {\n\t\tselect {\n\t\tcase <-c.create:\n\t\t\tif c.err != nil {\n\t\t\t\treturn 0, c.err\n\t\t\t}\n\t\tcase <-c.done:\n\t\t\treturn 0, c.err\n\t\t}\n\t}\n\treturn bufio.Copy(w, c.conn.Load())\n}\n"
  },
  {
    "path": "common/dialer/wireguard.go",
    "content": "package dialer\n\nimport (\n\t\"github.com/sagernet/sing/common/control\"\n)\n\ntype WireGuardListener interface {\n\tWireGuardControl() control.Func\n}\n"
  },
  {
    "path": "common/geoip/reader.go",
    "content": "package geoip\n\nimport (\n\t\"net/netip\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"github.com/oschwald/maxminddb-golang\"\n)\n\ntype Reader struct {\n\treader *maxminddb.Reader\n}\n\nfunc Open(path string) (*Reader, []string, error) {\n\tdatabase, err := maxminddb.Open(path)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif database.Metadata.DatabaseType != \"sing-geoip\" {\n\t\tdatabase.Close()\n\t\treturn nil, nil, E.New(\"incorrect database type, expected sing-geoip, got \", database.Metadata.DatabaseType)\n\t}\n\treturn &Reader{database}, database.Metadata.Languages, nil\n}\n\nfunc (r *Reader) Lookup(addr netip.Addr) string {\n\tvar code string\n\t_ = r.reader.Lookup(addr.AsSlice(), &code)\n\tif code != \"\" {\n\t\treturn code\n\t}\n\treturn \"unknown\"\n}\n\nfunc (r *Reader) Close() error {\n\treturn r.reader.Close()\n}\n"
  },
  {
    "path": "common/geosite/compat_test.go",
    "content": "package geosite\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing/common/varbin\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// Old implementation using varbin reflection-based serialization\n\nfunc oldWriteString(writer varbin.Writer, value string) error {\n\t//nolint:staticcheck\n\treturn varbin.Write(writer, binary.BigEndian, value)\n}\n\nfunc oldWriteItem(writer varbin.Writer, item Item) error {\n\t//nolint:staticcheck\n\treturn varbin.Write(writer, binary.BigEndian, item)\n}\n\nfunc oldReadString(reader varbin.Reader) (string, error) {\n\t//nolint:staticcheck\n\treturn varbin.ReadValue[string](reader, binary.BigEndian)\n}\n\nfunc oldReadItem(reader varbin.Reader) (Item, error) {\n\t//nolint:staticcheck\n\treturn varbin.ReadValue[Item](reader, binary.BigEndian)\n}\n\nfunc TestStringCompat(t *testing.T) {\n\tt.Parallel()\n\n\tcases := []struct {\n\t\tname  string\n\t\tinput string\n\t}{\n\t\t{\"empty\", \"\"},\n\t\t{\"single_char\", \"a\"},\n\t\t{\"ascii\", \"example.com\"},\n\t\t{\"utf8\", \"测试域名.中国\"},\n\t\t{\"special_chars\", \"\\x00\\xff\\n\\t\"},\n\t\t{\"127_bytes\", strings.Repeat(\"x\", 127)},\n\t\t{\"128_bytes\", strings.Repeat(\"x\", 128)},\n\t\t{\"16383_bytes\", strings.Repeat(\"x\", 16383)},\n\t\t{\"16384_bytes\", strings.Repeat(\"x\", 16384)},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\t// Old write\n\t\t\tvar oldBuf bytes.Buffer\n\t\t\terr := oldWriteString(&oldBuf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// New write\n\t\t\tvar newBuf bytes.Buffer\n\t\t\terr = writeString(&newBuf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Bytes must match\n\t\t\trequire.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),\n\t\t\t\t\"mismatch for %q\\nold: %x\\nnew: %x\", tc.name, oldBuf.Bytes(), newBuf.Bytes())\n\n\t\t\t// New write -> old read\n\t\t\treadBack, err := oldReadString(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.input, readBack)\n\n\t\t\t// Old write -> new read\n\t\t\treadBack2, err := readString(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.input, readBack2)\n\t\t})\n\t}\n}\n\nfunc TestItemCompat(t *testing.T) {\n\tt.Parallel()\n\n\t// Note: varbin.Write has a bug where struct values (not pointers) don't write their fields\n\t// because field.CanSet() returns false for non-addressable values.\n\t// The old geosite code passed Item values to varbin.Write, which silently wrote nothing.\n\t// The new code correctly writes Type + Value using manual serialization.\n\t// This test verifies the new serialization format and round-trip correctness.\n\n\tcases := []struct {\n\t\tname  string\n\t\tinput Item\n\t}{\n\t\t{\"domain_empty\", Item{Type: RuleTypeDomain, Value: \"\"}},\n\t\t{\"domain_normal\", Item{Type: RuleTypeDomain, Value: \"example.com\"}},\n\t\t{\"domain_suffix\", Item{Type: RuleTypeDomainSuffix, Value: \".example.com\"}},\n\t\t{\"domain_keyword\", Item{Type: RuleTypeDomainKeyword, Value: \"google\"}},\n\t\t{\"domain_regex\", Item{Type: RuleTypeDomainRegex, Value: `^.*\\.example\\.com$`}},\n\t\t{\"utf8_domain\", Item{Type: RuleTypeDomain, Value: \"测试.com\"}},\n\t\t{\"long_domain\", Item{Type: RuleTypeDomainSuffix, Value: strings.Repeat(\"a\", 200) + \".com\"}},\n\t\t{\"128_bytes_value\", Item{Type: RuleTypeDomain, Value: strings.Repeat(\"x\", 128)}},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\t// New write\n\t\t\tvar newBuf bytes.Buffer\n\t\t\terr := newBuf.WriteByte(byte(tc.input.Type))\n\t\t\trequire.NoError(t, err)\n\t\t\terr = writeString(&newBuf, tc.input.Value)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Verify format: Type (1 byte) + Value (uvarint len + bytes)\n\t\t\trequire.True(t, len(newBuf.Bytes()) >= 1, \"output too short\")\n\t\t\trequire.Equal(t, byte(tc.input.Type), newBuf.Bytes()[0], \"type byte mismatch\")\n\n\t\t\t// New write -> old read (varbin can read correctly when given addressable target)\n\t\t\treadBack, err := oldReadItem(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.input, readBack)\n\n\t\t\t// New write -> new read\n\t\t\treader := bufio.NewReader(bytes.NewReader(newBuf.Bytes()))\n\t\t\ttypeByte, err := reader.ReadByte()\n\t\t\trequire.NoError(t, err)\n\t\t\tvalue, err := readString(reader)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.input, Item{Type: ItemType(typeByte), Value: value})\n\t\t})\n\t}\n}\n\nfunc TestGeositeWriteReadCompat(t *testing.T) {\n\tt.Parallel()\n\n\tcases := []struct {\n\t\tname  string\n\t\tinput map[string][]Item\n\t}{\n\t\t{\n\t\t\t\"empty_map\",\n\t\t\tmap[string][]Item{},\n\t\t},\n\t\t{\n\t\t\t\"single_code_empty_items\",\n\t\t\tmap[string][]Item{\"test\": {}},\n\t\t},\n\t\t{\n\t\t\t\"single_code_single_item\",\n\t\t\tmap[string][]Item{\"test\": {{Type: RuleTypeDomain, Value: \"a.com\"}}},\n\t\t},\n\t\t{\n\t\t\t\"single_code_multi_items\",\n\t\t\tmap[string][]Item{\n\t\t\t\t\"test\": {\n\t\t\t\t\t{Type: RuleTypeDomain, Value: \"a.com\"},\n\t\t\t\t\t{Type: RuleTypeDomainSuffix, Value: \".b.com\"},\n\t\t\t\t\t{Type: RuleTypeDomainKeyword, Value: \"keyword\"},\n\t\t\t\t\t{Type: RuleTypeDomainRegex, Value: `^.*$`},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"multi_code\",\n\t\t\tmap[string][]Item{\n\t\t\t\t\"cn\": {{Type: RuleTypeDomain, Value: \"baidu.com\"}, {Type: RuleTypeDomainSuffix, Value: \".cn\"}},\n\t\t\t\t\"us\": {{Type: RuleTypeDomain, Value: \"google.com\"}},\n\t\t\t\t\"jp\": {{Type: RuleTypeDomainSuffix, Value: \".jp\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"utf8_values\",\n\t\t\tmap[string][]Item{\n\t\t\t\t\"test\": {\n\t\t\t\t\t{Type: RuleTypeDomain, Value: \"测试.中国\"},\n\t\t\t\t\t{Type: RuleTypeDomainSuffix, Value: \".テスト\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"large_items\",\n\t\t\tgenerateLargeItems(1000),\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\t// Write using new implementation\n\t\t\tvar buf bytes.Buffer\n\t\t\terr := Write(&buf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Read back and verify\n\t\t\treader, codes, err := NewReader(bytes.NewReader(buf.Bytes()))\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Verify all codes exist\n\t\t\tcodeSet := make(map[string]bool)\n\t\t\tfor _, code := range codes {\n\t\t\t\tcodeSet[code] = true\n\t\t\t}\n\t\t\tfor code := range tc.input {\n\t\t\t\trequire.True(t, codeSet[code], \"missing code: %s\", code)\n\t\t\t}\n\n\t\t\t// Verify items match\n\t\t\tfor code, expectedItems := range tc.input {\n\t\t\t\titems, err := reader.Read(code)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, expectedItems, items, \"items mismatch for code: %s\", code)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc generateLargeItems(count int) map[string][]Item {\n\titems := make([]Item, count)\n\tfor i := 0; i < count; i++ {\n\t\titems[i] = Item{\n\t\t\tType:  ItemType(i % 4),\n\t\t\tValue: strings.Repeat(\"x\", i%200) + \".com\",\n\t\t}\n\t}\n\treturn map[string][]Item{\"large\": items}\n}\n"
  },
  {
    "path": "common/geosite/geosite_test.go",
    "content": "package geosite_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/common/geosite\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGeosite(t *testing.T) {\n\tt.Parallel()\n\n\tvar buffer bytes.Buffer\n\terr := geosite.Write(&buffer, map[string][]geosite.Item{\n\t\t\"test\": {\n\t\t\t{\n\t\t\t\tType:  geosite.RuleTypeDomain,\n\t\t\t\tValue: \"example.org\",\n\t\t\t},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\treader, codes, err := geosite.NewReader(bytes.NewReader(buffer.Bytes()))\n\trequire.NoError(t, err)\n\trequire.Equal(t, []string{\"test\"}, codes)\n\titems, err := reader.Read(\"test\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, []geosite.Item{{\n\t\tType:  geosite.RuleTypeDomain,\n\t\tValue: \"example.org\",\n\t}}, items)\n}\n"
  },
  {
    "path": "common/geosite/reader.go",
    "content": "package geosite\n\nimport (\n\t\"bufio\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype Reader struct {\n\taccess         sync.Mutex\n\treader         io.ReadSeeker\n\tbufferedReader *bufio.Reader\n\tmetadataIndex  int64\n\tdomainIndex    map[string]int\n\tdomainLength   map[string]int\n}\n\nfunc Open(path string) (*Reader, []string, error) {\n\tcontent, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treader, codes, err := NewReader(content)\n\tif err != nil {\n\t\tcontent.Close()\n\t\treturn nil, nil, err\n\t}\n\treturn reader, codes, nil\n}\n\nfunc NewReader(readSeeker io.ReadSeeker) (*Reader, []string, error) {\n\treader := &Reader{\n\t\treader: readSeeker,\n\t}\n\terr := reader.readMetadata()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tcodes := make([]string, 0, len(reader.domainIndex))\n\tfor code := range reader.domainIndex {\n\t\tcodes = append(codes, code)\n\t}\n\treturn reader, codes, nil\n}\n\ntype geositeMetadata struct {\n\tCode   string\n\tIndex  uint64\n\tLength uint64\n}\n\nfunc (r *Reader) readMetadata() error {\n\tcounter := &readCounter{Reader: r.reader}\n\treader := bufio.NewReader(counter)\n\tversion, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif version != 0 {\n\t\treturn E.New(\"unknown version\")\n\t}\n\tentryLength, err := binary.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\tkeys := make([]string, entryLength)\n\tdomainIndex := make(map[string]int)\n\tdomainLength := make(map[string]int)\n\tfor i := 0; i < int(entryLength); i++ {\n\t\tvar (\n\t\t\tcode       string\n\t\t\tcodeIndex  uint64\n\t\t\tcodeLength uint64\n\t\t)\n\t\tcode, err = readString(reader)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tkeys[i] = code\n\t\tcodeIndex, err = binary.ReadUvarint(reader)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcodeLength, err = binary.ReadUvarint(reader)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdomainIndex[code] = int(codeIndex)\n\t\tdomainLength[code] = int(codeLength)\n\t}\n\tr.domainIndex = domainIndex\n\tr.domainLength = domainLength\n\tr.metadataIndex = counter.count - int64(reader.Buffered())\n\tr.bufferedReader = reader\n\treturn nil\n}\n\nfunc (r *Reader) Read(code string) ([]Item, error) {\n\tindex, exists := r.domainIndex[code]\n\tif !exists {\n\t\treturn nil, E.New(\"code \", code, \" not exists!\")\n\t}\n\t_, err := r.reader.Seek(r.metadataIndex+int64(index), io.SeekStart)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tr.bufferedReader.Reset(r.reader)\n\titemList := make([]Item, r.domainLength[code])\n\tfor i := range itemList {\n\t\ttypeByte, err := r.bufferedReader.ReadByte()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\titemList[i].Type = ItemType(typeByte)\n\t\titemList[i].Value, err = readString(r.bufferedReader)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn itemList, nil\n}\n\nfunc (r *Reader) Upstream() any {\n\treturn r.reader\n}\n\ntype readCounter struct {\n\tio.Reader\n\tcount int64\n}\n\nfunc (r *readCounter) Read(p []byte) (n int, err error) {\n\tn, err = r.Reader.Read(p)\n\tif n > 0 {\n\t\tatomic.AddInt64(&r.count, int64(n))\n\t}\n\treturn\n}\n\nfunc readString(reader io.ByteReader) (string, error) {\n\tlength, err := binary.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tbytes := make([]byte, length)\n\tfor i := range bytes {\n\t\tbytes[i], err = reader.ReadByte()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\treturn string(bytes), nil\n}\n"
  },
  {
    "path": "common/geosite/rule.go",
    "content": "package geosite\n\nimport \"github.com/sagernet/sing-box/option\"\n\ntype ItemType = uint8\n\nconst (\n\tRuleTypeDomain ItemType = iota\n\tRuleTypeDomainSuffix\n\tRuleTypeDomainKeyword\n\tRuleTypeDomainRegex\n)\n\ntype Item struct {\n\tType  ItemType\n\tValue string\n}\n\nfunc Compile(code []Item) option.DefaultRule {\n\tvar domainLength int\n\tvar domainSuffixLength int\n\tvar domainKeywordLength int\n\tvar domainRegexLength int\n\tfor _, item := range code {\n\t\tswitch item.Type {\n\t\tcase RuleTypeDomain:\n\t\t\tdomainLength++\n\t\tcase RuleTypeDomainSuffix:\n\t\t\tdomainSuffixLength++\n\t\tcase RuleTypeDomainKeyword:\n\t\t\tdomainKeywordLength++\n\t\tcase RuleTypeDomainRegex:\n\t\t\tdomainRegexLength++\n\t\t}\n\t}\n\tvar codeRule option.DefaultRule\n\tif domainLength > 0 {\n\t\tcodeRule.Domain = make([]string, 0, domainLength)\n\t}\n\tif domainSuffixLength > 0 {\n\t\tcodeRule.DomainSuffix = make([]string, 0, domainSuffixLength)\n\t}\n\tif domainKeywordLength > 0 {\n\t\tcodeRule.DomainKeyword = make([]string, 0, domainKeywordLength)\n\t}\n\tif domainRegexLength > 0 {\n\t\tcodeRule.DomainRegex = make([]string, 0, domainRegexLength)\n\t}\n\tfor _, item := range code {\n\t\tswitch item.Type {\n\t\tcase RuleTypeDomain:\n\t\t\tcodeRule.Domain = append(codeRule.Domain, item.Value)\n\t\tcase RuleTypeDomainSuffix:\n\t\t\tcodeRule.DomainSuffix = append(codeRule.DomainSuffix, item.Value)\n\t\tcase RuleTypeDomainKeyword:\n\t\t\tcodeRule.DomainKeyword = append(codeRule.DomainKeyword, item.Value)\n\t\tcase RuleTypeDomainRegex:\n\t\t\tcodeRule.DomainRegex = append(codeRule.DomainRegex, item.Value)\n\t\t}\n\t}\n\treturn codeRule\n}\n\nfunc Merge(rules []option.DefaultRule) option.DefaultRule {\n\tvar domainLength int\n\tvar domainSuffixLength int\n\tvar domainKeywordLength int\n\tvar domainRegexLength int\n\tfor _, subRule := range rules {\n\t\tdomainLength += len(subRule.Domain)\n\t\tdomainSuffixLength += len(subRule.DomainSuffix)\n\t\tdomainKeywordLength += len(subRule.DomainKeyword)\n\t\tdomainRegexLength += len(subRule.DomainRegex)\n\t}\n\tvar rule option.DefaultRule\n\tif domainLength > 0 {\n\t\trule.Domain = make([]string, 0, domainLength)\n\t}\n\tif domainSuffixLength > 0 {\n\t\trule.DomainSuffix = make([]string, 0, domainSuffixLength)\n\t}\n\tif domainKeywordLength > 0 {\n\t\trule.DomainKeyword = make([]string, 0, domainKeywordLength)\n\t}\n\tif domainRegexLength > 0 {\n\t\trule.DomainRegex = make([]string, 0, domainRegexLength)\n\t}\n\tfor _, subRule := range rules {\n\t\tif len(subRule.Domain) > 0 {\n\t\t\trule.Domain = append(rule.Domain, subRule.Domain...)\n\t\t}\n\t\tif len(subRule.DomainSuffix) > 0 {\n\t\t\trule.DomainSuffix = append(rule.DomainSuffix, subRule.DomainSuffix...)\n\t\t}\n\t\tif len(subRule.DomainKeyword) > 0 {\n\t\t\trule.DomainKeyword = append(rule.DomainKeyword, subRule.DomainKeyword...)\n\t\t}\n\t\tif len(subRule.DomainRegex) > 0 {\n\t\t\trule.DomainRegex = append(rule.DomainRegex, subRule.DomainRegex...)\n\t\t}\n\t}\n\treturn rule\n}\n"
  },
  {
    "path": "common/geosite/writer.go",
    "content": "package geosite\n\nimport (\n\t\"bytes\"\n\t\"sort\"\n\n\t\"github.com/sagernet/sing/common/varbin\"\n)\n\nfunc Write(writer varbin.Writer, domains map[string][]Item) error {\n\tkeys := make([]string, 0, len(domains))\n\tfor code := range domains {\n\t\tkeys = append(keys, code)\n\t}\n\tsort.Strings(keys)\n\n\tcontent := &bytes.Buffer{}\n\tindex := make(map[string]int)\n\tfor _, code := range keys {\n\t\tindex[code] = content.Len()\n\t\tfor _, item := range domains[code] {\n\t\t\terr := content.WriteByte(byte(item.Type))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = writeString(content, item.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\terr := writer.WriteByte(0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = varbin.WriteUvarint(writer, uint64(len(keys)))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, code := range keys {\n\t\terr = writeString(writer, code)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = varbin.WriteUvarint(writer, uint64(index[code]))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = varbin.WriteUvarint(writer, uint64(len(domains[code])))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = writer.Write(content.Bytes())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc writeString(writer varbin.Writer, value string) error {\n\t_, err := varbin.WriteUvarint(writer, uint64(len(value)))\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = writer.Write([]byte(value))\n\treturn err\n}\n"
  },
  {
    "path": "common/interrupt/conn.go",
    "content": "package interrupt\n\nimport (\n\t\"net\"\n\n\t\"github.com/sagernet/sing/common/x/list\"\n)\n\n/*type GroupedConn interface {\n\tMarkAsInternal()\n}\n\nfunc MarkAsInternal(conn any) {\n\tif groupedConn, isGroupConn := common.Cast[GroupedConn](conn); isGroupConn {\n\t\tgroupedConn.MarkAsInternal()\n\t}\n}*/\n\ntype Conn struct {\n\tnet.Conn\n\tgroup   *Group\n\telement *list.Element[*groupConnItem]\n}\n\n/*func (c *Conn) MarkAsInternal() {\n\tc.element.Value.internal = true\n}*/\n\nfunc (c *Conn) Close() error {\n\tc.group.access.Lock()\n\tdefer c.group.access.Unlock()\n\tc.group.connections.Remove(c.element)\n\treturn c.Conn.Close()\n}\n\nfunc (c *Conn) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (c *Conn) WriterReplaceable() bool {\n\treturn true\n}\n\nfunc (c *Conn) Upstream() any {\n\treturn c.Conn\n}\n\ntype PacketConn struct {\n\tnet.PacketConn\n\tgroup   *Group\n\telement *list.Element[*groupConnItem]\n}\n\n/*func (c *PacketConn) MarkAsInternal() {\n\tc.element.Value.internal = true\n}*/\n\nfunc (c *PacketConn) Close() error {\n\tc.group.access.Lock()\n\tdefer c.group.access.Unlock()\n\tc.group.connections.Remove(c.element)\n\treturn c.PacketConn.Close()\n}\n\nfunc (c *PacketConn) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (c *PacketConn) WriterReplaceable() bool {\n\treturn true\n}\n\nfunc (c *PacketConn) Upstream() any {\n\treturn c.PacketConn\n}\n"
  },
  {
    "path": "common/interrupt/context.go",
    "content": "package interrupt\n\nimport \"context\"\n\ntype contextKeyIsExternalConnection struct{}\n\nfunc ContextWithIsExternalConnection(ctx context.Context) context.Context {\n\treturn context.WithValue(ctx, contextKeyIsExternalConnection{}, true)\n}\n\nfunc IsExternalConnectionFromContext(ctx context.Context) bool {\n\treturn ctx.Value(contextKeyIsExternalConnection{}) != nil\n}\n"
  },
  {
    "path": "common/interrupt/group.go",
    "content": "package interrupt\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing/common/x/list\"\n)\n\ntype Group struct {\n\taccess      sync.Mutex\n\tconnections list.List[*groupConnItem]\n}\n\ntype groupConnItem struct {\n\tconn       io.Closer\n\tisExternal bool\n}\n\nfunc NewGroup() *Group {\n\treturn &Group{}\n}\n\nfunc (g *Group) NewConn(conn net.Conn, isExternal bool) net.Conn {\n\tg.access.Lock()\n\tdefer g.access.Unlock()\n\titem := g.connections.PushBack(&groupConnItem{conn, isExternal})\n\treturn &Conn{Conn: conn, group: g, element: item}\n}\n\nfunc (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketConn {\n\tg.access.Lock()\n\tdefer g.access.Unlock()\n\titem := g.connections.PushBack(&groupConnItem{conn, isExternal})\n\treturn &PacketConn{PacketConn: conn, group: g, element: item}\n}\n\nfunc (g *Group) Interrupt(interruptExternalConnections bool) {\n\tg.access.Lock()\n\tdefer g.access.Unlock()\n\tvar toDelete []*list.Element[*groupConnItem]\n\tfor element := g.connections.Front(); element != nil; element = element.Next() {\n\t\tif !element.Value.isExternal || interruptExternalConnections {\n\t\t\telement.Value.conn.Close()\n\t\t\ttoDelete = append(toDelete, element)\n\t\t}\n\t}\n\tfor _, element := range toDelete {\n\t\tg.connections.Remove(element)\n\t}\n}\n"
  },
  {
    "path": "common/ja3/LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2018, Open Systems AG\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "common/ja3/README.md",
    "content": "# JA3\n\nmod from: https://github.com/open-ch/ja3"
  },
  {
    "path": "common/ja3/error.go",
    "content": "// Copyright (c) 2018, Open Systems AG. All rights reserved.\n//\n// Use of this source code is governed by a BSD-style license\n// that can be found in the LICENSE file in the root of the source\n// tree.\n\npackage ja3\n\nimport \"fmt\"\n\n// Error types\nconst (\n\tLengthErr        string = \"length check %v failed\"\n\tContentTypeErr   string = \"content type not matching\"\n\tVersionErr       string = \"version check %v failed\"\n\tHandshakeTypeErr string = \"handshake type not matching\"\n\tSNITypeErr       string = \"SNI type not supported\"\n)\n\n// ParseError can be encountered while parsing a segment\ntype ParseError struct {\n\terrType string\n\tcheck   int\n}\n\nfunc (e *ParseError) Error() string {\n\tif e.errType == LengthErr || e.errType == VersionErr {\n\t\treturn fmt.Sprintf(e.errType, e.check)\n\t}\n\treturn fmt.Sprint(e.errType)\n}\n"
  },
  {
    "path": "common/ja3/ja3.go",
    "content": "// Copyright (c) 2018, Open Systems AG. All rights reserved.\n//\n// Use of this source code is governed by a BSD-style license\n// that can be found in the LICENSE file in the root of the source\n// tree.\n\npackage ja3\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n\n\t\"golang.org/x/exp/slices\"\n)\n\ntype ClientHello struct {\n\tVersion             uint16\n\tCipherSuites        []uint16\n\tExtensions          []uint16\n\tEllipticCurves      []uint16\n\tEllipticCurvePF     []uint8\n\tVersions            []uint16\n\tSignatureAlgorithms []uint16\n\tServerName          string\n\tja3ByteString       []byte\n\tja3Hash             string\n}\n\nfunc (j *ClientHello) Equals(another *ClientHello, ignoreExtensionsSequence bool) bool {\n\tif j.Version != another.Version {\n\t\treturn false\n\t}\n\tif !slices.Equal(j.CipherSuites, another.CipherSuites) {\n\t\treturn false\n\t}\n\tif !ignoreExtensionsSequence && !slices.Equal(j.Extensions, another.Extensions) {\n\t\treturn false\n\t}\n\tif ignoreExtensionsSequence && !slices.Equal(j.Extensions, another.sortedExtensions()) {\n\t\treturn false\n\t}\n\tif !slices.Equal(j.EllipticCurves, another.EllipticCurves) {\n\t\treturn false\n\t}\n\tif !slices.Equal(j.EllipticCurvePF, another.EllipticCurvePF) {\n\t\treturn false\n\t}\n\tif !slices.Equal(j.SignatureAlgorithms, another.SignatureAlgorithms) {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (j *ClientHello) sortedExtensions() []uint16 {\n\textensions := make([]uint16, len(j.Extensions))\n\tcopy(extensions, j.Extensions)\n\tslices.Sort(extensions)\n\treturn extensions\n}\n\nfunc Compute(payload []byte) (*ClientHello, error) {\n\tja3 := ClientHello{}\n\terr := ja3.parseSegment(payload)\n\treturn &ja3, err\n}\n\nfunc (j *ClientHello) String() string {\n\tif j.ja3ByteString == nil {\n\t\tj.marshalJA3()\n\t}\n\treturn string(j.ja3ByteString)\n}\n\nfunc (j *ClientHello) Hash() string {\n\tif j.ja3ByteString == nil {\n\t\tj.marshalJA3()\n\t}\n\tif j.ja3Hash == \"\" {\n\t\th := md5.Sum(j.ja3ByteString)\n\t\tj.ja3Hash = hex.EncodeToString(h[:])\n\t}\n\treturn j.ja3Hash\n}\n"
  },
  {
    "path": "common/ja3/parser.go",
    "content": "// Copyright (c) 2018, Open Systems AG. All rights reserved.\n//\n// Use of this source code is governed by a BSD-style license\n// that can be found in the LICENSE file in the root of the source\n// tree.\n\npackage ja3\n\nimport (\n\t\"encoding/binary\"\n\t\"strconv\"\n)\n\nconst (\n\t// Constants used for parsing\n\trecordLayerHeaderLen                  int    = 5\n\thandshakeHeaderLen                    int    = 6\n\trandomDataLen                         int    = 32\n\tsessionIDHeaderLen                    int    = 1\n\tcipherSuiteHeaderLen                  int    = 2\n\tcompressMethodHeaderLen               int    = 1\n\textensionsHeaderLen                   int    = 2\n\textensionHeaderLen                    int    = 4\n\tsniExtensionHeaderLen                 int    = 5\n\tecExtensionHeaderLen                  int    = 2\n\tecpfExtensionHeaderLen                int    = 1\n\tversionExtensionHeaderLen             int    = 1\n\tsignatureAlgorithmsExtensionHeaderLen int    = 2\n\tcontentType                           uint8  = 22\n\thandshakeType                         uint8  = 1\n\tsniExtensionType                      uint16 = 0\n\tsniNameDNSHostnameType                uint8  = 0\n\tecExtensionType                       uint16 = 10\n\tecpfExtensionType                     uint16 = 11\n\tversionExtensionType                  uint16 = 43\n\tsignatureAlgorithmsExtensionType      uint16 = 13\n\n\t// Versions\n\t// The bitmask covers the versions SSL3.0 to TLS1.2\n\ttlsVersionBitmask uint16 = 0xFFFC\n\ttls13             uint16 = 0x0304\n\n\t// GREASE values\n\t// The bitmask covers all GREASE values\n\tGreaseBitmask uint16 = 0x0F0F\n\n\t// Constants used for marshalling\n\tdashByte  = byte(45)\n\tcommaByte = byte(44)\n)\n\n// parseSegment to populate the corresponding ClientHello object or return an error\nfunc (j *ClientHello) parseSegment(segment []byte) error {\n\t// Check if we can decode the next fields\n\tif len(segment) < recordLayerHeaderLen {\n\t\treturn &ParseError{LengthErr, 1}\n\t}\n\n\t// Check if we have \"Content Type: Handshake (22)\"\n\tcontType := uint8(segment[0])\n\tif contType != contentType {\n\t\treturn &ParseError{errType: ContentTypeErr}\n\t}\n\n\t// Check if TLS record layer version is supported\n\ttlsRecordVersion := uint16(segment[1])<<8 | uint16(segment[2])\n\tif tlsRecordVersion&tlsVersionBitmask != 0x0300 && tlsRecordVersion != tls13 {\n\t\treturn &ParseError{VersionErr, 1}\n\t}\n\n\t// Check that the Handshake is as long as expected from the length field\n\tsegmentLen := uint16(segment[3])<<8 | uint16(segment[4])\n\tif len(segment[recordLayerHeaderLen:]) < int(segmentLen) {\n\t\treturn &ParseError{LengthErr, 2}\n\t}\n\t// Keep the Handshake messege, ignore any additional following record types\n\ths := segment[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)]\n\n\terr := j.parseHandshake(hs)\n\n\treturn err\n}\n\n// parseHandshake body\nfunc (j *ClientHello) parseHandshake(hs []byte) error {\n\t// Check if we can decode the next fields\n\tif len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {\n\t\treturn &ParseError{LengthErr, 3}\n\t}\n\n\t// Check if we have \"Handshake Type: Client Hello (1)\"\n\thandshType := uint8(hs[0])\n\tif handshType != handshakeType {\n\t\treturn &ParseError{errType: HandshakeTypeErr}\n\t}\n\n\t// Check if actual length of handshake matches (this is a great exclusion criterion for false positives,\n\t// as these fields have to match the actual length of the rest of the segment)\n\thandshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])\n\tif len(hs[4:]) != int(handshakeLen) {\n\t\treturn &ParseError{LengthErr, 4}\n\t}\n\n\t// Check if Client Hello version is supported\n\ttlsVersion := uint16(hs[4])<<8 | uint16(hs[5])\n\tif tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {\n\t\treturn &ParseError{VersionErr, 2}\n\t}\n\tj.Version = tlsVersion\n\n\t// Check if we can decode the next fields\n\tsessionIDLen := uint8(hs[38])\n\tif len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {\n\t\treturn &ParseError{LengthErr, 5}\n\t}\n\n\t// Cipher Suites\n\tcs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]\n\n\t// Check if we can decode the next fields\n\tif len(cs) < cipherSuiteHeaderLen {\n\t\treturn &ParseError{LengthErr, 6}\n\t}\n\n\tcsLen := uint16(cs[0])<<8 | uint16(cs[1])\n\tnumCiphers := int(csLen / 2)\n\tcipherSuites := make([]uint16, 0, numCiphers)\n\n\t// Check if we can decode the next fields\n\tif len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {\n\t\treturn &ParseError{LengthErr, 7}\n\t}\n\n\tfor i := 0; i < numCiphers; i++ {\n\t\tcipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1])\n\t\tcipherSuites = append(cipherSuites, cipherSuite)\n\t}\n\tj.CipherSuites = cipherSuites\n\n\t// Check if we can decode the next fields\n\tcompressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])\n\tif len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {\n\t\treturn &ParseError{LengthErr, 8}\n\t}\n\n\t// Extensions\n\texs := cs[cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen):]\n\n\terr := j.parseExtensions(exs)\n\n\treturn err\n}\n\n// parseExtensions of the handshake\nfunc (j *ClientHello) parseExtensions(exs []byte) error {\n\t// Check for no extensions, this fields header is nonexistent if no body is used\n\tif len(exs) == 0 {\n\t\treturn nil\n\t}\n\n\t// Check if we can decode the next fields\n\tif len(exs) < extensionsHeaderLen {\n\t\treturn &ParseError{LengthErr, 9}\n\t}\n\n\texsLen := uint16(exs[0])<<8 | uint16(exs[1])\n\texs = exs[extensionsHeaderLen:]\n\n\t// Check if we can decode the next fields\n\tif len(exs) < int(exsLen) {\n\t\treturn &ParseError{LengthErr, 10}\n\t}\n\n\tvar sni []byte\n\tvar extensions, ellipticCurves []uint16\n\tvar ellipticCurvePF []uint8\n\tvar versions []uint16\n\tvar signatureAlgorithms []uint16\n\tfor len(exs) > 0 {\n\n\t\t// Check if we can decode the next fields\n\t\tif len(exs) < extensionHeaderLen {\n\t\t\treturn &ParseError{LengthErr, 11}\n\t\t}\n\n\t\texType := uint16(exs[0])<<8 | uint16(exs[1])\n\t\texLen := uint16(exs[2])<<8 | uint16(exs[3])\n\t\t// Ignore any GREASE extensions\n\t\textensions = append(extensions, exType)\n\t\t// Check if we can decode the next fields\n\t\tif len(exs) < extensionHeaderLen+int(exLen) {\n\t\t\treturn &ParseError{LengthErr, 12}\n\t\t}\n\n\t\tsex := exs[extensionHeaderLen : extensionHeaderLen+int(exLen)]\n\n\t\tswitch exType {\n\t\tcase sniExtensionType: // Extensions: server_name\n\n\t\t\t// Check if we can decode the next fields\n\t\t\tif len(sex) < sniExtensionHeaderLen {\n\t\t\t\treturn &ParseError{LengthErr, 13}\n\t\t\t}\n\n\t\t\tsniType := uint8(sex[2])\n\t\t\tsniLen := uint16(sex[3])<<8 | uint16(sex[4])\n\t\t\tsex = sex[sniExtensionHeaderLen:]\n\n\t\t\t// Check if we can decode the next fields\n\t\t\tif len(sex) != int(sniLen) {\n\t\t\t\treturn &ParseError{LengthErr, 14}\n\t\t\t}\n\n\t\t\tswitch sniType {\n\t\t\tcase sniNameDNSHostnameType:\n\t\t\t\tsni = sex\n\t\t\tdefault:\n\t\t\t\treturn &ParseError{errType: SNITypeErr}\n\t\t\t}\n\t\tcase ecExtensionType: // Extensions: supported_groups\n\n\t\t\t// Check if we can decode the next fields\n\t\t\tif len(sex) < ecExtensionHeaderLen {\n\t\t\t\treturn &ParseError{LengthErr, 15}\n\t\t\t}\n\n\t\t\tecsLen := uint16(sex[0])<<8 | uint16(sex[1])\n\t\t\tnumCurves := int(ecsLen / 2)\n\t\t\tellipticCurves = make([]uint16, 0, numCurves)\n\t\t\tsex = sex[ecExtensionHeaderLen:]\n\n\t\t\t// Check if we can decode the next fields\n\t\t\tif len(sex) != int(ecsLen) {\n\t\t\t\treturn &ParseError{LengthErr, 16}\n\t\t\t}\n\n\t\t\tfor i := 0; i < numCurves; i++ {\n\t\t\t\tecType := uint16(sex[i*2])<<8 | uint16(sex[1+i*2])\n\t\t\t\tellipticCurves = append(ellipticCurves, ecType)\n\t\t\t}\n\n\t\tcase ecpfExtensionType: // Extensions: ec_point_formats\n\n\t\t\t// Check if we can decode the next fields\n\t\t\tif len(sex) < ecpfExtensionHeaderLen {\n\t\t\t\treturn &ParseError{LengthErr, 17}\n\t\t\t}\n\n\t\t\tecpfsLen := uint8(sex[0])\n\t\t\tnumPF := int(ecpfsLen)\n\t\t\tellipticCurvePF = make([]uint8, numPF)\n\t\t\tsex = sex[ecpfExtensionHeaderLen:]\n\n\t\t\t// Check if we can decode the next fields\n\t\t\tif len(sex) != numPF {\n\t\t\t\treturn &ParseError{LengthErr, 18}\n\t\t\t}\n\n\t\t\tfor i := 0; i < numPF; i++ {\n\t\t\t\tellipticCurvePF[i] = uint8(sex[i])\n\t\t\t}\n\t\tcase versionExtensionType:\n\t\t\tif len(sex) < versionExtensionHeaderLen {\n\t\t\t\treturn &ParseError{LengthErr, 19}\n\t\t\t}\n\t\t\tversionsLen := int(sex[0])\n\t\t\tfor i := 0; i < versionsLen; i += 2 {\n\t\t\t\tversions = append(versions, binary.BigEndian.Uint16(sex[1:][i:]))\n\t\t\t}\n\t\tcase signatureAlgorithmsExtensionType:\n\t\t\tif len(sex) < signatureAlgorithmsExtensionHeaderLen {\n\t\t\t\treturn &ParseError{LengthErr, 20}\n\t\t\t}\n\t\t\tssaLen := binary.BigEndian.Uint16(sex)\n\t\t\tfor i := 0; i < int(ssaLen); i += 2 {\n\t\t\t\tsignatureAlgorithms = append(signatureAlgorithms, binary.BigEndian.Uint16(sex[2:][i:]))\n\t\t\t}\n\t\t}\n\t\texs = exs[4+exLen:]\n\t}\n\tj.ServerName = string(sni)\n\tj.Extensions = extensions\n\tj.EllipticCurves = ellipticCurves\n\tj.EllipticCurvePF = ellipticCurvePF\n\tj.Versions = versions\n\tj.SignatureAlgorithms = signatureAlgorithms\n\treturn nil\n}\n\n// marshalJA3 into a byte string\nfunc (j *ClientHello) marshalJA3() {\n\t// An uint16 can contain numbers with up to 5 digits and an uint8 can contain numbers with up to 3 digits, but we\n\t// also need a byte for each separating character, except at the end.\n\tbyteStringLen := 6*(1+len(j.CipherSuites)+len(j.Extensions)+len(j.EllipticCurves)) + 4*len(j.EllipticCurvePF) - 1\n\tbyteString := make([]byte, 0, byteStringLen)\n\n\t// Version\n\tbyteString = strconv.AppendUint(byteString, uint64(j.Version), 10)\n\tbyteString = append(byteString, commaByte)\n\n\t// Cipher Suites\n\tif len(j.CipherSuites) != 0 {\n\t\tfor _, val := range j.CipherSuites {\n\t\t\tif val&GreaseBitmask != 0x0A0A {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbyteString = strconv.AppendUint(byteString, uint64(val), 10)\n\t\t\tbyteString = append(byteString, dashByte)\n\t\t}\n\t\t// Replace last dash with a comma\n\t\tbyteString[len(byteString)-1] = commaByte\n\t} else {\n\t\tbyteString = append(byteString, commaByte)\n\t}\n\n\t// Extensions\n\tif len(j.Extensions) != 0 {\n\t\tfor _, val := range j.Extensions {\n\t\t\tif val&GreaseBitmask != 0x0A0A {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbyteString = strconv.AppendUint(byteString, uint64(val), 10)\n\t\t\tbyteString = append(byteString, dashByte)\n\t\t}\n\t\t// Replace last dash with a comma\n\t\tbyteString[len(byteString)-1] = commaByte\n\t} else {\n\t\tbyteString = append(byteString, commaByte)\n\t}\n\n\t// Elliptic curves\n\tif len(j.EllipticCurves) != 0 {\n\t\tfor _, val := range j.EllipticCurves {\n\t\t\tif val&GreaseBitmask != 0x0A0A {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbyteString = strconv.AppendUint(byteString, uint64(val), 10)\n\t\t\tbyteString = append(byteString, dashByte)\n\t\t}\n\t\t// Replace last dash with a comma\n\t\tbyteString[len(byteString)-1] = commaByte\n\t} else {\n\t\tbyteString = append(byteString, commaByte)\n\t}\n\n\t// ECPF\n\tif len(j.EllipticCurvePF) != 0 {\n\t\tfor _, val := range j.EllipticCurvePF {\n\t\t\tbyteString = strconv.AppendUint(byteString, uint64(val), 10)\n\t\t\tbyteString = append(byteString, dashByte)\n\t\t}\n\t\t// Remove last dash\n\t\tbyteString = byteString[:len(byteString)-1]\n\t}\n\n\tj.ja3ByteString = byteString\n}\n"
  },
  {
    "path": "common/ktls/ktls.go",
    "content": "//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/common/badtls\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tN \"github.com/sagernet/sing/common/network\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\ntype Conn struct {\n\taTLS.Conn\n\tctx             context.Context\n\tlogger          logger.ContextLogger\n\tconn            net.Conn\n\trawConn         *badtls.RawConn\n\tsyscallConn     syscall.Conn\n\trawSyscallConn  syscall.RawConn\n\treadWaitOptions N.ReadWaitOptions\n\tkernelTx        bool\n\tkernelRx        bool\n\tpendingRxSplice bool\n}\n\nfunc NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {\n\terr := Load()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsyscallConn, isSyscallConn := N.CastReader[interface {\n\t\tio.Reader\n\t\tsyscall.Conn\n\t}](conn.NetConn())\n\tif !isSyscallConn {\n\t\treturn nil, os.ErrInvalid\n\t}\n\trawSyscallConn, err := syscallConn.SyscallConn()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trawConn, err := badtls.NewRawConn(conn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif *rawConn.Vers != tls.VersionTLS13 {\n\t\treturn nil, os.ErrInvalid\n\t}\n\tfor rawConn.RawInput.Len() > 0 {\n\t\terr = rawConn.ReadRecord()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor rawConn.Hand.Len() > 0 {\n\t\t\terr = rawConn.HandlePostHandshakeMessage()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, \"handle post-handshake messages\")\n\t\t\t}\n\t\t}\n\t}\n\tkConn := &Conn{\n\t\tConn:           conn,\n\t\tctx:            ctx,\n\t\tlogger:         logger,\n\t\tconn:           conn.NetConn(),\n\t\trawConn:        rawConn,\n\t\tsyscallConn:    syscallConn,\n\t\trawSyscallConn: rawSyscallConn,\n\t}\n\terr = kConn.setupKernel(txOffload, rxOffload)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn kConn, nil\n}\n\nfunc (c *Conn) Upstream() any {\n\treturn c.Conn\n}\n\nfunc (c *Conn) SyscallConnForRead() syscall.RawConn {\n\tif !c.kernelRx {\n\t\treturn nil\n\t}\n\tif !*c.rawConn.IsClient {\n\t\tc.logger.WarnContext(c.ctx, \"ktls: RX splice is unavailable on the server size, since it will cause an unknown failure\")\n\t\treturn nil\n\t}\n\tc.logger.DebugContext(c.ctx, \"ktls: RX splice requested\")\n\treturn c.rawSyscallConn\n}\n\nfunc (c *Conn) HandleSyscallReadError(inputErr error) ([]byte, error) {\n\tif errors.Is(inputErr, unix.EINVAL) {\n\t\tc.pendingRxSplice = true\n\t\terr := c.readRecord()\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"ktls: handle non-application-data record\")\n\t\t}\n\t\tvar input bytes.Buffer\n\t\tif c.rawConn.Input.Len() > 0 {\n\t\t\t_, err = c.rawConn.Input.WriteTo(&input)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\treturn input.Bytes(), nil\n\t} else if errors.Is(inputErr, unix.EBADMSG) {\n\t\treturn nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertBadRecordMAC))\n\t} else {\n\t\treturn nil, E.Cause(inputErr, \"ktls: unexpected errno\")\n\t}\n}\n\nfunc (c *Conn) SyscallConnForWrite() syscall.RawConn {\n\tif !c.kernelTx {\n\t\treturn nil\n\t}\n\tc.logger.DebugContext(c.ctx, \"ktls: TX splice requested\")\n\treturn c.rawSyscallConn\n}\n"
  },
  {
    "path": "common/ktls/ktls_alert.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n)\n\nconst (\n\t// alert level\n\talertLevelWarning = 1\n\talertLevelError   = 2\n)\n\nconst (\n\talertCloseNotify                  = 0\n\talertUnexpectedMessage            = 10\n\talertBadRecordMAC                 = 20\n\talertDecryptionFailed             = 21\n\talertRecordOverflow               = 22\n\talertDecompressionFailure         = 30\n\talertHandshakeFailure             = 40\n\talertBadCertificate               = 42\n\talertUnsupportedCertificate       = 43\n\talertCertificateRevoked           = 44\n\talertCertificateExpired           = 45\n\talertCertificateUnknown           = 46\n\talertIllegalParameter             = 47\n\talertUnknownCA                    = 48\n\talertAccessDenied                 = 49\n\talertDecodeError                  = 50\n\talertDecryptError                 = 51\n\talertExportRestriction            = 60\n\talertProtocolVersion              = 70\n\talertInsufficientSecurity         = 71\n\talertInternalError                = 80\n\talertInappropriateFallback        = 86\n\talertUserCanceled                 = 90\n\talertNoRenegotiation              = 100\n\talertMissingExtension             = 109\n\talertUnsupportedExtension         = 110\n\talertCertificateUnobtainable      = 111\n\talertUnrecognizedName             = 112\n\talertBadCertificateStatusResponse = 113\n\talertBadCertificateHashValue      = 114\n\talertUnknownPSKIdentity           = 115\n\talertCertificateRequired          = 116\n\talertNoApplicationProtocol        = 120\n\talertECHRequired                  = 121\n)\n\nfunc (c *Conn) sendAlertLocked(err uint8) error {\n\tswitch err {\n\tcase alertNoRenegotiation, alertCloseNotify:\n\t\tc.rawConn.Tmp[0] = alertLevelWarning\n\tdefault:\n\t\tc.rawConn.Tmp[0] = alertLevelError\n\t}\n\tc.rawConn.Tmp[1] = byte(err)\n\n\t_, writeErr := c.writeRecordLocked(recordTypeAlert, c.rawConn.Tmp[0:2])\n\tif err == alertCloseNotify {\n\t\t// closeNotify is a special case in that it isn't an error.\n\t\treturn writeErr\n\t}\n\n\treturn c.rawConn.Out.SetErrorLocked(&net.OpError{Op: \"local error\", Err: tls.AlertError(err)})\n}\n\n// sendAlert sends a TLS alert message.\nfunc (c *Conn) sendAlert(err uint8) error {\n\tc.rawConn.Out.Lock()\n\tdefer c.rawConn.Out.Unlock()\n\treturn c.sendAlertLocked(err)\n}\n"
  },
  {
    "path": "common/ktls/ktls_cipher_suites_linux.go",
    "content": "// Copyright 2010 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nimport (\n\t\"crypto/tls\"\n\t\"unsafe\"\n\n\t\"github.com/sagernet/sing-box/common/badtls\"\n)\n\ntype kernelCryptoCipherType uint16\n\nconst (\n\tTLS_CIPHER_AES_GCM_128              kernelCryptoCipherType = 51\n\tTLS_CIPHER_AES_GCM_128_IV_SIZE      kernelCryptoCipherType = 8\n\tTLS_CIPHER_AES_GCM_128_KEY_SIZE     kernelCryptoCipherType = 16\n\tTLS_CIPHER_AES_GCM_128_SALT_SIZE    kernelCryptoCipherType = 4\n\tTLS_CIPHER_AES_GCM_128_TAG_SIZE     kernelCryptoCipherType = 16\n\tTLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8\n\n\tTLS_CIPHER_AES_GCM_256              kernelCryptoCipherType = 52\n\tTLS_CIPHER_AES_GCM_256_IV_SIZE      kernelCryptoCipherType = 8\n\tTLS_CIPHER_AES_GCM_256_KEY_SIZE     kernelCryptoCipherType = 32\n\tTLS_CIPHER_AES_GCM_256_SALT_SIZE    kernelCryptoCipherType = 4\n\tTLS_CIPHER_AES_GCM_256_TAG_SIZE     kernelCryptoCipherType = 16\n\tTLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE kernelCryptoCipherType = 8\n\n\tTLS_CIPHER_AES_CCM_128              kernelCryptoCipherType = 53\n\tTLS_CIPHER_AES_CCM_128_IV_SIZE      kernelCryptoCipherType = 8\n\tTLS_CIPHER_AES_CCM_128_KEY_SIZE     kernelCryptoCipherType = 16\n\tTLS_CIPHER_AES_CCM_128_SALT_SIZE    kernelCryptoCipherType = 4\n\tTLS_CIPHER_AES_CCM_128_TAG_SIZE     kernelCryptoCipherType = 16\n\tTLS_CIPHER_AES_CCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8\n\n\tTLS_CIPHER_CHACHA20_POLY1305              kernelCryptoCipherType = 54\n\tTLS_CIPHER_CHACHA20_POLY1305_IV_SIZE      kernelCryptoCipherType = 12\n\tTLS_CIPHER_CHACHA20_POLY1305_KEY_SIZE     kernelCryptoCipherType = 32\n\tTLS_CIPHER_CHACHA20_POLY1305_SALT_SIZE    kernelCryptoCipherType = 0\n\tTLS_CIPHER_CHACHA20_POLY1305_TAG_SIZE     kernelCryptoCipherType = 16\n\tTLS_CIPHER_CHACHA20_POLY1305_REC_SEQ_SIZE kernelCryptoCipherType = 8\n\n\t// TLS_CIPHER_SM4_GCM              kernelCryptoCipherType = 55\n\t// TLS_CIPHER_SM4_GCM_IV_SIZE      kernelCryptoCipherType = 8\n\t// TLS_CIPHER_SM4_GCM_KEY_SIZE     kernelCryptoCipherType = 16\n\t// TLS_CIPHER_SM4_GCM_SALT_SIZE    kernelCryptoCipherType = 4\n\t// TLS_CIPHER_SM4_GCM_TAG_SIZE     kernelCryptoCipherType = 16\n\t// TLS_CIPHER_SM4_GCM_REC_SEQ_SIZE kernelCryptoCipherType = 8\n\n\t// TLS_CIPHER_SM4_CCM              kernelCryptoCipherType = 56\n\t// TLS_CIPHER_SM4_CCM_IV_SIZE      kernelCryptoCipherType = 8\n\t// TLS_CIPHER_SM4_CCM_KEY_SIZE     kernelCryptoCipherType = 16\n\t// TLS_CIPHER_SM4_CCM_SALT_SIZE    kernelCryptoCipherType = 4\n\t// TLS_CIPHER_SM4_CCM_TAG_SIZE     kernelCryptoCipherType = 16\n\t// TLS_CIPHER_SM4_CCM_REC_SEQ_SIZE kernelCryptoCipherType = 8\n\n\tTLS_CIPHER_ARIA_GCM_128              kernelCryptoCipherType = 57\n\tTLS_CIPHER_ARIA_GCM_128_IV_SIZE      kernelCryptoCipherType = 8\n\tTLS_CIPHER_ARIA_GCM_128_KEY_SIZE     kernelCryptoCipherType = 16\n\tTLS_CIPHER_ARIA_GCM_128_SALT_SIZE    kernelCryptoCipherType = 4\n\tTLS_CIPHER_ARIA_GCM_128_TAG_SIZE     kernelCryptoCipherType = 16\n\tTLS_CIPHER_ARIA_GCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8\n\n\tTLS_CIPHER_ARIA_GCM_256              kernelCryptoCipherType = 58\n\tTLS_CIPHER_ARIA_GCM_256_IV_SIZE      kernelCryptoCipherType = 8\n\tTLS_CIPHER_ARIA_GCM_256_KEY_SIZE     kernelCryptoCipherType = 32\n\tTLS_CIPHER_ARIA_GCM_256_SALT_SIZE    kernelCryptoCipherType = 4\n\tTLS_CIPHER_ARIA_GCM_256_TAG_SIZE     kernelCryptoCipherType = 16\n\tTLS_CIPHER_ARIA_GCM_256_REC_SEQ_SIZE kernelCryptoCipherType = 8\n)\n\ntype kernelCrypto interface {\n\tString() string\n}\n\ntype kernelCryptoInfo struct {\n\tversion     uint16\n\tcipher_type kernelCryptoCipherType\n}\n\nvar _ kernelCrypto = &kernelCryptoAES128GCM{}\n\ntype kernelCryptoAES128GCM struct {\n\tkernelCryptoInfo\n\tiv      [TLS_CIPHER_AES_GCM_128_IV_SIZE]byte\n\tkey     [TLS_CIPHER_AES_GCM_128_KEY_SIZE]byte\n\tsalt    [TLS_CIPHER_AES_GCM_128_SALT_SIZE]byte\n\trec_seq [TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE]byte\n}\n\nfunc (crypto *kernelCryptoAES128GCM) String() string {\n\tcrypto.cipher_type = TLS_CIPHER_AES_GCM_128\n\treturn string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])\n}\n\nvar _ kernelCrypto = &kernelCryptoAES256GCM{}\n\ntype kernelCryptoAES256GCM struct {\n\tkernelCryptoInfo\n\tiv      [TLS_CIPHER_AES_GCM_256_IV_SIZE]byte\n\tkey     [TLS_CIPHER_AES_GCM_256_KEY_SIZE]byte\n\tsalt    [TLS_CIPHER_AES_GCM_256_SALT_SIZE]byte\n\trec_seq [TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE]byte\n}\n\nfunc (crypto *kernelCryptoAES256GCM) String() string {\n\tcrypto.cipher_type = TLS_CIPHER_AES_GCM_256\n\treturn string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])\n}\n\nvar _ kernelCrypto = &kernelCryptoAES128CCM{}\n\ntype kernelCryptoAES128CCM struct {\n\tkernelCryptoInfo\n\tiv      [TLS_CIPHER_AES_CCM_128_IV_SIZE]byte\n\tkey     [TLS_CIPHER_AES_CCM_128_KEY_SIZE]byte\n\tsalt    [TLS_CIPHER_AES_CCM_128_SALT_SIZE]byte\n\trec_seq [TLS_CIPHER_AES_CCM_128_REC_SEQ_SIZE]byte\n}\n\nfunc (crypto *kernelCryptoAES128CCM) String() string {\n\tcrypto.cipher_type = TLS_CIPHER_AES_CCM_128\n\treturn string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])\n}\n\nvar _ kernelCrypto = &kernelCryptoChacha20Poly1035{}\n\ntype kernelCryptoChacha20Poly1035 struct {\n\tkernelCryptoInfo\n\tiv      [TLS_CIPHER_CHACHA20_POLY1305_IV_SIZE]byte\n\tkey     [TLS_CIPHER_CHACHA20_POLY1305_KEY_SIZE]byte\n\tsalt    [TLS_CIPHER_CHACHA20_POLY1305_SALT_SIZE]byte\n\trec_seq [TLS_CIPHER_CHACHA20_POLY1305_REC_SEQ_SIZE]byte\n}\n\nfunc (crypto *kernelCryptoChacha20Poly1035) String() string {\n\tcrypto.cipher_type = TLS_CIPHER_CHACHA20_POLY1305\n\treturn string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])\n}\n\n// var _ kernelCrypto = &kernelCryptoSM4GCM{}\n\n// type kernelCryptoSM4GCM struct {\n// \tkernelCryptoInfo\n// \tiv      [TLS_CIPHER_SM4_GCM_IV_SIZE]byte\n// \tkey     [TLS_CIPHER_SM4_GCM_KEY_SIZE]byte\n// \tsalt    [TLS_CIPHER_SM4_GCM_SALT_SIZE]byte\n// \trec_seq [TLS_CIPHER_SM4_GCM_REC_SEQ_SIZE]byte\n// }\n\n// func (crypto *kernelCryptoSM4GCM) String() string {\n// \tcrypto.cipher_type = TLS_CIPHER_SM4_GCM\n// \treturn string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])\n// }\n\n// var _ kernelCrypto = &kernelCryptoSM4CCM{}\n\n// type kernelCryptoSM4CCM struct {\n// \tkernelCryptoInfo\n// \tiv      [TLS_CIPHER_SM4_CCM_IV_SIZE]byte\n// \tkey     [TLS_CIPHER_SM4_CCM_KEY_SIZE]byte\n// \tsalt    [TLS_CIPHER_SM4_CCM_SALT_SIZE]byte\n// \trec_seq [TLS_CIPHER_SM4_CCM_REC_SEQ_SIZE]byte\n// }\n\n// func (crypto *kernelCryptoSM4CCM) String() string {\n// \tcrypto.cipher_type = TLS_CIPHER_SM4_CCM\n// \treturn string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])\n// }\n\nvar _ kernelCrypto = &kernelCryptoARIA128GCM{}\n\ntype kernelCryptoARIA128GCM struct {\n\tkernelCryptoInfo\n\tiv      [TLS_CIPHER_ARIA_GCM_128_IV_SIZE]byte\n\tkey     [TLS_CIPHER_ARIA_GCM_128_KEY_SIZE]byte\n\tsalt    [TLS_CIPHER_ARIA_GCM_128_SALT_SIZE]byte\n\trec_seq [TLS_CIPHER_ARIA_GCM_128_REC_SEQ_SIZE]byte\n}\n\nfunc (crypto *kernelCryptoARIA128GCM) String() string {\n\tcrypto.cipher_type = TLS_CIPHER_ARIA_GCM_128\n\treturn string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])\n}\n\nvar _ kernelCrypto = &kernelCryptoARIA256GCM{}\n\ntype kernelCryptoARIA256GCM struct {\n\tkernelCryptoInfo\n\tiv      [TLS_CIPHER_ARIA_GCM_256_IV_SIZE]byte\n\tkey     [TLS_CIPHER_ARIA_GCM_256_KEY_SIZE]byte\n\tsalt    [TLS_CIPHER_ARIA_GCM_256_SALT_SIZE]byte\n\trec_seq [TLS_CIPHER_ARIA_GCM_256_REC_SEQ_SIZE]byte\n}\n\nfunc (crypto *kernelCryptoARIA256GCM) String() string {\n\tcrypto.cipher_type = TLS_CIPHER_ARIA_GCM_256\n\treturn string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])\n}\n\nfunc kernelCipher(kernel *Support, hc *badtls.RawHalfConn, cipherSuite uint16, isRX bool) kernelCrypto {\n\tif !kernel.TLS {\n\t\treturn nil\n\t}\n\n\tswitch *hc.Version {\n\tcase tls.VersionTLS12:\n\t\tif isRX && !kernel.TLS_Version13_RX {\n\t\t\treturn nil\n\t\t}\n\n\tcase tls.VersionTLS13:\n\t\tif !kernel.TLS_Version13 {\n\t\t\treturn nil\n\t\t}\n\n\t\tif isRX && !kernel.TLS_Version13_RX {\n\t\t\treturn nil\n\t\t}\n\n\tdefault:\n\t\treturn nil\n\t}\n\n\tvar key, iv []byte\n\tif *hc.Version == tls.VersionTLS13 {\n\t\tkey, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), *hc.TrafficSecret)\n\t\t/*if isRX {\n\t\t\tkey, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), keyLog.RemoteTrafficSecret)\n\t\t} else {\n\t\t\tkey, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), keyLog.TrafficSecret)\n\t\t}*/\n\t} else {\n\t\t// csPtr := cipherSuiteByID(cipherSuite)\n\t\t// keysFromMasterSecret(*hc.Version, csPtr, keyLog.Secret, keyLog.Random)\n\t\treturn nil\n\t}\n\n\tswitch cipherSuite {\n\tcase tls.TLS_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:\n\t\tcrypto := new(kernelCryptoAES128GCM)\n\n\t\tcrypto.version = *hc.Version\n\t\tcopy(crypto.key[:], key)\n\t\tcopy(crypto.iv[:], iv[4:])\n\t\tcopy(crypto.salt[:], iv[:4])\n\t\tcrypto.rec_seq = *hc.Seq\n\n\t\treturn crypto\n\tcase tls.TLS_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:\n\t\tif !kernel.TLS_AES_256_GCM {\n\t\t\treturn nil\n\t\t}\n\n\t\tcrypto := new(kernelCryptoAES256GCM)\n\n\t\tcrypto.version = *hc.Version\n\t\tcopy(crypto.key[:], key)\n\t\tcopy(crypto.iv[:], iv[4:])\n\t\tcopy(crypto.salt[:], iv[:4])\n\t\tcrypto.rec_seq = *hc.Seq\n\n\t\treturn crypto\n\t//case tls.TLS_AES_128_CCM_SHA256, tls.TLS_RSA_WITH_AES_128_CCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_SHA256:\n\t//\tif !kernel.TLS_AES_128_CCM {\n\t//\t\treturn nil\n\t//\t}\n\t//\n\t//\tcrypto := new(kernelCryptoAES128CCM)\n\t//\n\t//\tcrypto.version = *hc.Version\n\t//\tcopy(crypto.key[:], key)\n\t//\tcopy(crypto.iv[:], iv[4:])\n\t//\tcopy(crypto.salt[:], iv[:4])\n\t//\tcrypto.rec_seq = *hc.Seq\n\t//\n\t//\treturn crypto\n\tcase tls.TLS_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:\n\t\tif !kernel.TLS_CHACHA20_POLY1305 {\n\t\t\treturn nil\n\t\t}\n\n\t\tcrypto := new(kernelCryptoChacha20Poly1035)\n\n\t\tcrypto.version = *hc.Version\n\t\tcopy(crypto.key[:], key)\n\t\tcopy(crypto.iv[:], iv)\n\t\tcrypto.rec_seq = *hc.Seq\n\n\t\treturn crypto\n\t//case tls.TLS_RSA_WITH_ARIA_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256:\n\t//\tif !kernel.TLS_ARIA_GCM {\n\t//\t\treturn nil\n\t//\t}\n\t//\n\t//\tcrypto := new(kernelCryptoARIA128GCM)\n\t//\n\t//\tcrypto.version = *hc.Version\n\t//\tcopy(crypto.key[:], key)\n\t//\tcopy(crypto.iv[:], iv[4:])\n\t//\tcopy(crypto.salt[:], iv[:4])\n\t//\tcrypto.rec_seq = *hc.Seq\n\t//\n\t//\treturn crypto\n\t//case tls.TLS_RSA_WITH_ARIA_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384:\n\t//\tif !kernel.TLS_ARIA_GCM {\n\t//\t\treturn nil\n\t//\t}\n\t//\n\t//\tcrypto := new(kernelCryptoARIA256GCM)\n\t//\n\t//\tcrypto.version = *hc.Version\n\t//\tcopy(crypto.key[:], key)\n\t//\tcopy(crypto.iv[:], iv[4:])\n\t//\tcopy(crypto.salt[:], iv[:4])\n\t//\tcrypto.rec_seq = *hc.Seq\n\t//\n\t//\treturn crypto\n\tdefault:\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "common/ktls/ktls_close.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n)\n\nfunc (c *Conn) Close() error {\n\tif !c.kernelTx {\n\t\treturn c.Conn.Close()\n\t}\n\n\t// Interlock with Conn.Write above.\n\tvar x int32\n\tfor {\n\t\tx = c.rawConn.ActiveCall.Load()\n\t\tif x&1 != 0 {\n\t\t\treturn net.ErrClosed\n\t\t}\n\t\tif c.rawConn.ActiveCall.CompareAndSwap(x, x|1) {\n\t\t\tbreak\n\t\t}\n\t}\n\tif x != 0 {\n\t\t// io.Writer and io.Closer should not be used concurrently.\n\t\t// If Close is called while a Write is currently in-flight,\n\t\t// interpret that as a sign that this Close is really just\n\t\t// being used to break the Write and/or clean up resources and\n\t\t// avoid sending the alertCloseNotify, which may block\n\t\t// waiting on handshakeMutex or the c.out mutex.\n\t\treturn c.conn.Close()\n\t}\n\n\tvar alertErr error\n\tif c.rawConn.IsHandshakeComplete.Load() {\n\t\tif err := c.closeNotify(); err != nil {\n\t\t\talertErr = fmt.Errorf(\"tls: failed to send closeNotify alert (but connection was closed anyway): %w\", err)\n\t\t}\n\t}\n\n\tif err := c.conn.Close(); err != nil {\n\t\treturn err\n\t}\n\treturn alertErr\n}\n\nfunc (c *Conn) closeNotify() error {\n\tc.rawConn.Out.Lock()\n\tdefer c.rawConn.Out.Unlock()\n\n\tif !*c.rawConn.CloseNotifySent {\n\t\t// Set a Write Deadline to prevent possibly blocking forever.\n\t\tc.SetWriteDeadline(time.Now().Add(time.Second * 5))\n\t\t*c.rawConn.CloseNotifyErr = c.sendAlertLocked(alertCloseNotify)\n\t\t*c.rawConn.CloseNotifySent = true\n\t\t// Any subsequent writes will fail.\n\t\tc.SetWriteDeadline(time.Now())\n\t}\n\treturn *c.rawConn.CloseNotifyErr\n}\n"
  },
  {
    "path": "common/ktls/ktls_const.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nconst (\n\tmaxPlaintext               = 16384        // maximum plaintext payload length\n\tmaxCiphertext              = 16384 + 2048 // maximum ciphertext payload length\n\tmaxCiphertextTLS13         = 16384 + 256  // maximum ciphertext length in TLS 1.3\n\trecordHeaderLen            = 5            // record header length\n\tmaxHandshake               = 65536        // maximum handshake we support (protocol max is 16 MB)\n\tmaxHandshakeCertificateMsg = 262144       // maximum certificate message size (256 KiB)\n\tmaxUselessRecords          = 16           // maximum number of consecutive non-advancing records\n)\n\nconst (\n\trecordTypeChangeCipherSpec = 20\n\trecordTypeAlert            = 21\n\trecordTypeHandshake        = 22\n\trecordTypeApplicationData  = 23\n)\n"
  },
  {
    "path": "common/ktls/ktls_handshake_messages.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nimport (\n\t\"fmt\"\n\n\t\"golang.org/x/crypto/cryptobyte\"\n)\n\n// The marshalingFunction type is an adapter to allow the use of ordinary\n// functions as cryptobyte.MarshalingValue.\ntype marshalingFunction func(b *cryptobyte.Builder) error\n\nfunc (f marshalingFunction) Marshal(b *cryptobyte.Builder) error {\n\treturn f(b)\n}\n\n// addBytesWithLength appends a sequence of bytes to the cryptobyte.Builder. If\n// the length of the sequence is not the value specified, it produces an error.\nfunc addBytesWithLength(b *cryptobyte.Builder, v []byte, n int) {\n\tb.AddValue(marshalingFunction(func(b *cryptobyte.Builder) error {\n\t\tif len(v) != n {\n\t\t\treturn fmt.Errorf(\"invalid value length: expected %d, got %d\", n, len(v))\n\t\t}\n\t\tb.AddBytes(v)\n\t\treturn nil\n\t}))\n}\n\n// addUint64 appends a big-endian, 64-bit value to the cryptobyte.Builder.\nfunc addUint64(b *cryptobyte.Builder, v uint64) {\n\tb.AddUint32(uint32(v >> 32))\n\tb.AddUint32(uint32(v))\n}\n\n// readUint64 decodes a big-endian, 64-bit value into out and advances over it.\n// It reports whether the read was successful.\nfunc readUint64(s *cryptobyte.String, out *uint64) bool {\n\tvar hi, lo uint32\n\tif !s.ReadUint32(&hi) || !s.ReadUint32(&lo) {\n\t\treturn false\n\t}\n\t*out = uint64(hi)<<32 | uint64(lo)\n\treturn true\n}\n\n// readUint8LengthPrefixed acts like s.ReadUint8LengthPrefixed, but targets a\n// []byte instead of a cryptobyte.String.\nfunc readUint8LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {\n\treturn s.ReadUint8LengthPrefixed((*cryptobyte.String)(out))\n}\n\n// readUint16LengthPrefixed acts like s.ReadUint16LengthPrefixed, but targets a\n// []byte instead of a cryptobyte.String.\nfunc readUint16LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {\n\treturn s.ReadUint16LengthPrefixed((*cryptobyte.String)(out))\n}\n\n// readUint24LengthPrefixed acts like s.ReadUint24LengthPrefixed, but targets a\n// []byte instead of a cryptobyte.String.\nfunc readUint24LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {\n\treturn s.ReadUint24LengthPrefixed((*cryptobyte.String)(out))\n}\n\ntype keyUpdateMsg struct {\n\tupdateRequested bool\n}\n\nfunc (m *keyUpdateMsg) marshal() ([]byte, error) {\n\tvar b cryptobyte.Builder\n\tb.AddUint8(typeKeyUpdate)\n\tb.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {\n\t\tif m.updateRequested {\n\t\t\tb.AddUint8(1)\n\t\t} else {\n\t\t\tb.AddUint8(0)\n\t\t}\n\t})\n\n\treturn b.Bytes()\n}\n\nfunc (m *keyUpdateMsg) unmarshal(data []byte) bool {\n\ts := cryptobyte.String(data)\n\n\tvar updateRequested uint8\n\tif !s.Skip(4) || // message type and uint24 length field\n\t\t!s.ReadUint8(&updateRequested) || !s.Empty() {\n\t\treturn false\n\t}\n\tswitch updateRequested {\n\tcase 0:\n\t\tm.updateRequested = false\n\tcase 1:\n\t\tm.updateRequested = true\n\tdefault:\n\t\treturn false\n\t}\n\treturn true\n}\n\n// TLS handshake message types.\nconst (\n\ttypeHelloRequest          uint8 = 0\n\ttypeClientHello           uint8 = 1\n\ttypeServerHello           uint8 = 2\n\ttypeNewSessionTicket      uint8 = 4\n\ttypeEndOfEarlyData        uint8 = 5\n\ttypeEncryptedExtensions   uint8 = 8\n\ttypeCertificate           uint8 = 11\n\ttypeServerKeyExchange     uint8 = 12\n\ttypeCertificateRequest    uint8 = 13\n\ttypeServerHelloDone       uint8 = 14\n\ttypeCertificateVerify     uint8 = 15\n\ttypeClientKeyExchange     uint8 = 16\n\ttypeFinished              uint8 = 20\n\ttypeCertificateStatus     uint8 = 22\n\ttypeKeyUpdate             uint8 = 24\n\ttypeCompressedCertificate uint8 = 25\n\ttypeMessageHash           uint8 = 254 // synthetic message\n)\n\n// TLS compression types.\nconst (\n\tcompressionNone uint8 = 0\n)\n\n// TLS extension numbers\nconst (\n\textensionServerName              uint16 = 0\n\textensionStatusRequest           uint16 = 5\n\textensionSupportedCurves         uint16 = 10 // supported_groups in TLS 1.3, see RFC 8446, Section 4.2.7\n\textensionSupportedPoints         uint16 = 11\n\textensionSignatureAlgorithms     uint16 = 13\n\textensionALPN                    uint16 = 16\n\textensionSCT                     uint16 = 18\n\textensionPadding                 uint16 = 21\n\textensionExtendedMasterSecret    uint16 = 23\n\textensionCompressCertificate     uint16 = 27 // compress_certificate in TLS 1.3\n\textensionSessionTicket           uint16 = 35\n\textensionPreSharedKey            uint16 = 41\n\textensionEarlyData               uint16 = 42\n\textensionSupportedVersions       uint16 = 43\n\textensionCookie                  uint16 = 44\n\textensionPSKModes                uint16 = 45\n\textensionCertificateAuthorities  uint16 = 47\n\textensionSignatureAlgorithmsCert uint16 = 50\n\textensionKeyShare                uint16 = 51\n\textensionQUICTransportParameters uint16 = 57\n\textensionALPS                    uint16 = 17513\n\textensionRenegotiationInfo       uint16 = 0xff01\n\textensionECHOuterExtensions      uint16 = 0xfd00\n\textensionEncryptedClientHello    uint16 = 0xfe0d\n)\n\ntype handshakeMessage interface {\n\tmarshal() ([]byte, error)\n\tunmarshal([]byte) bool\n}\ntype newSessionTicketMsgTLS13 struct {\n\tlifetime     uint32\n\tageAdd       uint32\n\tnonce        []byte\n\tlabel        []byte\n\tmaxEarlyData uint32\n}\n\nfunc (m *newSessionTicketMsgTLS13) marshal() ([]byte, error) {\n\tvar b cryptobyte.Builder\n\tb.AddUint8(typeNewSessionTicket)\n\tb.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {\n\t\tb.AddUint32(m.lifetime)\n\t\tb.AddUint32(m.ageAdd)\n\t\tb.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {\n\t\t\tb.AddBytes(m.nonce)\n\t\t})\n\t\tb.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {\n\t\t\tb.AddBytes(m.label)\n\t\t})\n\n\t\tb.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {\n\t\t\tif m.maxEarlyData > 0 {\n\t\t\t\tb.AddUint16(extensionEarlyData)\n\t\t\t\tb.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {\n\t\t\t\t\tb.AddUint32(m.maxEarlyData)\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t})\n\n\treturn b.Bytes()\n}\n\nfunc (m *newSessionTicketMsgTLS13) unmarshal(data []byte) bool {\n\t*m = newSessionTicketMsgTLS13{}\n\ts := cryptobyte.String(data)\n\n\tvar extensions cryptobyte.String\n\tif !s.Skip(4) || // message type and uint24 length field\n\t\t!s.ReadUint32(&m.lifetime) ||\n\t\t!s.ReadUint32(&m.ageAdd) ||\n\t\t!readUint8LengthPrefixed(&s, &m.nonce) ||\n\t\t!readUint16LengthPrefixed(&s, &m.label) ||\n\t\t!s.ReadUint16LengthPrefixed(&extensions) ||\n\t\t!s.Empty() {\n\t\treturn false\n\t}\n\n\tfor !extensions.Empty() {\n\t\tvar extension uint16\n\t\tvar extData cryptobyte.String\n\t\tif !extensions.ReadUint16(&extension) ||\n\t\t\t!extensions.ReadUint16LengthPrefixed(&extData) {\n\t\t\treturn false\n\t\t}\n\n\t\tswitch extension {\n\t\tcase extensionEarlyData:\n\t\t\tif !extData.ReadUint32(&m.maxEarlyData) {\n\t\t\t\treturn false\n\t\t\t}\n\t\tdefault:\n\t\t\t// Ignore unknown extensions.\n\t\t\tcontinue\n\t\t}\n\n\t\tif !extData.Empty() {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "common/ktls/ktls_key_update.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nimport (\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n)\n\n// handlePostHandshakeMessage processes a handshake message arrived after the\n// handshake is complete. Up to TLS 1.2, it indicates the start of a renegotiation.\nfunc (c *Conn) handlePostHandshakeMessage() error {\n\tif *c.rawConn.Vers != tls.VersionTLS13 {\n\t\treturn errors.New(\"ktls: kernel does not support TLS 1.2 renegotiation\")\n\t}\n\n\tmsg, err := c.readHandshake(nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\t//c.retryCount++\n\t//if c.retryCount > maxUselessRecords {\n\t//\tc.sendAlert(alertUnexpectedMessage)\n\t//\treturn c.in.setErrorLocked(errors.New(\"tls: too many non-advancing records\"))\n\t//}\n\n\tswitch msg := msg.(type) {\n\tcase *newSessionTicketMsgTLS13:\n\t\t// return errors.New(\"ktls: received new session ticket\")\n\t\treturn nil\n\tcase *keyUpdateMsg:\n\t\treturn c.handleKeyUpdate(msg)\n\t}\n\t// The QUIC layer is supposed to treat an unexpected post-handshake CertificateRequest\n\t// as a QUIC-level PROTOCOL_VIOLATION error (RFC 9001, Section 4.4). Returning an\n\t// unexpected_message alert here doesn't provide it with enough information to distinguish\n\t// this condition from other unexpected messages. This is probably fine.\n\tc.sendAlert(alertUnexpectedMessage)\n\treturn fmt.Errorf(\"tls: received unexpected handshake message of type %T\", msg)\n}\n\nfunc (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error {\n\t//if c.quic != nil {\n\t//\tc.sendAlert(alertUnexpectedMessage)\n\t//\treturn c.in.setErrorLocked(errors.New(\"tls: received unexpected key update message\"))\n\t//}\n\n\tcipherSuite := cipherSuiteTLS13ByID(*c.rawConn.CipherSuite)\n\tif cipherSuite == nil {\n\t\treturn c.rawConn.In.SetErrorLocked(c.sendAlert(alertInternalError))\n\t}\n\n\tnewSecret := nextTrafficSecret(cipherSuite, *c.rawConn.In.TrafficSecret)\n\tc.rawConn.In.SetTrafficSecret(cipherSuite, 0 /*tls.QUICEncryptionLevelInitial*/, newSecret)\n\n\terr := c.resetupRX()\n\tif err != nil {\n\t\tc.sendAlert(alertInternalError)\n\t\treturn c.rawConn.In.SetErrorLocked(fmt.Errorf(\"ktls: resetupRX failed: %w\", err))\n\t}\n\n\tif keyUpdate.updateRequested {\n\t\tc.rawConn.Out.Lock()\n\t\tdefer c.rawConn.Out.Unlock()\n\n\t\tresetup, err := c.resetupTX()\n\t\tif err != nil {\n\t\t\tc.sendAlertLocked(alertInternalError)\n\t\t\treturn c.rawConn.Out.SetErrorLocked(fmt.Errorf(\"ktls: resetupTX failed: %w\", err))\n\t\t}\n\n\t\tmsg := &keyUpdateMsg{}\n\t\tmsgBytes, err := msg.marshal()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = c.writeRecordLocked(recordTypeHandshake, msgBytes)\n\t\tif err != nil {\n\t\t\t// Surface the error at the next write.\n\t\t\tc.rawConn.Out.SetErrorLocked(err)\n\t\t\treturn nil\n\t\t}\n\n\t\tnewSecret := nextTrafficSecret(cipherSuite, *c.rawConn.Out.TrafficSecret)\n\t\tc.rawConn.Out.SetTrafficSecret(cipherSuite, 0 /*QUICEncryptionLevelInitial*/, newSecret)\n\n\t\terr = resetup()\n\t\tif err != nil {\n\t\t\treturn c.rawConn.Out.SetErrorLocked(fmt.Errorf(\"ktls: resetupTX failed: %w\", err))\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *Conn) readHandshakeBytes(n int) error {\n\t//if c.quic != nil {\n\t//\treturn c.quicReadHandshakeBytes(n)\n\t//}\n\tfor c.rawConn.Hand.Len() < n {\n\t\tif err := c.readRecord(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *Conn) readHandshake(transcript io.Writer) (any, error) {\n\tif err := c.readHandshakeBytes(4); err != nil {\n\t\treturn nil, err\n\t}\n\tdata := c.rawConn.Hand.Bytes()\n\n\tmaxHandshakeSize := maxHandshake\n\t// hasVers indicates we're past the first message, forcing someone trying to\n\t// make us just allocate a large buffer to at least do the initial part of\n\t// the handshake first.\n\t//if c.haveVers && data[0] == typeCertificate {\n\t// Since certificate messages are likely to be the only messages that\n\t// can be larger than maxHandshake, we use a special limit for just\n\t// those messages.\n\t//maxHandshakeSize = maxHandshakeCertificateMsg\n\t//}\n\n\tn := int(data[1])<<16 | int(data[2])<<8 | int(data[3])\n\tif n > maxHandshakeSize {\n\t\tc.sendAlertLocked(alertInternalError)\n\t\treturn nil, c.rawConn.In.SetErrorLocked(fmt.Errorf(\"tls: handshake message of length %d bytes exceeds maximum of %d bytes\", n, maxHandshakeSize))\n\t}\n\tif err := c.readHandshakeBytes(4 + n); err != nil {\n\t\treturn nil, err\n\t}\n\tdata = c.rawConn.Hand.Next(4 + n)\n\treturn c.unmarshalHandshakeMessage(data, transcript)\n}\n\nfunc (c *Conn) unmarshalHandshakeMessage(data []byte, transcript io.Writer) (any, error) {\n\tvar m handshakeMessage\n\tswitch data[0] {\n\tcase typeNewSessionTicket:\n\t\tif *c.rawConn.Vers == tls.VersionTLS13 {\n\t\t\tm = new(newSessionTicketMsgTLS13)\n\t\t} else {\n\t\t\treturn nil, os.ErrInvalid\n\t\t}\n\tcase typeKeyUpdate:\n\t\tm = new(keyUpdateMsg)\n\tdefault:\n\t\treturn nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))\n\t}\n\n\t// The handshake message unmarshalers\n\t// expect to be able to keep references to data,\n\t// so pass in a fresh copy that won't be overwritten.\n\tdata = append([]byte(nil), data...)\n\n\tif !m.unmarshal(data) {\n\t\treturn nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecodeError))\n\t}\n\n\tif transcript != nil {\n\t\ttranscript.Write(data)\n\t}\n\n\treturn m, nil\n}\n"
  },
  {
    "path": "common/ktls/ktls_linux.go",
    "content": "//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nimport (\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/sagernet/sing-box/common/badversion\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/shell\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// mod from https://gitlab.com/go-extension/tls\n\nconst (\n\tTLS_TX               = 1\n\tTLS_RX               = 2\n\tTLS_TX_ZEROCOPY_RO   = 3 // TX zerocopy (only sendfile now)\n\tTLS_RX_EXPECT_NO_PAD = 4 // Attempt opportunistic zero-copy, TLS 1.3 only\n\n\tTLS_SET_RECORD_TYPE = 1\n\tTLS_GET_RECORD_TYPE = 2\n)\n\ntype Support struct {\n\tTLS, TLS_RX                     bool\n\tTLS_Version13, TLS_Version13_RX bool\n\n\tTLS_TX_ZEROCOPY  bool\n\tTLS_RX_NOPADDING bool\n\n\tTLS_AES_256_GCM       bool\n\tTLS_AES_128_CCM       bool\n\tTLS_CHACHA20_POLY1305 bool\n\tTLS_SM4               bool\n\tTLS_ARIA_GCM          bool\n\n\tTLS_Version13_KeyUpdate bool\n}\n\nvar KernelSupport = sync.OnceValues(func() (*Support, error) {\n\tvar uname unix.Utsname\n\terr := unix.Uname(&uname)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkernelVersion := badversion.Parse(strings.Trim(string(uname.Release[:]), \"\\x00\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar support Support\n\tswitch {\n\tcase kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6, Minor: 14}):\n\t\tsupport.TLS_Version13_KeyUpdate = true\n\t\tfallthrough\n\tcase kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6, Minor: 1}):\n\t\tsupport.TLS_ARIA_GCM = true\n\t\tfallthrough\n\tcase kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6}):\n\t\tsupport.TLS_Version13_RX = true\n\t\tsupport.TLS_RX_NOPADDING = true\n\t\tfallthrough\n\tcase kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 19}):\n\t\tsupport.TLS_TX_ZEROCOPY = true\n\t\tfallthrough\n\tcase kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 16}):\n\t\tsupport.TLS_SM4 = true\n\t\tfallthrough\n\tcase kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 11}):\n\t\tsupport.TLS_CHACHA20_POLY1305 = true\n\t\tfallthrough\n\tcase kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 2}):\n\t\tsupport.TLS_AES_128_CCM = true\n\t\tfallthrough\n\tcase kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 1}):\n\t\tsupport.TLS_AES_256_GCM = true\n\t\tsupport.TLS_Version13 = true\n\t\tfallthrough\n\tcase kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 4, Minor: 17}):\n\t\tsupport.TLS_RX = true\n\t\tfallthrough\n\tcase kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 4, Minor: 13}):\n\t\tsupport.TLS = true\n\t}\n\n\tif support.TLS && support.TLS_Version13 {\n\t\t_, err := os.Stat(\"/sys/module/tls\")\n\t\tif err != nil {\n\t\t\tif os.Getuid() == 0 {\n\t\t\t\toutput, err := shell.Exec(\"modprobe\", \"tls\").Read()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, E.Extend(E.Cause(err, \"modprobe tls\"), output)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn nil, E.New(\"ktls: kernel TLS module not loaded\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &support, nil\n})\n\nfunc Load() error {\n\tsupport, err := KernelSupport()\n\tif err != nil {\n\t\treturn E.Cause(err, \"ktls: check availability\")\n\t}\n\tif !support.TLS || !support.TLS_Version13 {\n\t\treturn E.New(\"ktls: kernel does not support TLS 1.3\")\n\t}\n\treturn nil\n}\n\nfunc (c *Conn) setupKernel(txOffload, rxOffload bool) error {\n\tif !txOffload && !rxOffload {\n\t\treturn os.ErrInvalid\n\t}\n\tsupport, err := KernelSupport()\n\tif err != nil {\n\t\treturn E.Cause(err, \"check availability\")\n\t}\n\tif !support.TLS || !support.TLS_Version13 {\n\t\treturn E.New(\"kernel does not support TLS 1.3\")\n\t}\n\tc.rawConn.Out.Lock()\n\tdefer c.rawConn.Out.Unlock()\n\terr = control.Raw(c.rawSyscallConn, func(fd uintptr) error {\n\t\treturn syscall.SetsockoptString(int(fd), unix.SOL_TCP, unix.TCP_ULP, \"tls\")\n\t})\n\tif err != nil {\n\t\treturn os.NewSyscallError(\"setsockopt\", err)\n\t}\n\n\tif txOffload {\n\t\ttxCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false)\n\t\tif txCrypto == nil {\n\t\t\treturn E.New(\"unsupported cipher suite\")\n\t\t}\n\t\terr = control.Raw(c.rawSyscallConn, func(fd uintptr) error {\n\t\t\treturn syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String())\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif support.TLS_TX_ZEROCOPY {\n\t\t\terr = control.Raw(c.rawSyscallConn, func(fd uintptr) error {\n\t\t\t\treturn syscall.SetsockoptInt(int(fd), unix.SOL_TLS, TLS_TX_ZEROCOPY_RO, 1)\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tc.kernelTx = true\n\t\tc.logger.DebugContext(c.ctx, \"ktls: kernel TLS TX enabled\")\n\t}\n\n\tif rxOffload {\n\t\trxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true)\n\t\tif rxCrypto == nil {\n\t\t\treturn E.New(\"unsupported cipher suite\")\n\t\t}\n\t\terr = control.Raw(c.rawSyscallConn, func(fd uintptr) error {\n\t\t\treturn syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String())\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif *c.rawConn.Vers >= tls.VersionTLS13 && support.TLS_RX_NOPADDING {\n\t\t\terr = control.Raw(c.rawSyscallConn, func(fd uintptr) error {\n\t\t\t\treturn syscall.SetsockoptInt(int(fd), unix.SOL_TLS, TLS_RX_EXPECT_NO_PAD, 1)\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tc.kernelRx = true\n\t\tc.logger.DebugContext(c.ctx, \"ktls: kernel TLS RX enabled\")\n\t}\n\treturn nil\n}\n\nfunc (c *Conn) resetupTX() (func() error, error) {\n\tif !c.kernelTx {\n\t\treturn nil, nil\n\t}\n\tsupport, err := KernelSupport()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !support.TLS_Version13_KeyUpdate {\n\t\treturn nil, errors.New(\"ktls: kernel does not support rekey\")\n\t}\n\ttxCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false)\n\tif txCrypto == nil {\n\t\treturn nil, errors.New(\"ktls: set kernelCipher on unsupported tls session\")\n\t}\n\treturn func() error {\n\t\treturn control.Raw(c.rawSyscallConn, func(fd uintptr) error {\n\t\t\treturn syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String())\n\t\t})\n\t}, nil\n}\n\nfunc (c *Conn) resetupRX() error {\n\tif !c.kernelRx {\n\t\treturn nil\n\t}\n\tsupport, err := KernelSupport()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !support.TLS_Version13_KeyUpdate {\n\t\treturn errors.New(\"ktls: kernel does not support rekey\")\n\t}\n\trxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true)\n\tif rxCrypto == nil {\n\t\treturn errors.New(\"ktls: set kernelCipher on unsupported tls session\")\n\t}\n\treturn control.Raw(c.rawSyscallConn, func(fd uintptr) error {\n\t\treturn syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String())\n\t})\n}\n\nfunc (c *Conn) readKernelRecord() (uint8, []byte, error) {\n\tif c.rawConn.RawInput.Len() < maxPlaintext {\n\t\tc.rawConn.RawInput.Grow(maxPlaintext - c.rawConn.RawInput.Len())\n\t}\n\n\tdata := c.rawConn.RawInput.Bytes()[:maxPlaintext]\n\n\t// cmsg for record type\n\tbuffer := make([]byte, unix.CmsgSpace(1))\n\tcmsg := (*unix.Cmsghdr)(unsafe.Pointer(&buffer[0]))\n\tcmsg.SetLen(unix.CmsgLen(1))\n\n\tvar iov unix.Iovec\n\tiov.Base = &data[0]\n\tiov.SetLen(len(data))\n\n\tvar msg unix.Msghdr\n\tmsg.Control = &buffer[0]\n\tmsg.Controllen = cmsg.Len\n\tmsg.Iov = &iov\n\tmsg.Iovlen = 1\n\n\tvar n int\n\tvar err error\n\ter := c.rawSyscallConn.Read(func(fd uintptr) bool {\n\t\tn, err = recvmsg(int(fd), &msg, 0)\n\t\treturn err != unix.EAGAIN || c.pendingRxSplice\n\t})\n\tif er != nil {\n\t\treturn 0, nil, er\n\t}\n\tswitch err {\n\tcase nil:\n\tcase syscall.EINVAL, syscall.EAGAIN:\n\t\treturn 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertProtocolVersion))\n\tcase syscall.EMSGSIZE:\n\t\treturn 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertRecordOverflow))\n\tcase syscall.EBADMSG:\n\t\treturn 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecryptError))\n\tdefault:\n\t\treturn 0, nil, err\n\t}\n\n\tif n <= 0 {\n\t\treturn 0, nil, c.rawConn.In.SetErrorLocked(io.EOF)\n\t}\n\n\tif cmsg.Level == unix.SOL_TLS && cmsg.Type == TLS_GET_RECORD_TYPE {\n\t\ttyp := buffer[unix.CmsgLen(0)]\n\t\treturn typ, data[:n], nil\n\t}\n\n\treturn recordTypeApplicationData, data[:n], nil\n}\n\nfunc (c *Conn) writeKernelRecord(typ uint16, data []byte) (int, error) {\n\tif typ == recordTypeApplicationData {\n\t\treturn c.conn.Write(data)\n\t}\n\n\t// cmsg for record type\n\tbuffer := make([]byte, unix.CmsgSpace(1))\n\tcmsg := (*unix.Cmsghdr)(unsafe.Pointer(&buffer[0]))\n\tcmsg.SetLen(unix.CmsgLen(1))\n\tbuffer[unix.CmsgLen(0)] = byte(typ)\n\tcmsg.Level = unix.SOL_TLS\n\tcmsg.Type = TLS_SET_RECORD_TYPE\n\n\tvar iov unix.Iovec\n\tiov.Base = &data[0]\n\tiov.SetLen(len(data))\n\n\tvar msg unix.Msghdr\n\tmsg.Control = &buffer[0]\n\tmsg.Controllen = cmsg.Len\n\tmsg.Iov = &iov\n\tmsg.Iovlen = 1\n\n\tvar n int\n\tvar err error\n\tew := c.rawSyscallConn.Write(func(fd uintptr) bool {\n\t\tn, err = sendmsg(int(fd), &msg, 0)\n\t\treturn err != unix.EAGAIN\n\t})\n\tif ew != nil {\n\t\treturn 0, ew\n\t}\n\treturn n, err\n}\n\n//go:linkname recvmsg golang.org/x/sys/unix.recvmsg\nfunc recvmsg(fd int, msg *unix.Msghdr, flags int) (n int, err error)\n\n//go:linkname sendmsg golang.org/x/sys/unix.sendmsg\nfunc sendmsg(fd int, msg *unix.Msghdr, flags int) (n int, err error)\n"
  },
  {
    "path": "common/ktls/ktls_prf.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nimport \"unsafe\"\n\n//go:linkname cipherSuiteByID github.com/metacubex/utls.cipherSuiteByID\nfunc cipherSuiteByID(id uint16) unsafe.Pointer\n\n//go:linkname keysFromMasterSecret github.com/metacubex/utls.keysFromMasterSecret\nfunc keysFromMasterSecret(version uint16, suite unsafe.Pointer, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte)\n\n//go:linkname cipherSuiteTLS13ByID github.com/metacubex/utls.cipherSuiteTLS13ByID\nfunc cipherSuiteTLS13ByID(id uint16) unsafe.Pointer\n\n//go:linkname nextTrafficSecret github.com/metacubex/utls.(*cipherSuiteTLS13).nextTrafficSecret\nfunc nextTrafficSecret(cs unsafe.Pointer, trafficSecret []byte) []byte\n\n//go:linkname trafficKey github.com/metacubex/utls.(*cipherSuiteTLS13).trafficKey\nfunc trafficKey(cs unsafe.Pointer, trafficSecret []byte) (key, iv []byte)\n"
  },
  {
    "path": "common/ktls/ktls_read.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"unsafe\"\n)\n\nfunc (c *Conn) Read(b []byte) (int, error) {\n\tif !c.kernelRx {\n\t\treturn c.Conn.Read(b)\n\t}\n\n\tif len(b) == 0 {\n\t\t// Put this after Handshake, in case people were calling\n\t\t// Read(nil) for the side effect of the Handshake.\n\t\treturn 0, nil\n\t}\n\n\tc.rawConn.In.Lock()\n\tdefer c.rawConn.In.Unlock()\n\n\tfor c.rawConn.Input.Len() == 0 {\n\t\tif err := c.readRecord(); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tfor c.rawConn.Hand.Len() > 0 {\n\t\t\tif err := c.handlePostHandshakeMessage(); err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\t}\n\n\tn, _ := c.rawConn.Input.Read(b)\n\n\t// If a close-notify alert is waiting, read it so that we can return (n,\n\t// EOF) instead of (n, nil), to signal to the HTTP response reading\n\t// goroutine that the connection is now closed. This eliminates a race\n\t// where the HTTP response reading goroutine would otherwise not observe\n\t// the EOF until its next read, by which time a client goroutine might\n\t// have already tried to reuse the HTTP connection for a new request.\n\t// See https://golang.org/cl/76400046 and https://golang.org/issue/3514\n\tif n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.RawInput.Len() > 0 &&\n\t\tc.rawConn.RawInput.Bytes()[0] == recordTypeAlert {\n\t\tif err := c.readRecord(); err != nil {\n\t\t\treturn n, err // will be io.EOF on closeNotify\n\t\t}\n\t}\n\n\treturn n, nil\n}\n\nfunc (c *Conn) readRecord() error {\n\tif *c.rawConn.In.Err != nil {\n\t\treturn *c.rawConn.In.Err\n\t}\n\n\ttyp, data, err := c.readRawRecord()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(data) > maxPlaintext {\n\t\treturn c.rawConn.In.SetErrorLocked(c.sendAlert(alertRecordOverflow))\n\t}\n\n\t// Application Data messages are always protected.\n\tif c.rawConn.In.Cipher == nil && typ == recordTypeApplicationData {\n\t\treturn c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))\n\t}\n\n\t//if typ != recordTypeAlert && typ != recordTypeChangeCipherSpec && len(data) > 0 {\n\t// This is a state-advancing message: reset the retry count.\n\t// c.retryCount = 0\n\t//}\n\n\t// Handshake messages MUST NOT be interleaved with other record types in TLS 1.3.\n\tif *c.rawConn.Vers == tls.VersionTLS13 && typ != recordTypeHandshake && c.rawConn.Hand.Len() > 0 {\n\t\treturn c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))\n\t}\n\n\tswitch typ {\n\tdefault:\n\t\treturn c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))\n\tcase recordTypeAlert:\n\t\t//if c.quic != nil {\n\t\t//\treturn c.rawConn.In.setErrorLocked(c.sendAlert(alertUnexpectedMessage))\n\t\t//}\n\t\tif len(data) != 2 {\n\t\t\treturn c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))\n\t\t}\n\t\tif data[1] == alertCloseNotify {\n\t\t\treturn c.rawConn.In.SetErrorLocked(io.EOF)\n\t\t}\n\t\tif *c.rawConn.Vers == tls.VersionTLS13 {\n\t\t\t// TLS 1.3 removed warning-level alerts except for alertUserCanceled\n\t\t\t// (RFC 8446, § 6.1). Since at least one major implementation\n\t\t\t// (https://bugs.openjdk.org/browse/JDK-8323517) misuses this alert,\n\t\t\t// many TLS stacks now ignore it outright when seen in a TLS 1.3\n\t\t\t// handshake (e.g. BoringSSL, NSS, Rustls).\n\t\t\tif data[1] == alertUserCanceled {\n\t\t\t\t// Like TLS 1.2 alertLevelWarning alerts, we drop the record and retry.\n\t\t\t\treturn c.retryReadRecord( /*expectChangeCipherSpec*/ )\n\t\t\t}\n\t\t\treturn c.rawConn.In.SetErrorLocked(&net.OpError{Op: \"remote error\", Err: tls.AlertError(data[1])})\n\t\t}\n\t\tswitch data[0] {\n\t\tcase alertLevelWarning:\n\t\t\t// Drop the record on the floor and retry.\n\t\t\treturn c.retryReadRecord( /*expectChangeCipherSpec*/ )\n\t\tcase alertLevelError:\n\t\t\treturn c.rawConn.In.SetErrorLocked(&net.OpError{Op: \"remote error\", Err: tls.AlertError(data[1])})\n\t\tdefault:\n\t\t\treturn c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))\n\t\t}\n\n\tcase recordTypeChangeCipherSpec:\n\t\tif len(data) != 1 || data[0] != 1 {\n\t\t\treturn c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecodeError))\n\t\t}\n\t\t// Handshake messages are not allowed to fragment across the CCS.\n\t\tif c.rawConn.Hand.Len() > 0 {\n\t\t\treturn c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))\n\t\t}\n\t\t// In TLS 1.3, change_cipher_spec records are ignored until the\n\t\t// Finished. See RFC 8446, Appendix D.4. Note that according to Section\n\t\t// 5, a server can send a ChangeCipherSpec before its ServerHello, when\n\t\t// c.vers is still unset. That's not useful though and suspicious if the\n\t\t// server then selects a lower protocol version, so don't allow that.\n\t\tif *c.rawConn.Vers == tls.VersionTLS13 {\n\t\t\treturn c.retryReadRecord( /*expectChangeCipherSpec*/ )\n\t\t}\n\t\t// if !expectChangeCipherSpec {\n\t\treturn c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))\n\t\t//}\n\t\t//if err := c.rawConn.In.changeCipherSpec(); err != nil {\n\t\t//\treturn c.rawConn.In.setErrorLocked(c.sendAlert(err.(alert)))\n\t\t//}\n\n\tcase recordTypeApplicationData:\n\t\t// Some OpenSSL servers send empty records in order to randomize the\n\t\t// CBC RawIV. Ignore a limited number of empty records.\n\t\tif len(data) == 0 {\n\t\t\treturn c.retryReadRecord( /*expectChangeCipherSpec*/ )\n\t\t}\n\t\t// Note that data is owned by c.rawInput, following the Next call above,\n\t\t// to avoid copying the plaintext. This is safe because c.rawInput is\n\t\t// not read from or written to until c.input is drained.\n\t\tc.rawConn.Input.Reset(data)\n\tcase recordTypeHandshake:\n\t\tif len(data) == 0 {\n\t\t\treturn c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))\n\t\t}\n\t\tc.rawConn.Hand.Write(data)\n\t}\n\n\treturn nil\n}\n\n//nolint:staticcheck\nfunc (c *Conn) readRawRecord() (typ uint8, data []byte, err error) {\n\t// Read from kernel.\n\tif c.kernelRx {\n\t\treturn c.readKernelRecord()\n\t}\n\n\t// Read header, payload.\n\tif err = c.readFromUntil(c.conn, recordHeaderLen); err != nil {\n\t\t// RFC 8446, Section 6.1 suggests that EOF without an alertCloseNotify\n\t\t// is an error, but popular web sites seem to do this, so we accept it\n\t\t// if and only if at the record boundary.\n\t\tif err == io.ErrUnexpectedEOF && c.rawConn.RawInput.Len() == 0 {\n\t\t\terr = io.EOF\n\t\t}\n\t\tif e, ok := err.(net.Error); !ok || !e.Temporary() {\n\t\t\tc.rawConn.In.SetErrorLocked(err)\n\t\t}\n\t\treturn\n\t}\n\thdr := c.rawConn.RawInput.Bytes()[:recordHeaderLen]\n\ttyp = hdr[0]\n\n\tvers := uint16(hdr[1])<<8 | uint16(hdr[2])\n\texpectedVers := *c.rawConn.Vers\n\tif expectedVers == tls.VersionTLS13 {\n\t\t// All TLS 1.3 records are expected to have 0x0303 (1.2) after\n\t\t// the initial hello (RFC 8446 Section 5.1).\n\t\texpectedVers = tls.VersionTLS12\n\t}\n\tn := int(hdr[3])<<8 | int(hdr[4])\n\tif /*c.haveVers && */ vers != expectedVers {\n\t\tc.sendAlert(alertProtocolVersion)\n\t\tmsg := fmt.Sprintf(\"received record with version %x when expecting version %x\", vers, expectedVers)\n\t\terr = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(nil, msg))\n\t\treturn\n\t}\n\t//if !c.haveVers {\n\t//\t// First message, be extra suspicious: this might not be a TLS\n\t//\t// client. Bail out before reading a full 'body', if possible.\n\t//\t// The current max version is 3.3 so if the version is >= 16.0,\n\t//\t// it's probably not real.\n\t//\tif (typ != recordTypeAlert && typ != recordTypeHandshake) || vers >= 0x1000 {\n\t//\t\terr = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(c.conn, \"first record does not look like a TLS handshake\"))\n\t//\t\treturn\n\t//\t}\n\t//}\n\tif *c.rawConn.Vers == tls.VersionTLS13 && n > maxCiphertextTLS13 || n > maxCiphertext {\n\t\tc.sendAlert(alertRecordOverflow)\n\t\tmsg := fmt.Sprintf(\"oversized record received with length %d\", n)\n\t\terr = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(nil, msg))\n\t\treturn\n\t}\n\tif err = c.readFromUntil(c.conn, recordHeaderLen+n); err != nil {\n\t\tif e, ok := err.(net.Error); !ok || !e.Temporary() {\n\t\t\tc.rawConn.In.SetErrorLocked(err)\n\t\t}\n\t\treturn\n\t}\n\n\t// Process message.\n\trecord := c.rawConn.RawInput.Next(recordHeaderLen + n)\n\tdata, typ, err = c.rawConn.In.Decrypt(record)\n\tif err != nil {\n\t\terr = c.rawConn.In.SetErrorLocked(c.sendAlert(*(*uint8)((*[2]unsafe.Pointer)(unsafe.Pointer(&err))[1])))\n\t\treturn\n\t}\n\treturn\n}\n\n// retryReadRecord recurs into readRecordOrCCS to drop a non-advancing record, like\n// a warning alert, empty application_data, or a change_cipher_spec in TLS 1.3.\nfunc (c *Conn) retryReadRecord( /*expectChangeCipherSpec bool*/ ) error {\n\t//c.retryCount++\n\t//if c.retryCount > maxUselessRecords {\n\t//\tc.sendAlert(alertUnexpectedMessage)\n\t//\treturn c.in.setErrorLocked(errors.New(\"tls: too many ignored records\"))\n\t//}\n\treturn c.readRecord( /*expectChangeCipherSpec*/ )\n}\n\n// atLeastReader reads from R, stopping with EOF once at least N bytes have been\n// read. It is different from an io.LimitedReader in that it doesn't cut short\n// the last Read call, and in that it considers an early EOF an error.\ntype atLeastReader struct {\n\tR io.Reader\n\tN int64\n}\n\nfunc (r *atLeastReader) Read(p []byte) (int, error) {\n\tif r.N <= 0 {\n\t\treturn 0, io.EOF\n\t}\n\tn, err := r.R.Read(p)\n\tr.N -= int64(n) // won't underflow unless len(p) >= n > 9223372036854775809\n\tif r.N > 0 && err == io.EOF {\n\t\treturn n, io.ErrUnexpectedEOF\n\t}\n\tif r.N <= 0 && err == nil {\n\t\treturn n, io.EOF\n\t}\n\treturn n, err\n}\n\n// readFromUntil reads from r into c.rawConn.RawInput until c.rawConn.RawInput contains\n// at least n bytes or else returns an error.\nfunc (c *Conn) readFromUntil(r io.Reader, n int) error {\n\tif c.rawConn.RawInput.Len() >= n {\n\t\treturn nil\n\t}\n\tneeds := n - c.rawConn.RawInput.Len()\n\t// There might be extra input waiting on the wire. Make a best effort\n\t// attempt to fetch it so that it can be used in (*Conn).Read to\n\t// \"predict\" closeNotify alerts.\n\tc.rawConn.RawInput.Grow(needs + bytes.MinRead)\n\t_, err := c.rawConn.RawInput.ReadFrom(&atLeastReader{r, int64(needs)})\n\treturn err\n}\n\nfunc (c *Conn) newRecordHeaderError(conn net.Conn, msg string) (err tls.RecordHeaderError) {\n\terr.Msg = msg\n\terr.Conn = conn\n\tcopy(err.RecordHeader[:], c.rawConn.RawInput.Bytes())\n\treturn err\n}\n"
  },
  {
    "path": "common/ktls/ktls_read_wait.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nimport (\n\t\"github.com/sagernet/sing/common/buf\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc (c *Conn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {\n\tc.readWaitOptions = options\n\treturn false\n}\n\nfunc (c *Conn) WaitReadBuffer() (buffer *buf.Buffer, err error) {\n\tc.rawConn.In.Lock()\n\tdefer c.rawConn.In.Unlock()\n\tfor c.rawConn.Input.Len() == 0 {\n\t\terr = c.readRecord()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tbuffer = c.readWaitOptions.NewBuffer()\n\tn, err := c.rawConn.Input.Read(buffer.FreeBytes())\n\tif err != nil {\n\t\tbuffer.Release()\n\t\treturn\n\t}\n\tbuffer.Truncate(n)\n\tif n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.Input.Len() > 0 &&\n\t\tc.rawConn.RawInput.Bytes()[0] == recordTypeAlert {\n\t\t_ = c.rawConn.ReadRecord()\n\t}\n\tc.readWaitOptions.PostReturn(buffer)\n\treturn\n}\n"
  },
  {
    "path": "common/ktls/ktls_stub_nolinkname.go",
    "content": "//go:build linux && go1.25 && !badlinkname\n\npackage ktls\n\nimport (\n\t\"context\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n)\n\nfunc NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {\n\treturn nil, E.New(\"kTLS requires build flags `badlinkname` and `-ldflags=-checklinkname=0`, please recompile your binary\")\n}\n"
  },
  {
    "path": "common/ktls/ktls_stub_nonlinux.go",
    "content": "//go:build !linux\n\npackage ktls\n\nimport (\n\t\"context\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n)\n\nfunc NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {\n\treturn nil, E.New(\"kTLS is only supported on Linux\")\n}\n"
  },
  {
    "path": "common/ktls/ktls_stub_oldgo.go",
    "content": "//go:build linux && !go1.25\n\npackage ktls\n\nimport (\n\t\"context\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n)\n\nfunc NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {\n\treturn nil, E.New(\"kTLS requires Go 1.25 or later, please recompile your binary\")\n}\n"
  },
  {
    "path": "common/ktls/ktls_write.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build linux && go1.25 && badlinkname\n\npackage ktls\n\nimport (\n\t\"crypto/cipher\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"net\"\n)\n\nfunc (c *Conn) Write(b []byte) (int, error) {\n\tif !c.kernelTx {\n\t\treturn c.Conn.Write(b)\n\t}\n\t// interlock with Close below\n\tfor {\n\t\tx := c.rawConn.ActiveCall.Load()\n\t\tif x&1 != 0 {\n\t\t\treturn 0, net.ErrClosed\n\t\t}\n\t\tif c.rawConn.ActiveCall.CompareAndSwap(x, x+2) {\n\t\t\tbreak\n\t\t}\n\t}\n\tdefer c.rawConn.ActiveCall.Add(-2)\n\n\t//if err := c.Conn.HandshakeContext(context.Background()); err != nil {\n\t//\treturn 0, err\n\t//}\n\n\tc.rawConn.Out.Lock()\n\tdefer c.rawConn.Out.Unlock()\n\n\tif err := *c.rawConn.Out.Err; err != nil {\n\t\treturn 0, err\n\t}\n\n\tif !c.rawConn.IsHandshakeComplete.Load() {\n\t\treturn 0, tls.AlertError(alertInternalError)\n\t}\n\n\tif *c.rawConn.CloseNotifySent {\n\t\t// return 0, errShutdown\n\t\treturn 0, errors.New(\"tls: protocol is shutdown\")\n\t}\n\n\t// TLS 1.0 is susceptible to a chosen-plaintext\n\t// attack when using block mode ciphers due to predictable IVs.\n\t// This can be prevented by splitting each Application Data\n\t// record into two records, effectively randomizing the RawIV.\n\t//\n\t// https://www.openssl.org/~bodo/tls-cbc.txt\n\t// https://bugzilla.mozilla.org/show_bug.cgi?id=665814\n\t// https://www.imperialviolet.org/2012/01/15/beastfollowup.html\n\n\tvar m int\n\tif len(b) > 1 && *c.rawConn.Vers == tls.VersionTLS10 {\n\t\tif _, ok := (*c.rawConn.Out.Cipher).(cipher.BlockMode); ok {\n\t\t\tn, err := c.writeRecordLocked(recordTypeApplicationData, b[:1])\n\t\t\tif err != nil {\n\t\t\t\treturn n, c.rawConn.Out.SetErrorLocked(err)\n\t\t\t}\n\t\t\tm, b = 1, b[1:]\n\t\t}\n\t}\n\n\tn, err := c.writeRecordLocked(recordTypeApplicationData, b)\n\treturn n + m, c.rawConn.Out.SetErrorLocked(err)\n}\n\nfunc (c *Conn) writeRecordLocked(typ uint16, data []byte) (n int, err error) {\n\tif !c.kernelTx {\n\t\treturn c.rawConn.WriteRecordLocked(typ, data)\n\t}\n\t/*for len(data) > 0 {\n\t\tm := len(data)\n\t\tif maxPayload := c.maxPayloadSizeForWrite(typ); m > maxPayload {\n\t\t\tm = maxPayload\n\t\t}\n\t\t_, err = c.writeKernelRecord(typ, data[:m])\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += m\n\t\tdata = data[m:]\n\t}*/\n\treturn c.writeKernelRecord(typ, data)\n}\n\nconst (\n\t// tcpMSSEstimate is a conservative estimate of the TCP maximum segment\n\t// size (MSS). A constant is used, rather than querying the kernel for\n\t// the actual MSS, to avoid complexity. The value here is the IPv6\n\t// minimum MTU (1280 bytes) minus the overhead of an IPv6 header (40\n\t// bytes) and a TCP header with timestamps (32 bytes).\n\ttcpMSSEstimate = 1208\n\n\t// recordSizeBoostThreshold is the number of bytes of application data\n\t// sent after which the TLS record size will be increased to the\n\t// maximum.\n\trecordSizeBoostThreshold = 128 * 1024\n)\n\nfunc (c *Conn) maxPayloadSizeForWrite(typ uint16) int {\n\tif /*c.config.DynamicRecordSizingDisabled ||*/ typ != recordTypeApplicationData {\n\t\treturn maxPlaintext\n\t}\n\n\tif *c.rawConn.PacketsSent >= recordSizeBoostThreshold {\n\t\treturn maxPlaintext\n\t}\n\n\t// Subtract TLS overheads to get the maximum payload size.\n\tpayloadBytes := tcpMSSEstimate - recordHeaderLen - c.rawConn.Out.ExplicitNonceLen()\n\tif rawCipher := *c.rawConn.Out.Cipher; rawCipher != nil {\n\t\tswitch ciph := rawCipher.(type) {\n\t\tcase cipher.Stream:\n\t\t\tpayloadBytes -= (*c.rawConn.Out.Mac).Size()\n\t\tcase cipher.AEAD:\n\t\t\tpayloadBytes -= ciph.Overhead()\n\t\t/*case cbcMode:\n\t\tblockSize := ciph.BlockSize()\n\t\t// The payload must fit in a multiple of blockSize, with\n\t\t// room for at least one padding byte.\n\t\tpayloadBytes = (payloadBytes & ^(blockSize - 1)) - 1\n\t\t// The RawMac is appended before padding so affects the\n\t\t// payload size directly.\n\t\tpayloadBytes -= c.out.mac.Size()*/\n\t\tdefault:\n\t\t\tpanic(\"unknown cipher type\")\n\t\t}\n\t}\n\tif *c.rawConn.Vers == tls.VersionTLS13 {\n\t\tpayloadBytes-- // encrypted ContentType\n\t}\n\n\t// Allow packet growth in arithmetic progression up to max.\n\tpkt := *c.rawConn.PacketsSent\n\t*c.rawConn.PacketsSent++\n\tif pkt > 1000 {\n\t\treturn maxPlaintext // avoid overflow in multiply below\n\t}\n\n\tn := payloadBytes * int(pkt+1)\n\tif n > maxPlaintext {\n\t\tn = maxPlaintext\n\t}\n\treturn n\n}\n"
  },
  {
    "path": "common/listener/listener.go",
    "content": "package listener\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/settings\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"github.com/vishvananda/netns\"\n)\n\ntype Listener struct {\n\tctx                      context.Context\n\tlogger                   logger.ContextLogger\n\tnetwork                  []string\n\tlistenOptions            option.ListenOptions\n\tconnHandler              adapter.ConnectionHandlerEx\n\tpacketHandler            adapter.PacketHandlerEx\n\toobPacketHandler         adapter.OOBPacketHandlerEx\n\tthreadUnsafePacketWriter bool\n\tdisablePacketOutput      bool\n\tsetSystemProxy           bool\n\tsystemProxySOCKS         bool\n\ttproxy                   bool\n\n\ttcpListener          net.Listener\n\tsystemProxy          settings.SystemProxy\n\tudpConn              *net.UDPConn\n\tudpAddr              M.Socksaddr\n\tpacketOutbound       chan *N.PacketBuffer\n\tpacketOutboundClosed chan struct{}\n\tshutdown             atomic.Bool\n}\n\ntype Options struct {\n\tContext                  context.Context\n\tLogger                   logger.ContextLogger\n\tNetwork                  []string\n\tListen                   option.ListenOptions\n\tConnectionHandler        adapter.ConnectionHandlerEx\n\tPacketHandler            adapter.PacketHandlerEx\n\tOOBPacketHandler         adapter.OOBPacketHandlerEx\n\tThreadUnsafePacketWriter bool\n\tDisablePacketOutput      bool\n\tSetSystemProxy           bool\n\tSystemProxySOCKS         bool\n\tTProxy                   bool\n}\n\nfunc New(\n\toptions Options,\n) *Listener {\n\treturn &Listener{\n\t\tctx:                      options.Context,\n\t\tlogger:                   options.Logger,\n\t\tnetwork:                  options.Network,\n\t\tlistenOptions:            options.Listen,\n\t\tconnHandler:              options.ConnectionHandler,\n\t\tpacketHandler:            options.PacketHandler,\n\t\toobPacketHandler:         options.OOBPacketHandler,\n\t\tthreadUnsafePacketWriter: options.ThreadUnsafePacketWriter,\n\t\tdisablePacketOutput:      options.DisablePacketOutput,\n\t\tsetSystemProxy:           options.SetSystemProxy,\n\t\tsystemProxySOCKS:         options.SystemProxySOCKS,\n\t\ttproxy:                   options.TProxy,\n\t}\n}\n\nfunc (l *Listener) Start() error {\n\tif common.Contains(l.network, N.NetworkTCP) {\n\t\t_, err := l.ListenTCP()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgo l.loopTCPIn()\n\t}\n\tif common.Contains(l.network, N.NetworkUDP) {\n\t\t_, err := l.ListenUDP()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tl.packetOutboundClosed = make(chan struct{})\n\t\tl.packetOutbound = make(chan *N.PacketBuffer, 64)\n\t\tgo l.loopUDPIn()\n\t\tif !l.disablePacketOutput {\n\t\t\tgo l.loopUDPOut()\n\t\t}\n\t}\n\tif l.setSystemProxy {\n\t\tlistenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port\n\t\tvar listenAddrString string\n\t\tlistenAddr := l.listenOptions.Listen.Build(netip.IPv4Unspecified())\n\t\tif listenAddr.IsUnspecified() {\n\t\t\tlistenAddrString = \"127.0.0.1\"\n\t\t} else {\n\t\t\tlistenAddrString = listenAddr.String()\n\t\t}\n\t\tsystemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"initialize system proxy\")\n\t\t}\n\t\terr = systemProxy.Enable()\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"set system proxy\")\n\t\t}\n\t\tl.systemProxy = systemProxy\n\t}\n\treturn nil\n}\n\nfunc (l *Listener) Close() error {\n\tl.shutdown.Store(true)\n\tvar err error\n\tif l.systemProxy != nil && l.systemProxy.IsEnabled() {\n\t\terr = l.systemProxy.Disable()\n\t}\n\treturn E.Errors(err, common.Close(\n\t\tl.tcpListener,\n\t\tcommon.PtrOrNil(l.udpConn),\n\t))\n}\n\nfunc (l *Listener) TCPListener() net.Listener {\n\treturn l.tcpListener\n}\n\nfunc (l *Listener) UDPConn() *net.UDPConn {\n\treturn l.udpConn\n}\n\nfunc (l *Listener) ListenOptions() option.ListenOptions {\n\treturn l.listenOptions\n}\n\nfunc ListenNetworkNamespace[T any](nameOrPath string, block func() (T, error)) (T, error) {\n\tif nameOrPath != \"\" {\n\t\truntime.LockOSThread()\n\t\tdefer runtime.UnlockOSThread()\n\t\tcurrentNs, err := netns.Get()\n\t\tif err != nil {\n\t\t\treturn common.DefaultValue[T](), E.Cause(err, \"get current netns\")\n\t\t}\n\t\tdefer currentNs.Close()\n\t\tdefer netns.Set(currentNs)\n\t\tvar targetNs netns.NsHandle\n\t\tif strings.HasPrefix(nameOrPath, \"/\") {\n\t\t\ttargetNs, err = netns.GetFromPath(nameOrPath)\n\t\t} else {\n\t\t\ttargetNs, err = netns.GetFromName(nameOrPath)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn common.DefaultValue[T](), E.Cause(err, \"get netns \", nameOrPath)\n\t\t}\n\t\tdefer targetNs.Close()\n\t\terr = netns.Set(targetNs)\n\t\tif err != nil {\n\t\t\treturn common.DefaultValue[T](), E.Cause(err, \"set netns to \", nameOrPath)\n\t\t}\n\t}\n\treturn block()\n}\n"
  },
  {
    "path": "common/listener/listener_tcp.go",
    "content": "package listener\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/redir\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"github.com/database64128/tfo-go/v2\"\n)\n\nfunc (l *Listener) ListenTCP() (net.Listener, error) {\n\t//nolint:staticcheck\n\tif l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {\n\t\treturn nil, E.New(\"Proxy Protocol is deprecated and removed in sing-box 1.6.0\")\n\t}\n\tvar err error\n\tbindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)\n\tvar listenConfig net.ListenConfig\n\tif l.listenOptions.BindInterface != \"\" {\n\t\tlistenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))\n\t}\n\tif l.listenOptions.RoutingMark != 0 {\n\t\tlistenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))\n\t}\n\tif l.listenOptions.ReuseAddr {\n\t\tlistenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())\n\t}\n\tif !l.listenOptions.DisableTCPKeepAlive {\n\t\tkeepIdle := time.Duration(l.listenOptions.TCPKeepAlive)\n\t\tif keepIdle == 0 {\n\t\t\tkeepIdle = C.TCPKeepAliveInitial\n\t\t}\n\t\tkeepInterval := time.Duration(l.listenOptions.TCPKeepAliveInterval)\n\t\tif keepInterval == 0 {\n\t\t\tkeepInterval = C.TCPKeepAliveInterval\n\t\t}\n\t\tlistenConfig.KeepAliveConfig = net.KeepAliveConfig{\n\t\t\tEnable:   true,\n\t\t\tIdle:     keepIdle,\n\t\t\tInterval: keepInterval,\n\t\t}\n\t}\n\tif l.listenOptions.TCPMultiPath {\n\t\tlistenConfig.SetMultipathTCP(true)\n\t}\n\tif l.tproxy {\n\t\tlistenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {\n\t\t\treturn control.Raw(conn, func(fd uintptr) error {\n\t\t\t\treturn redir.TProxy(fd, !strings.HasSuffix(network, \"4\"), false)\n\t\t\t})\n\t\t})\n\t}\n\ttcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {\n\t\tif l.listenOptions.TCPFastOpen {\n\t\t\tvar tfoConfig tfo.ListenConfig\n\t\t\ttfoConfig.ListenConfig = listenConfig\n\t\t\treturn tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())\n\t\t} else {\n\t\t\treturn listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())\n\t\t}\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tl.logger.Info(\"tcp server started at \", tcpListener.Addr())\n\tl.tcpListener = tcpListener\n\treturn tcpListener, err\n}\n\nfunc (l *Listener) loopTCPIn() {\n\ttcpListener := l.tcpListener\n\tvar metadata adapter.InboundContext\n\tfor {\n\t\tconn, err := tcpListener.Accept()\n\t\tif err != nil {\n\t\t\t//nolint:staticcheck\n\t\t\tif netError, isNetError := err.(net.Error); isNetError && netError.Temporary() {\n\t\t\t\tl.logger.Error(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif l.shutdown.Load() && E.IsClosed(err) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tl.tcpListener.Close()\n\t\t\tl.logger.Error(\"tcp listener closed: \", err)\n\t\t\tcontinue\n\t\t}\n\t\t//nolint:staticcheck\n\t\tmetadata.InboundDetour = l.listenOptions.Detour\n\t\tmetadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()\n\t\tmetadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()\n\t\tctx := log.ContextWithNewID(l.ctx)\n\t\tl.logger.InfoContext(ctx, \"inbound connection from \", metadata.Source)\n\t\tgo l.connHandler.NewConnectionEx(ctx, conn, metadata, nil)\n\t}\n}\n"
  },
  {
    "path": "common/listener/listener_udp.go",
    "content": "package listener\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/redir\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nfunc (l *Listener) ListenUDP() (net.PacketConn, error) {\n\tbindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)\n\tvar listenConfig net.ListenConfig\n\tif l.listenOptions.BindInterface != \"\" {\n\t\tlistenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))\n\t}\n\tif l.listenOptions.RoutingMark != 0 {\n\t\tlistenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))\n\t}\n\tif l.listenOptions.ReuseAddr {\n\t\tlistenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())\n\t}\n\tvar udpFragment bool\n\tif l.listenOptions.UDPFragment != nil {\n\t\tudpFragment = *l.listenOptions.UDPFragment\n\t} else {\n\t\tudpFragment = l.listenOptions.UDPFragmentDefault\n\t}\n\tif !udpFragment {\n\t\tlistenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment())\n\t}\n\tif l.tproxy {\n\t\tlistenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {\n\t\t\treturn control.Raw(conn, func(fd uintptr) error {\n\t\t\t\treturn redir.TProxy(fd, !strings.HasSuffix(network, \"4\"), true)\n\t\t\t})\n\t\t})\n\t}\n\tudpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {\n\t\treturn listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tl.udpConn = udpConn.(*net.UDPConn)\n\tl.udpAddr = bindAddr\n\tl.logger.Info(\"udp server started at \", udpConn.LocalAddr())\n\treturn udpConn, err\n}\n\nfunc (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) {\n\treturn ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) {\n\t\tif l.listenOptions.BindInterface != \"\" {\n\t\t\tdialer.Control = control.Append(dialer.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))\n\t\t}\n\t\tif l.listenOptions.RoutingMark != 0 {\n\t\t\tdialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))\n\t\t}\n\t\tif l.listenOptions.ReuseAddr {\n\t\t\tdialer.Control = control.Append(dialer.Control, control.ReuseAddr())\n\t\t}\n\t\treturn dialer.DialContext(ctx, network, address)\n\t})\n}\n\nfunc (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {\n\treturn ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {\n\t\tif l.listenOptions.BindInterface != \"\" {\n\t\t\tlistenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))\n\t\t}\n\t\tif l.listenOptions.RoutingMark != 0 {\n\t\t\tlistenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))\n\t\t}\n\t\tif l.listenOptions.ReuseAddr {\n\t\t\tlistenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())\n\t\t}\n\t\treturn listenConfig.ListenPacket(ctx, network, address)\n\t})\n}\n\nfunc (l *Listener) UDPAddr() M.Socksaddr {\n\treturn l.udpAddr\n}\n\nfunc (l *Listener) PacketWriter() N.PacketWriter {\n\treturn (*packetWriter)(l)\n}\n\nfunc (l *Listener) loopUDPIn() {\n\tdefer close(l.packetOutboundClosed)\n\tvar buffer *buf.Buffer\n\tif !l.threadUnsafePacketWriter {\n\t\tbuffer = buf.NewPacket()\n\t\tdefer buffer.Release()\n\t\tbuffer.IncRef()\n\t\tdefer buffer.DecRef()\n\t}\n\tif l.oobPacketHandler != nil {\n\t\toob := make([]byte, 1024)\n\t\tfor {\n\t\t\tif l.threadUnsafePacketWriter {\n\t\t\t\tbuffer = buf.NewPacket()\n\t\t\t} else {\n\t\t\t\tbuffer.Reset()\n\t\t\t}\n\t\t\tn, oobN, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob)\n\t\t\tif err != nil {\n\t\t\t\tif l.threadUnsafePacketWriter {\n\t\t\t\t\tbuffer.Release()\n\t\t\t\t}\n\t\t\t\tif l.shutdown.Load() && E.IsClosed(err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tl.udpConn.Close()\n\t\t\t\tl.logger.Error(\"udp listener closed: \", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbuffer.Truncate(n)\n\t\t\tl.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap())\n\t\t}\n\t} else {\n\t\tfor {\n\t\t\tif l.threadUnsafePacketWriter {\n\t\t\t\tbuffer = buf.NewPacket()\n\t\t\t} else {\n\t\t\t\tbuffer.Reset()\n\t\t\t}\n\t\t\tn, addr, err := l.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())\n\t\t\tif err != nil {\n\t\t\t\tif l.threadUnsafePacketWriter {\n\t\t\t\t\tbuffer.Release()\n\t\t\t\t}\n\t\t\t\tif l.shutdown.Load() && E.IsClosed(err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tl.udpConn.Close()\n\t\t\t\tl.logger.Error(\"udp listener closed: \", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbuffer.Truncate(n)\n\t\t\tl.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap())\n\t\t}\n\t}\n}\n\nfunc (l *Listener) loopUDPOut() {\n\tfor {\n\t\tselect {\n\t\tcase packet := <-l.packetOutbound:\n\t\t\tdestination := packet.Destination.AddrPort()\n\t\t\t_, err := l.udpConn.WriteToUDPAddrPort(packet.Buffer.Bytes(), destination)\n\t\t\tpacket.Buffer.Release()\n\t\t\tN.PutPacketBuffer(packet)\n\t\t\tif err != nil {\n\t\t\t\tif l.shutdown.Load() && E.IsClosed(err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tl.logger.Error(\"udp listener write back: \", destination, \": \", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcontinue\n\t\tcase <-l.packetOutboundClosed:\n\t\t}\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase packet := <-l.packetOutbound:\n\t\t\t\tpacket.Buffer.Release()\n\t\t\t\tN.PutPacketBuffer(packet)\n\t\t\tdefault:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype packetWriter Listener\n\nfunc (w *packetWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {\n\tpacket := N.NewPacketBuffer()\n\tpacket.Buffer = buffer\n\tpacket.Destination = destination\n\tselect {\n\tcase w.packetOutbound <- packet:\n\t\treturn nil\n\tdefault:\n\t\tbuffer.Release()\n\t\tN.PutPacketBuffer(packet)\n\t\tif w.shutdown.Load() {\n\t\t\treturn os.ErrClosed\n\t\t}\n\t\tw.logger.Trace(\"dropped packet to \", destination)\n\t\treturn nil\n\t}\n}\n\nfunc (w *packetWriter) WriteIsThreadUnsafe() {\n}\n"
  },
  {
    "path": "common/mux/client.go",
    "content": "package mux\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-mux\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype Client = mux.Client\n\nfunc NewClientWithOptions(dialer N.Dialer, logger logger.Logger, options option.OutboundMultiplexOptions) (*Client, error) {\n\tif !options.Enabled {\n\t\treturn nil, nil\n\t}\n\tvar brutalOptions mux.BrutalOptions\n\tif options.Brutal != nil && options.Brutal.Enabled {\n\t\tbrutalOptions = mux.BrutalOptions{\n\t\t\tEnabled:    true,\n\t\t\tSendBPS:    uint64(options.Brutal.UpMbps * C.MbpsToBps),\n\t\t\tReceiveBPS: uint64(options.Brutal.DownMbps * C.MbpsToBps),\n\t\t}\n\t\tif brutalOptions.SendBPS < mux.BrutalMinSpeedBPS {\n\t\t\treturn nil, E.New(\"brutal: invalid upload speed\")\n\t\t}\n\t\tif brutalOptions.ReceiveBPS < mux.BrutalMinSpeedBPS {\n\t\t\treturn nil, E.New(\"brutal: invalid download speed\")\n\t\t}\n\t}\n\treturn mux.NewClient(mux.Options{\n\t\tDialer:         &clientDialer{dialer},\n\t\tLogger:         logger,\n\t\tProtocol:       options.Protocol,\n\t\tMaxConnections: options.MaxConnections,\n\t\tMinStreams:     options.MinStreams,\n\t\tMaxStreams:     options.MaxStreams,\n\t\tPadding:        options.Padding,\n\t\tBrutal:         brutalOptions,\n\t})\n}\n\ntype clientDialer struct {\n\tN.Dialer\n}\n\nfunc (d *clientDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\treturn d.Dialer.DialContext(adapter.OverrideContext(ctx), network, destination)\n}\n\nfunc (d *clientDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn d.Dialer.ListenPacket(adapter.OverrideContext(ctx), destination)\n}\n"
  },
  {
    "path": "common/mux/router.go",
    "content": "package mux\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-mux\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype Router struct {\n\trouter  adapter.ConnectionRouterEx\n\tservice *mux.Service\n}\n\nfunc NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) {\n\tif !options.Enabled {\n\t\treturn router, nil\n\t}\n\tvar brutalOptions mux.BrutalOptions\n\tif options.Brutal != nil && options.Brutal.Enabled {\n\t\tbrutalOptions = mux.BrutalOptions{\n\t\t\tEnabled:    true,\n\t\t\tSendBPS:    uint64(options.Brutal.UpMbps * C.MbpsToBps),\n\t\t\tReceiveBPS: uint64(options.Brutal.DownMbps * C.MbpsToBps),\n\t\t}\n\t\tif brutalOptions.SendBPS < mux.BrutalMinSpeedBPS {\n\t\t\treturn nil, E.New(\"brutal: invalid upload speed\")\n\t\t}\n\t\tif brutalOptions.ReceiveBPS < mux.BrutalMinSpeedBPS {\n\t\t\treturn nil, E.New(\"brutal: invalid download speed\")\n\t\t}\n\t}\n\tservice, err := mux.NewService(mux.ServiceOptions{\n\t\tNewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {\n\t\t\treturn log.ContextWithNewID(ctx)\n\t\t},\n\t\tLogger:    logger,\n\t\tHandlerEx: adapter.NewRouteContextHandlerEx(router),\n\t\tPadding:   options.Padding,\n\t\tBrutal:    brutalOptions,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Router{router, service}, nil\n}\n\n// Deprecated: Use RouteConnectionEx instead.\nfunc (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {\n\tif metadata.Destination == mux.Destination {\n\t\t// TODO: check if WithContext is necessary\n\t\treturn r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))\n\t} else {\n\t\treturn r.router.RouteConnection(ctx, conn, metadata)\n\t}\n}\n\n// Deprecated: Use RoutePacketConnectionEx instead.\nfunc (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {\n\treturn r.router.RoutePacketConnection(ctx, conn, metadata)\n}\n\nfunc (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tif metadata.Destination == mux.Destination {\n\t\tr.service.NewConnectionEx(adapter.WithContext(ctx, &metadata), conn, metadata.Source, metadata.Destination, onClose)\n\t\treturn\n\t}\n\tr.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tr.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "common/pipelistener/listener.go",
    "content": "package pipelistener\n\nimport (\n\t\"io\"\n\t\"net\"\n)\n\nvar _ net.Listener = (*Listener)(nil)\n\ntype Listener struct {\n\tpipe chan net.Conn\n\tdone chan struct{}\n}\n\nfunc New(channelSize int) *Listener {\n\treturn &Listener{\n\t\tpipe: make(chan net.Conn, channelSize),\n\t\tdone: make(chan struct{}),\n\t}\n}\n\nfunc (l *Listener) Serve(conn net.Conn) {\n\tl.pipe <- conn\n}\n\nfunc (l *Listener) Accept() (net.Conn, error) {\n\tselect {\n\tcase conn := <-l.pipe:\n\t\treturn conn, nil\n\tcase <-l.done:\n\t\treturn nil, io.ErrClosedPipe\n\t}\n}\n\nfunc (l *Listener) Close() error {\n\tselect {\n\tcase <-l.done:\n\t\treturn io.ErrClosedPipe\n\tdefault:\n\t}\n\tclose(l.done)\n\treturn nil\n}\n\nfunc (l *Listener) Addr() net.Addr {\n\treturn addr{}\n}\n\ntype addr struct{}\n\nfunc (a addr) Network() string {\n\treturn \"pipe\"\n}\n\nfunc (a addr) String() string {\n\treturn \"pipe\"\n}\n"
  },
  {
    "path": "common/process/searcher.go",
    "content": "package process\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"os/user\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-tun\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\ntype Searcher interface {\n\tFindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error)\n}\n\nvar ErrNotFound = E.New(\"process not found\")\n\ntype Config struct {\n\tLogger         log.ContextLogger\n\tPackageManager tun.PackageManager\n}\n\nfunc FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {\n\tinfo, err := searcher.FindProcessInfo(ctx, network, source, destination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif info.UserId != -1 {\n\t\tosUser, _ := user.LookupId(F.ToString(info.UserId))\n\t\tif osUser != nil {\n\t\t\tinfo.UserName = osUser.Username\n\t\t}\n\t}\n\treturn info, nil\n}\n"
  },
  {
    "path": "common/process/searcher_android.go",
    "content": "package process\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-tun\"\n)\n\nvar _ Searcher = (*androidSearcher)(nil)\n\ntype androidSearcher struct {\n\tpackageManager tun.PackageManager\n}\n\nfunc NewSearcher(config Config) (Searcher, error) {\n\treturn &androidSearcher{config.PackageManager}, nil\n}\n\nfunc (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {\n\t_, uid, err := resolveSocketByNetlink(network, source, destination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded {\n\t\treturn &adapter.ConnectionOwner{\n\t\t\tUserId:             int32(uid),\n\t\t\tAndroidPackageName: sharedPackage,\n\t\t}, nil\n\t}\n\tif packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded {\n\t\treturn &adapter.ConnectionOwner{\n\t\t\tUserId:             int32(uid),\n\t\t\tAndroidPackageName: packageName,\n\t\t}, nil\n\t}\n\treturn &adapter.ConnectionOwner{UserId: int32(uid)}, nil\n}\n"
  },
  {
    "path": "common/process/searcher_darwin.go",
    "content": "package process\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nvar _ Searcher = (*darwinSearcher)(nil)\n\ntype darwinSearcher struct{}\n\nfunc NewSearcher(_ Config) (Searcher, error) {\n\treturn &darwinSearcher{}, nil\n}\n\nfunc (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {\n\tprocessName, err := findProcessName(network, source.Addr(), int(source.Port()))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &adapter.ConnectionOwner{ProcessPath: processName, UserId: -1}, nil\n}\n\nvar structSize = func() int {\n\tvalue, _ := syscall.Sysctl(\"kern.osrelease\")\n\tmajor, _, _ := strings.Cut(value, \".\")\n\tn, _ := strconv.ParseInt(major, 10, 64)\n\tswitch true {\n\tcase n >= 22:\n\t\treturn 408\n\tdefault:\n\t\t// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n\n\t\t// size/offset are round up (aligned) to 8 bytes in darwin\n\t\t// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +\n\t\t// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))\n\t\treturn 384\n\t}\n}()\n\nfunc findProcessName(network string, ip netip.Addr, port int) (string, error) {\n\tvar spath string\n\tswitch network {\n\tcase N.NetworkTCP:\n\t\tspath = \"net.inet.tcp.pcblist_n\"\n\tcase N.NetworkUDP:\n\t\tspath = \"net.inet.udp.pcblist_n\"\n\tdefault:\n\t\treturn \"\", os.ErrInvalid\n\t}\n\n\tisIPv4 := ip.Is4()\n\n\tvalue, err := unix.SysctlRaw(spath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbuf := value\n\n\t// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n\n\t// size/offset are round up (aligned) to 8 bytes in darwin\n\t// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +\n\t// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))\n\titemSize := structSize\n\tif network == N.NetworkTCP {\n\t\t// rup8(sizeof(xtcpcb_n))\n\t\titemSize += 208\n\t}\n\n\tvar fallbackUDPProcess string\n\t// skip the first xinpgen(24 bytes) block\n\tfor i := 24; i+itemSize <= len(buf); i += itemSize {\n\t\t// offset of xinpcb_n and xsocket_n\n\t\tinp, so := i, i+104\n\n\t\tsrcPort := binary.BigEndian.Uint16(buf[inp+18 : inp+20])\n\t\tif uint16(port) != srcPort {\n\t\t\tcontinue\n\t\t}\n\n\t\t// xinpcb_n.inp_vflag\n\t\tflag := buf[inp+44]\n\n\t\tvar srcIP netip.Addr\n\t\tsrcIsIPv4 := false\n\t\tswitch {\n\t\tcase flag&0x1 > 0 && isIPv4:\n\t\t\t// ipv4\n\t\t\tsrcIP = netip.AddrFrom4([4]byte(buf[inp+76 : inp+80]))\n\t\t\tsrcIsIPv4 = true\n\t\tcase flag&0x2 > 0 && !isIPv4:\n\t\t\t// ipv6\n\t\t\tsrcIP = netip.AddrFrom16([16]byte(buf[inp+64 : inp+80]))\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\n\t\tif ip == srcIP {\n\t\t\t// xsocket_n.so_last_pid\n\t\t\tpid := readNativeUint32(buf[so+68 : so+72])\n\t\t\treturn getExecPathFromPID(pid)\n\t\t}\n\n\t\t// udp packet connection may be not equal with srcIP\n\t\tif network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {\n\t\t\tpid := readNativeUint32(buf[so+68 : so+72])\n\t\t\tfallbackUDPProcess, _ = getExecPathFromPID(pid)\n\t\t}\n\t}\n\n\tif network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {\n\t\treturn fallbackUDPProcess, nil\n\t}\n\n\treturn \"\", ErrNotFound\n}\n\nfunc getExecPathFromPID(pid uint32) (string, error) {\n\tconst (\n\t\tprocpidpathinfo     = 0xb\n\t\tprocpidpathinfosize = 1024\n\t\tproccallnumpidinfo  = 0x2\n\t)\n\tbuf := make([]byte, procpidpathinfosize)\n\t_, _, errno := syscall.Syscall6(\n\t\tsyscall.SYS_PROC_INFO,\n\t\tproccallnumpidinfo,\n\t\tuintptr(pid),\n\t\tprocpidpathinfo,\n\t\t0,\n\t\tuintptr(unsafe.Pointer(&buf[0])),\n\t\tprocpidpathinfosize)\n\tif errno != 0 {\n\t\treturn \"\", errno\n\t}\n\n\treturn unix.ByteSliceToString(buf), nil\n}\n\nfunc readNativeUint32(b []byte) uint32 {\n\treturn *(*uint32)(unsafe.Pointer(&b[0]))\n}\n"
  },
  {
    "path": "common/process/searcher_linux.go",
    "content": "//go:build linux && !android\n\npackage process\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n)\n\nvar _ Searcher = (*linuxSearcher)(nil)\n\ntype linuxSearcher struct {\n\tlogger log.ContextLogger\n}\n\nfunc NewSearcher(config Config) (Searcher, error) {\n\treturn &linuxSearcher{config.Logger}, nil\n}\n\nfunc (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {\n\tinode, uid, err := resolveSocketByNetlink(network, source, destination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tprocessPath, err := resolveProcessNameByProcSearch(inode, uid)\n\tif err != nil {\n\t\ts.logger.DebugContext(ctx, \"find process path: \", err)\n\t}\n\treturn &adapter.ConnectionOwner{\n\t\tUserId:      int32(uid),\n\t\tProcessPath: processPath,\n\t}, nil\n}\n"
  },
  {
    "path": "common/process/searcher_linux_shared.go",
    "content": "//go:build linux\n\npackage process\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"syscall\"\n\t\"unicode\"\n\t\"unsafe\"\n\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\n// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62\nvar nativeEndian = func() binary.ByteOrder {\n\tvar x uint32 = 0x01020304\n\tif *(*byte)(unsafe.Pointer(&x)) == 0x01 {\n\t\treturn binary.BigEndian\n\t}\n\n\treturn binary.LittleEndian\n}()\n\nconst (\n\tsizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48\n\tsocketDiagByFamily      = 20\n\tpathProc                = \"/proc\"\n)\n\nfunc resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) {\n\tvar family uint8\n\tvar protocol uint8\n\n\tswitch network {\n\tcase N.NetworkTCP:\n\t\tprotocol = syscall.IPPROTO_TCP\n\tcase N.NetworkUDP:\n\t\tprotocol = syscall.IPPROTO_UDP\n\tdefault:\n\t\treturn 0, 0, os.ErrInvalid\n\t}\n\n\tif source.Addr().Is4() {\n\t\tfamily = syscall.AF_INET\n\t} else {\n\t\tfamily = syscall.AF_INET6\n\t}\n\n\treq := packSocketDiagRequest(family, protocol, source)\n\n\tsocket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)\n\tif err != nil {\n\t\treturn 0, 0, E.Cause(err, \"dial netlink\")\n\t}\n\tdefer syscall.Close(socket)\n\n\tsyscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})\n\tsyscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})\n\n\terr = syscall.Connect(socket, &syscall.SockaddrNetlink{\n\t\tFamily: syscall.AF_NETLINK,\n\t\tPad:    0,\n\t\tPid:    0,\n\t\tGroups: 0,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = syscall.Write(socket, req)\n\tif err != nil {\n\t\treturn 0, 0, E.Cause(err, \"write netlink request\")\n\t}\n\n\tbuffer := buf.New()\n\tdefer buffer.Release()\n\n\tn, err := syscall.Read(socket, buffer.FreeBytes())\n\tif err != nil {\n\t\treturn 0, 0, E.Cause(err, \"read netlink response\")\n\t}\n\n\tbuffer.Truncate(n)\n\n\tmessages, err := syscall.ParseNetlinkMessage(buffer.Bytes())\n\tif err != nil {\n\t\treturn 0, 0, E.Cause(err, \"parse netlink message\")\n\t} else if len(messages) == 0 {\n\t\treturn 0, 0, E.New(\"unexcepted netlink response\")\n\t}\n\n\tmessage := messages[0]\n\tif message.Header.Type&syscall.NLMSG_ERROR != 0 {\n\t\treturn 0, 0, E.New(\"netlink message: NLMSG_ERROR\")\n\t}\n\n\tinode, uid = unpackSocketDiagResponse(&messages[0])\n\treturn\n}\n\nfunc packSocketDiagRequest(family, protocol byte, source netip.AddrPort) []byte {\n\ts := make([]byte, 16)\n\tcopy(s, source.Addr().AsSlice())\n\n\tbuf := make([]byte, sizeOfSocketDiagRequest)\n\n\tnativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest)\n\tnativeEndian.PutUint16(buf[4:6], socketDiagByFamily)\n\tnativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP)\n\tnativeEndian.PutUint32(buf[8:12], 0)\n\tnativeEndian.PutUint32(buf[12:16], 0)\n\n\tbuf[16] = family\n\tbuf[17] = protocol\n\tbuf[18] = 0\n\tbuf[19] = 0\n\tnativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)\n\n\tbinary.BigEndian.PutUint16(buf[24:26], source.Port())\n\tbinary.BigEndian.PutUint16(buf[26:28], 0)\n\n\tcopy(buf[28:44], s)\n\tcopy(buf[44:60], net.IPv6zero)\n\n\tnativeEndian.PutUint32(buf[60:64], 0)\n\tnativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF)\n\n\treturn buf\n}\n\nfunc unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {\n\tif len(msg.Data) < 72 {\n\t\treturn 0, 0\n\t}\n\n\tdata := msg.Data\n\n\tuid = nativeEndian.Uint32(data[64:68])\n\tinode = nativeEndian.Uint32(data[68:72])\n\n\treturn\n}\n\nfunc resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {\n\tfiles, err := os.ReadDir(pathProc)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbuffer := make([]byte, syscall.PathMax)\n\tsocket := []byte(fmt.Sprintf(\"socket:[%d]\", inode))\n\n\tfor _, f := range files {\n\t\tif !f.IsDir() || !isPid(f.Name()) {\n\t\t\tcontinue\n\t\t}\n\n\t\tinfo, err := f.Info()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif info.Sys().(*syscall.Stat_t).Uid != uid {\n\t\t\tcontinue\n\t\t}\n\n\t\tprocessPath := path.Join(pathProc, f.Name())\n\t\tfdPath := path.Join(processPath, \"fd\")\n\n\t\tfds, err := os.ReadDir(fdPath)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, fd := range fds {\n\t\t\tn, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif bytes.Equal(buffer[:n], socket) {\n\t\t\t\treturn os.Readlink(path.Join(processPath, \"exe\"))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn \"\", fmt.Errorf(\"process of uid(%d),inode(%d) not found\", uid, inode)\n}\n\nfunc isPid(s string) bool {\n\treturn strings.IndexFunc(s, func(r rune) bool {\n\t\treturn !unicode.IsDigit(r)\n\t}) == -1\n}\n"
  },
  {
    "path": "common/process/searcher_stub.go",
    "content": "//go:build !linux && !windows && !darwin\n\npackage process\n\nimport (\n\t\"os\"\n)\n\nfunc NewSearcher(_ Config) (Searcher, error) {\n\treturn nil, os.ErrInvalid\n}\n"
  },
  {
    "path": "common/process/searcher_windows.go",
    "content": "package process\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/winiphlpapi\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\nvar _ Searcher = (*windowsSearcher)(nil)\n\ntype windowsSearcher struct{}\n\nfunc NewSearcher(_ Config) (Searcher, error) {\n\terr := initWin32API()\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"init win32 api\")\n\t}\n\treturn &windowsSearcher{}, nil\n}\n\nfunc initWin32API() error {\n\treturn winiphlpapi.LoadExtendedTable()\n}\n\nfunc (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {\n\tpid, err := winiphlpapi.FindPid(network, source)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpath, err := getProcessPath(pid)\n\tif err != nil {\n\t\treturn &adapter.ConnectionOwner{ProcessID: pid, UserId: -1}, err\n\t}\n\treturn &adapter.ConnectionOwner{ProcessID: pid, ProcessPath: path, UserId: -1}, nil\n}\n\nfunc getProcessPath(pid uint32) (string, error) {\n\tswitch pid {\n\tcase 0:\n\t\treturn \":System Idle Process\", nil\n\tcase 4:\n\t\treturn \":System\", nil\n\t}\n\thandle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer windows.CloseHandle(handle)\n\tsize := uint32(syscall.MAX_LONG_PATH)\n\tbuf := make([]uint16, syscall.MAX_LONG_PATH)\n\terr = windows.QueryFullProcessImageName(handle, 0, &buf[0], &size)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn windows.UTF16ToString(buf[:size]), nil\n}\n"
  },
  {
    "path": "common/redir/redir_darwin.go",
    "content": "package redir\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\tM \"github.com/sagernet/sing/common/metadata\"\n)\n\nconst (\n\tPF_OUT      = 0x2\n\tDIOCNATLOOK = 0xc0544417\n)\n\nfunc GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) {\n\tfd, err := syscall.Open(\"/dev/pf\", 0, syscall.O_RDONLY)\n\tif err != nil {\n\t\treturn netip.AddrPort{}, err\n\t}\n\tdefer syscall.Close(fd)\n\tnl := struct {\n\t\tsaddr, daddr, rsaddr, rdaddr       [16]byte\n\t\tsxport, dxport, rsxport, rdxport   [4]byte\n\t\taf, proto, protoVariant, direction uint8\n\t}{\n\t\taf:        syscall.AF_INET,\n\t\tproto:     syscall.IPPROTO_TCP,\n\t\tdirection: PF_OUT,\n\t}\n\tla := conn.LocalAddr().(*net.TCPAddr)\n\tra := conn.RemoteAddr().(*net.TCPAddr)\n\traIP, laIP := ra.IP, la.IP\n\traPort, laPort := ra.Port, la.Port\n\tswitch {\n\tcase raIP.To4() != nil:\n\t\tcopy(nl.saddr[:net.IPv4len], raIP.To4())\n\t\tcopy(nl.daddr[:net.IPv4len], laIP.To4())\n\t\tnl.af = syscall.AF_INET\n\tdefault:\n\t\tcopy(nl.saddr[:], raIP.To16())\n\t\tcopy(nl.daddr[:], laIP.To16())\n\t\tnl.af = syscall.AF_INET6\n\t}\n\tnl.sxport[0], nl.sxport[1] = byte(raPort>>8), byte(raPort)\n\tnl.dxport[0], nl.dxport[1] = byte(laPort>>8), byte(laPort)\n\tif _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 {\n\t\treturn netip.AddrPort{}, errno\n\t}\n\n\tvar ip net.IP\n\tswitch nl.af {\n\tcase syscall.AF_INET:\n\t\tip = make(net.IP, net.IPv4len)\n\t\tcopy(ip, nl.rdaddr[:net.IPv4len])\n\tcase syscall.AF_INET6:\n\t\tip = make(net.IP, net.IPv6len)\n\t\tcopy(ip, nl.rdaddr[:])\n\t}\n\tport := uint16(nl.rdxport[0])<<8 | uint16(nl.rdxport[1])\n\tdestination = netip.AddrPortFrom(M.AddrFromIP(ip), port)\n\treturn\n}\n"
  },
  {
    "path": "common/redir/redir_linux.go",
    "content": "package redir\n\nimport (\n\t\"encoding/binary\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/control\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n)\n\nfunc GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) {\n\tsyscallConn, ok := common.Cast[syscall.Conn](conn)\n\tif !ok {\n\t\treturn netip.AddrPort{}, os.ErrInvalid\n\t}\n\terr = control.Conn(syscallConn, func(fd uintptr) error {\n\t\tconst SO_ORIGINAL_DST = 80\n\t\tif conn.RemoteAddr().(*net.TCPAddr).IP.To4() != nil {\n\t\t\traw, err := syscall.GetsockoptIPv6Mreq(int(fd), syscall.IPPROTO_IP, SO_ORIGINAL_DST)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdestination = netip.AddrPortFrom(M.AddrFromIP(raw.Multiaddr[4:8]), uint16(raw.Multiaddr[2])<<8+uint16(raw.Multiaddr[3]))\n\t\t} else {\n\t\t\traw, err := syscall.GetsockoptIPv6MTUInfo(int(fd), syscall.IPPROTO_IPV6, SO_ORIGINAL_DST)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tvar port [2]byte\n\t\t\tbinary.BigEndian.PutUint16(port[:], raw.Addr.Port)\n\t\t\tdestination = netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), binary.LittleEndian.Uint16(port[:]))\n\t\t}\n\t\treturn nil\n\t})\n\treturn\n}\n"
  },
  {
    "path": "common/redir/redir_other.go",
    "content": "//go:build !linux && !darwin\n\npackage redir\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n)\n\nfunc GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) {\n\treturn netip.AddrPort{}, os.ErrInvalid\n}\n"
  },
  {
    "path": "common/redir/tproxy_linux.go",
    "content": "package redir\n\nimport (\n\t\"encoding/binary\"\n\t\"net/netip\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {\n\terr := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)\n\tif err == nil {\n\t\terr = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)\n\t}\n\tif err == nil && isIPv6 {\n\t\terr = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)\n\t}\n\tif isUDP {\n\t\tif err == nil {\n\t\t\terr = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)\n\t\t}\n\t\tif err == nil && isIPv6 {\n\t\t\terr = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)\n\t\t}\n\t}\n\treturn err\n}\n\nfunc TProxyWriteBack() control.Func {\n\treturn func(network, address string, conn syscall.RawConn) error {\n\t\treturn control.Raw(conn, func(fd uintptr) error {\n\t\t\tif M.ParseSocksaddr(address).Addr.Is6() {\n\t\t\t\treturn syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)\n\t\t\t} else {\n\t\t\t\treturn syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {\n\tcontrolMessages, err := unix.ParseSocketControlMessage(oob)\n\tif err != nil {\n\t\treturn netip.AddrPort{}, err\n\t}\n\tfor _, message := range controlMessages {\n\t\tif message.Header.Level == unix.SOL_IP && message.Header.Type == unix.IP_RECVORIGDSTADDR {\n\t\t\treturn netip.AddrPortFrom(M.AddrFromIP(message.Data[4:8]), binary.BigEndian.Uint16(message.Data[2:4])), nil\n\t\t} else if message.Header.Level == unix.SOL_IPV6 && message.Header.Type == unix.IPV6_RECVORIGDSTADDR {\n\t\t\treturn netip.AddrPortFrom(M.AddrFromIP(message.Data[8:24]), binary.BigEndian.Uint16(message.Data[2:4])), nil\n\t\t}\n\t}\n\treturn netip.AddrPort{}, E.New(\"not found\")\n}\n"
  },
  {
    "path": "common/redir/tproxy_other.go",
    "content": "//go:build !linux\n\npackage redir\n\nimport (\n\t\"net/netip\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing/common/control\"\n)\n\nfunc TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {\n\treturn os.ErrInvalid\n}\n\nfunc TProxyWriteBack() control.Func {\n\treturn nil\n}\n\nfunc GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {\n\treturn netip.AddrPort{}, os.ErrInvalid\n}\n"
  },
  {
    "path": "common/settings/proxy_android.go",
    "content": "package settings\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"strings\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/shell\"\n)\n\ntype AndroidSystemProxy struct {\n\tuseRish      bool\n\trishPath     string\n\tserverAddr   M.Socksaddr\n\tsupportSOCKS bool\n\tisEnabled    bool\n}\n\nfunc NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*AndroidSystemProxy, error) {\n\tuserId := os.Getuid()\n\tvar (\n\t\tuseRish  bool\n\t\trishPath string\n\t)\n\tif userId == 0 || userId == 1000 || userId == 2000 {\n\t\tuseRish = false\n\t} else {\n\t\trishPath, useRish = C.FindPath(\"rish\")\n\t\tif !useRish {\n\t\t\treturn nil, E.Cause(os.ErrPermission, \"root or system (adb) permission is required for set system proxy\")\n\t\t}\n\t}\n\treturn &AndroidSystemProxy{\n\t\tuseRish:      useRish,\n\t\trishPath:     rishPath,\n\t\tserverAddr:   serverAddr,\n\t\tsupportSOCKS: supportSOCKS,\n\t}, nil\n}\n\nfunc (p *AndroidSystemProxy) IsEnabled() bool {\n\treturn p.isEnabled\n}\n\nfunc (p *AndroidSystemProxy) Enable() error {\n\terr := p.runAndroidShell(\"settings\", \"put\", \"global\", \"http_proxy\", p.serverAddr.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.isEnabled = true\n\treturn nil\n}\n\nfunc (p *AndroidSystemProxy) Disable() error {\n\terr := p.runAndroidShell(\"settings\", \"put\", \"global\", \"http_proxy\", \":0\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.isEnabled = false\n\treturn nil\n}\n\nfunc (p *AndroidSystemProxy) runAndroidShell(name string, args ...string) error {\n\tif !p.useRish {\n\t\treturn shell.Exec(name, args...).Attach().Run()\n\t} else {\n\t\treturn shell.Exec(\"sh\", p.rishPath, \"-c\", F.ToString(name, \" \", strings.Join(args, \" \"))).Attach().Run()\n\t}\n}\n"
  },
  {
    "path": "common/settings/proxy_darwin.go",
    "content": "package settings\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/shell\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\t\"github.com/sagernet/sing/service\"\n)\n\ntype DarwinSystemProxy struct {\n\tmonitor       tun.DefaultInterfaceMonitor\n\tinterfaceName string\n\telement       *list.Element[tun.DefaultInterfaceUpdateCallback]\n\tserverAddr    M.Socksaddr\n\tsupportSOCKS  bool\n\tisEnabled     bool\n}\n\nfunc NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {\n\tinterfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()\n\tif interfaceMonitor == nil {\n\t\treturn nil, E.New(\"missing interface monitor\")\n\t}\n\tproxy := &DarwinSystemProxy{\n\t\tmonitor:      interfaceMonitor,\n\t\tserverAddr:   serverAddr,\n\t\tsupportSOCKS: supportSOCKS,\n\t}\n\tproxy.element = interfaceMonitor.RegisterCallback(proxy.routeUpdate)\n\treturn proxy, nil\n}\n\nfunc (p *DarwinSystemProxy) IsEnabled() bool {\n\treturn p.isEnabled\n}\n\nfunc (p *DarwinSystemProxy) Enable() error {\n\treturn p.update0()\n}\n\nfunc (p *DarwinSystemProxy) Disable() error {\n\tinterfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif p.supportSOCKS {\n\t\terr = shell.Exec(\"networksetup\", \"-setsocksfirewallproxystate\", interfaceDisplayName, \"off\").Attach().Run()\n\t}\n\tif err == nil {\n\t\terr = shell.Exec(\"networksetup\", \"-setwebproxystate\", interfaceDisplayName, \"off\").Attach().Run()\n\t}\n\tif err == nil {\n\t\terr = shell.Exec(\"networksetup\", \"-setsecurewebproxystate\", interfaceDisplayName, \"off\").Attach().Run()\n\t}\n\tif err == nil {\n\t\tp.isEnabled = false\n\t}\n\treturn err\n}\n\nfunc (p *DarwinSystemProxy) routeUpdate(defaultInterface *control.Interface, flags int) {\n\tif !p.isEnabled || defaultInterface == nil {\n\t\treturn\n\t}\n\t_ = p.update0()\n}\n\nfunc (p *DarwinSystemProxy) update0() error {\n\tnewInterface := p.monitor.DefaultInterface()\n\tif p.interfaceName == newInterface.Name {\n\t\treturn nil\n\t}\n\tif p.interfaceName != \"\" {\n\t\t_ = p.Disable()\n\t}\n\tp.interfaceName = newInterface.Name\n\tinterfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif p.supportSOCKS {\n\t\terr = shell.Exec(\"networksetup\", \"-setsocksfirewallproxy\", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = shell.Exec(\"networksetup\", \"-setwebproxy\", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = shell.Exec(\"networksetup\", \"-setsecurewebproxy\", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.isEnabled = true\n\treturn nil\n}\n\nfunc getInterfaceDisplayName(name string) (string, error) {\n\tcontent, err := shell.Exec(\"networksetup\", \"-listallhardwareports\").ReadOutput()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfor _, deviceSpan := range strings.Split(string(content), \"Ethernet Address\") {\n\t\tif strings.Contains(deviceSpan, \"Device: \"+name) {\n\t\t\tsubstr := \"Hardware Port: \"\n\t\t\tdeviceSpan = deviceSpan[strings.Index(deviceSpan, substr)+len(substr):]\n\t\t\tdeviceSpan = deviceSpan[:strings.Index(deviceSpan, \"\\n\")]\n\t\t\treturn deviceSpan, nil\n\t\t}\n\t}\n\treturn \"\", E.New(name, \" not found in networksetup -listallhardwareports\")\n}\n"
  },
  {
    "path": "common/settings/proxy_linux.go",
    "content": "//go:build linux && !android\n\npackage settings\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/shell\"\n)\n\ntype LinuxSystemProxy struct {\n\thasGSettings    bool\n\tkWriteConfigCmd string\n\tsudoUser        string\n\tserverAddr      M.Socksaddr\n\tsupportSOCKS    bool\n\tisEnabled       bool\n}\n\nfunc NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) {\n\thasGSettings := common.Error(exec.LookPath(\"gsettings\")) == nil\n\tkWriteConfigCmds := []string{\n\t\t\"kwriteconfig5\",\n\t\t\"kwriteconfig6\",\n\t}\n\tvar kWriteConfigCmd string\n\tfor _, cmd := range kWriteConfigCmds {\n\t\tif common.Error(exec.LookPath(cmd)) == nil {\n\t\t\tkWriteConfigCmd = cmd\n\t\t\tbreak\n\t\t}\n\t}\n\tvar sudoUser string\n\tif os.Getuid() == 0 {\n\t\tsudoUser = os.Getenv(\"SUDO_USER\")\n\t}\n\tif !hasGSettings && kWriteConfigCmd == \"\" {\n\t\treturn nil, E.New(\"unsupported desktop environment\")\n\t}\n\treturn &LinuxSystemProxy{\n\t\thasGSettings:    hasGSettings,\n\t\tkWriteConfigCmd: kWriteConfigCmd,\n\t\tsudoUser:        sudoUser,\n\t\tserverAddr:      serverAddr,\n\t\tsupportSOCKS:    supportSOCKS,\n\t}, nil\n}\n\nfunc (p *LinuxSystemProxy) IsEnabled() bool {\n\treturn p.isEnabled\n}\n\nfunc (p *LinuxSystemProxy) Enable() error {\n\tif p.hasGSettings {\n\t\terr := p.runAsUser(\"gsettings\", \"set\", \"org.gnome.system.proxy.http\", \"enabled\", \"true\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif p.supportSOCKS {\n\t\t\terr = p.setGnomeProxy(\"ftp\", \"http\", \"https\", \"socks\")\n\t\t} else {\n\t\t\terr = p.setGnomeProxy(\"http\", \"https\")\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = p.runAsUser(\"gsettings\", \"set\", \"org.gnome.system.proxy\", \"use-same-proxy\", F.ToString(p.supportSOCKS))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = p.runAsUser(\"gsettings\", \"set\", \"org.gnome.system.proxy\", \"mode\", \"manual\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif p.kWriteConfigCmd != \"\" {\n\t\terr := p.runAsUser(p.kWriteConfigCmd, \"--file\", \"kioslaverc\", \"--group\", \"Proxy Settings\", \"--key\", \"ProxyType\", \"1\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif p.supportSOCKS {\n\t\t\terr = p.setKDEProxy(\"ftp\", \"http\", \"https\", \"socks\")\n\t\t} else {\n\t\t\terr = p.setKDEProxy(\"http\", \"https\")\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = p.runAsUser(p.kWriteConfigCmd, \"--file\", \"kioslaverc\", \"--group\", \"Proxy Settings\", \"--key\", \"Authmode\", \"0\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = p.runAsUser(\"dbus-send\", \"--type=signal\", \"/KIO/Scheduler\", \"org.kde.KIO.Scheduler.reparseSlaveConfiguration\", \"string:''\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tp.isEnabled = true\n\treturn nil\n}\n\nfunc (p *LinuxSystemProxy) Disable() error {\n\tif p.hasGSettings {\n\t\terr := p.runAsUser(\"gsettings\", \"set\", \"org.gnome.system.proxy\", \"mode\", \"none\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif p.kWriteConfigCmd != \"\" {\n\t\terr := p.runAsUser(p.kWriteConfigCmd, \"--file\", \"kioslaverc\", \"--group\", \"Proxy Settings\", \"--key\", \"ProxyType\", \"0\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = p.runAsUser(\"dbus-send\", \"--type=signal\", \"/KIO/Scheduler\", \"org.kde.KIO.Scheduler.reparseSlaveConfiguration\", \"string:''\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tp.isEnabled = false\n\treturn nil\n}\n\nfunc (p *LinuxSystemProxy) runAsUser(name string, args ...string) error {\n\tif os.Getuid() != 0 {\n\t\treturn shell.Exec(name, args...).Attach().Run()\n\t} else if p.sudoUser != \"\" {\n\t\treturn shell.Exec(\"su\", \"-\", p.sudoUser, \"-c\", F.ToString(name, \" \", strings.Join(args, \" \"))).Attach().Run()\n\t} else {\n\t\treturn E.New(\"set system proxy: unable to set as root\")\n\t}\n}\n\nfunc (p *LinuxSystemProxy) setGnomeProxy(proxyTypes ...string) error {\n\tfor _, proxyType := range proxyTypes {\n\t\terr := p.runAsUser(\"gsettings\", \"set\", \"org.gnome.system.proxy.\"+proxyType, \"host\", p.serverAddr.AddrString())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = p.runAsUser(\"gsettings\", \"set\", \"org.gnome.system.proxy.\"+proxyType, \"port\", F.ToString(p.serverAddr.Port))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (p *LinuxSystemProxy) setKDEProxy(proxyTypes ...string) error {\n\tfor _, proxyType := range proxyTypes {\n\t\tvar proxyUrl string\n\t\tif proxyType == \"socks\" {\n\t\t\tproxyUrl = \"socks://\" + p.serverAddr.String()\n\t\t} else {\n\t\t\tproxyUrl = \"http://\" + p.serverAddr.String()\n\t\t}\n\t\terr := p.runAsUser(\n\t\t\tp.kWriteConfigCmd,\n\t\t\t\"--file\",\n\t\t\t\"kioslaverc\",\n\t\t\t\"--group\",\n\t\t\t\"Proxy Settings\",\n\t\t\t\"--key\", proxyType+\"Proxy\",\n\t\t\tproxyUrl,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/settings/proxy_stub.go",
    "content": "//go:build !(windows || linux || darwin)\n\npackage settings\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\tM \"github.com/sagernet/sing/common/metadata\"\n)\n\nfunc NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (SystemProxy, error) {\n\treturn nil, os.ErrInvalid\n}\n"
  },
  {
    "path": "common/settings/proxy_windows.go",
    "content": "package settings\n\nimport (\n\t\"context\"\n\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/wininet\"\n)\n\ntype WindowsSystemProxy struct {\n\tserverAddr   M.Socksaddr\n\tsupportSOCKS bool\n\tisEnabled    bool\n}\n\nfunc NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*WindowsSystemProxy, error) {\n\treturn &WindowsSystemProxy{\n\t\tserverAddr:   serverAddr,\n\t\tsupportSOCKS: supportSOCKS,\n\t}, nil\n}\n\nfunc (p *WindowsSystemProxy) IsEnabled() bool {\n\treturn p.isEnabled\n}\n\nfunc (p *WindowsSystemProxy) Enable() error {\n\terr := wininet.SetSystemProxy(\"http://\"+p.serverAddr.String(), \"\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.isEnabled = true\n\treturn nil\n}\n\nfunc (p *WindowsSystemProxy) Disable() error {\n\terr := wininet.ClearSystemProxy()\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.isEnabled = false\n\treturn nil\n}\n"
  },
  {
    "path": "common/settings/system_proxy.go",
    "content": "package settings\n\ntype SystemProxy interface {\n\tIsEnabled() bool\n\tEnable() error\n\tDisable() error\n}\n"
  },
  {
    "path": "common/settings/wifi.go",
    "content": "package settings\n\nimport \"github.com/sagernet/sing-box/adapter\"\n\ntype WIFIMonitor interface {\n\tReadWIFIState() adapter.WIFIState\n\tStart() error\n\tClose() error\n}\n"
  },
  {
    "path": "common/settings/wifi_linux.go",
    "content": "package settings\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype LinuxWIFIMonitor struct {\n\tmonitor WIFIMonitor\n}\n\nfunc NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {\n\tmonitors := []func(func(adapter.WIFIState)) (WIFIMonitor, error){\n\t\tnewNetworkManagerMonitor,\n\t\tnewIWDMonitor,\n\t\tnewWpaSupplicantMonitor,\n\t\tnewConnManMonitor,\n\t}\n\tvar errors []error\n\tfor _, factory := range monitors {\n\t\tmonitor, err := factory(callback)\n\t\tif err == nil {\n\t\t\treturn &LinuxWIFIMonitor{monitor: monitor}, nil\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\treturn nil, E.Cause(E.Errors(errors...), \"no supported WIFI manager found\")\n}\n\nfunc (m *LinuxWIFIMonitor) ReadWIFIState() adapter.WIFIState {\n\treturn m.monitor.ReadWIFIState()\n}\n\nfunc (m *LinuxWIFIMonitor) Start() error {\n\tif m.monitor != nil {\n\t\treturn m.monitor.Start()\n\t}\n\treturn nil\n}\n\nfunc (m *LinuxWIFIMonitor) Close() error {\n\tif m.monitor != nil {\n\t\treturn m.monitor.Close()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/settings/wifi_linux_connman.go",
    "content": "//go:build linux\n\npackage settings\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\n\t\"github.com/godbus/dbus/v5\"\n)\n\ntype connmanMonitor struct {\n\tconn       *dbus.Conn\n\tcallback   func(adapter.WIFIState)\n\tcancel     context.CancelFunc\n\tsignalChan chan *dbus.Signal\n}\n\nfunc newConnManMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {\n\tconn, err := dbus.ConnectSystemBus()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcmObj := conn.Object(\"net.connman\", \"/\")\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\tdefer cancel()\n\tcall := cmObj.CallWithContext(ctx, \"net.connman.Manager.GetServices\", 0)\n\tif call.Err != nil {\n\t\tconn.Close()\n\t\treturn nil, call.Err\n\t}\n\treturn &connmanMonitor{conn: conn, callback: callback}, nil\n}\n\nfunc (m *connmanMonitor) ReadWIFIState() adapter.WIFIState {\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\n\tcmObj := m.conn.Object(\"net.connman\", \"/\")\n\tvar services []interface{}\n\terr := cmObj.CallWithContext(ctx, \"net.connman.Manager.GetServices\", 0).Store(&services)\n\tif err != nil {\n\t\treturn adapter.WIFIState{}\n\t}\n\n\tfor _, service := range services {\n\t\tservicePair, ok := service.([]interface{})\n\t\tif !ok || len(servicePair) != 2 {\n\t\t\tcontinue\n\t\t}\n\n\t\tserviceProps, ok := servicePair[1].(map[string]dbus.Variant)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\ttypeVariant, hasType := serviceProps[\"Type\"]\n\t\tif !hasType {\n\t\t\tcontinue\n\t\t}\n\t\tserviceType, ok := typeVariant.Value().(string)\n\t\tif !ok || serviceType != \"wifi\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tstateVariant, hasState := serviceProps[\"State\"]\n\t\tif !hasState {\n\t\t\tcontinue\n\t\t}\n\t\tstate, ok := stateVariant.Value().(string)\n\t\tif !ok || (state != \"online\" && state != \"ready\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tnameVariant, hasName := serviceProps[\"Name\"]\n\t\tif !hasName {\n\t\t\tcontinue\n\t\t}\n\t\tssid, ok := nameVariant.Value().(string)\n\t\tif !ok || ssid == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tbssidVariant, hasBSSID := serviceProps[\"BSSID\"]\n\t\tif !hasBSSID {\n\t\t\treturn adapter.WIFIState{SSID: ssid}\n\t\t}\n\t\tbssid, ok := bssidVariant.Value().(string)\n\t\tif !ok {\n\t\t\treturn adapter.WIFIState{SSID: ssid}\n\t\t}\n\n\t\treturn adapter.WIFIState{\n\t\t\tSSID:  ssid,\n\t\t\tBSSID: strings.ToUpper(strings.ReplaceAll(bssid, \":\", \"\")),\n\t\t}\n\t}\n\n\treturn adapter.WIFIState{}\n}\n\nfunc (m *connmanMonitor) Start() error {\n\tif m.callback == nil {\n\t\treturn nil\n\t}\n\tctx, cancel := context.WithCancel(context.Background())\n\tm.cancel = cancel\n\n\tm.signalChan = make(chan *dbus.Signal, 10)\n\tm.conn.Signal(m.signalChan)\n\n\terr := m.conn.AddMatchSignal(\n\t\tdbus.WithMatchInterface(\"net.connman.Service\"),\n\t\tdbus.WithMatchSender(\"net.connman\"),\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstate := m.ReadWIFIState()\n\tgo m.monitorSignals(ctx, m.signalChan, state)\n\tm.callback(state)\n\n\treturn nil\n}\n\nfunc (m *connmanMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase signal, ok := <-signalChan:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// godbus Signal.Name uses \"interface.member\" format (e.g. \"net.connman.Service.PropertyChanged\"),\n\t\t\t// not just the member name. This differs from the D-Bus signal member in the match rule.\n\t\t\tif signal.Name == \"net.connman.Service.PropertyChanged\" {\n\t\t\t\tstate := m.ReadWIFIState()\n\t\t\t\tif state != lastState {\n\t\t\t\t\tlastState = state\n\t\t\t\t\tm.callback(state)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (m *connmanMonitor) Close() error {\n\tif m.cancel != nil {\n\t\tm.cancel()\n\t}\n\tif m.signalChan != nil {\n\t\tm.conn.RemoveSignal(m.signalChan)\n\t\tclose(m.signalChan)\n\t}\n\tif m.conn != nil {\n\t\tm.conn.RemoveMatchSignal(\n\t\t\tdbus.WithMatchInterface(\"net.connman.Service\"),\n\t\t\tdbus.WithMatchSender(\"net.connman\"),\n\t\t)\n\t\treturn m.conn.Close()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/settings/wifi_linux_iwd.go",
    "content": "//go:build linux\n\npackage settings\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\n\t\"github.com/godbus/dbus/v5\"\n)\n\ntype iwdMonitor struct {\n\tconn       *dbus.Conn\n\tcallback   func(adapter.WIFIState)\n\tcancel     context.CancelFunc\n\tsignalChan chan *dbus.Signal\n}\n\nfunc newIWDMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {\n\tconn, err := dbus.ConnectSystemBus()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tiwdObj := conn.Object(\"net.connman.iwd\", \"/\")\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\tdefer cancel()\n\tcall := iwdObj.CallWithContext(ctx, \"org.freedesktop.DBus.ObjectManager.GetManagedObjects\", 0)\n\tif call.Err != nil {\n\t\tconn.Close()\n\t\treturn nil, call.Err\n\t}\n\treturn &iwdMonitor{conn: conn, callback: callback}, nil\n}\n\nfunc (m *iwdMonitor) ReadWIFIState() adapter.WIFIState {\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\n\tiwdObj := m.conn.Object(\"net.connman.iwd\", \"/\")\n\tvar objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant\n\terr := iwdObj.CallWithContext(ctx, \"org.freedesktop.DBus.ObjectManager.GetManagedObjects\", 0).Store(&objects)\n\tif err != nil {\n\t\treturn adapter.WIFIState{}\n\t}\n\n\tfor _, interfaces := range objects {\n\t\tstationProps, hasStation := interfaces[\"net.connman.iwd.Station\"]\n\t\tif !hasStation {\n\t\t\tcontinue\n\t\t}\n\n\t\tstateVariant, hasState := stationProps[\"State\"]\n\t\tif !hasState {\n\t\t\tcontinue\n\t\t}\n\t\tstate, ok := stateVariant.Value().(string)\n\t\tif !ok || state != \"connected\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tconnectedNetworkVariant, hasNetwork := stationProps[\"ConnectedNetwork\"]\n\t\tif !hasNetwork {\n\t\t\tcontinue\n\t\t}\n\t\tnetworkPath, ok := connectedNetworkVariant.Value().(dbus.ObjectPath)\n\t\tif !ok || networkPath == \"/\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tnetworkInterfaces, hasNetworkPath := objects[networkPath]\n\t\tif !hasNetworkPath {\n\t\t\tcontinue\n\t\t}\n\n\t\tnetworkProps, hasNetworkInterface := networkInterfaces[\"net.connman.iwd.Network\"]\n\t\tif !hasNetworkInterface {\n\t\t\tcontinue\n\t\t}\n\n\t\tnameVariant, hasName := networkProps[\"Name\"]\n\t\tif !hasName {\n\t\t\tcontinue\n\t\t}\n\t\tssid, ok := nameVariant.Value().(string)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tconnectedBSSVariant, hasBSS := stationProps[\"ConnectedAccessPoint\"]\n\t\tif !hasBSS {\n\t\t\treturn adapter.WIFIState{SSID: ssid}\n\t\t}\n\t\tbssPath, ok := connectedBSSVariant.Value().(dbus.ObjectPath)\n\t\tif !ok || bssPath == \"/\" {\n\t\t\treturn adapter.WIFIState{SSID: ssid}\n\t\t}\n\n\t\tbssInterfaces, hasBSSPath := objects[bssPath]\n\t\tif !hasBSSPath {\n\t\t\treturn adapter.WIFIState{SSID: ssid}\n\t\t}\n\n\t\tbssProps, hasBSSInterface := bssInterfaces[\"net.connman.iwd.BasicServiceSet\"]\n\t\tif !hasBSSInterface {\n\t\t\treturn adapter.WIFIState{SSID: ssid}\n\t\t}\n\n\t\taddressVariant, hasAddress := bssProps[\"Address\"]\n\t\tif !hasAddress {\n\t\t\treturn adapter.WIFIState{SSID: ssid}\n\t\t}\n\t\tbssid, ok := addressVariant.Value().(string)\n\t\tif !ok {\n\t\t\treturn adapter.WIFIState{SSID: ssid}\n\t\t}\n\n\t\treturn adapter.WIFIState{\n\t\t\tSSID:  ssid,\n\t\t\tBSSID: strings.ToUpper(strings.ReplaceAll(bssid, \":\", \"\")),\n\t\t}\n\t}\n\n\treturn adapter.WIFIState{}\n}\n\nfunc (m *iwdMonitor) Start() error {\n\tif m.callback == nil {\n\t\treturn nil\n\t}\n\tctx, cancel := context.WithCancel(context.Background())\n\tm.cancel = cancel\n\n\tm.signalChan = make(chan *dbus.Signal, 10)\n\tm.conn.Signal(m.signalChan)\n\n\terr := m.conn.AddMatchSignal(\n\t\tdbus.WithMatchInterface(\"org.freedesktop.DBus.Properties\"),\n\t\tdbus.WithMatchSender(\"net.connman.iwd\"),\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstate := m.ReadWIFIState()\n\tgo m.monitorSignals(ctx, m.signalChan, state)\n\tm.callback(state)\n\n\treturn nil\n}\n\nfunc (m *iwdMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase signal, ok := <-signalChan:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif signal.Name == \"org.freedesktop.DBus.Properties.PropertiesChanged\" {\n\t\t\t\tstate := m.ReadWIFIState()\n\t\t\t\tif state != lastState {\n\t\t\t\t\tlastState = state\n\t\t\t\t\tm.callback(state)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (m *iwdMonitor) Close() error {\n\tif m.cancel != nil {\n\t\tm.cancel()\n\t}\n\tif m.signalChan != nil {\n\t\tm.conn.RemoveSignal(m.signalChan)\n\t\tclose(m.signalChan)\n\t}\n\tif m.conn != nil {\n\t\tm.conn.RemoveMatchSignal(\n\t\t\tdbus.WithMatchInterface(\"org.freedesktop.DBus.Properties\"),\n\t\t\tdbus.WithMatchSender(\"net.connman.iwd\"),\n\t\t)\n\t\treturn m.conn.Close()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/settings/wifi_linux_nm.go",
    "content": "//go:build linux\n\npackage settings\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\n\t\"github.com/godbus/dbus/v5\"\n)\n\ntype networkManagerMonitor struct {\n\tconn       *dbus.Conn\n\tcallback   func(adapter.WIFIState)\n\tcancel     context.CancelFunc\n\tsignalChan chan *dbus.Signal\n}\n\nfunc newNetworkManagerMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {\n\tconn, err := dbus.ConnectSystemBus()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnmObj := conn.Object(\"org.freedesktop.NetworkManager\", \"/org/freedesktop/NetworkManager\")\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\tdefer cancel()\n\tvar state uint32\n\terr = nmObj.CallWithContext(ctx, \"org.freedesktop.DBus.Properties.Get\", 0, \"org.freedesktop.NetworkManager\", \"State\").Store(&state)\n\tif err != nil {\n\t\tconn.Close()\n\t\treturn nil, err\n\t}\n\treturn &networkManagerMonitor{conn: conn, callback: callback}, nil\n}\n\nfunc (m *networkManagerMonitor) ReadWIFIState() adapter.WIFIState {\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\n\tnmObj := m.conn.Object(\"org.freedesktop.NetworkManager\", \"/org/freedesktop/NetworkManager\")\n\n\tvar activeConnectionPaths []dbus.ObjectPath\n\terr := nmObj.CallWithContext(ctx, \"org.freedesktop.DBus.Properties.Get\", 0, \"org.freedesktop.NetworkManager\", \"ActiveConnections\").Store(&activeConnectionPaths)\n\tif err != nil || len(activeConnectionPaths) == 0 {\n\t\treturn adapter.WIFIState{}\n\t}\n\n\tfor _, connectionPath := range activeConnectionPaths {\n\t\tconnObj := m.conn.Object(\"org.freedesktop.NetworkManager\", connectionPath)\n\n\t\tvar devicePaths []dbus.ObjectPath\n\t\terr = connObj.CallWithContext(ctx, \"org.freedesktop.DBus.Properties.Get\", 0, \"org.freedesktop.NetworkManager.Connection.Active\", \"Devices\").Store(&devicePaths)\n\t\tif err != nil || len(devicePaths) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, devicePath := range devicePaths {\n\t\t\tdeviceObj := m.conn.Object(\"org.freedesktop.NetworkManager\", devicePath)\n\n\t\t\tvar deviceType uint32\n\t\t\terr = deviceObj.CallWithContext(ctx, \"org.freedesktop.DBus.Properties.Get\", 0, \"org.freedesktop.NetworkManager.Device\", \"DeviceType\").Store(&deviceType)\n\t\t\tif err != nil || deviceType != 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar accessPointPath dbus.ObjectPath\n\t\t\terr = deviceObj.CallWithContext(ctx, \"org.freedesktop.DBus.Properties.Get\", 0, \"org.freedesktop.NetworkManager.Device.Wireless\", \"ActiveAccessPoint\").Store(&accessPointPath)\n\t\t\tif err != nil || accessPointPath == \"/\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tapObj := m.conn.Object(\"org.freedesktop.NetworkManager\", accessPointPath)\n\n\t\t\tvar ssidBytes []byte\n\t\t\terr = apObj.CallWithContext(ctx, \"org.freedesktop.DBus.Properties.Get\", 0, \"org.freedesktop.NetworkManager.AccessPoint\", \"Ssid\").Store(&ssidBytes)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar hwAddress string\n\t\t\terr = apObj.CallWithContext(ctx, \"org.freedesktop.DBus.Properties.Get\", 0, \"org.freedesktop.NetworkManager.AccessPoint\", \"HwAddress\").Store(&hwAddress)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tssid := strings.TrimSpace(string(ssidBytes))\n\t\t\tif ssid == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treturn adapter.WIFIState{\n\t\t\t\tSSID:  ssid,\n\t\t\t\tBSSID: strings.ToUpper(strings.ReplaceAll(hwAddress, \":\", \"\")),\n\t\t\t}\n\t\t}\n\t}\n\n\treturn adapter.WIFIState{}\n}\n\nfunc (m *networkManagerMonitor) Start() error {\n\tif m.callback == nil {\n\t\treturn nil\n\t}\n\tctx, cancel := context.WithCancel(context.Background())\n\tm.cancel = cancel\n\n\tm.signalChan = make(chan *dbus.Signal, 10)\n\tm.conn.Signal(m.signalChan)\n\n\terr := m.conn.AddMatchSignal(\n\t\tdbus.WithMatchSender(\"org.freedesktop.NetworkManager\"),\n\t\tdbus.WithMatchInterface(\"org.freedesktop.DBus.Properties\"),\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstate := m.ReadWIFIState()\n\tgo m.monitorSignals(ctx, m.signalChan, state)\n\tm.callback(state)\n\n\treturn nil\n}\n\nfunc (m *networkManagerMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase signal, ok := <-signalChan:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif signal.Name == \"org.freedesktop.DBus.Properties.PropertiesChanged\" {\n\t\t\t\tstate := m.ReadWIFIState()\n\t\t\t\tif state != lastState {\n\t\t\t\t\tlastState = state\n\t\t\t\t\tm.callback(state)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (m *networkManagerMonitor) Close() error {\n\tif m.cancel != nil {\n\t\tm.cancel()\n\t}\n\tif m.signalChan != nil {\n\t\tm.conn.RemoveSignal(m.signalChan)\n\t\tclose(m.signalChan)\n\t}\n\tif m.conn != nil {\n\t\tm.conn.RemoveMatchSignal(\n\t\t\tdbus.WithMatchSender(\"org.freedesktop.NetworkManager\"),\n\t\t\tdbus.WithMatchInterface(\"org.freedesktop.DBus.Properties\"),\n\t\t)\n\t\treturn m.conn.Close()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/settings/wifi_linux_wpa.go",
    "content": "package settings\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\nvar wpaSocketCounter atomic.Uint64\n\ntype wpaSupplicantMonitor struct {\n\tsocketPath  string\n\tcallback    func(adapter.WIFIState)\n\tcancel      context.CancelFunc\n\tmonitorConn *net.UnixConn\n\tconnMutex   sync.Mutex\n}\n\nfunc newWpaSupplicantMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {\n\tsocketDirs := []string{\"/var/run/wpa_supplicant\", \"/run/wpa_supplicant\"}\n\tfor _, socketDir := range socketDirs {\n\t\tentries, err := os.ReadDir(socketDir)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, entry := range entries {\n\t\t\tif entry.IsDir() || entry.Name() == \".\" || entry.Name() == \"..\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsocketPath := filepath.Join(socketDir, entry.Name())\n\t\t\tid := wpaSocketCounter.Add(1)\n\t\t\tlocalAddr := &net.UnixAddr{Name: fmt.Sprintf(\"@sing-box-wpa-%d-%d\", os.Getpid(), id), Net: \"unixgram\"}\n\t\t\tremoteAddr := &net.UnixAddr{Name: socketPath, Net: \"unixgram\"}\n\t\t\tconn, err := net.DialUnix(\"unixgram\", localAddr, remoteAddr)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconn.Close()\n\t\t\treturn &wpaSupplicantMonitor{socketPath: socketPath, callback: callback}, nil\n\t\t}\n\t}\n\treturn nil, os.ErrNotExist\n}\n\nfunc (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState {\n\tid := wpaSocketCounter.Add(1)\n\tlocalAddr := &net.UnixAddr{Name: fmt.Sprintf(\"@sing-box-wpa-%d-%d\", os.Getpid(), id), Net: \"unixgram\"}\n\tremoteAddr := &net.UnixAddr{Name: m.socketPath, Net: \"unixgram\"}\n\tconn, err := net.DialUnix(\"unixgram\", localAddr, remoteAddr)\n\tif err != nil {\n\t\treturn adapter.WIFIState{}\n\t}\n\tdefer conn.Close()\n\n\tconn.SetDeadline(time.Now().Add(3 * time.Second))\n\n\tstatus, err := m.sendCommand(conn, \"STATUS\")\n\tif err != nil {\n\t\treturn adapter.WIFIState{}\n\t}\n\n\tvar ssid, bssid string\n\tvar connected bool\n\tscanner := bufio.NewScanner(strings.NewReader(status))\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tif strings.HasPrefix(line, \"wpa_state=\") {\n\t\t\tstate := strings.TrimPrefix(line, \"wpa_state=\")\n\t\t\tconnected = state == \"COMPLETED\"\n\t\t} else if strings.HasPrefix(line, \"ssid=\") {\n\t\t\tssid = strings.TrimPrefix(line, \"ssid=\")\n\t\t} else if strings.HasPrefix(line, \"bssid=\") {\n\t\t\tbssid = strings.TrimPrefix(line, \"bssid=\")\n\t\t}\n\t}\n\n\tif !connected || ssid == \"\" {\n\t\treturn adapter.WIFIState{}\n\t}\n\n\treturn adapter.WIFIState{\n\t\tSSID:  ssid,\n\t\tBSSID: strings.ToUpper(strings.ReplaceAll(bssid, \":\", \"\")),\n\t}\n}\n\n// sendCommand sends a command to wpa_supplicant and returns the response.\n// Commands are sent without trailing newlines per the wpa_supplicant control\n// interface protocol - the official wpa_ctrl.c sends raw command strings.\nfunc (m *wpaSupplicantMonitor) sendCommand(conn *net.UnixConn, command string) (string, error) {\n\t_, err := conn.Write([]byte(command))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbuf := make([]byte, 4096)\n\tn, err := conn.Read(buf)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tresponse := string(buf[:n])\n\tif strings.HasPrefix(response, \"FAIL\") {\n\t\treturn \"\", os.ErrInvalid\n\t}\n\n\treturn strings.TrimSpace(response), nil\n}\n\nfunc (m *wpaSupplicantMonitor) Start() error {\n\tif m.callback == nil {\n\t\treturn nil\n\t}\n\tctx, cancel := context.WithCancel(context.Background())\n\tm.cancel = cancel\n\n\tstate := m.ReadWIFIState()\n\tgo m.monitorEvents(ctx, state)\n\tm.callback(state)\n\n\treturn nil\n}\n\nfunc (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adapter.WIFIState) {\n\tvar consecutiveErrors int\n\tvar debounceTimer *time.Timer\n\tvar debounceMutex sync.Mutex\n\n\tlocalAddr := &net.UnixAddr{Name: fmt.Sprintf(\"@sing-box-wpa-mon-%d\", os.Getpid()), Net: \"unixgram\"}\n\tremoteAddr := &net.UnixAddr{Name: m.socketPath, Net: \"unixgram\"}\n\tconn, err := net.DialUnix(\"unixgram\", localAddr, remoteAddr)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\tm.connMutex.Lock()\n\tm.monitorConn = conn\n\tm.connMutex.Unlock()\n\n\t// ATTACH/DETACH commands use os_strcmp() for exact matching in wpa_supplicant,\n\t// so they must be sent without trailing newlines.\n\t// See: https://w1.fi/cgit/hostap/tree/wpa_supplicant/ctrl_iface_unix.c\n\t_, err = conn.Write([]byte(\"ATTACH\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tbuf := make([]byte, 4096)\n\tn, err := conn.Read(buf)\n\tif err != nil || !strings.HasPrefix(string(buf[:n]), \"OK\") {\n\t\treturn\n\t}\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tdebounceMutex.Lock()\n\t\t\tif debounceTimer != nil {\n\t\t\t\tdebounceTimer.Stop()\n\t\t\t}\n\t\t\tdebounceMutex.Unlock()\n\t\t\tconn.Write([]byte(\"DETACH\"))\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\n\t\tconn.SetReadDeadline(time.Now().Add(30 * time.Second))\n\t\tn, err := conn.Read(buf)\n\t\tif err != nil {\n\t\t\tif netErr, ok := err.(net.Error); ok && netErr.Timeout() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t\tconsecutiveErrors++\n\t\t\tif consecutiveErrors > 10 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttime.Sleep(time.Second)\n\t\t\tcontinue\n\t\t}\n\t\tconsecutiveErrors = 0\n\n\t\tmsg := string(buf[:n])\n\t\tif strings.Contains(msg, \"CTRL-EVENT-CONNECTED\") || strings.Contains(msg, \"CTRL-EVENT-DISCONNECTED\") {\n\t\t\tdebounceMutex.Lock()\n\t\t\tif debounceTimer != nil {\n\t\t\t\tdebounceTimer.Stop()\n\t\t\t}\n\t\t\tdebounceTimer = time.AfterFunc(500*time.Millisecond, func() {\n\t\t\t\tstate := m.ReadWIFIState()\n\t\t\t\tif state != lastState {\n\t\t\t\t\tlastState = state\n\t\t\t\t\tm.callback(state)\n\t\t\t\t}\n\t\t\t})\n\t\t\tdebounceMutex.Unlock()\n\t\t}\n\t}\n}\n\nfunc (m *wpaSupplicantMonitor) Close() error {\n\tif m.cancel != nil {\n\t\tm.cancel()\n\t}\n\tm.connMutex.Lock()\n\tif m.monitorConn != nil {\n\t\tm.monitorConn.Close()\n\t}\n\tm.connMutex.Unlock()\n\treturn nil\n}\n"
  },
  {
    "path": "common/settings/wifi_stub.go",
    "content": "//go:build !linux && !windows\n\npackage settings\n\nimport (\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\ntype stubWIFIMonitor struct{}\n\nfunc NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {\n\treturn nil, os.ErrInvalid\n}\n\nfunc (m *stubWIFIMonitor) ReadWIFIState() adapter.WIFIState {\n\treturn adapter.WIFIState{}\n}\n\nfunc (m *stubWIFIMonitor) Start() error {\n\treturn nil\n}\n\nfunc (m *stubWIFIMonitor) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "common/settings/wifi_windows.go",
    "content": "//go:build windows\n\npackage settings\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common/winwlanapi\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\ntype windowsWIFIMonitor struct {\n\thandle    windows.Handle\n\tcallback  func(adapter.WIFIState)\n\tcancel    context.CancelFunc\n\tlastState adapter.WIFIState\n\tmutex     sync.Mutex\n}\n\nfunc NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {\n\thandle, err := winwlanapi.OpenHandle()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinterfaces, err := winwlanapi.EnumInterfaces(handle)\n\tif err != nil {\n\t\twinwlanapi.CloseHandle(handle)\n\t\treturn nil, err\n\t}\n\tif len(interfaces) == 0 {\n\t\twinwlanapi.CloseHandle(handle)\n\t\treturn nil, fmt.Errorf(\"no wireless interfaces found\")\n\t}\n\n\treturn &windowsWIFIMonitor{\n\t\thandle:   handle,\n\t\tcallback: callback,\n\t}, nil\n}\n\nfunc (m *windowsWIFIMonitor) ReadWIFIState() adapter.WIFIState {\n\tinterfaces, err := winwlanapi.EnumInterfaces(m.handle)\n\tif err != nil || len(interfaces) == 0 {\n\t\treturn adapter.WIFIState{}\n\t}\n\n\tfor _, iface := range interfaces {\n\t\tif iface.InterfaceState != winwlanapi.InterfaceStateConnected {\n\t\t\tcontinue\n\t\t}\n\n\t\tguid := iface.InterfaceGUID\n\t\tattrs, err := winwlanapi.QueryCurrentConnection(m.handle, &guid)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tssidLength := attrs.AssociationAttributes.SSID.Length\n\t\tif ssidLength == 0 || ssidLength > winwlanapi.Dot11SSIDMaxLength {\n\t\t\tcontinue\n\t\t}\n\n\t\tssid := string(attrs.AssociationAttributes.SSID.SSID[:ssidLength])\n\t\tbssid := formatBSSID(attrs.AssociationAttributes.BSSID)\n\n\t\treturn adapter.WIFIState{\n\t\t\tSSID:  strings.TrimSpace(ssid),\n\t\t\tBSSID: bssid,\n\t\t}\n\t}\n\n\treturn adapter.WIFIState{}\n}\n\nfunc formatBSSID(mac winwlanapi.Dot11MacAddress) string {\n\treturn fmt.Sprintf(\"%02X%02X%02X%02X%02X%02X\",\n\t\tmac[0], mac[1], mac[2], mac[3], mac[4], mac[5])\n}\n\nfunc (m *windowsWIFIMonitor) Start() error {\n\tif m.callback == nil {\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tm.cancel = cancel\n\n\tm.lastState = m.ReadWIFIState()\n\n\tcallbackFunc := func(data *winwlanapi.NotificationData, callbackContext uintptr) uintptr {\n\t\tif data.NotificationSource != winwlanapi.NotificationSourceACM {\n\t\t\treturn 0\n\t\t}\n\t\tswitch data.NotificationCode {\n\t\tcase winwlanapi.NotificationACMConnectionComplete,\n\t\t\twinwlanapi.NotificationACMDisconnected:\n\t\t\tm.checkAndNotify()\n\t\t}\n\t\treturn 0\n\t}\n\n\tcallbackPointer := syscall.NewCallback(callbackFunc)\n\n\terr := winwlanapi.RegisterNotification(m.handle, winwlanapi.NotificationSourceACM, callbackPointer, 0)\n\tif err != nil {\n\t\tcancel()\n\t\treturn err\n\t}\n\n\tgo func() {\n\t\t<-ctx.Done()\n\t}()\n\n\tm.callback(m.lastState)\n\treturn nil\n}\n\nfunc (m *windowsWIFIMonitor) checkAndNotify() {\n\tm.mutex.Lock()\n\tdefer m.mutex.Unlock()\n\n\tstate := m.ReadWIFIState()\n\tif state != m.lastState {\n\t\tm.lastState = state\n\t\tif m.callback != nil {\n\t\t\tm.callback(state)\n\t\t}\n\t}\n}\n\nfunc (m *windowsWIFIMonitor) Close() error {\n\tif m.cancel != nil {\n\t\tm.cancel()\n\t}\n\twinwlanapi.UnregisterNotification(m.handle)\n\treturn winwlanapi.CloseHandle(m.handle)\n}\n"
  },
  {
    "path": "common/sniff/bittorrent.go",
    "content": "package sniff\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nconst (\n\ttrackerConnectFlag    = 0\n\ttrackerProtocolID     = 0x41727101980\n\ttrackerConnectMinSize = 16\n)\n\n// BitTorrent detects if the stream is a BitTorrent connection.\n// For the BitTorrent protocol specification, see https://www.bittorrent.org/beps/bep_0003.html\nfunc BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {\n\tvar first byte\n\terr := binary.Read(reader, binary.BigEndian, &first)\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\n\tif first != 19 {\n\t\treturn os.ErrInvalid\n\t}\n\n\tconst header = \"BitTorrent protocol\"\n\tvar protocol [19]byte\n\tvar n int\n\tn, err = reader.Read(protocol[:])\n\tif string(protocol[:n]) != header[:n] {\n\t\treturn os.ErrInvalid\n\t}\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\tif n < 19 {\n\t\treturn ErrNeedMoreData\n\t}\n\n\tmetadata.Protocol = C.ProtocolBitTorrent\n\treturn nil\n}\n\n// UTP detects if the packet is a uTP connection packet.\n// For the uTP protocol specification, see\n//  1. https://www.bittorrent.org/beps/bep_0029.html\n//  2. https://github.com/bittorrent/libutp/blob/2b364cbb0650bdab64a5de2abb4518f9f228ec44/utp_internal.cpp#L112\nfunc UTP(_ context.Context, metadata *adapter.InboundContext, packet []byte) error {\n\t// A valid uTP packet must be at least 20 bytes long.\n\tif len(packet) < 20 {\n\t\treturn os.ErrInvalid\n\t}\n\n\tversion := packet[0] & 0x0F\n\tty := packet[0] >> 4\n\tif version != 1 || ty > 4 {\n\t\treturn os.ErrInvalid\n\t}\n\n\t// Validate the extensions\n\textension := packet[1]\n\treader := bytes.NewReader(packet[20:])\n\tfor extension != 0 {\n\t\terr := binary.Read(reader, binary.BigEndian, &extension)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif extension > 0x04 {\n\t\t\treturn os.ErrInvalid\n\t\t}\n\t\tvar length byte\n\t\terr = binary.Read(reader, binary.BigEndian, &length)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = reader.Seek(int64(length), io.SeekCurrent)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tmetadata.Protocol = C.ProtocolBitTorrent\n\treturn nil\n}\n\n// UDPTracker detects if the packet is a UDP Tracker Protocol packet.\n// For the UDP Tracker Protocol specification, see https://www.bittorrent.org/beps/bep_0015.html\nfunc UDPTracker(_ context.Context, metadata *adapter.InboundContext, packet []byte) error {\n\tif len(packet) < trackerConnectMinSize {\n\t\treturn os.ErrInvalid\n\t}\n\tif binary.BigEndian.Uint64(packet[:8]) != trackerProtocolID {\n\t\treturn os.ErrInvalid\n\t}\n\tif binary.BigEndian.Uint32(packet[8:12]) != trackerConnectFlag {\n\t\treturn os.ErrInvalid\n\t}\n\tmetadata.Protocol = C.ProtocolBitTorrent\n\treturn nil\n}\n"
  },
  {
    "path": "common/sniff/bittorrent_test.go",
    "content": "package sniff_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSniffBittorrent(t *testing.T) {\n\tt.Parallel()\n\n\tpackets := []string{\n\t\t\"13426974546f7272656e742070726f746f636f6c0000000000100000e21ea9569b69bab33c97851d0298bdfa89bc90922d5554313631302dea812fcd6a3563e3be40c1d1\",\n\t\t\"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452333030302d653369733079647675763638\",\n\t\t\"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452343035302d6f7a316c6e79377931716130\",\n\t}\n\n\tfor _, pkt := range packets {\n\t\tpkt, err := hex.DecodeString(pkt)\n\t\trequire.NoError(t, err)\n\t\tvar metadata adapter.InboundContext\n\t\terr = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)\n\t}\n}\n\nfunc TestSniffIncompleteBittorrent(t *testing.T) {\n\tt.Parallel()\n\n\tpkt, err := hex.DecodeString(\"13426974546f7272656e74\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))\n\trequire.ErrorIs(t, err, sniff.ErrNeedMoreData)\n}\n\nfunc TestSniffNotBittorrent(t *testing.T) {\n\tt.Parallel()\n\n\tpkt, err := hex.DecodeString(\"13426974546f7272656e75\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))\n\trequire.NotEmpty(t, err)\n\trequire.NotErrorIs(t, err, sniff.ErrNeedMoreData)\n}\n\nfunc TestSniffUTP(t *testing.T) {\n\tt.Parallel()\n\n\tpackets := []string{\n\t\t\"010041a282d7ee7b583afb160004000006d8318da776968f92d666f7963f32dae23ba0d2c810d8b8209cc4939f54fde9eeaa521c2c20c9ba7f43f4fb0375f28de06643b5e3ca4685ab7ac76adca99783be72ef05ed59ef4234f5712b75b4c7c0d7bee8fe2ca20ad626ba5bb0ffcc16bf06790896f888048cf72716419a07db1a3dca4550fbcea75b53e97235168a221cf3e553dfbb723961bd719fab038d86e0ecb74747f5a2cd669de1c4b9ad375f3a492d09d98cdfad745435625401315bbba98d35d32086299801377b93495a63a9efddb8d05f5b37a5c5b1c0a25e917f12007bb5e05013ada8aff544fab8cadf61d80ddb0b60f12741e44515a109d144fd53ef845acb4b5ccf0d6fc302d7003d76df3fc3423bb0237301c9e88f900c2d392a8e0fdb36d143cf7527a93fd0a2638b746e72f6699fffcd4fd15348fce780d4caa04382fd9faf1ca0ae377ca805da7536662b84f5ee18dd3ae38fcb095a7543e55f9069ae92c8cf54ae44e97b558d35e2545c66601ed2149cbc32bd6df199a2be7cf0da8b2ff137e0d23e776bc87248425013876d3a3cc31a83b424b752bd0346437f24b532978005d8f5b1b0be1a37a2489c32a18a9ad3118e3f9d30eb299bffae18e1f0677c2a5c185e62519093fe6bc2b7339299ea50a587989f726ca6443a75dd5bb936f6367c6355d80fae53ff529d740b2e5576e3eefdf1fdbfc69c3c8d8ac750512635de63e054bee1d3b689bc1b2bc3d2601e42a00b5c89066d173d4ae7ffedfd2274e5cf6d868fbe640aedb69b8246142f00b32d459974287537ddd5373460dcbc92f5cfdd7a3ed6020822ae922d947893752ca1983d0d32977374c384ac8f5ab566859019b7351526b9f13e932037a55bb052d9deb3b3c23317e0784fdc51a64f2159bfea3b069cf5caf02ee2c3c1a6b6b427bb16165713e8802d95b5c8ed77953690e994bd38c9ae113fedaf6ee7fc2b96c032ceafc2a530ad0422e84546b9c6ad8ef6ea02fa508abddd1805c38a7b42e9b7c971b1b636865ebec06ed754bb404cd6b4e6cc8cb77bd4a0c43410d5cd5ef8fe853a66d49b3b9e06cb141236cdbfdd5761601dc54d1250b86c660e0f898fe62526fdd9acf0eab60a3bbbb2151970461f28f10b31689594bea646c4b03ee197d63bdef4e5a7c22716b3bb9494a83b78ecd81b338b80ac6c09c43485b1b09ba41c74343832c78f0520c1d659ac9eb1502094141e82fb9e5e620970ebc0655514c43c294a7714cbf9a499d277daf089f556398a01589a77494bec8bfb60a108f3813b55368672b88c1af40f6b3c8b513f7c70c3e0efce85228b8b9ec67ba0393f9f7305024d8e2da6a26cf85613d14f249170ce1000089df4c9c260df7f8292aa2ecb5d5bac97656d59aa248caedea2d198e51ce87baece338716d114b458de02d65c9ff808ca5b5b73723b4d1e962d9ac2d98176544dc9984cf8554d07820ef3dd0861cfe57b478328046380de589adad94ee44743ffac73bb7361feca5d56f07cf8ce75080e261282ae30350d7882679b15cab9e7e53ddf93310b33f7390ae5d318bb53f387e6af5d0ef4f947fc9cb8e7e38b52c7f8d772ece6156b38d88796ea19df02c53723b44df7c76315a0de9462f27287e682d2b4cda1a68fe00d7e48c51ee981be44e1ca940fb5190c12655edb4a83c3a4f33e48a015692df4f0b3d61656e362aca657b5ae8c12db5a0db3db1e45135ee918b66918f40e53c4f83e9da0cddfe63f736ae751ab3837a30ae3220d8e8e311487093a7b90c7e7e40dd54ca750e19452f9193aa892aa6a6229ab493dadae988b1724f7898ee69c36d3eb7364c4adbeca811cfe2065873e78c2b6dfdf1595f7a7831c07e03cda82e4f86f76438dfb2b07c13638ce7b509cfa71b88b5102b39a203b423202088e1c2103319cb32c13c1e546ff8612fa194c95a7808ab767c265a1bd5fa0efed5c8ec1701876a00ec8\",\n\t\t\"01001ecb68176f215d04326300100000dbcf30292d14b54e9ee2d115ee5b8ebc7fad3e882d4fcdd0c14c6b917c11cb4c6a9f410b52a33ae97c2ac77c7a2b122b8955e09af3c5c595f1b2e79ca57cfe44c44e069610773b9bc9ba223d7f6b383e3adddd03fb88a8476028e30979c2ef321ffc97c5c132bcf9ac5b410bbb5ec6cefca3c7209202a14c5ae922b6b157b0a80249d13ffe5b996af0bc8e54ba576d148372494303e7ead0602b05b9c8fc97d48508a028a04d63a1fd28b0edfcd5c51715f63188b53eefede98a76912dca98518551a8856567307a56a702cbfcc115ea0c755b418bc2c7b57721239b82f09fb24328a4b0ce0f109bcb2a64e04b8aadb1f8487585425acdf8fc4ec8ea93cfcec5ac098bb29d42ddef6e46b03f34a5de28316726699b7cb5195c33e5c48abe87d591d63f9991c84c30819d186d6e0e95fd83c8dff07aa669c4430989bcaccfeacb9bcadbdb4d8f1964dbeb9687745656edd30b21c66cc0a1d742a78717d134a19a7f02d285a4973b1a198c00cfdff4676608dc4f3e817e3463c3b4e2c80d3e8d4fbac541a58a2fb7ad6939f607f8144eff6c8b0adc28ee5609ea158987519892fb\",\n\t\t\"21001ecb6817f2805d044fd700100000dbd03029\",\n\t\t\"410277ef0b1fb1f60000000000040000c233000000080000000000000000\",\n\t}\n\n\tfor _, pkt := range packets {\n\t\tpkt, err := hex.DecodeString(pkt)\n\t\trequire.NoError(t, err)\n\t\tvar metadata adapter.InboundContext\n\t\terr = sniff.UTP(context.TODO(), &metadata, pkt)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)\n\t}\n}\n\nfunc TestSniffUDPTracker(t *testing.T) {\n\tt.Parallel()\n\n\tconnectPackets := []string{\n\t\t\"00000417271019800000000078e90560\",\n\t\t\"00000417271019800000000022c5d64d\",\n\t\t\"000004172710198000000000b3863541\",\n\t}\n\n\tfor _, pkt := range connectPackets {\n\t\tpkt, err := hex.DecodeString(pkt)\n\t\trequire.NoError(t, err)\n\n\t\tvar metadata adapter.InboundContext\n\t\terr = sniff.UDPTracker(context.TODO(), &metadata, pkt)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)\n\t}\n}\n\nfunc TestSniffNotUTP(t *testing.T) {\n\tt.Parallel()\n\n\tpackets := []string{\n\t\t\"0102736470696e674958d580121500000000000079aaed6717a39c27b07c0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n\t}\n\tfor _, pkt := range packets {\n\t\tpkt, err := hex.DecodeString(pkt)\n\t\trequire.NoError(t, err)\n\n\t\tvar metadata adapter.InboundContext\n\t\terr = sniff.UTP(context.TODO(), &metadata, pkt)\n\t\trequire.Error(t, err)\n\t}\n}\n"
  },
  {
    "path": "common/sniff/dns.go",
    "content": "package sniff\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {\n\tvar length uint16\n\terr := binary.Read(reader, binary.BigEndian, &length)\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\tif length < 12 {\n\t\treturn os.ErrInvalid\n\t}\n\tbuffer := buf.NewSize(int(length))\n\tdefer buffer.Release()\n\tvar n int\n\tn, err = buffer.ReadFullFrom(reader, buffer.FreeLen())\n\tpacket := buffer.Bytes()\n\tif n > 2 && packet[2]&0x80 != 0 { // QR\n\t\treturn os.ErrInvalid\n\t}\n\tif n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT\n\t\treturn os.ErrInvalid\n\t}\n\tfor i := 6; i < 10; i++ {\n\t\t// ANCOUNT, NSCOUNT\n\t\tif n > i && packet[i] != 0 {\n\t\t\treturn os.ErrInvalid\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\treturn DomainNameQuery(readCtx, metadata, packet)\n}\n\nfunc DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {\n\tvar msg mDNS.Msg\n\terr := msg.Unpack(packet)\n\tif err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 {\n\t\treturn err\n\t}\n\tmetadata.Protocol = C.ProtocolDNS\n\treturn nil\n}\n"
  },
  {
    "path": "common/sniff/dns_test.go",
    "content": "package sniff_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSniffDNS(t *testing.T) {\n\tt.Parallel()\n\tquery, err := hex.DecodeString(\"740701000001000000000000012a06676f6f676c6503636f6d0000010001\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.DomainNameQuery(context.TODO(), &metadata, query)\n\trequire.NoError(t, err)\n\trequire.Equal(t, C.ProtocolDNS, metadata.Protocol)\n}\n\nfunc TestSniffStreamDNS(t *testing.T) {\n\tt.Parallel()\n\tquery, err := hex.DecodeString(\"001e740701000001000000000000012a06676f6f676c6503636f6d0000010001\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))\n\trequire.NoError(t, err)\n\trequire.Equal(t, C.ProtocolDNS, metadata.Protocol)\n}\n\nfunc TestSniffIncompleteStreamDNS(t *testing.T) {\n\tt.Parallel()\n\tquery, err := hex.DecodeString(\"001e740701000001000000000000\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))\n\trequire.ErrorIs(t, err, sniff.ErrNeedMoreData)\n}\n\nfunc TestSniffNotStreamDNS(t *testing.T) {\n\tt.Parallel()\n\tquery, err := hex.DecodeString(\"001e740701000000000000000000\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))\n\trequire.NotEmpty(t, err)\n\trequire.NotErrorIs(t, err, sniff.ErrNeedMoreData)\n}\n"
  },
  {
    "path": "common/sniff/dtls.go",
    "content": "package sniff\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n)\n\nfunc DTLSRecord(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {\n\tconst fixedHeaderSize = 13\n\tif len(packet) < fixedHeaderSize {\n\t\treturn os.ErrInvalid\n\t}\n\tcontentType := packet[0]\n\tswitch contentType {\n\tcase 20, 21, 22, 23, 25:\n\tdefault:\n\t\treturn os.ErrInvalid\n\t}\n\tversionMajor := packet[1]\n\tif versionMajor != 0xfe {\n\t\treturn os.ErrInvalid\n\t}\n\tversionMinor := packet[2]\n\tif versionMinor != 0xff && versionMinor != 0xfd {\n\t\treturn os.ErrInvalid\n\t}\n\tmetadata.Protocol = C.ProtocolDTLS\n\treturn nil\n}\n"
  },
  {
    "path": "common/sniff/dtls_test.go",
    "content": "package sniff_test\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSniffDTLSClientHello(t *testing.T) {\n\tt.Parallel()\n\tpacket, err := hex.DecodeString(\"16fefd0000000000000000007e010000720000000000000072fefd668a43523798e064bd806d0c87660de9c611a59bbdfc3892c4e072d94f2cafc40000000cc02bc02fc00ac014c02cc0300100003c000d0010000e0403050306030401050106010807ff01000100000a00080006001d00170018000b00020100000e000900060008000700010000170000\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.DTLSRecord(context.Background(), &metadata, packet)\n\trequire.NoError(t, err)\n\trequire.Equal(t, metadata.Protocol, C.ProtocolDTLS)\n}\n\nfunc TestSniffDTLSClientApplicationData(t *testing.T) {\n\tt.Parallel()\n\tpacket, err := hex.DecodeString(\"17fefd000100000000000100440001000000000001a4f682b77ecadd10f3f3a2f78d90566212366ff8209fd77314f5a49352f9bb9bd12f4daba0b4736ae29e46b9714d3b424b3e6d0234736619b5aa0d3f\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.DTLSRecord(context.Background(), &metadata, packet)\n\trequire.NoError(t, err)\n\trequire.Equal(t, metadata.Protocol, C.ProtocolDTLS)\n}\n"
  },
  {
    "path": "common/sniff/http.go",
    "content": "package sniff\n\nimport (\n\tstd_bufio \"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/protocol/http\"\n)\n\nfunc HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {\n\trequest, err := http.ReadRequest(std_bufio.NewReader(reader))\n\tif err != nil {\n\t\tif errors.Is(err, io.ErrUnexpectedEOF) {\n\t\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t\t} else {\n\t\t\treturn err\n\t\t}\n\t}\n\tmetadata.Protocol = C.ProtocolHTTP\n\tmetadata.Domain = M.ParseSocksaddr(request.Host).AddrString()\n\treturn nil\n}\n"
  },
  {
    "path": "common/sniff/http_test.go",
    "content": "package sniff_test\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSniffHTTP1(t *testing.T) {\n\tt.Parallel()\n\tpkt := \"GET / HTTP/1.1\\r\\nHost: www.google.com\\r\\nAccept: */*\\r\\n\\r\\n\"\n\tvar metadata adapter.InboundContext\n\terr := sniff.HTTPHost(context.Background(), &metadata, strings.NewReader(pkt))\n\trequire.NoError(t, err)\n\trequire.Equal(t, metadata.Domain, \"www.google.com\")\n}\n\nfunc TestSniffHTTP1WithPort(t *testing.T) {\n\tt.Parallel()\n\tpkt := \"GET / HTTP/1.1\\r\\nHost: www.gov.cn:8080\\r\\nAccept: */*\\r\\n\\r\\n\"\n\tvar metadata adapter.InboundContext\n\terr := sniff.HTTPHost(context.Background(), &metadata, strings.NewReader(pkt))\n\trequire.NoError(t, err)\n\trequire.Equal(t, metadata.Domain, \"www.gov.cn\")\n}\n"
  },
  {
    "path": "common/sniff/internal/qtls/qtls.go",
    "content": "package qtls\n\nimport (\n\t\"crypto\"\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"golang.org/x/crypto/hkdf\"\n)\n\nconst (\n\tVersionDraft29 = 0xff00001d\n\tVersion1       = 0x1\n\tVersion2       = 0x6b3343cf\n)\n\nvar (\n\tSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}\n\tSaltV1  = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}\n\tSaltV2  = []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9}\n)\n\nconst (\n\tHKDFLabelKeyV1              = \"quic key\"\n\tHKDFLabelKeyV2              = \"quicv2 key\"\n\tHKDFLabelIVV1               = \"quic iv\"\n\tHKDFLabelIVV2               = \"quicv2 iv\"\n\tHKDFLabelHeaderProtectionV1 = \"quic hp\"\n\tHKDFLabelHeaderProtectionV2 = \"quicv2 hp\"\n)\n\nfunc AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD {\n\tif len(nonceMask) != 12 {\n\t\tpanic(\"tls: internal error: wrong nonce length\")\n\t}\n\taes, err := aes.NewCipher(key)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\taead, err := cipher.NewGCM(aes)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tret := &xorNonceAEAD{aead: aead}\n\tcopy(ret.nonceMask[:], nonceMask)\n\treturn ret\n}\n\ntype xorNonceAEAD struct {\n\tnonceMask [12]byte\n\taead      cipher.AEAD\n}\n\nfunc (f *xorNonceAEAD) NonceSize() int        { return 8 } // 64-bit sequence number\nfunc (f *xorNonceAEAD) Overhead() int         { return f.aead.Overhead() }\nfunc (f *xorNonceAEAD) explicitNonceLen() int { return 0 }\n\nfunc (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {\n\tfor i, b := range nonce {\n\t\tf.nonceMask[4+i] ^= b\n\t}\n\tresult := f.aead.Seal(out, f.nonceMask[:], plaintext, additionalData)\n\tfor i, b := range nonce {\n\t\tf.nonceMask[4+i] ^= b\n\t}\n\n\treturn result\n}\n\nfunc (f *xorNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) {\n\tfor i, b := range nonce {\n\t\tf.nonceMask[4+i] ^= b\n\t}\n\tresult, err := f.aead.Open(out, f.nonceMask[:], ciphertext, additionalData)\n\tfor i, b := range nonce {\n\t\tf.nonceMask[4+i] ^= b\n\t}\n\n\treturn result, err\n}\n\nfunc HKDFExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {\n\tb := make([]byte, 3, 3+6+len(label)+1+len(context))\n\tbinary.BigEndian.PutUint16(b, uint16(length))\n\tb[2] = uint8(6 + len(label))\n\tb = append(b, []byte(\"tls13 \")...)\n\tb = append(b, []byte(label)...)\n\tb = b[:3+6+len(label)+1]\n\tb[3+6+len(label)] = uint8(len(context))\n\tb = append(b, context...)\n\tout := make([]byte, length)\n\tn, err := hkdf.Expand(hash.New, secret, b).Read(out)\n\tif err != nil || n != length {\n\t\tpanic(\"quic: HKDF-Expand-Label invocation failed unexpectedly\")\n\t}\n\treturn out\n}\n\nfunc ReadUvarint(r io.ByteReader) (uint64, error) {\n\tfirstByte, err := r.ReadByte()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\t// the first two bits of the first byte encode the length\n\tlen := 1 << ((firstByte & 0xc0) >> 6)\n\tb1 := firstByte & (0xff - 0xc0)\n\tif len == 1 {\n\t\treturn uint64(b1), nil\n\t}\n\tb2, err := r.ReadByte()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif len == 2 {\n\t\treturn uint64(b2) + uint64(b1)<<8, nil\n\t}\n\tb3, err := r.ReadByte()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tb4, err := r.ReadByte()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif len == 4 {\n\t\treturn uint64(b4) + uint64(b3)<<8 + uint64(b2)<<16 + uint64(b1)<<24, nil\n\t}\n\tb5, err := r.ReadByte()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tb6, err := r.ReadByte()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tb7, err := r.ReadByte()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tb8, err := r.ReadByte()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn uint64(b8) + uint64(b7)<<8 + uint64(b6)<<16 + uint64(b5)<<24 + uint64(b4)<<32 + uint64(b3)<<40 + uint64(b2)<<48 + uint64(b1)<<56, nil\n}\n"
  },
  {
    "path": "common/sniff/ntp.go",
    "content": "package sniff\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n)\n\nfunc NTP(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {\n\t// NTP packets must be at least 48 bytes long (standard NTP header size).\n\tpLen := len(packet)\n\tif pLen < 48 {\n\t\treturn os.ErrInvalid\n\t}\n\t// Check the LI (Leap Indicator) and Version Number (VN) in the first byte.\n\t// We'll primarily focus on ensuring the version is valid for NTP.\n\t// Many NTP versions are used, but let's check for generally accepted ones (3 & 4 for IPv4, plus potential extensions/customizations)\n\tfirstByte := packet[0]\n\tli := (firstByte >> 6) & 0x03 // Extract LI\n\tvn := (firstByte >> 3) & 0x07 // Extract VN\n\tmode := firstByte & 0x07      // Extract Mode\n\n\t// Leap Indicator should be a valid value (0-3).\n\tif li > 3 {\n\t\treturn os.ErrInvalid\n\t}\n\n\t// Version Check (common NTP versions are 3 and 4)\n\tif vn != 3 && vn != 4 {\n\t\treturn os.ErrInvalid\n\t}\n\n\t// Check the Mode field for a client request (Mode 3).  This validates it *is* a request.\n\tif mode != 3 {\n\t\treturn os.ErrInvalid\n\t}\n\n\t// Check Root Delay and Root Dispersion. While not strictly *required* for a request,\n\t// we can check if they appear to be reasonable values (not excessively large).\n\trootDelay := binary.BigEndian.Uint32(packet[4:8])\n\trootDispersion := binary.BigEndian.Uint32(packet[8:12])\n\n\t// Check for unreasonably large root delay and dispersion.  NTP RFC specifies max values of approximately 16 seconds.\n\t// Convert to milliseconds for easy comparison.  Each unit is 1/2^16 seconds.\n\tif float64(rootDelay)/65536.0 > 16.0 {\n\t\treturn os.ErrInvalid\n\t}\n\tif float64(rootDispersion)/65536.0 > 16.0 {\n\t\treturn os.ErrInvalid\n\t}\n\n\tmetadata.Protocol = C.ProtocolNTP\n\n\treturn nil\n}\n"
  },
  {
    "path": "common/sniff/ntp_test.go",
    "content": "package sniff_test\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSniffNTP(t *testing.T) {\n\tt.Parallel()\n\tpacket, err := hex.DecodeString(\"1b0006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.NTP(context.Background(), &metadata, packet)\n\trequire.NoError(t, err)\n\trequire.Equal(t, metadata.Protocol, C.ProtocolNTP)\n}\n\nfunc TestSniffNTPFailed(t *testing.T) {\n\tt.Parallel()\n\tpacket, err := hex.DecodeString(\"400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.NTP(context.Background(), &metadata, packet)\n\trequire.ErrorIs(t, err, os.ErrInvalid)\n}\n"
  },
  {
    "path": "common/sniff/quic.go",
    "content": "package sniff\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto\"\n\t\"crypto/aes\"\n\t\"crypto/tls\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/ja3\"\n\t\"github.com/sagernet/sing-box/common/sniff/internal/qtls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"golang.org/x/crypto/hkdf\"\n)\n\nfunc QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {\n\treader := bytes.NewReader(packet)\n\ttypeByte, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif typeByte&0x40 == 0 {\n\t\treturn E.New(\"bad type byte\")\n\t}\n\tvar versionNumber uint32\n\terr = binary.Read(reader, binary.BigEndian, &versionNumber)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif versionNumber != qtls.VersionDraft29 && versionNumber != qtls.Version1 && versionNumber != qtls.Version2 {\n\t\treturn E.New(\"bad version\")\n\t}\n\tpacketType := (typeByte & 0x30) >> 4\n\tif packetType == 0 && versionNumber == qtls.Version2 || packetType == 2 && versionNumber != qtls.Version2 || packetType > 2 {\n\t\treturn E.New(\"bad packet type\")\n\t}\n\n\tdestConnIDLen, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif destConnIDLen == 0 || destConnIDLen > 20 {\n\t\treturn E.New(\"bad destination connection id length\")\n\t}\n\n\tdestConnID := make([]byte, destConnIDLen)\n\t_, err = io.ReadFull(reader, destConnID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsrcConnIDLen, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = io.CopyN(io.Discard, reader, int64(srcConnIDLen))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttokenLen, err := qtls.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = io.CopyN(io.Discard, reader, int64(tokenLen))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpacketLen, err := qtls.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\thdrLen := int(reader.Size()) - reader.Len()\n\tif hdrLen+int(packetLen) > len(packet) {\n\t\treturn os.ErrInvalid\n\t}\n\n\t_, err = io.CopyN(io.Discard, reader, 4)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpnBytes := make([]byte, aes.BlockSize)\n\t_, err = io.ReadFull(reader, pnBytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar salt []byte\n\tswitch versionNumber {\n\tcase qtls.Version1:\n\t\tsalt = qtls.SaltV1\n\tcase qtls.Version2:\n\t\tsalt = qtls.SaltV2\n\tdefault:\n\t\tsalt = qtls.SaltOld\n\t}\n\tvar hkdfHeaderProtectionLabel string\n\tswitch versionNumber {\n\tcase qtls.Version2:\n\t\thkdfHeaderProtectionLabel = qtls.HKDFLabelHeaderProtectionV2\n\tdefault:\n\t\thkdfHeaderProtectionLabel = qtls.HKDFLabelHeaderProtectionV1\n\t}\n\tinitialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)\n\tsecret := qtls.HKDFExpandLabel(crypto.SHA256, initialSecret, []byte{}, \"client in\", crypto.SHA256.Size())\n\thpKey := qtls.HKDFExpandLabel(crypto.SHA256, secret, []byte{}, hkdfHeaderProtectionLabel, 16)\n\tblock, err := aes.NewCipher(hpKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\tmask := make([]byte, aes.BlockSize)\n\tblock.Encrypt(mask, pnBytes)\n\tnewPacket := make([]byte, len(packet))\n\tcopy(newPacket, packet)\n\tnewPacket[0] ^= mask[0] & 0xf\n\tfor i := range newPacket[hdrLen : hdrLen+4] {\n\t\tnewPacket[hdrLen+i] ^= mask[i+1]\n\t}\n\tpacketNumberLength := newPacket[0]&0x3 + 1\n\tif hdrLen+int(packetNumberLength) > int(packetLen)+hdrLen {\n\t\treturn os.ErrInvalid\n\t}\n\tvar packetNumber uint32\n\tswitch packetNumberLength {\n\tcase 1:\n\t\tpacketNumber = uint32(newPacket[hdrLen])\n\tcase 2:\n\t\tpacketNumber = uint32(binary.BigEndian.Uint16(newPacket[hdrLen:]))\n\tcase 3:\n\t\tpacketNumber = uint32(newPacket[hdrLen+2]) | uint32(newPacket[hdrLen+1])<<8 | uint32(newPacket[hdrLen])<<16\n\tcase 4:\n\t\tpacketNumber = binary.BigEndian.Uint32(newPacket[hdrLen:])\n\tdefault:\n\t\treturn E.New(\"bad packet number length\")\n\t}\n\textHdrLen := hdrLen + int(packetNumberLength)\n\tcopy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:])\n\tdata := newPacket[extHdrLen : int(packetLen)+hdrLen]\n\n\tvar keyLabel string\n\tvar ivLabel string\n\tswitch versionNumber {\n\tcase qtls.Version2:\n\t\tkeyLabel = qtls.HKDFLabelKeyV2\n\t\tivLabel = qtls.HKDFLabelIVV2\n\tdefault:\n\t\tkeyLabel = qtls.HKDFLabelKeyV1\n\t\tivLabel = qtls.HKDFLabelIVV1\n\t}\n\n\tkey := qtls.HKDFExpandLabel(crypto.SHA256, secret, []byte{}, keyLabel, 16)\n\tiv := qtls.HKDFExpandLabel(crypto.SHA256, secret, []byte{}, ivLabel, 12)\n\tcipher := qtls.AEADAESGCMTLS13(key, iv)\n\tnonce := make([]byte, int32(cipher.NonceSize()))\n\tbinary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber))\n\tdecrypted, err := cipher.Open(newPacket[extHdrLen:extHdrLen], nonce, data, newPacket[:extHdrLen])\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar frameType byte\n\tvar fragments []qCryptoFragment\n\tdecryptedReader := bytes.NewReader(decrypted)\n\tconst (\n\t\tframeTypePadding         = 0x00\n\t\tframeTypePing            = 0x01\n\t\tframeTypeAck             = 0x02\n\t\tframeTypeAck2            = 0x03\n\t\tframeTypeCrypto          = 0x06\n\t\tframeTypeConnectionClose = 0x1c\n\t)\n\tvar frameTypeList []uint8\n\tfor {\n\t\tframeType, err = decryptedReader.ReadByte()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tframeTypeList = append(frameTypeList, frameType)\n\t\tswitch frameType {\n\t\tcase frameTypePadding:\n\t\t\tcontinue\n\t\tcase frameTypePing:\n\t\t\tcontinue\n\t\tcase frameTypeAck, frameTypeAck2:\n\t\t\t_, err = qtls.ReadUvarint(decryptedReader) // Largest Acknowledged\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = qtls.ReadUvarint(decryptedReader) // ACK Delay\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tackRangeCount, err := qtls.ReadUvarint(decryptedReader) // ACK Range Count\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = qtls.ReadUvarint(decryptedReader) // First ACK Range\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfor i := 0; i < int(ackRangeCount); i++ {\n\t\t\t\t_, err = qtls.ReadUvarint(decryptedReader) // Gap\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t_, err = qtls.ReadUvarint(decryptedReader) // ACK Range Length\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif frameType == 0x03 {\n\t\t\t\t_, err = qtls.ReadUvarint(decryptedReader) // ECT0 Count\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t_, err = qtls.ReadUvarint(decryptedReader) // ECT1 Count\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t_, err = qtls.ReadUvarint(decryptedReader) // ECN-CE Count\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tcase frameTypeCrypto:\n\t\t\tvar offset uint64\n\t\t\toffset, err = qtls.ReadUvarint(decryptedReader)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tvar length uint64\n\t\t\tlength, err = qtls.ReadUvarint(decryptedReader)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tindex := len(decrypted) - decryptedReader.Len()\n\t\t\tfragments = append(fragments, qCryptoFragment{offset, length, decrypted[index : index+int(length)]})\n\t\t\t_, err = decryptedReader.Seek(int64(length), io.SeekCurrent)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase frameTypeConnectionClose:\n\t\t\t_, err = qtls.ReadUvarint(decryptedReader) // Error Code\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = qtls.ReadUvarint(decryptedReader) // Frame Type\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tvar length uint64\n\t\t\tlength, err = qtls.ReadUvarint(decryptedReader) // Reason Phrase Length\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = decryptedReader.Seek(int64(length), io.SeekCurrent) // Reason Phrase\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tdefault:\n\t\t\treturn os.ErrInvalid\n\t\t}\n\t}\n\tif metadata.SniffContext != nil {\n\t\tfragments = append(fragments, metadata.SniffContext.([]qCryptoFragment)...)\n\t\tmetadata.SniffContext = nil\n\t}\n\tvar frameLen uint64\n\tfor _, fragment := range fragments {\n\t\tframeLen += fragment.length\n\t}\n\tbuffer := buf.NewSize(5 + int(frameLen))\n\tdefer buffer.Release()\n\tbuffer.WriteByte(0x16)\n\tbinary.Write(buffer, binary.BigEndian, uint16(0x0303))\n\tbinary.Write(buffer, binary.BigEndian, uint16(frameLen))\n\tvar index uint64\n\tvar length int\nfind:\n\tfor {\n\t\tfor _, fragment := range fragments {\n\t\t\tif fragment.offset == index {\n\t\t\t\tbuffer.Write(fragment.payload)\n\t\t\t\tindex = fragment.offset + fragment.length\n\t\t\t\tlength++\n\t\t\t\tcontinue find\n\t\t\t}\n\t\t}\n\t\tbreak\n\t}\n\tmetadata.Protocol = C.ProtocolQUIC\n\tfingerprint, err := ja3.Compute(buffer.Bytes())\n\tif err != nil {\n\t\tmetadata.SniffContext = fragments\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\tmetadata.Domain = fingerprint.ServerName\n\tfor metadata.Client == \"\" {\n\t\tif len(frameTypeList) == 1 {\n\t\t\tmetadata.Client = C.ClientFirefox\n\t\t\tbreak\n\t\t}\n\t\tif frameTypeList[0] == frameTypeCrypto && isZero(frameTypeList[1:]) {\n\t\t\tif len(fingerprint.Versions) == 2 && fingerprint.Versions[0]&ja3.GreaseBitmask == 0x0A0A &&\n\t\t\t\tlen(fingerprint.EllipticCurves) == 5 && fingerprint.EllipticCurves[0]&ja3.GreaseBitmask == 0x0A0A {\n\t\t\t\tmetadata.Client = C.ClientSafari\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif len(fingerprint.CipherSuites) == 1 && fingerprint.CipherSuites[0] == tls.TLS_AES_256_GCM_SHA384 &&\n\t\t\t\tlen(fingerprint.EllipticCurves) == 1 && fingerprint.EllipticCurves[0] == uint16(tls.X25519) &&\n\t\t\t\tlen(fingerprint.SignatureAlgorithms) == 1 && fingerprint.SignatureAlgorithms[0] == uint16(tls.ECDSAWithP256AndSHA256) {\n\t\t\t\tmetadata.Client = C.ClientSafari\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif frameTypeList[len(frameTypeList)-1] == frameTypeCrypto && isZero(frameTypeList[:len(frameTypeList)-1]) {\n\t\t\tmetadata.Client = C.ClientQUICGo\n\t\t\tbreak\n\t\t}\n\n\t\tif count(frameTypeList, frameTypeCrypto) > 1 || count(frameTypeList, frameTypePing) > 0 {\n\t\t\tif isQUICGo(fingerprint) {\n\t\t\t\tmetadata.Client = C.ClientQUICGo\n\t\t\t} else {\n\t\t\t\tmetadata.Client = C.ClientChromium\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\tmetadata.Client = C.ClientUnknown\n\t\t//nolint:staticcheck\n\t\tbreak\n\t}\n\treturn nil\n}\n\nfunc isZero(slices []uint8) bool {\n\tfor _, slice := range slices {\n\t\tif slice != 0 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc count(slices []uint8, value uint8) int {\n\tvar times int\n\tfor _, slice := range slices {\n\t\tif slice == value {\n\t\t\ttimes++\n\t\t}\n\t}\n\treturn times\n}\n\ntype qCryptoFragment struct {\n\toffset  uint64\n\tlength  uint64\n\tpayload []byte\n}\n"
  },
  {
    "path": "common/sniff/quic_blacklist.go",
    "content": "package sniff\n\nimport (\n\t\"github.com/sagernet/sing-box/common/ja3\"\n)\n\nconst (\n\t// X25519Kyber768Draft00 - post-quantum curve used by Go crypto/tls\n\tx25519Kyber768Draft00 uint16 = 0x11EC // 4588\n\t// renegotiation_info extension used by Go crypto/tls\n\textensionRenegotiationInfo uint16 = 0xFF01 // 65281\n)\n\n// isQUICGo detects native quic-go by checking for Go crypto/tls specific features.\n// Note: uQUIC with Chromium mimicry cannot be reliably distinguished from real Chromium\n// since it uses the same TLS fingerprint, so it will be identified as Chromium.\nfunc isQUICGo(fingerprint *ja3.ClientHello) bool {\n\tfor _, curve := range fingerprint.EllipticCurves {\n\t\tif curve == x25519Kyber768Draft00 {\n\t\t\treturn true\n\t\t}\n\t}\n\tfor _, ext := range fingerprint.Extensions {\n\t\tif ext == extensionRenegotiationInfo {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "common/sniff/quic_capture_test.go",
    "content": "package sniff_test\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/sagernet/quic-go\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSniffQUICQuicGoFingerprint(t *testing.T) {\n\tt.Parallel()\n\tconst testSNI = \"test.example.com\"\n\n\tudpConn, err := net.ListenUDP(\"udp\", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})\n\trequire.NoError(t, err)\n\tdefer udpConn.Close()\n\n\tserverAddr := udpConn.LocalAddr().(*net.UDPAddr)\n\tpacketsChan := make(chan [][]byte, 1)\n\n\tgo func() {\n\t\tvar packets [][]byte\n\t\tudpConn.SetReadDeadline(time.Now().Add(3 * time.Second))\n\t\tfor i := 0; i < 10; i++ {\n\t\t\tbuf := make([]byte, 2048)\n\t\t\tn, _, err := udpConn.ReadFromUDP(buf)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tpackets = append(packets, buf[:n])\n\t\t}\n\t\tpacketsChan <- packets\n\t}()\n\n\tclientConn, err := net.ListenUDP(\"udp\", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})\n\trequire.NoError(t, err)\n\tdefer clientConn.Close()\n\n\ttlsConfig := &tls.Config{\n\t\tServerName:         testSNI,\n\t\tInsecureSkipVerify: true,\n\t\tNextProtos:         []string{\"h3\"},\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\n\t_, _ = quic.Dial(ctx, clientConn, serverAddr, tlsConfig, &quic.Config{})\n\n\tselect {\n\tcase packets := <-packetsChan:\n\t\tt.Logf(\"Captured %d packets\", len(packets))\n\n\t\tvar metadata adapter.InboundContext\n\t\tfor i, pkt := range packets {\n\t\t\terr := sniff.QUICClientHello(context.Background(), &metadata, pkt)\n\t\t\tt.Logf(\"Packet %d: err=%v, domain=%s, client=%s\", i, err, metadata.Domain, metadata.Client)\n\t\t\tif metadata.Domain != \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tt.Logf(\"\\n=== quic-go TLS Fingerprint Analysis ===\")\n\t\tt.Logf(\"Domain: %s\", metadata.Domain)\n\t\tt.Logf(\"Client: %s\", metadata.Client)\n\t\tt.Logf(\"Protocol: %s\", metadata.Protocol)\n\n\t\t// The client should be identified as quic-go, not chromium\n\t\t// Current issue: it's being identified as chromium\n\t\tif metadata.Client == \"chromium\" {\n\t\t\tt.Log(\"WARNING: quic-go is being misidentified as chromium!\")\n\t\t}\n\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatal(\"Timeout\")\n\t}\n}\n\nfunc TestSniffQUICInitialFromQuicGo(t *testing.T) {\n\tt.Parallel()\n\n\tconst testSNI = \"test.example.com\"\n\n\t// Create UDP listener to capture ALL initial packets\n\tudpConn, err := net.ListenUDP(\"udp\", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})\n\trequire.NoError(t, err)\n\tdefer udpConn.Close()\n\n\tserverAddr := udpConn.LocalAddr().(*net.UDPAddr)\n\n\t// Channel to receive captured packets\n\tpacketsChan := make(chan [][]byte, 1)\n\n\t// Start goroutine to capture packets\n\tgo func() {\n\t\tvar packets [][]byte\n\t\tudpConn.SetReadDeadline(time.Now().Add(3 * time.Second))\n\t\tfor i := 0; i < 5; i++ { // Capture up to 5 packets\n\t\t\tbuf := make([]byte, 2048)\n\t\t\tn, _, err := udpConn.ReadFromUDP(buf)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tpackets = append(packets, buf[:n])\n\t\t}\n\t\tpacketsChan <- packets\n\t}()\n\n\t// Create QUIC client connection (will fail but we capture the initial packet)\n\tclientConn, err := net.ListenUDP(\"udp\", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})\n\trequire.NoError(t, err)\n\tdefer clientConn.Close()\n\n\ttlsConfig := &tls.Config{\n\t\tServerName:         testSNI,\n\t\tInsecureSkipVerify: true,\n\t\tNextProtos:         []string{\"h3\"},\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\n\t// This will fail (no server) but sends initial packet\n\t_, _ = quic.Dial(ctx, clientConn, serverAddr, tlsConfig, &quic.Config{})\n\n\t// Wait for captured packets\n\tselect {\n\tcase packets := <-packetsChan:\n\t\tt.Logf(\"Captured %d QUIC packets\", len(packets))\n\n\t\tfor i, packet := range packets {\n\t\t\tt.Logf(\"Packet %d: length=%d, first 30 bytes: %x\", i, len(packet), packet[:min(30, len(packet))])\n\t\t}\n\n\t\t// Test sniffer with first packet\n\t\tif len(packets) > 0 {\n\t\t\tvar metadata adapter.InboundContext\n\t\t\terr := sniff.QUICClientHello(context.Background(), &metadata, packets[0])\n\n\t\t\tt.Logf(\"First packet sniff error: %v\", err)\n\t\t\tt.Logf(\"Protocol: %s\", metadata.Protocol)\n\t\t\tt.Logf(\"Domain: %s\", metadata.Domain)\n\t\t\tt.Logf(\"Client: %s\", metadata.Client)\n\n\t\t\t// If first packet needs more data, try with subsequent packets\n\t\t\t// IMPORTANT: reuse metadata to accumulate CRYPTO fragments via SniffContext\n\t\t\tif errors.Is(err, sniff.ErrNeedMoreData) && len(packets) > 1 {\n\t\t\t\tt.Log(\"First packet needs more data, trying subsequent packets with shared context...\")\n\t\t\t\tfor i := 1; i < len(packets); i++ {\n\t\t\t\t\t// Reuse same metadata to accumulate fragments\n\t\t\t\t\terr = sniff.QUICClientHello(context.Background(), &metadata, packets[i])\n\t\t\t\t\tt.Logf(\"Packet %d sniff result: err=%v, domain=%s, sniffCtx=%v\", i, err, metadata.Domain, metadata.SniffContext != nil)\n\t\t\t\t\tif metadata.Domain != \"\" || (err != nil && !errors.Is(err, sniff.ErrNeedMoreData)) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Print hex dump for debugging\n\t\t\tt.Logf(\"First packet hex:\\n%s\", hex.Dump(packets[0][:min(256, len(packets[0]))]))\n\n\t\t\t// Log final results\n\t\t\tt.Logf(\"Final: Protocol=%s, Domain=%s, Client=%s\", metadata.Protocol, metadata.Domain, metadata.Client)\n\n\t\t\t// Verify SNI extraction\n\t\t\tif metadata.Domain == \"\" {\n\t\t\t\tt.Errorf(\"Failed to extract SNI, expected: %s\", testSNI)\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, testSNI, metadata.Domain, \"SNI should match\")\n\t\t\t}\n\n\t\t\t// Check client identification - quic-go should be identified as quic-go, not chromium\n\t\t\tt.Logf(\"Client identified as: %s (expected: quic-go)\", metadata.Client)\n\t\t}\n\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatal(\"Timeout waiting for QUIC packets\")\n\t}\n}\n"
  },
  {
    "path": "common/sniff/quic_test.go",
    "content": "package sniff_test\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSniffQUICChromeNew(t *testing.T) {\n\tt.Parallel()\n\tpkt, err := hex.DecodeString(\"ca0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489ad89c322f75f9a383c90d126a0b21104cb519c2bb32e6a134e86896452e942b26c519b8c7ac9e4c99fae5e1f65cf08fb98443b30e4567932e8fb0789820d8f33037b59ac8113530258c9467dfb52489396dae01f099d28b234efa107fa411f2a1ffa2abe74988e03d662d4296024e95ce0fe1671724937157f77b84990478a2d4060676cf0827b4e8c600654111750414dafa0cccb332f3020c2922a015f445df5edc9c7d2d1ceea9fddcc9ff821c9183aa39a70da20fcc057579e1051c1c899148d6cf9d08b4919822082d040d1ce03ca4f216be6cb7ef03db6df0993ef1ccce5c8c648980554f41704526e1809d2545739f5872e75ec797db1c99f5682e2eda9363cb32aa367b7b363c782ddbacf874183cc15c8a2db068dd4093eebdd096ad33832a7939deb0a872279744f5a56dc001ba62fac973bf680f3b362bdd336add4dd102f462b773bf70bfce1921070a802a92025273a177186d1a643081b42175eb789ccddadb71033ef4feacbf6fd282ab622cf61669d73cda559e411c6ccdd8f003443b6933b7729b7a357aa4aa2fba0f365f829a4d497afb5dc2648a53bc9f3e786d955069d0a4781088a5463747dfe9958ea19ea444eae947ec6a67640955f710f93640084f3fbb8ad259b68dbc0ee0b7fab2d81bffd83ed8a6d33522dbfef43bec0a0fb4bdf1cb712dc4ced0680c0687fa240fd157baa232b1c84e14adce6421cf9270f9b3972f98fc67b344b8a4f1fb551e26f7f76d484ed9f8197f231dc5d9a44cc0ddce73d7f810a620851f4e97eb5037ab5135d7c3be5b80cc32d19910b8387aca64c93c02dc3e35238b78e6aff470722078982e58802844932b6041446bfdcc97ba640cbb86721bcd0f40f27b77aa6287ce5674ec1720134b9302875482c3269787e004b9edb483d44f326eef38c0e83cb46af96488c2e696bc2524567fb29c1e8edcd5a73615496d172d46a9d29e0505c0018b7bbb00165eca0389e09c4b1d73b6cc4a2f735a720650134a2e98e8105e20695cf231b92586237dfe0f99c897414e51c21627496276535f07abb53fb2b554376fe520fa45a3e944fd91dfe7a72aead08842b6b63d8edf861fb911954c83bd9a896eb9da4af5eff646455069d747facd4e77c254096843bff7c3e9031dbdf8dc37ea45f1122922fcbc322ec1378f3c7c1af0da62e1052e6210f1b23073f93a82d90e14cb20bc4501d487a1c848674d57a7c269b13590b3a99d8b8b4f6d0dfbd1d2cbbe7a32c0d5c84ae7ec438b0b19f3862d8fabaa828d06c7e3c6967405cd56a1ae90f38633e2ee0e3ecfca3df399fe12f029e0860a1a30da010300d0c94f0bf56091d00011488c1429928b21c739ebf50ba8be91116315d3173f6d2c56735722478c4d74392ba84d1727036b3d64e8c2263b0f33cb8086be587ca6b3940259c06afa2683868856529303ae12e91d7ca874568be7f2bfaa0656dfab0ed31ed90eaea10fb7f3433ec59a334abe6211d547fa0c825ac45d3691e749d15432008de83e9f6d98f368359137ae803d9189b3386f800c7c0cf4b615d1983cf82d9981a8105b60a80fe66c9b0d439b5ba153dd19e9e7483a01cf3b02b4597540b38e658d4eb8455e030b2bf2690bdd78c23f16fe5\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.QUICClientHello(context.Background(), &metadata, pkt)\n\trequire.Equal(t, metadata.Protocol, C.ProtocolQUIC)\n\trequire.Empty(t, metadata.Client)\n\trequire.ErrorIs(t, err, sniff.ErrNeedMoreData)\n\tpkt, err = hex.DecodeString(\"cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894\")\n\trequire.NoError(t, err)\n\terr = sniff.QUICClientHello(context.Background(), &metadata, pkt)\n\trequire.ErrorIs(t, err, sniff.ErrNeedMoreData)\n\tpkt, err = hex.DecodeString(\"c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f\")\n\trequire.NoError(t, err)\n\terr = sniff.QUICClientHello(context.Background(), &metadata, pkt)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"www.google.com\", metadata.Domain)\n}\n\nfunc TestSniffQUICChromium(t *testing.T) {\n\tt.Parallel()\n\tpkt, err := hex.DecodeString(\"c30000000108f40d654cc09b27f5000044d08a94548e57e43cc5483f129986187c432d58d46674830442988f869566a6e31e2ae37c9f7acbf61cc81621594fab0b3dfdc1635460b32389563dc8e74006315661cd22694114612973c1c45910621713a48b375854f095e8a77ccf3afa64e972f0f7f7002f50e0b014b1b146ea47c07fb20b73ad5587872b51a0b3fafdf1c4cf4fe6f8b112142392efa25d993abe2f42582be145148bdfe12edcd96c3655b65a4781b093e5594ba8e3ae5320f12e8314fc3ca374128cc43381046c322b964681ed4395c813b28534505118201459665a44b8f0abead877de322e9040631d20b05f15b81fa7ff785d4041aecc37c7e2ccdc5d1532787ce566517e8985fd5c200dbfd1e67bc255efaba94cfc07bb52fea4a90887413b134f2715b5643542aa897c6116486f428d82da64d2a2c1e1bdd40bd592558901a554b003d6966ac5a7b8b9413eddbf6ef21f28386c74981e3ce1d724c341e95494907626659692720c81114ca4acea35a14c402cfa3dc2228446e78dc1b81fa4325cf7e314a9cad6a6bdff33b3351dcba74eb15fae67f1227283aa4cdd64bcadf8f19358333f8549b596f4350297b5c65274565869d497398339947b9d3d064e5b06d39d34b436d8a41c1a3880de10bd26c3b1c5b4e2a49b0d4d07b8d90cd9e92bc611564d19ea8ec33099e92033caf21f5307dbeaa4708b99eb313bff99e2081ac25fd12d6a72e8335e0724f6718fe023cd0ad0d6e6a6309f09c9c391eec2bc08e9c3210a043c08e1759f354c121f6517fff4d6e20711a871e41285d48d930352fddffb92c96ba57df045ce99f8bfdfa8edc0969ce68a51e9fbb4f54b956d9df74a9e4af27ed2b27839bce1cffeca8333c0aaee81a570217442f9029ba8fedb84a2cf4be4d910982d891ea00e816c7fb98e8020e896a9c6fdd9106611da0a99dde18df1b7a8f6327acb1eed9ad93314451e48cb0dfb9571728521ca3db2ac0968159d5622556a55d51a422d11995b650949aaefc5d24c16080446dfc4fbc10353f9f93ce161ab513367bb89ab83988e0630b689e174e27bcfcc31996ee7b0bca909e251b82d69a28fee5a5d662e127508cd19dbbe5097b7d5b62a49203d66764197a527e472e2627e44a93d44177dace9d60e7d0e03305ddf4cfe47cdf2362e14de79ef46a6763ce696cd7854a48d9419a0817507a4713ffd4977b906d4f2b5fb6dbe1bd15bc505d5fea582190bf531a45d5ee026da8918547fd5105f15e5d061c7b0cf80a34990366ed8e91e13c2f0d85e5dad537298808d193cf54b7eaac33f10051f74cb6b75e52f81618c36f03d86aef613ba237a1a793ba1539938a38f62ccaf7bd5f6c5e0ce53cde4012fcf2b758214a0422d2faaa798e86e19d7481b42df2b36a73d287ff28c20cce01ce598771fec16a8f1f00305c06010126013a6c1de9f589b4e79d693717cd88ad1c42a2d99fa96617ba0bc6365b68e21a70ebc447904aa27979e1514433cfd83bfec09f137c747d47582cb63eb28f873fb94cf7a59ff764ddfbb687d79a58bb10f85949269f7f72c611a5e0fbb52adfa298ff060ec2eb7216fd7302ea8fb07798cbb3be25cb53ac8161aac2b5bbcfbcfb01c113d28bd1cb0333fb89ac82a95930f7abded0a2f5a623cc6a1f62bf3f38ef1b81c1e50a634f657dbb6770e4af45879e2fb1e00c742e7b52205c8015b5c0f5b1e40186ff9aa7288ab3e01a51fb87761f9bc6837082af109b39cc9f620\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.QUICClientHello(context.Background(), &metadata, pkt)\n\trequire.Equal(t, metadata.Protocol, C.ProtocolQUIC)\n\trequire.Empty(t, metadata.Client)\n\trequire.ErrorIs(t, err, sniff.ErrNeedMoreData)\n\tpkt, err = hex.DecodeString(\"c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28\")\n\trequire.NoError(t, err)\n\terr = sniff.QUICClientHello(context.Background(), &metadata, pkt)\n\trequire.NoError(t, err)\n\trequire.Equal(t, metadata.Domain, \"google.com\")\n}\n\nfunc TestSniffUQUICChrome115(t *testing.T) {\n\tt.Parallel()\n\tpkt, err := hex.DecodeString(\"cb0000000108181e17c387120abc000044d0705b6a3ef9ee37a8d3949a7d393ed078243c2ee2c3627fad1c3f107c117f4f071131ad61848068fcbbe5c65803c147f7f8ec5e2cd77b77beea23ba779d936dccac540f8396400e3190ea35cc2942af4171a04cb14272491920f90124959f44e80143678c0b52f5d31af319aaa589db2f940f004562724d0af40f737e1bb0002a071e6a1dbc9f52c64f070806a5010abed0298053634d9c9126bd7949ae5087998ade762c0ad06691d99c0875a38c601fc1ee77bfc3b8c11381829f2c9bdd022f4499c43ff1d6aee1a0d296861461dda217d22c568b276016ef3929e59d2f7d7ddf7809920fb7dc805641608949f3f8466ab3d37149aac501f0b107d808f3add4acfc657e4a82e2b88e97a6c74a00c419548760ab3414ba13915c78a1ca79dceee8d59fbe299f20b671ac44823218368b2a026baa55170cf549519ac21dbb6d31d248bd339438a4e663bcdca1fe3ae3f045a5dc19b122e9db9d7af9757076666dda4e9ace1c67def77fa14786f0cab3ebf7a270ea6e2b37838318c95779f80c3b8471948d0046c3614b3a13477c939a39a7855d85d13522a45ae0765739cd5eedef87237e824a929983ace27640c6495dbf5a72fa0b96893dc5d28f3988249a57bdb458d460b4a57043de3da750a76b6e5d2259247ca27cd864ea18f0d09aa62ab6eb7c014fb43179b2a1963d170b756cce83eeaebff78a828d025c811848e16ff862a8080d093478cd2208c8ab0803178325bc0d9d6bb25e62fa50c4ad15cf80916da6578796932036c72e43eb480d1e423ed812ac75a97722f8416529b82ba8ee2219c535012282bb17066bd53e78b87a71abdb7ebdb2a7c2766ff8397962e87d0f85485b64b4ee81cc84f99c47f33f2b0872716441992773f59186e38d32dbf5609a6fda94cb928cd25f5a7a3ab736b5a4236b6d5409ab18892c6a4d3480fc2350abfdf0bab1cedb55bdf0760fdb703e6688f4de596254eed4ed3e67eb03d0717b8e15b31e735214e588c87ae36bc6c310e1894b4c15143e4ccf287b2dbc707a946bf9671ae3c574f9486b2c82eec784bba4cbc76113cbe0f97ac8c13cfa38f2925ab9d06887a612ce48280a91d7e074e6caf898d88e2bbf71360899abf48a03f9a70cf2891199f2d63b116f4871af0ebb4f4906792f66cc21d1609f189138532875c129a68c73e7bcd3b5d8100beac1d8ac4b20d94a59ac8df5a5af58a9acb20413eadf97189f5f19ff889155f0c4d37514ec184eb6903967ff38a41fc087abb0f2cad3761d6e3f95f92a09a72f5c065b16e188088b87460241f27ecdb1bc6ece92c8d36b2d68b58d0fb4d4b3c928c579ade8ae5a995833aadd297c30a37f7bc35440fc97070e1b198e0fac00157452177d16d2803b4239997452b4ad3a951173bdec47a033fd7f8a7942accaa9aaa905b3c5a2175e7c3e07c48bf25331727fd69cd1e64d74d8c9d4a6f8f4491adb7bc911505cb19877083d8f21a12475e313fccf57877ff3556318e81ed9145dd9427f2b65275440893035f417481f721c69215af8ae103530cd0a1d35bf2cb5a27628f8d44d7c6f5ec12ce79d0a8333e0eb48771115d0a191304e46b8db19bbe5c40f1c346dde98e76ff5e21ff38d2c34e60cb07766ed529dd6d2cbacd7fbf1ed8a0e6e40decad0ca5021e91552be87c156d3ae2fffef41c65b14ba6d488f2c3227a1ab11ffce0e2dc47723a69da27a67a7f26e1cb13a7103af9b87a8db8e18ea\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.QUICClientHello(context.Background(), &metadata, pkt)\n\trequire.NoError(t, err)\n\trequire.Equal(t, metadata.Protocol, C.ProtocolQUIC)\n\trequire.Equal(t, metadata.Client, C.ClientChromium)\n\trequire.Equal(t, metadata.Domain, \"www.google.com\")\n}\n\nfunc TestSniffQUICFirefox(t *testing.T) {\n\tt.Parallel()\n\tpkt, err := hex.DecodeString(\"c8000000010867f174d7ebfe1b0803cd9c20004286de068f7963cf1736349ee6ebe0ddcd3e4cd0041a51ced3f7ce9eea1fb595458e74bdb4b792b16449bd8cae71419862c4fcbe766eaec7d1af65cd298e1dd46f8bd94a77ab4ca28c54b8e9773de3f02d7cb2463c9f7dcacfb311f024b0266ec6ab7bfb615b4148333fb4d4ece7c4cd90029ca30c2cbae2216b428499ec873fa125797e71c5a5da85087760ad37ca610020f71b76e82651c47576e20bf33cf676cb2d400b8c09d3c8cb4e21c47d2b21f6b68732bef30c8cefd5c723fc23eb29e6f7f65a5e52aad9055c1fb3d8b1811f0380b38d7e2eee8eb37dd5bd5d4ca4b66540175d916289d88a9df7c161964d713999c5057d27edb298ef5164352568b0d4bac3c15d90456e8fd460e41b81d0ec1b1e94b87d3333cc6908b018e0914ae1f214d73e75398da3d55a0106161d3a75897b4eb66e98c59010fae75f0d367d38be48c3a5c58bc8a30773c3fff50690ac9d487822f85d4f5713d626baa92d36e858dd21259cf814bce0b90d18da88a1ade40113e5a088cdb304a2558879152a8cf15c1839e056378aa41acba6fcb9974dee54bd50b5d4eb2c475654e06c0ec06b7f18f4462c808684843a1071041b9bfb2688324e0120144944416e30e83eedbbbcbc275b1f53762d3db18f0998ce54f0e1c512946b4098f07781d49264fa148f4c8220a3b02e73d7f15554aa370aafeff73cb75c52c494edf90f0261abfdd32a4d670f729de50266162687aa8efe14b8506f313b058b02aaaab5825428f5f4510b8e49451fdcb7b5a4af4b59c831afcb89fb4f64dba78e3b38387e87e9e8cdaa1f3b700a87c7d442388863b8950296e5773b38f308d62f52548c0bbf308e40540747cca5bf99b1345bc0d70b8f0e69a83b85a8d69f795b87f93e2bfccf52b529afea4ff6fd456957000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.QUICClientHello(context.Background(), &metadata, pkt)\n\trequire.NoError(t, err)\n\trequire.Equal(t, metadata.Protocol, C.ProtocolQUIC)\n\trequire.Equal(t, metadata.Client, C.ClientFirefox)\n\trequire.Equal(t, metadata.Domain, \"www.google.com\")\n}\n\nfunc TestSniffQUICSafari(t *testing.T) {\n\tt.Parallel()\n\tpkt, err := hex.DecodeString(\"c70000000108e4e75af2e223198a0000449ef2d83cb4473a62765eba67424cd4a5817315cbf55a9e8daaca360904b0bae60b1629cfeba11e2dfbbf5ea4c588cb134e31af36fd7a409fb0fcc0187e9b56037ac37964ed20a8c1ca19fd6cfd53398324b3d0c71537294f769db208fa998b6811234a4a7eb3b5eceb457ae92e3a2d98f7c110702db8064b5c29fa3298eb1d0529fd445a84a5fd6ff8709be90f8af4f94998d8a8f2953bb05ad08c80668eca784c6aec959114e68e5b827e7c41c79f2277c716a967e7fcc8d1b77442e6cb18329dbedb34b473516b468cba5fc20659e655fbe37f36408289b9a475fcee091bd82828d3be00367e9e5cec9423bb97854abdada1d7562a3777756eb3bddef826ddc1ef46137cb01bb504a54d410d9bcb74cd5f959050c84edf343fa6a49708c228a758ee7adbbadf260b2f1984911489712e2cb364a3d6520badba4b7e539b9c163eeddfd96c0abb0de151e47496bb9750be76ee17ccdb61d35d2c6795174037d6f9d282c3f36c4d9a90b64f3b6ddd0cf4d9ed8e6f7805e25928fa04b087e63ae02761df30720cc01dfc32b64c575c8a66ef82e9a17400ff80cd8609b93ba16d668f4aa734e71c4a5d145f14ee1151bec970214e0ff83fc3e1e85d8694f2975f9155c57c18b7b69bb6a36832a9435f1f4b346a7be188f3a75f9ad2cc6ad0a3d26d6fa7d4c1179bd49bd5989d15ba43ff602890107db96484695086627356750d7b2b3b714ba65d564654e8f60ac10f5b6d3bfb507e8eaa31bab1da2d676195046d165c7f8b32829c9f9b68d97b2af7ac04a1369357e4b65de2b2f24eaf27cc8d95e05db001adebe726f927a94e43e62ce671e6e306e16f05aafcbe6c49080e80286d7939f375023d110a5ad9069364ae928ca480454a9dcddd61bc48b7efeb716a5bd6c7cd39c486ceb20c738af6abf22ba1ddd8b4a3b781fc2f251173409e1aadccbd7514e97106d0ebfc3af6e59445f74cd733a1ba99b10fce3fb4e9f7c88f5e25b567f5ba2b8dabacd375e7faf7634bfa178cbe51aee63032c5126b196ea47b02385fc3062a000fb7e4b4d0d12e74579f8830ede20d10829496032b2cc56743287f9a9b4d5091877a82fea44deb2cffac8a379f78a151d99e28cbc74d732c083bf06d50584e3f18f254e71a48d6ababaf6fff6f425e9be001510dfbe6a32a27792c00ada036b62ddb90c706d7b882c76a7072f5dd11c69a1f49d4ba183cb0b57545419fa27b9b9706098848935ae9c9e8fbe9fac165d1339128b991a73d20e7795e8d6a8c6adfbf20bf13ada43f2aef3ba78c14697910507132623f721387dce60c4707225b84d9782d469a5d9eaa099f35d6a590ef142ddef766495cf3337815ceef5ff2b3ed352637e72b5c23a2a8ff7d7440236a19b981d47f8e519a0431ebfbc0b78d8a36798b4c060c0c6793499f1e2e818862560a5b501c8d02ba1517be1941da2af5b174e0189c62978d878eb0f9c9db3a9221c28fb94645cf6e85ff2eea8c65ba3083a7382b131b83102dd67aa5453ad7375a4eb8c69fc479fbd29dab8924f801d253f2c997120b705c6e5217fb74702e2f1038917dd5fb0eeb7ae1bf7a668fc7d50c034b4cd5a057a8482e6bc9c921297f44e76967265623a167cd9883eb6e64bc77856dc333bd605d7df3bed0e5cecb5a99fe8b62873d58530f\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.QUICClientHello(context.Background(), &metadata, pkt)\n\trequire.NoError(t, err)\n\trequire.Equal(t, metadata.Protocol, C.ProtocolQUIC)\n\trequire.Equal(t, metadata.Client, C.ClientSafari)\n\trequire.Equal(t, metadata.Domain, \"www.google.com\")\n}\n\nfunc FuzzSniffQUIC(f *testing.F) {\n\tf.Fuzz(func(t *testing.T, data []byte) {\n\t\tvar metadata adapter.InboundContext\n\t\terr := sniff.QUICClientHello(context.Background(), &metadata, data)\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "common/sniff/rdp.go",
    "content": "package sniff\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/rw\"\n)\n\nfunc RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {\n\tvar tpktVersion uint8\n\terr := binary.Read(reader, binary.BigEndian, &tpktVersion)\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\tif tpktVersion != 0x03 {\n\t\treturn os.ErrInvalid\n\t}\n\n\tvar tpktReserved uint8\n\terr = binary.Read(reader, binary.BigEndian, &tpktReserved)\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\tif tpktReserved != 0x00 {\n\t\treturn os.ErrInvalid\n\t}\n\n\tvar tpktLength uint16\n\terr = binary.Read(reader, binary.BigEndian, &tpktLength)\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\n\tif tpktLength != 19 {\n\t\treturn os.ErrInvalid\n\t}\n\n\tvar cotpLength uint8\n\terr = binary.Read(reader, binary.BigEndian, &cotpLength)\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\n\tif cotpLength != 14 {\n\t\treturn os.ErrInvalid\n\t}\n\n\tvar cotpTpduType uint8\n\terr = binary.Read(reader, binary.BigEndian, &cotpTpduType)\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\tif cotpTpduType != 0xE0 {\n\t\treturn os.ErrInvalid\n\t}\n\n\terr = rw.SkipN(reader, 5)\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\n\tvar rdpType uint8\n\terr = binary.Read(reader, binary.BigEndian, &rdpType)\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\tif rdpType != 0x01 {\n\t\treturn os.ErrInvalid\n\t}\n\tvar rdpFlags uint8\n\terr = binary.Read(reader, binary.BigEndian, &rdpFlags)\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\tvar rdpLength uint8\n\terr = binary.Read(reader, binary.BigEndian, &rdpLength)\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\tif rdpLength != 8 {\n\t\treturn os.ErrInvalid\n\t}\n\tmetadata.Protocol = C.ProtocolRDP\n\treturn nil\n}\n"
  },
  {
    "path": "common/sniff/rdp_test.go",
    "content": "package sniff_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSniffRDP(t *testing.T) {\n\tt.Parallel()\n\n\tpkt, err := hex.DecodeString(\"030000130ee00000000000010008000b000000010008000b000000\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.RDP(context.TODO(), &metadata, bytes.NewReader(pkt))\n\trequire.NoError(t, err)\n\trequire.Equal(t, C.ProtocolRDP, metadata.Protocol)\n}\n"
  },
  {
    "path": "common/sniff/sniff.go",
    "content": "package sniff\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype (\n\tStreamSniffer = func(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error\n\tPacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error\n)\n\nvar ErrNeedMoreData = E.New(\"need more data\")\n\nfunc Skip(metadata *adapter.InboundContext) bool {\n\t// skip server first protocols\n\tswitch metadata.Destination.Port {\n\tcase 25, 465, 587:\n\t\t// SMTP\n\t\treturn true\n\tcase 143, 993:\n\t\t// IMAP\n\t\treturn true\n\tcase 110, 995:\n\t\t// POP3\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffers []*buf.Buffer, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {\n\tif timeout == 0 {\n\t\ttimeout = C.ReadPayloadTimeout\n\t}\n\tdeadline := time.Now().Add(timeout)\n\tvar sniffError error\n\tfor i := 0; ; i++ {\n\t\terr := conn.SetReadDeadline(deadline)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"set read deadline\")\n\t\t}\n\t\t_, err = buffer.ReadOnceFrom(conn)\n\t\t_ = conn.SetReadDeadline(time.Time{})\n\t\tif err != nil {\n\t\t\tif i > 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn E.Cause(err, \"read payload\")\n\t\t}\n\t\tsniffError = nil\n\t\tfor _, sniffer := range sniffers {\n\t\t\treader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {\n\t\t\t\treturn bytes.NewReader(it.Bytes())\n\t\t\t})...)\n\t\t\terr = sniffer(ctx, metadata, reader)\n\t\t\tif err == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tsniffError = E.Errors(sniffError, err)\n\t\t}\n\t\tif !errors.Is(sniffError, ErrNeedMoreData) {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn sniffError\n}\n\nfunc PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {\n\tvar sniffError []error\n\tfor _, sniffer := range sniffers {\n\t\terr := sniffer(ctx, metadata, packet)\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\t\tsniffError = append(sniffError, err)\n\t}\n\treturn E.Errors(sniffError...)\n}\n"
  },
  {
    "path": "common/sniff/ssh.go",
    "content": "package sniff\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {\n\tconst sshPrefix = \"SSH-2.0-\"\n\tbReader := bufio.NewReader(reader)\n\tprefix, err := bReader.Peek(len(sshPrefix))\n\tif string(prefix[:]) != sshPrefix[:len(prefix)] {\n\t\treturn os.ErrInvalid\n\t}\n\tif err != nil {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t}\n\tfistLine, _, err := bReader.ReadLine()\n\tif err != nil {\n\t\treturn err\n\t}\n\tmetadata.Protocol = C.ProtocolSSH\n\tmetadata.Client = string(fistLine)[8:]\n\treturn nil\n}\n"
  },
  {
    "path": "common/sniff/ssh_test.go",
    "content": "package sniff_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSniffSSH(t *testing.T) {\n\tt.Parallel()\n\n\tpkt, err := hex.DecodeString(\"5353482d322e302d64726f70626561720d0a000001a40a1492892570d1223aef61b0d647972c8bd30000009f637572766532353531392d7368613235362c637572766532353531392d736861323536406c69627373682e6f72672c6469666669652d68656c6c6d616e2d67726f757031342d7368613235362c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6b6578677565737332406d6174742e7563632e61736e2e61752c6b65782d7374726963742d732d763030406f70656e7373682e636f6d000000207373682d656432353531392c7273612d736861322d3235362c7373682d7273610000003363686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733132382d6374722c6165733235362d6374720000003363686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733132382d6374722c6165733235362d63747200000017686d61632d736861312c686d61632d736861322d32353600000017686d61632d736861312c686d61632d736861322d323536000000046e6f6e65000000046e6f6e65000000000000000000000000002aa6ed090585b7d635b6\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))\n\trequire.NoError(t, err)\n\trequire.Equal(t, C.ProtocolSSH, metadata.Protocol)\n\trequire.Equal(t, \"dropbear\", metadata.Client)\n}\n\nfunc TestSniffIncompleteSSH(t *testing.T) {\n\tt.Parallel()\n\n\tpkt, err := hex.DecodeString(\"5353482d322e30\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))\n\trequire.ErrorIs(t, err, sniff.ErrNeedMoreData)\n}\n\nfunc TestSniffNotSSH(t *testing.T) {\n\tt.Parallel()\n\n\tpkt, err := hex.DecodeString(\"5353482d322e31\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))\n\trequire.NotEmpty(t, err)\n\trequire.NotErrorIs(t, err, sniff.ErrNeedMoreData)\n}\n"
  },
  {
    "path": "common/sniff/stun.go",
    "content": "package sniff\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n)\n\nfunc STUNMessage(_ context.Context, metadata *adapter.InboundContext, packet []byte) error {\n\tpLen := len(packet)\n\tif pLen < 20 {\n\t\treturn os.ErrInvalid\n\t}\n\tif binary.BigEndian.Uint32(packet[4:8]) != 0x2112A442 {\n\t\treturn os.ErrInvalid\n\t}\n\tif len(packet) < 20+int(binary.BigEndian.Uint16(packet[2:4])) {\n\t\treturn os.ErrInvalid\n\t}\n\tmetadata.Protocol = C.ProtocolSTUN\n\treturn nil\n}\n"
  },
  {
    "path": "common/sniff/stun_test.go",
    "content": "package sniff_test\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSniffSTUN(t *testing.T) {\n\tt.Parallel()\n\tpacket, err := hex.DecodeString(\"000100002112a44224b1a025d0c180c484341306\")\n\trequire.NoError(t, err)\n\tvar metadata adapter.InboundContext\n\terr = sniff.STUNMessage(context.Background(), &metadata, packet)\n\trequire.NoError(t, err)\n\trequire.Equal(t, metadata.Protocol, C.ProtocolSTUN)\n}\n\nfunc FuzzSniffSTUN(f *testing.F) {\n\tf.Fuzz(func(t *testing.T, data []byte) {\n\t\tvar metadata adapter.InboundContext\n\t\tif err := sniff.STUNMessage(context.Background(), &metadata, data); err == nil {\n\t\t\tt.Fail()\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "common/sniff/tls.go",
    "content": "package sniff\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {\n\tvar clientHello *tls.ClientHelloInfo\n\terr := tls.Server(bufio.NewReadOnlyConn(reader), &tls.Config{\n\t\tGetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) {\n\t\t\tclientHello = argHello\n\t\t\treturn nil, nil\n\t\t},\n\t}).HandshakeContext(ctx)\n\tif clientHello != nil {\n\t\tmetadata.Protocol = C.ProtocolTLS\n\t\tmetadata.Domain = clientHello.ServerName\n\t\treturn nil\n\t}\n\tif errors.Is(err, io.ErrUnexpectedEOF) {\n\t\treturn E.Cause1(ErrNeedMoreData, err)\n\t} else {\n\t\treturn err\n\t}\n}\n"
  },
  {
    "path": "common/srs/binary.go",
    "content": "package srs\n\nimport (\n\t\"bufio\"\n\t\"compress/zlib\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"net/netip\"\n\t\"unsafe\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/domain\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\t\"github.com/sagernet/sing/common/varbin\"\n\n\t\"go4.org/netipx\"\n)\n\nvar MagicBytes = [3]byte{0x53, 0x52, 0x53} // SRS\n\nconst (\n\truleItemQueryType uint8 = iota\n\truleItemNetwork\n\truleItemDomain\n\truleItemDomainKeyword\n\truleItemDomainRegex\n\truleItemSourceIPCIDR\n\truleItemIPCIDR\n\truleItemSourcePort\n\truleItemSourcePortRange\n\truleItemPort\n\truleItemPortRange\n\truleItemProcessName\n\truleItemProcessPath\n\truleItemPackageName\n\truleItemWIFISSID\n\truleItemWIFIBSSID\n\truleItemAdGuardDomain\n\truleItemProcessPathRegex\n\truleItemNetworkType\n\truleItemNetworkIsExpensive\n\truleItemNetworkIsConstrained\n\truleItemNetworkInterfaceAddress\n\truleItemDefaultInterfaceAddress\n\truleItemFinal uint8 = 0xFF\n)\n\nfunc Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetCompat, err error) {\n\tvar magicBytes [3]byte\n\t_, err = io.ReadFull(reader, magicBytes[:])\n\tif err != nil {\n\t\treturn\n\t}\n\tif magicBytes != MagicBytes {\n\t\terr = E.New(\"invalid sing-box rule-set file\")\n\t\treturn\n\t}\n\tvar version uint8\n\terr = binary.Read(reader, binary.BigEndian, &version)\n\tif err != nil {\n\t\treturn ruleSetCompat, err\n\t}\n\tif version > C.RuleSetVersionCurrent {\n\t\treturn ruleSetCompat, E.New(\"unsupported version: \", version)\n\t}\n\tcompressReader, err := zlib.NewReader(reader)\n\tif err != nil {\n\t\treturn\n\t}\n\tbReader := bufio.NewReader(compressReader)\n\tlength, err := binary.ReadUvarint(bReader)\n\tif err != nil {\n\t\treturn\n\t}\n\truleSetCompat.Version = version\n\truleSetCompat.Options.Rules = make([]option.HeadlessRule, length)\n\tfor i := uint64(0); i < length; i++ {\n\t\truleSetCompat.Options.Rules[i], err = readRule(bReader, recover)\n\t\tif err != nil {\n\t\t\terr = E.Cause(err, \"read rule[\", i, \"]\")\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\nfunc Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8) error {\n\t_, err := writer.Write(MagicBytes[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = binary.Write(writer, binary.BigEndian, generateVersion)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcompressWriter, err := zlib.NewWriterLevel(writer, zlib.BestCompression)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbWriter := bufio.NewWriter(compressWriter)\n\t_, err = varbin.WriteUvarint(bWriter, uint64(len(ruleSet.Rules)))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, rule := range ruleSet.Rules {\n\t\terr = writeRule(bWriter, rule, generateVersion)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\terr = bWriter.Flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn compressWriter.Close()\n}\n\nfunc readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err error) {\n\tvar ruleType uint8\n\terr = binary.Read(reader, binary.BigEndian, &ruleType)\n\tif err != nil {\n\t\treturn\n\t}\n\tswitch ruleType {\n\tcase 0:\n\t\trule.Type = C.RuleTypeDefault\n\t\trule.DefaultOptions, err = readDefaultRule(reader, recover)\n\tcase 1:\n\t\trule.Type = C.RuleTypeLogical\n\t\trule.LogicalOptions, err = readLogicalRule(reader, recover)\n\tdefault:\n\t\terr = E.New(\"unknown rule type: \", ruleType)\n\t}\n\treturn\n}\n\nfunc writeRule(writer varbin.Writer, rule option.HeadlessRule, generateVersion uint8) error {\n\tswitch rule.Type {\n\tcase C.RuleTypeDefault:\n\t\treturn writeDefaultRule(writer, rule.DefaultOptions, generateVersion)\n\tcase C.RuleTypeLogical:\n\t\treturn writeLogicalRule(writer, rule.LogicalOptions, generateVersion)\n\tdefault:\n\t\tpanic(\"unknown rule type: \" + rule.Type)\n\t}\n}\n\nfunc readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHeadlessRule, err error) {\n\tvar lastItemType uint8\n\tfor {\n\t\tvar itemType uint8\n\t\terr = binary.Read(reader, binary.BigEndian, &itemType)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tswitch itemType {\n\t\tcase ruleItemQueryType:\n\t\t\tvar rawQueryType []uint16\n\t\t\trawQueryType, err = readRuleItemUint16(reader)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\trule.QueryType = common.Map(rawQueryType, func(it uint16) option.DNSQueryType {\n\t\t\t\treturn option.DNSQueryType(it)\n\t\t\t})\n\t\tcase ruleItemNetwork:\n\t\t\trule.Network, err = readRuleItemString(reader)\n\t\tcase ruleItemDomain:\n\t\t\tvar matcher *domain.Matcher\n\t\t\tmatcher, err = domain.ReadMatcher(reader)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\trule.DomainMatcher = matcher\n\t\t\tif recover {\n\t\t\t\trule.Domain, rule.DomainSuffix = matcher.Dump()\n\t\t\t}\n\t\tcase ruleItemDomainKeyword:\n\t\t\trule.DomainKeyword, err = readRuleItemString(reader)\n\t\tcase ruleItemDomainRegex:\n\t\t\trule.DomainRegex, err = readRuleItemString(reader)\n\t\tcase ruleItemSourceIPCIDR:\n\t\t\trule.SourceIPSet, err = readIPSet(reader)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif recover {\n\t\t\t\trule.SourceIPCIDR = common.Map(rule.SourceIPSet.Prefixes(), netip.Prefix.String)\n\t\t\t}\n\t\tcase ruleItemIPCIDR:\n\t\t\trule.IPSet, err = readIPSet(reader)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif recover {\n\t\t\t\trule.IPCIDR = common.Map(rule.IPSet.Prefixes(), netip.Prefix.String)\n\t\t\t}\n\t\tcase ruleItemSourcePort:\n\t\t\trule.SourcePort, err = readRuleItemUint16(reader)\n\t\tcase ruleItemSourcePortRange:\n\t\t\trule.SourcePortRange, err = readRuleItemString(reader)\n\t\tcase ruleItemPort:\n\t\t\trule.Port, err = readRuleItemUint16(reader)\n\t\tcase ruleItemPortRange:\n\t\t\trule.PortRange, err = readRuleItemString(reader)\n\t\tcase ruleItemProcessName:\n\t\t\trule.ProcessName, err = readRuleItemString(reader)\n\t\tcase ruleItemProcessPath:\n\t\t\trule.ProcessPath, err = readRuleItemString(reader)\n\t\tcase ruleItemProcessPathRegex:\n\t\t\trule.ProcessPathRegex, err = readRuleItemString(reader)\n\t\tcase ruleItemPackageName:\n\t\t\trule.PackageName, err = readRuleItemString(reader)\n\t\tcase ruleItemWIFISSID:\n\t\t\trule.WIFISSID, err = readRuleItemString(reader)\n\t\tcase ruleItemWIFIBSSID:\n\t\t\trule.WIFIBSSID, err = readRuleItemString(reader)\n\t\tcase ruleItemAdGuardDomain:\n\t\t\tvar matcher *domain.AdGuardMatcher\n\t\t\tmatcher, err = domain.ReadAdGuardMatcher(reader)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\trule.AdGuardDomainMatcher = matcher\n\t\t\tif recover {\n\t\t\t\trule.AdGuardDomain = matcher.Dump()\n\t\t\t}\n\t\tcase ruleItemNetworkType:\n\t\t\trule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)\n\t\tcase ruleItemNetworkIsExpensive:\n\t\t\trule.NetworkIsExpensive = true\n\t\tcase ruleItemNetworkIsConstrained:\n\t\t\trule.NetworkIsConstrained = true\n\t\tcase ruleItemNetworkInterfaceAddress:\n\t\t\trule.NetworkInterfaceAddress = new(badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]])\n\t\t\tvar size uint64\n\t\t\tsize, err = binary.ReadUvarint(reader)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := uint64(0); i < size; i++ {\n\t\t\t\tvar key uint8\n\t\t\t\terr = binary.Read(reader, binary.BigEndian, &key)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tvar value []*badoption.Prefixable\n\t\t\t\tvar prefixCount uint64\n\t\t\t\tprefixCount, err = binary.ReadUvarint(reader)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor j := uint64(0); j < prefixCount; j++ {\n\t\t\t\t\tvar prefix netip.Prefix\n\t\t\t\t\tprefix, err = readPrefix(reader)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tvalue = append(value, common.Ptr(badoption.Prefixable(prefix)))\n\t\t\t\t}\n\t\t\t\trule.NetworkInterfaceAddress.Put(option.InterfaceType(key), value)\n\t\t\t}\n\t\tcase ruleItemDefaultInterfaceAddress:\n\t\t\tvar value []*badoption.Prefixable\n\t\t\tvar prefixCount uint64\n\t\t\tprefixCount, err = binary.ReadUvarint(reader)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor j := uint64(0); j < prefixCount; j++ {\n\t\t\t\tvar prefix netip.Prefix\n\t\t\t\tprefix, err = readPrefix(reader)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tvalue = append(value, common.Ptr(badoption.Prefixable(prefix)))\n\t\t\t}\n\t\t\trule.DefaultInterfaceAddress = value\n\t\tcase ruleItemFinal:\n\t\t\terr = binary.Read(reader, binary.BigEndian, &rule.Invert)\n\t\t\treturn\n\t\tdefault:\n\t\t\terr = E.New(\"unknown rule item type: \", itemType, \", last type: \", lastItemType)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tlastItemType = itemType\n\t}\n}\n\nfunc writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateVersion uint8) error {\n\terr := binary.Write(writer, binary.BigEndian, uint8(0))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(rule.QueryType) > 0 {\n\t\terr = writeRuleItemUint16(writer, ruleItemQueryType, common.Map(rule.QueryType, func(it option.DNSQueryType) uint16 {\n\t\t\treturn uint16(it)\n\t\t}))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.Network) > 0 {\n\t\terr = writeRuleItemString(writer, ruleItemNetwork, rule.Network)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.Domain) > 0 || len(rule.DomainSuffix) > 0 {\n\t\terr = binary.Write(writer, binary.BigEndian, ruleItemDomain)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = domain.NewMatcher(rule.Domain, rule.DomainSuffix, generateVersion == C.RuleSetVersion1).Write(writer)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.DomainKeyword) > 0 {\n\t\terr = writeRuleItemString(writer, ruleItemDomainKeyword, rule.DomainKeyword)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.DomainRegex) > 0 {\n\t\terr = writeRuleItemString(writer, ruleItemDomainRegex, rule.DomainRegex)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.SourceIPCIDR) > 0 {\n\t\terr = writeRuleItemCIDR(writer, ruleItemSourceIPCIDR, rule.SourceIPCIDR)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"source_ip_cidr\")\n\t\t}\n\t}\n\tif len(rule.IPCIDR) > 0 {\n\t\terr = writeRuleItemCIDR(writer, ruleItemIPCIDR, rule.IPCIDR)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"ipcidr\")\n\t\t}\n\t}\n\tif len(rule.SourcePort) > 0 {\n\t\terr = writeRuleItemUint16(writer, ruleItemSourcePort, rule.SourcePort)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.SourcePortRange) > 0 {\n\t\terr = writeRuleItemString(writer, ruleItemSourcePortRange, rule.SourcePortRange)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.Port) > 0 {\n\t\terr = writeRuleItemUint16(writer, ruleItemPort, rule.Port)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.PortRange) > 0 {\n\t\terr = writeRuleItemString(writer, ruleItemPortRange, rule.PortRange)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.ProcessName) > 0 {\n\t\terr = writeRuleItemString(writer, ruleItemProcessName, rule.ProcessName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.ProcessPath) > 0 {\n\t\terr = writeRuleItemString(writer, ruleItemProcessPath, rule.ProcessPath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.ProcessPathRegex) > 0 {\n\t\terr = writeRuleItemString(writer, ruleItemProcessPathRegex, rule.ProcessPathRegex)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.PackageName) > 0 {\n\t\terr = writeRuleItemString(writer, ruleItemPackageName, rule.PackageName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.NetworkType) > 0 {\n\t\tif generateVersion < C.RuleSetVersion3 {\n\t\t\treturn E.New(\"`network_type` rule item is only supported in version 3 or later\")\n\t\t}\n\t\terr = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif rule.NetworkIsExpensive {\n\t\tif generateVersion < C.RuleSetVersion3 {\n\t\t\treturn E.New(\"`network_is_expensive` rule item is only supported in version 3 or later\")\n\t\t}\n\t\terr = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif rule.NetworkIsConstrained {\n\t\tif generateVersion < C.RuleSetVersion3 {\n\t\t\treturn E.New(\"`network_is_constrained` rule item is only supported in version 3 or later\")\n\t\t}\n\t\terr = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 {\n\t\tif generateVersion < C.RuleSetVersion4 {\n\t\t\treturn E.New(\"`network_interface_address` rule item is only supported in version 4 or later\")\n\t\t}\n\t\terr = writer.WriteByte(ruleItemNetworkInterfaceAddress)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = varbin.WriteUvarint(writer, uint64(rule.NetworkInterfaceAddress.Size()))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, entry := range rule.NetworkInterfaceAddress.Entries() {\n\t\t\terr = binary.Write(writer, binary.BigEndian, uint8(entry.Key.Build()))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = varbin.WriteUvarint(writer, uint64(len(entry.Value)))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfor _, rawPrefix := range entry.Value {\n\t\t\t\terr = writePrefix(writer, rawPrefix.Build(netip.Prefix{}))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(rule.DefaultInterfaceAddress) > 0 {\n\t\tif generateVersion < C.RuleSetVersion4 {\n\t\t\treturn E.New(\"`default_interface_address` rule item is only supported in version 4 or later\")\n\t\t}\n\t\terr = writer.WriteByte(ruleItemDefaultInterfaceAddress)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = varbin.WriteUvarint(writer, uint64(len(rule.DefaultInterfaceAddress)))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, rawPrefix := range rule.DefaultInterfaceAddress {\n\t\t\terr = writePrefix(writer, rawPrefix.Build(netip.Prefix{}))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tif len(rule.WIFISSID) > 0 {\n\t\terr = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.WIFIBSSID) > 0 {\n\t\terr = writeRuleItemString(writer, ruleItemWIFIBSSID, rule.WIFIBSSID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(rule.AdGuardDomain) > 0 {\n\t\tif generateVersion < C.RuleSetVersion2 {\n\t\t\treturn E.New(\"AdGuard rule items is only supported in version 2 or later\")\n\t\t}\n\t\terr = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = domain.NewAdGuardMatcher(rule.AdGuardDomain).Write(writer)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\terr = binary.Write(writer, binary.BigEndian, ruleItemFinal)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = binary.Write(writer, binary.BigEndian, rule.Invert)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc readRuleItemString(reader varbin.Reader) ([]string, error) {\n\tlength, err := binary.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresult := make([]string, length)\n\tfor i := range result {\n\t\tstrLen, err := binary.ReadUvarint(reader)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tbuf := make([]byte, strLen)\n\t\t_, err = io.ReadFull(reader, buf)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult[i] = string(buf)\n\t}\n\treturn result, nil\n}\n\nfunc writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error {\n\terr := writer.WriteByte(itemType)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = varbin.WriteUvarint(writer, uint64(len(value)))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, s := range value {\n\t\t_, err = varbin.WriteUvarint(writer, uint64(len(s)))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = writer.Write([]byte(s))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) {\n\tlength, err := binary.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresult := make([]E, length)\n\t_, err = io.ReadFull(reader, *(*[]byte)(unsafe.Pointer(&result)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn result, nil\n}\n\nfunc writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error {\n\terr := writer.WriteByte(itemType)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = varbin.WriteUvarint(writer, uint64(len(value)))\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = writer.Write(*(*[]byte)(unsafe.Pointer(&value)))\n\treturn err\n}\n\nfunc readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {\n\tlength, err := binary.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresult := make([]uint16, length)\n\terr = binary.Read(reader, binary.BigEndian, result)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn result, nil\n}\n\nfunc writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error {\n\terr := writer.WriteByte(itemType)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = varbin.WriteUvarint(writer, uint64(len(value)))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn binary.Write(writer, binary.BigEndian, value)\n}\n\nfunc writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error {\n\tvar builder netipx.IPSetBuilder\n\tfor i, prefixString := range value {\n\t\tprefix, err := netip.ParsePrefix(prefixString)\n\t\tif err == nil {\n\t\t\tbuilder.AddPrefix(prefix)\n\t\t\tcontinue\n\t\t}\n\t\taddr, addrErr := netip.ParseAddr(prefixString)\n\t\tif addrErr == nil {\n\t\t\tbuilder.Add(addr)\n\t\t\tcontinue\n\t\t}\n\t\treturn E.Cause(err, \"parse [\", i, \"]\")\n\t}\n\tipSet, err := builder.IPSet()\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = binary.Write(writer, binary.BigEndian, itemType)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn writeIPSet(writer, ipSet)\n}\n\nfunc readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) {\n\tmode, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn\n\t}\n\tswitch mode {\n\tcase 0:\n\t\tlogicalRule.Mode = C.LogicalTypeAnd\n\tcase 1:\n\t\tlogicalRule.Mode = C.LogicalTypeOr\n\tdefault:\n\t\terr = E.New(\"unknown logical mode: \", mode)\n\t\treturn\n\t}\n\tlength, err := binary.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn\n\t}\n\tlogicalRule.Rules = make([]option.HeadlessRule, length)\n\tfor i := uint64(0); i < length; i++ {\n\t\tlogicalRule.Rules[i], err = readRule(reader, recovery)\n\t\tif err != nil {\n\t\t\terr = E.Cause(err, \"read logical rule [\", i, \"]\")\n\t\t\treturn\n\t\t}\n\t}\n\terr = binary.Read(reader, binary.BigEndian, &logicalRule.Invert)\n\tif err != nil {\n\t\treturn\n\t}\n\treturn\n}\n\nfunc writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateVersion uint8) error {\n\terr := binary.Write(writer, binary.BigEndian, uint8(1))\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch logicalRule.Mode {\n\tcase C.LogicalTypeAnd:\n\t\terr = binary.Write(writer, binary.BigEndian, uint8(0))\n\tcase C.LogicalTypeOr:\n\t\terr = binary.Write(writer, binary.BigEndian, uint8(1))\n\tdefault:\n\t\tpanic(\"unknown logical mode: \" + logicalRule.Mode)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = varbin.WriteUvarint(writer, uint64(len(logicalRule.Rules)))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, rule := range logicalRule.Rules {\n\t\terr = writeRule(writer, rule, generateVersion)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\terr = binary.Write(writer, binary.BigEndian, logicalRule.Invert)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/srs/compat_test.go",
    "content": "package srs\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"testing\"\n\t\"unsafe\"\n\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/varbin\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go4.org/netipx\"\n)\n\n// Old implementations using varbin reflection-based serialization\n\nfunc oldWriteStringSlice(writer varbin.Writer, value []string) error {\n\t//nolint:staticcheck\n\treturn varbin.Write(writer, binary.BigEndian, value)\n}\n\nfunc oldReadStringSlice(reader varbin.Reader) ([]string, error) {\n\t//nolint:staticcheck\n\treturn varbin.ReadValue[[]string](reader, binary.BigEndian)\n}\n\nfunc oldWriteUint8Slice[E ~uint8](writer varbin.Writer, value []E) error {\n\t//nolint:staticcheck\n\treturn varbin.Write(writer, binary.BigEndian, value)\n}\n\nfunc oldReadUint8Slice[E ~uint8](reader varbin.Reader) ([]E, error) {\n\t//nolint:staticcheck\n\treturn varbin.ReadValue[[]E](reader, binary.BigEndian)\n}\n\nfunc oldWriteUint16Slice(writer varbin.Writer, value []uint16) error {\n\t//nolint:staticcheck\n\treturn varbin.Write(writer, binary.BigEndian, value)\n}\n\nfunc oldReadUint16Slice(reader varbin.Reader) ([]uint16, error) {\n\t//nolint:staticcheck\n\treturn varbin.ReadValue[[]uint16](reader, binary.BigEndian)\n}\n\nfunc oldWritePrefix(writer varbin.Writer, prefix netip.Prefix) error {\n\t//nolint:staticcheck\n\terr := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn binary.Write(writer, binary.BigEndian, uint8(prefix.Bits()))\n}\n\ntype oldIPRangeData struct {\n\tFrom []byte\n\tTo   []byte\n}\n\n// Note: The old writeIPSet had a bug where varbin.Write(writer, binary.BigEndian, data)\n// with a struct VALUE (not pointer) silently wrote nothing because field.CanSet() returned false.\n// This caused IP range data to be missing from the output.\n// The new implementation correctly writes all range data.\n//\n// The old readIPSet used varbin.Read with a pre-allocated slice, which worked because\n// slice elements are addressable and CanSet() returns true for them.\n//\n// For compatibility testing, we verify:\n// 1. New write produces correct output with range data\n// 2. New read can parse the new format correctly\n// 3. Round-trip works correctly\n\nfunc oldReadIPSet(reader varbin.Reader) (*netipx.IPSet, error) {\n\tversion, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif version != 1 {\n\t\treturn nil, err\n\t}\n\tvar length uint64\n\terr = binary.Read(reader, binary.BigEndian, &length)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tranges := make([]oldIPRangeData, length)\n\t//nolint:staticcheck\n\terr = varbin.Read(reader, binary.BigEndian, &ranges)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmySet := &myIPSet{\n\t\trr: make([]myIPRange, len(ranges)),\n\t}\n\tfor i, rangeData := range ranges {\n\t\tmySet.rr[i].from = M.AddrFromIP(rangeData.From)\n\t\tmySet.rr[i].to = M.AddrFromIP(rangeData.To)\n\t}\n\treturn (*netipx.IPSet)(unsafe.Pointer(mySet)), nil\n}\n\n// New write functions (without itemType prefix for testing)\n\nfunc newWriteStringSlice(writer varbin.Writer, value []string) error {\n\t_, err := varbin.WriteUvarint(writer, uint64(len(value)))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, s := range value {\n\t\t_, err = varbin.WriteUvarint(writer, uint64(len(s)))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = writer.Write([]byte(s))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc newWriteUint8Slice[E ~uint8](writer varbin.Writer, value []E) error {\n\t_, err := varbin.WriteUvarint(writer, uint64(len(value)))\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = writer.Write(*(*[]byte)(unsafe.Pointer(&value)))\n\treturn err\n}\n\nfunc newWriteUint16Slice(writer varbin.Writer, value []uint16) error {\n\t_, err := varbin.WriteUvarint(writer, uint64(len(value)))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn binary.Write(writer, binary.BigEndian, value)\n}\n\nfunc newWritePrefix(writer varbin.Writer, prefix netip.Prefix) error {\n\taddrSlice := prefix.Addr().AsSlice()\n\t_, err := varbin.WriteUvarint(writer, uint64(len(addrSlice)))\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = writer.Write(addrSlice)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn writer.WriteByte(uint8(prefix.Bits()))\n}\n\n// Tests\n\nfunc TestStringSliceCompat(t *testing.T) {\n\tt.Parallel()\n\n\tcases := []struct {\n\t\tname  string\n\t\tinput []string\n\t}{\n\t\t{\"nil\", nil},\n\t\t{\"empty\", []string{}},\n\t\t{\"single_empty\", []string{\"\"}},\n\t\t{\"single\", []string{\"test\"}},\n\t\t{\"multi\", []string{\"a\", \"b\", \"c\"}},\n\t\t{\"with_empty\", []string{\"a\", \"\", \"c\"}},\n\t\t{\"utf8\", []string{\"测试\", \"テスト\", \"тест\"}},\n\t\t{\"long_string\", []string{strings.Repeat(\"x\", 128)}},\n\t\t{\"many_elements\", generateStrings(128)},\n\t\t{\"many_elements_256\", generateStrings(256)},\n\t\t{\"127_byte_string\", []string{strings.Repeat(\"x\", 127)}},\n\t\t{\"128_byte_string\", []string{strings.Repeat(\"x\", 128)}},\n\t\t{\"mixed_lengths\", []string{\"a\", strings.Repeat(\"b\", 100), \"\", strings.Repeat(\"c\", 200)}},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\t// Old write\n\t\t\tvar oldBuf bytes.Buffer\n\t\t\terr := oldWriteStringSlice(&oldBuf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// New write\n\t\t\tvar newBuf bytes.Buffer\n\t\t\terr = newWriteStringSlice(&newBuf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Bytes must match\n\t\t\trequire.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),\n\t\t\t\t\"mismatch for %q\\nold: %x\\nnew: %x\", tc.name, oldBuf.Bytes(), newBuf.Bytes())\n\n\t\t\t// New write -> old read\n\t\t\treadBack, err := oldReadStringSlice(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequireStringSliceEqual(t, tc.input, readBack)\n\n\t\t\t// Old write -> new read\n\t\t\treadBack2, err := readRuleItemString(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequireStringSliceEqual(t, tc.input, readBack2)\n\t\t})\n\t}\n}\n\nfunc TestUint8SliceCompat(t *testing.T) {\n\tt.Parallel()\n\n\tcases := []struct {\n\t\tname  string\n\t\tinput []uint8\n\t}{\n\t\t{\"nil\", nil},\n\t\t{\"empty\", []uint8{}},\n\t\t{\"single_zero\", []uint8{0}},\n\t\t{\"single_max\", []uint8{255}},\n\t\t{\"multi\", []uint8{0, 1, 127, 128, 255}},\n\t\t{\"boundary\", []uint8{0x00, 0x7f, 0x80, 0xff}},\n\t\t{\"sequential\", generateUint8Slice(256)},\n\t\t{\"127_elements\", generateUint8Slice(127)},\n\t\t{\"128_elements\", generateUint8Slice(128)},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\t// Old write\n\t\t\tvar oldBuf bytes.Buffer\n\t\t\terr := oldWriteUint8Slice(&oldBuf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// New write\n\t\t\tvar newBuf bytes.Buffer\n\t\t\terr = newWriteUint8Slice(&newBuf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Bytes must match\n\t\t\trequire.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),\n\t\t\t\t\"mismatch for %q\\nold: %x\\nnew: %x\", tc.name, oldBuf.Bytes(), newBuf.Bytes())\n\n\t\t\t// New write -> old read\n\t\t\treadBack, err := oldReadUint8Slice[uint8](bufio.NewReader(bytes.NewReader(newBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequireUint8SliceEqual(t, tc.input, readBack)\n\n\t\t\t// Old write -> new read\n\t\t\treadBack2, err := readRuleItemUint8[uint8](bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequireUint8SliceEqual(t, tc.input, readBack2)\n\t\t})\n\t}\n}\n\nfunc TestUint16SliceCompat(t *testing.T) {\n\tt.Parallel()\n\n\tcases := []struct {\n\t\tname  string\n\t\tinput []uint16\n\t}{\n\t\t{\"nil\", nil},\n\t\t{\"empty\", []uint16{}},\n\t\t{\"single_zero\", []uint16{0}},\n\t\t{\"single_max\", []uint16{65535}},\n\t\t{\"multi\", []uint16{0, 255, 256, 32767, 32768, 65535}},\n\t\t{\"ports\", []uint16{80, 443, 8080, 8443}},\n\t\t{\"127_elements\", generateUint16Slice(127)},\n\t\t{\"128_elements\", generateUint16Slice(128)},\n\t\t{\"256_elements\", generateUint16Slice(256)},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\t// Old write\n\t\t\tvar oldBuf bytes.Buffer\n\t\t\terr := oldWriteUint16Slice(&oldBuf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// New write\n\t\t\tvar newBuf bytes.Buffer\n\t\t\terr = newWriteUint16Slice(&newBuf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Bytes must match\n\t\t\trequire.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),\n\t\t\t\t\"mismatch for %q\\nold: %x\\nnew: %x\", tc.name, oldBuf.Bytes(), newBuf.Bytes())\n\n\t\t\t// New write -> old read\n\t\t\treadBack, err := oldReadUint16Slice(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequireUint16SliceEqual(t, tc.input, readBack)\n\n\t\t\t// Old write -> new read\n\t\t\treadBack2, err := readRuleItemUint16(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequireUint16SliceEqual(t, tc.input, readBack2)\n\t\t})\n\t}\n}\n\nfunc TestPrefixCompat(t *testing.T) {\n\tt.Parallel()\n\n\tcases := []struct {\n\t\tname  string\n\t\tinput netip.Prefix\n\t}{\n\t\t{\"ipv4_0\", netip.MustParsePrefix(\"0.0.0.0/0\")},\n\t\t{\"ipv4_8\", netip.MustParsePrefix(\"10.0.0.0/8\")},\n\t\t{\"ipv4_16\", netip.MustParsePrefix(\"192.168.0.0/16\")},\n\t\t{\"ipv4_24\", netip.MustParsePrefix(\"192.168.1.0/24\")},\n\t\t{\"ipv4_32\", netip.MustParsePrefix(\"1.2.3.4/32\")},\n\t\t{\"ipv6_0\", netip.MustParsePrefix(\"::/0\")},\n\t\t{\"ipv6_64\", netip.MustParsePrefix(\"2001:db8::/64\")},\n\t\t{\"ipv6_128\", netip.MustParsePrefix(\"::1/128\")},\n\t\t{\"ipv6_full\", netip.MustParsePrefix(\"2001:0db8:85a3:0000:0000:8a2e:0370:7334/128\")},\n\t\t{\"ipv4_private\", netip.MustParsePrefix(\"172.16.0.0/12\")},\n\t\t{\"ipv6_link_local\", netip.MustParsePrefix(\"fe80::/10\")},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\t// Old write\n\t\t\tvar oldBuf bytes.Buffer\n\t\t\terr := oldWritePrefix(&oldBuf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// New write\n\t\t\tvar newBuf bytes.Buffer\n\t\t\terr = newWritePrefix(&newBuf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Bytes must match\n\t\t\trequire.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),\n\t\t\t\t\"mismatch for %q\\nold: %x\\nnew: %x\", tc.name, oldBuf.Bytes(), newBuf.Bytes())\n\n\t\t\t// New write -> new read (no old read for prefix)\n\t\t\treadBack, err := readPrefix(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.input, readBack)\n\n\t\t\t// Old write -> new read\n\t\t\treadBack2, err := readPrefix(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.input, readBack2)\n\t\t})\n\t}\n}\n\nfunc TestIPSetCompat(t *testing.T) {\n\tt.Parallel()\n\n\t// Note: The old writeIPSet was buggy (varbin.Write with struct values wrote nothing).\n\t// This test verifies the new implementation writes correct data and round-trips correctly.\n\n\tcases := []struct {\n\t\tname  string\n\t\tinput *netipx.IPSet\n\t}{\n\t\t{\"single_ipv4\", buildIPSet(\"1.2.3.4\")},\n\t\t{\"ipv4_range\", buildIPSet(\"192.168.0.0/16\")},\n\t\t{\"multi_ipv4\", buildIPSet(\"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\")},\n\t\t{\"single_ipv6\", buildIPSet(\"::1\")},\n\t\t{\"ipv6_range\", buildIPSet(\"2001:db8::/32\")},\n\t\t{\"mixed\", buildIPSet(\"10.0.0.0/8\", \"::1\", \"2001:db8::/32\")},\n\t\t{\"large\", buildLargeIPSet(100)},\n\t\t{\"adjacent_ranges\", buildIPSet(\"192.168.0.0/24\", \"192.168.1.0/24\", \"192.168.2.0/24\")},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\t// New write\n\t\t\tvar newBuf bytes.Buffer\n\t\t\terr := writeIPSet(&newBuf, tc.input)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Verify format starts with version byte (1) + uint64 count\n\t\t\trequire.True(t, len(newBuf.Bytes()) >= 9, \"output too short\")\n\t\t\trequire.Equal(t, byte(1), newBuf.Bytes()[0], \"version byte mismatch\")\n\n\t\t\t// New write -> old read (varbin.Read with pre-allocated slice works correctly)\n\t\t\treadBack, err := oldReadIPSet(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequireIPSetEqual(t, tc.input, readBack)\n\n\t\t\t// New write -> new read\n\t\t\treadBack2, err := readIPSet(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))\n\t\t\trequire.NoError(t, err)\n\t\t\trequireIPSetEqual(t, tc.input, readBack2)\n\t\t})\n\t}\n}\n\n// Helper functions\n\nfunc generateStrings(count int) []string {\n\tresult := make([]string, count)\n\tfor i := range result {\n\t\tresult[i] = strings.Repeat(\"x\", i%50)\n\t}\n\treturn result\n}\n\nfunc generateUint8Slice(count int) []uint8 {\n\tresult := make([]uint8, count)\n\tfor i := range result {\n\t\tresult[i] = uint8(i % 256)\n\t}\n\treturn result\n}\n\nfunc generateUint16Slice(count int) []uint16 {\n\tresult := make([]uint16, count)\n\tfor i := range result {\n\t\tresult[i] = uint16(i * 257)\n\t}\n\treturn result\n}\n\nfunc buildIPSet(cidrs ...string) *netipx.IPSet {\n\tvar builder netipx.IPSetBuilder\n\tfor _, cidr := range cidrs {\n\t\tprefix, err := netip.ParsePrefix(cidr)\n\t\tif err != nil {\n\t\t\taddr, err := netip.ParseAddr(cidr)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tbuilder.Add(addr)\n\t\t} else {\n\t\t\tbuilder.AddPrefix(prefix)\n\t\t}\n\t}\n\tset, _ := builder.IPSet()\n\treturn set\n}\n\nfunc buildLargeIPSet(count int) *netipx.IPSet {\n\tvar builder netipx.IPSetBuilder\n\tfor i := 0; i < count; i++ {\n\t\tprefix := netip.PrefixFrom(netip.AddrFrom4([4]byte{10, byte(i / 256), byte(i % 256), 0}), 24)\n\t\tbuilder.AddPrefix(prefix)\n\t}\n\tset, _ := builder.IPSet()\n\treturn set\n}\n\nfunc requireStringSliceEqual(t *testing.T, expected, actual []string) {\n\tt.Helper()\n\tif len(expected) == 0 && len(actual) == 0 {\n\t\treturn\n\t}\n\trequire.Equal(t, expected, actual)\n}\n\nfunc requireUint8SliceEqual(t *testing.T, expected, actual []uint8) {\n\tt.Helper()\n\tif len(expected) == 0 && len(actual) == 0 {\n\t\treturn\n\t}\n\trequire.Equal(t, expected, actual)\n}\n\nfunc requireUint16SliceEqual(t *testing.T, expected, actual []uint16) {\n\tt.Helper()\n\tif len(expected) == 0 && len(actual) == 0 {\n\t\treturn\n\t}\n\trequire.Equal(t, expected, actual)\n}\n\nfunc requireIPSetEqual(t *testing.T, expected, actual *netipx.IPSet) {\n\tt.Helper()\n\texpectedRanges := expected.Ranges()\n\tactualRanges := actual.Ranges()\n\trequire.Equal(t, len(expectedRanges), len(actualRanges), \"range count mismatch\")\n\tfor i := range expectedRanges {\n\t\trequire.Equal(t, expectedRanges[i].From(), actualRanges[i].From(), \"range[%d].from mismatch\", i)\n\t\trequire.Equal(t, expectedRanges[i].To(), actualRanges[i].To(), \"range[%d].to mismatch\", i)\n\t}\n}\n"
  },
  {
    "path": "common/srs/ip_cidr.go",
    "content": "package srs\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\t\"net/netip\"\n\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/varbin\"\n)\n\nfunc readPrefix(reader varbin.Reader) (netip.Prefix, error) {\n\taddrLen, err := binary.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn netip.Prefix{}, err\n\t}\n\taddrSlice := make([]byte, addrLen)\n\t_, err = io.ReadFull(reader, addrSlice)\n\tif err != nil {\n\t\treturn netip.Prefix{}, err\n\t}\n\tprefixBits, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn netip.Prefix{}, err\n\t}\n\treturn netip.PrefixFrom(M.AddrFromIP(addrSlice), int(prefixBits)), nil\n}\n\nfunc writePrefix(writer varbin.Writer, prefix netip.Prefix) error {\n\taddrSlice := prefix.Addr().AsSlice()\n\t_, err := varbin.WriteUvarint(writer, uint64(len(addrSlice)))\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = writer.Write(addrSlice)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = writer.WriteByte(uint8(prefix.Bits()))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/srs/ip_set.go",
    "content": "package srs\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"unsafe\"\n\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/varbin\"\n\n\t\"go4.org/netipx\"\n)\n\ntype myIPSet struct {\n\trr []myIPRange\n}\n\ntype myIPRange struct {\n\tfrom netip.Addr\n\tto   netip.Addr\n}\n\nfunc readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {\n\tversion, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif version != 1 {\n\t\treturn nil, os.ErrInvalid\n\t}\n\t// WTF why using uint64 here\n\tvar length uint64\n\terr = binary.Read(reader, binary.BigEndian, &length)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmySet := &myIPSet{\n\t\trr: make([]myIPRange, length),\n\t}\n\tfor i := range mySet.rr {\n\t\tfromLen, err := binary.ReadUvarint(reader)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfromBytes := make([]byte, fromLen)\n\t\t_, err = io.ReadFull(reader, fromBytes)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttoLen, err := binary.ReadUvarint(reader)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttoBytes := make([]byte, toLen)\n\t\t_, err = io.ReadFull(reader, toBytes)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmySet.rr[i].from = M.AddrFromIP(fromBytes)\n\t\tmySet.rr[i].to = M.AddrFromIP(toBytes)\n\t}\n\treturn (*netipx.IPSet)(unsafe.Pointer(mySet)), nil\n}\n\nfunc writeIPSet(writer varbin.Writer, set *netipx.IPSet) error {\n\terr := writer.WriteByte(1)\n\tif err != nil {\n\t\treturn err\n\t}\n\tmySet := (*myIPSet)(unsafe.Pointer(set))\n\terr = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr)))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, rr := range mySet.rr {\n\t\tfromBytes := rr.from.AsSlice()\n\t\t_, err = varbin.WriteUvarint(writer, uint64(len(fromBytes)))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = writer.Write(fromBytes)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttoBytes := rr.to.AsSlice()\n\t\t_, err = varbin.WriteUvarint(writer, uint64(len(toBytes)))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = writer.Write(toBytes)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/taskmonitor/monitor.go",
    "content": "package taskmonitor\n\nimport (\n\t\"time\"\n\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\ntype Monitor struct {\n\tlogger  logger.Logger\n\ttimeout time.Duration\n\ttimer   *time.Timer\n}\n\nfunc New(logger logger.Logger, timeout time.Duration) *Monitor {\n\treturn &Monitor{\n\t\tlogger:  logger,\n\t\ttimeout: timeout,\n\t}\n}\n\nfunc (m *Monitor) Start(taskName ...any) {\n\tm.timer = time.AfterFunc(m.timeout, func() {\n\t\tm.logger.Warn(F.ToString(taskName...), \" take too much time to finish!\")\n\t})\n}\n\nfunc (m *Monitor) Finish() {\n\tm.timer.Stop()\n}\n"
  },
  {
    "path": "common/tls/acme.go",
    "content": "//go:build with_acme\n\npackage tls\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\n\t\"github.com/caddyserver/certmagic\"\n\t\"github.com/libdns/acmedns\"\n\t\"github.com/libdns/alidns\"\n\t\"github.com/libdns/cloudflare\"\n\t\"github.com/mholt/acmez/v3/acme\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\ntype acmeWrapper struct {\n\tctx    context.Context\n\tcfg    *certmagic.Config\n\tcache  *certmagic.Cache\n\tdomain []string\n}\n\nfunc (w *acmeWrapper) Start() error {\n\treturn w.cfg.ManageSync(w.ctx, w.domain)\n}\n\nfunc (w *acmeWrapper) Close() error {\n\tw.cache.Stop()\n\treturn nil\n}\n\ntype acmeLogWriter struct {\n\tlogger logger.Logger\n}\n\nfunc (w *acmeLogWriter) Write(p []byte) (n int, err error) {\n\tlogLine := strings.ReplaceAll(string(p), \"\t\", \": \")\n\tswitch {\n\tcase strings.HasPrefix(logLine, \"error: \"):\n\t\tw.logger.Error(logLine[7:])\n\tcase strings.HasPrefix(logLine, \"warn: \"):\n\t\tw.logger.Warn(logLine[6:])\n\tcase strings.HasPrefix(logLine, \"info: \"):\n\t\tw.logger.Info(logLine[6:])\n\tcase strings.HasPrefix(logLine, \"debug: \"):\n\t\tw.logger.Debug(logLine[7:])\n\tdefault:\n\t\tw.logger.Debug(logLine)\n\t}\n\treturn len(p), nil\n}\n\nfunc (w *acmeLogWriter) Sync() error {\n\treturn nil\n}\n\nfunc encoderConfig() zapcore.EncoderConfig {\n\tconfig := zap.NewProductionEncoderConfig()\n\tconfig.TimeKey = zapcore.OmitKey\n\treturn config\n}\n\nfunc startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {\n\tvar acmeServer string\n\tswitch options.Provider {\n\tcase \"\", \"letsencrypt\":\n\t\tacmeServer = certmagic.LetsEncryptProductionCA\n\tcase \"zerossl\":\n\t\tacmeServer = certmagic.ZeroSSLProductionCA\n\tdefault:\n\t\tif !strings.HasPrefix(options.Provider, \"https://\") {\n\t\t\treturn nil, nil, E.New(\"unsupported acme provider: \" + options.Provider)\n\t\t}\n\t\tacmeServer = options.Provider\n\t}\n\tvar storage certmagic.Storage\n\tif options.DataDirectory != \"\" {\n\t\tstorage = &certmagic.FileStorage{\n\t\t\tPath: options.DataDirectory,\n\t\t}\n\t} else {\n\t\tstorage = certmagic.Default.Storage\n\t}\n\tzapLogger := zap.New(zapcore.NewCore(\n\t\tzapcore.NewConsoleEncoder(encoderConfig()),\n\t\t&acmeLogWriter{logger: logger},\n\t\tzap.DebugLevel,\n\t))\n\tconfig := &certmagic.Config{\n\t\tDefaultServerName: options.DefaultServerName,\n\t\tStorage:           storage,\n\t\tLogger:            zapLogger,\n\t}\n\tacmeConfig := certmagic.ACMEIssuer{\n\t\tCA:                      acmeServer,\n\t\tEmail:                   options.Email,\n\t\tAgreed:                  true,\n\t\tDisableHTTPChallenge:    options.DisableHTTPChallenge,\n\t\tDisableTLSALPNChallenge: options.DisableTLSALPNChallenge,\n\t\tAltHTTPPort:             int(options.AlternativeHTTPPort),\n\t\tAltTLSALPNPort:          int(options.AlternativeTLSPort),\n\t\tLogger:                  zapLogger,\n\t}\n\tif dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != \"\" {\n\t\tvar solver certmagic.DNS01Solver\n\t\tswitch dnsOptions.Provider {\n\t\tcase C.DNSProviderAliDNS:\n\t\t\tsolver.DNSProvider = &alidns.Provider{\n\t\t\t\tCredentialInfo: alidns.CredentialInfo{\n\t\t\t\t\tAccessKeyID:     dnsOptions.AliDNSOptions.AccessKeyID,\n\t\t\t\t\tAccessKeySecret: dnsOptions.AliDNSOptions.AccessKeySecret,\n\t\t\t\t\tRegionID:        dnsOptions.AliDNSOptions.RegionID,\n\t\t\t\t\tSecurityToken:   dnsOptions.AliDNSOptions.SecurityToken,\n\t\t\t\t},\n\t\t\t}\n\t\tcase C.DNSProviderCloudflare:\n\t\t\tsolver.DNSProvider = &cloudflare.Provider{\n\t\t\t\tAPIToken:  dnsOptions.CloudflareOptions.APIToken,\n\t\t\t\tZoneToken: dnsOptions.CloudflareOptions.ZoneToken,\n\t\t\t}\n\t\tcase C.DNSProviderACMEDNS:\n\t\t\tsolver.DNSProvider = &acmedns.Provider{\n\t\t\t\tUsername:  dnsOptions.ACMEDNSOptions.Username,\n\t\t\t\tPassword:  dnsOptions.ACMEDNSOptions.Password,\n\t\t\t\tSubdomain: dnsOptions.ACMEDNSOptions.Subdomain,\n\t\t\t\tServerURL: dnsOptions.ACMEDNSOptions.ServerURL,\n\t\t\t}\n\t\tdefault:\n\t\t\treturn nil, nil, E.New(\"unsupported ACME DNS01 provider type: \" + dnsOptions.Provider)\n\t\t}\n\t\tacmeConfig.DNS01Solver = &solver\n\t}\n\tif options.ExternalAccount != nil && options.ExternalAccount.KeyID != \"\" {\n\t\tacmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)\n\t}\n\tconfig.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)}\n\tcache := certmagic.NewCache(certmagic.CacheOptions{\n\t\tGetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {\n\t\t\treturn config, nil\n\t\t},\n\t\tLogger: zapLogger,\n\t})\n\tconfig = certmagic.New(cache, *config)\n\tvar tlsConfig *tls.Config\n\tif acmeConfig.DisableTLSALPNChallenge || acmeConfig.DNS01Solver != nil {\n\t\ttlsConfig = &tls.Config{\n\t\t\tGetCertificate: config.GetCertificate,\n\t\t}\n\t} else {\n\t\ttlsConfig = &tls.Config{\n\t\t\tGetCertificate: config.GetCertificate,\n\t\t\tNextProtos:     []string{ACMETLS1Protocol},\n\t\t}\n\t}\n\treturn tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil\n}\n"
  },
  {
    "path": "common/tls/acme_contstant.go",
    "content": "package tls\n\nconst ACMETLS1Protocol = \"acme-tls/1\"\n"
  },
  {
    "path": "common/tls/acme_stub.go",
    "content": "//go:build !with_acme\n\npackage tls\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\nfunc startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {\n\treturn nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)\n}\n"
  },
  {
    "path": "common/tls/client.go",
    "content": "package tls\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/common/badtls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n)\n\nfunc NewDialerFromOptions(ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {\n\tif !options.Enabled {\n\t\treturn dialer, nil\n\t}\n\tconfig, err := NewClientWithOptions(ClientOptions{\n\t\tContext:       ctx,\n\t\tLogger:        logger,\n\t\tServerAddress: serverAddress,\n\t\tOptions:       options,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewDialer(dialer, config), nil\n}\n\nfunc NewClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) {\n\treturn NewClientWithOptions(ClientOptions{\n\t\tContext:       ctx,\n\t\tLogger:        logger,\n\t\tServerAddress: serverAddress,\n\t\tOptions:       options,\n\t})\n}\n\ntype ClientOptions struct {\n\tContext        context.Context\n\tLogger         logger.ContextLogger\n\tServerAddress  string\n\tOptions        option.OutboundTLSOptions\n\tKTLSCompatible bool\n}\n\nfunc NewClientWithOptions(options ClientOptions) (Config, error) {\n\tif !options.Options.Enabled {\n\t\treturn nil, nil\n\t}\n\tif !options.KTLSCompatible {\n\t\tif options.Options.KernelTx {\n\t\t\toptions.Logger.Warn(\"enabling kTLS TX in current scenarios will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_tx\")\n\t\t}\n\t}\n\tif options.Options.KernelRx {\n\t\toptions.Logger.Warn(\"enabling kTLS RX will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_rx\")\n\t}\n\tif options.Options.Reality != nil && options.Options.Reality.Enabled {\n\t\treturn NewRealityClient(options.Context, options.Logger, options.ServerAddress, options.Options)\n\t} else if options.Options.UTLS != nil && options.Options.UTLS.Enabled {\n\t\treturn NewUTLSClient(options.Context, options.Logger, options.ServerAddress, options.Options)\n\t}\n\treturn NewSTDClient(options.Context, options.Logger, options.ServerAddress, options.Options)\n}\n\nfunc ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {\n\tctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)\n\tdefer cancel()\n\ttlsConn, err := aTLS.ClientHandshake(ctx, conn, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treadWaitConn, err := badtls.NewReadWaitConn(tlsConn)\n\tif err == nil {\n\t\treturn readWaitConn, nil\n\t} else if err != os.ErrInvalid {\n\t\treturn nil, err\n\t}\n\treturn tlsConn, nil\n}\n\ntype Dialer interface {\n\tN.Dialer\n\tDialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error)\n}\n\ntype defaultDialer struct {\n\tdialer N.Dialer\n\tconfig Config\n}\n\nfunc NewDialer(dialer N.Dialer, config Config) Dialer {\n\treturn &defaultDialer{dialer, config}\n}\n\nfunc (d *defaultDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tif N.NetworkName(network) != N.NetworkTCP {\n\t\treturn nil, os.ErrInvalid\n\t}\n\treturn d.DialTLSContext(ctx, destination)\n}\n\nfunc (d *defaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn nil, os.ErrInvalid\n}\n\nfunc (d *defaultDialer) DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error) {\n\treturn d.dialContext(ctx, destination, true)\n}\n\nfunc (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr, echRetry bool) (Conn, error) {\n\tconn, err := d.dialer.DialContext(ctx, N.NetworkTCP, destination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttlsConn, err := aTLS.ClientHandshake(ctx, conn, d.config)\n\tif err != nil {\n\t\tconn.Close()\n\t\tvar echErr *tls.ECHRejectionError\n\t\tif echRetry && errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 {\n\t\t\tif echConfig, isECH := d.config.(ECHCapableConfig); isECH {\n\t\t\t\techConfig.SetECHConfigList(echErr.RetryConfigList)\n\t\t\t\treturn d.dialContext(ctx, destination, false)\n\t\t\t}\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn tlsConn, nil\n}\n\nfunc (d *defaultDialer) Upstream() any {\n\treturn d.dialer\n}\n"
  },
  {
    "path": "common/tls/common.go",
    "content": "package tls\n\nconst (\n\tVersionTLS10 = 0x0301\n\tVersionTLS11 = 0x0302\n\tVersionTLS12 = 0x0303\n\tVersionTLS13 = 0x0304\n\n\t// Deprecated: SSLv3 is cryptographically broken, and is no longer\n\t// supported by this package. See golang.org/issue/32716.\n\tVersionSSL30 = 0x0300\n)\n"
  },
  {
    "path": "common/tls/config.go",
    "content": "package tls\n\nimport (\n\t\"crypto/tls\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n)\n\ntype (\n\tConfig                 = aTLS.Config\n\tConfigCompat           = aTLS.ConfigCompat\n\tServerConfig           = aTLS.ServerConfig\n\tServerConfigCompat     = aTLS.ServerConfigCompat\n\tWithSessionIDGenerator = aTLS.WithSessionIDGenerator\n\tConn                   = aTLS.Conn\n\n\tSTDConfig       = tls.Config\n\tSTDConn         = tls.Conn\n\tConnectionState = tls.ConnectionState\n\tCurveID         = tls.CurveID\n)\n\nfunc ParseTLSVersion(version string) (uint16, error) {\n\tswitch version {\n\tcase \"1.0\":\n\t\treturn tls.VersionTLS10, nil\n\tcase \"1.1\":\n\t\treturn tls.VersionTLS11, nil\n\tcase \"1.2\":\n\t\treturn tls.VersionTLS12, nil\n\tcase \"1.3\":\n\t\treturn tls.VersionTLS13, nil\n\tdefault:\n\t\treturn 0, E.New(\"unknown tls version:\", version)\n\t}\n}\n"
  },
  {
    "path": "common/tls/ech.go",
    "content": "//go:build go1.24\n\npackage tls\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"encoding/pem\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\t\"github.com/sagernet/sing/service\"\n\n\tmDNS \"github.com/miekg/dns\"\n\t\"golang.org/x/crypto/cryptobyte\"\n)\n\nfunc parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {\n\tvar echConfig []byte\n\tif len(options.ECH.Config) > 0 {\n\t\techConfig = []byte(strings.Join(options.ECH.Config, \"\\n\"))\n\t} else if options.ECH.ConfigPath != \"\" {\n\t\tcontent, err := os.ReadFile(options.ECH.ConfigPath)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"read ECH config\")\n\t\t}\n\t\techConfig = content\n\t}\n\t//nolint:staticcheck\n\tif options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {\n\t\treturn nil, E.New(\"legacy ECH options are deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0\")\n\t}\n\tif len(echConfig) > 0 {\n\t\tblock, rest := pem.Decode(echConfig)\n\t\tif block == nil || block.Type != \"ECH CONFIGS\" || len(rest) > 0 {\n\t\t\treturn nil, E.New(\"invalid ECH configs pem\")\n\t\t}\n\t\tclientConfig.SetECHConfigList(block.Bytes)\n\t\treturn clientConfig, nil\n\t} else {\n\t\treturn &ECHClientConfig{\n\t\t\tECHCapableConfig: clientConfig,\n\t\t\tdnsRouter:        service.FromContext[adapter.DNSRouter](ctx),\n\t\t\tqueryServerName:  options.ECH.QueryServerName,\n\t\t}, nil\n\t}\n}\n\nfunc parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, tlsConfig *tls.Config, echKeyPath *string) error {\n\tvar echKey []byte\n\tif len(options.ECH.Key) > 0 {\n\t\techKey = []byte(strings.Join(options.ECH.Key, \"\\n\"))\n\t} else if options.ECH.KeyPath != \"\" {\n\t\tcontent, err := os.ReadFile(options.ECH.KeyPath)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"read ECH keys\")\n\t\t}\n\t\techKey = content\n\t\t*echKeyPath = options.ECH.KeyPath\n\t} else {\n\t\treturn E.New(\"missing ECH keys\")\n\t}\n\techKeys, err := parseECHKeys(echKey)\n\tif err != nil {\n\t\treturn E.Cause(err, \"parse ECH keys\")\n\t}\n\ttlsConfig.EncryptedClientHelloKeys = echKeys\n\t//nolint:staticcheck\n\tif options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {\n\t\treturn E.New(\"legacy ECH options are deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0\")\n\t}\n\treturn nil\n}\n\nfunc (c *STDServerConfig) setECHServerConfig(echKey []byte) error {\n\techKeys, err := parseECHKeys(echKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.access.Lock()\n\tconfig := c.config.Clone()\n\tconfig.EncryptedClientHelloKeys = echKeys\n\tc.config = config\n\tc.access.Unlock()\n\treturn nil\n}\n\nfunc parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) {\n\tblock, _ := pem.Decode(echKey)\n\tif block == nil || block.Type != \"ECH KEYS\" {\n\t\treturn nil, E.New(\"invalid ECH keys pem\")\n\t}\n\techKeys, err := UnmarshalECHKeys(block.Bytes)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"parse ECH keys\")\n\t}\n\treturn echKeys, nil\n}\n\ntype ECHClientConfig struct {\n\tECHCapableConfig\n\taccess          sync.Mutex\n\tdnsRouter       adapter.DNSRouter\n\tqueryServerName string\n\tlastTTL         time.Duration\n\tlastUpdate      time.Time\n}\n\nfunc (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {\n\ttlsConn, err := s.fetchAndHandshake(ctx, conn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = tlsConn.HandshakeContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn tlsConn, nil\n}\n\nfunc (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {\n\ts.access.Lock()\n\tdefer s.access.Unlock()\n\tif len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Since(s.lastUpdate) > s.lastTTL {\n\t\tqueryServerName := s.queryServerName\n\t\tif queryServerName == \"\" {\n\t\t\tqueryServerName = s.ServerName()\n\t\t}\n\t\tmessage := &mDNS.Msg{\n\t\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\t\tRecursionDesired: true,\n\t\t\t},\n\t\t\tQuestion: []mDNS.Question{\n\t\t\t\t{\n\t\t\t\t\tName:   mDNS.Fqdn(queryServerName),\n\t\t\t\t\tQtype:  mDNS.TypeHTTPS,\n\t\t\t\t\tQclass: mDNS.ClassINET,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tresponse, err := s.dnsRouter.Exchange(ctx, message, adapter.DNSQueryOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"fetch ECH config list\")\n\t\t}\n\t\tif response.Rcode != mDNS.RcodeSuccess {\n\t\t\treturn nil, E.Cause(dns.RcodeError(response.Rcode), \"fetch ECH config list\")\n\t\t}\n\tmatch:\n\t\tfor _, rr := range response.Answer {\n\t\t\tswitch resource := rr.(type) {\n\t\t\tcase *mDNS.HTTPS:\n\t\t\t\tfor _, value := range resource.Value {\n\t\t\t\t\tif value.Key().String() == \"ech\" {\n\t\t\t\t\t\techConfigList, err := base64.StdEncoding.DecodeString(value.String())\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn nil, E.Cause(err, \"decode ECH config\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\ts.lastTTL = time.Duration(rr.Header().Ttl) * time.Second\n\t\t\t\t\t\ts.lastUpdate = time.Now()\n\t\t\t\t\t\ts.SetECHConfigList(echConfigList)\n\t\t\t\t\t\tbreak match\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif len(s.ECHConfigList()) == 0 {\n\t\t\treturn nil, E.New(\"no ECH config found in DNS records\")\n\t\t}\n\t}\n\treturn s.Client(conn)\n}\n\nfunc (s *ECHClientConfig) Clone() Config {\n\treturn &ECHClientConfig{\n\t\tECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig),\n\t\tdnsRouter:        s.dnsRouter,\n\t\tqueryServerName:  s.queryServerName,\n\t\tlastUpdate:       s.lastUpdate,\n\t}\n}\n\nfunc UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {\n\tvar keys []tls.EncryptedClientHelloKey\n\trawString := cryptobyte.String(raw)\n\tfor !rawString.Empty() {\n\t\tvar key tls.EncryptedClientHelloKey\n\t\tif !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) {\n\t\t\treturn nil, E.New(\"error parsing private key\")\n\t\t}\n\t\tif !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.Config)) {\n\t\t\treturn nil, E.New(\"error parsing config\")\n\t\t}\n\t\tkeys = append(keys, key)\n\t}\n\tif len(keys) == 0 {\n\t\treturn nil, E.New(\"empty ECH keys\")\n\t}\n\treturn keys, nil\n}\n"
  },
  {
    "path": "common/tls/ech_shared.go",
    "content": "package tls\n\nimport (\n\t\"crypto/ecdh\"\n\t\"crypto/rand\"\n\t\"encoding/pem\"\n\n\t\"golang.org/x/crypto/cryptobyte\"\n)\n\ntype ECHCapableConfig interface {\n\tConfig\n\tECHConfigList() []byte\n\tSetECHConfigList([]byte)\n}\n\nfunc ECHKeygenDefault(publicName string) (configPem string, keyPem string, err error) {\n\techKey, err := ecdh.X25519().GenerateKey(rand.Reader)\n\tif err != nil {\n\t\treturn\n\t}\n\techConfig, err := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)\n\tif err != nil {\n\t\treturn\n\t}\n\tconfigBuilder := cryptobyte.NewBuilder(nil)\n\tconfigBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {\n\t\tbuilder.AddBytes(echConfig)\n\t})\n\tconfigBytes, err := configBuilder.Bytes()\n\tif err != nil {\n\t\treturn\n\t}\n\tkeyBuilder := cryptobyte.NewBuilder(nil)\n\tkeyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {\n\t\tbuilder.AddBytes(echKey.Bytes())\n\t})\n\tkeyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {\n\t\tbuilder.AddBytes(echConfig)\n\t})\n\tkeyBytes, err := keyBuilder.Bytes()\n\tif err != nil {\n\t\treturn\n\t}\n\tconfigPem = string(pem.EncodeToMemory(&pem.Block{Type: \"ECH CONFIGS\", Bytes: configBytes}))\n\tkeyPem = string(pem.EncodeToMemory(&pem.Block{Type: \"ECH KEYS\", Bytes: keyBytes}))\n\treturn\n}\n\nfunc marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) ([]byte, error) {\n\tconst extensionEncryptedClientHello = 0xfe0d\n\tconst DHKEM_X25519_HKDF_SHA256 = 0x0020\n\tconst KDF_HKDF_SHA256 = 0x0001\n\tbuilder := cryptobyte.NewBuilder(nil)\n\tbuilder.AddUint16(extensionEncryptedClientHello)\n\tbuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {\n\t\tbuilder.AddUint8(id)\n\n\t\tbuilder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support\n\t\tbuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {\n\t\t\tbuilder.AddBytes(pubKey)\n\t\t})\n\t\tbuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {\n\t\t\tconst (\n\t\t\t\tAEAD_AES_128_GCM      = 0x0001\n\t\t\t\tAEAD_AES_256_GCM      = 0x0002\n\t\t\t\tAEAD_ChaCha20Poly1305 = 0x0003\n\t\t\t)\n\t\t\tfor _, aeadID := range []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305} {\n\t\t\t\tbuilder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support\n\t\t\t\tbuilder.AddUint16(aeadID)\n\t\t\t}\n\t\t})\n\t\tbuilder.AddUint8(maxNameLen)\n\t\tbuilder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {\n\t\t\tbuilder.AddBytes([]byte(publicName))\n\t\t})\n\t\tbuilder.AddUint16(0) // extensions\n\t})\n\treturn builder.Bytes()\n}\n"
  },
  {
    "path": "common/tls/ech_tag_stub.go",
    "content": "//go:build with_ech\n\npackage tls\n\nvar _ int = \"Due to the migration to stdlib, the separate `with_ech` build tag has been deprecated and is no longer needed, please update your build configuration.\"\n"
  },
  {
    "path": "common/tls/ktls.go",
    "content": "package tls\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/common/ktls\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n)\n\ntype KTLSClientConfig struct {\n\tConfig\n\tlogger             logger.ContextLogger\n\tkernelTx, kernelRx bool\n}\n\nfunc (w *KTLSClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {\n\ttlsConn, err := aTLS.ClientHandshake(ctx, conn, w.Config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkConn, err := ktls.NewConn(ctx, w.logger, tlsConn, w.kernelTx, w.kernelRx)\n\tif err != nil {\n\t\ttlsConn.Close()\n\t\treturn nil, E.Cause(err, \"initialize kernel TLS\")\n\t}\n\treturn kConn, nil\n}\n\nfunc (w *KTLSClientConfig) Clone() Config {\n\treturn &KTLSClientConfig{\n\t\tw.Config.Clone(),\n\t\tw.logger,\n\t\tw.kernelTx,\n\t\tw.kernelRx,\n\t}\n}\n\ntype KTlSServerConfig struct {\n\tServerConfig\n\tlogger             logger.ContextLogger\n\tkernelTx, kernelRx bool\n}\n\nfunc (w *KTlSServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {\n\ttlsConn, err := aTLS.ServerHandshake(ctx, conn, w.ServerConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkConn, err := ktls.NewConn(ctx, w.logger, tlsConn, w.kernelTx, w.kernelRx)\n\tif err != nil {\n\t\ttlsConn.Close()\n\t\treturn nil, E.Cause(err, \"initialize kernel TLS\")\n\t}\n\treturn kConn, nil\n}\n\nfunc (w *KTlSServerConfig) Clone() Config {\n\treturn &KTlSServerConfig{\n\t\tw.ServerConfig.Clone().(ServerConfig),\n\t\tw.logger,\n\t\tw.kernelTx,\n\t\tw.kernelRx,\n\t}\n}\n"
  },
  {
    "path": "common/tls/mkcert.go",
    "content": "package tls\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"math/big\"\n\t\"time\"\n)\n\nfunc GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {\n\tif timeFunc == nil {\n\t\ttimeFunc = time.Now\n\t}\n\tprivateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcertificate, err := tls.X509KeyPair(publicKeyPem, privateKeyPem)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &certificate, err\n}\n\nfunc GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {\n\tkey, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\treturn\n\t}\n\tserialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))\n\tif err != nil {\n\t\treturn\n\t}\n\ttemplate := &x509.Certificate{\n\t\tSerialNumber:          serialNumber,\n\t\tNotBefore:             timeFunc().Add(time.Hour * -1),\n\t\tNotAfter:              expire,\n\t\tKeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tBasicConstraintsValid: true,\n\t\tSubject: pkix.Name{\n\t\t\tCommonName: serverName,\n\t\t},\n\t\tDNSNames: []string{serverName},\n\t}\n\tif parent == nil {\n\t\tparent = template\n\t\tparentKey = key\n\t}\n\tpublicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)\n\tif err != nil {\n\t\treturn\n\t}\n\tprivateDer, err := x509.MarshalPKCS8PrivateKey(key)\n\tif err != nil {\n\t\treturn\n\t}\n\tpublicKeyPem = pem.EncodeToMemory(&pem.Block{Type: \"CERTIFICATE\", Bytes: publicDer})\n\tprivateKeyPem = pem.EncodeToMemory(&pem.Block{Type: \"PRIVATE KEY\", Bytes: privateDer})\n\treturn\n}\n"
  },
  {
    "path": "common/tls/reality_client.go",
    "content": "//go:build with_utls\n\npackage tls\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"crypto/ecdh\"\n\t\"crypto/ed25519\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"crypto/sha512\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\tmRand \"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/debug\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\n\tutls \"github.com/metacubex/utls\"\n\t\"golang.org/x/crypto/hkdf\"\n\t\"golang.org/x/net/http2\"\n)\n\nvar _ ConfigCompat = (*RealityClientConfig)(nil)\n\ntype RealityClientConfig struct {\n\tctx       context.Context\n\tuClient   *UTLSClientConfig\n\tpublicKey []byte\n\tshortID   [8]byte\n}\n\nfunc NewRealityClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) {\n\tif options.UTLS == nil || !options.UTLS.Enabled {\n\t\treturn nil, E.New(\"uTLS is required by reality client\")\n\t}\n\n\tuClient, err := NewUTLSClient(ctx, logger, serverAddress, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpublicKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PublicKey)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"decode public_key\")\n\t}\n\tif len(publicKey) != 32 {\n\t\treturn nil, E.New(\"invalid public_key\")\n\t}\n\tvar shortID [8]byte\n\tdecodedLen, err := hex.Decode(shortID[:], []byte(options.Reality.ShortID))\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"decode short_id\")\n\t}\n\tif decodedLen > 8 {\n\t\treturn nil, E.New(\"invalid short_id\")\n\t}\n\n\tvar config Config = &RealityClientConfig{ctx, uClient.(*UTLSClientConfig), publicKey, shortID}\n\tif options.KernelRx || options.KernelTx {\n\t\tif !C.IsLinux {\n\t\t\treturn nil, E.New(\"kTLS is only supported on Linux\")\n\t\t}\n\t\tconfig = &KTLSClientConfig{\n\t\t\tConfig:   config,\n\t\t\tlogger:   logger,\n\t\t\tkernelTx: options.KernelTx,\n\t\t\tkernelRx: options.KernelRx,\n\t\t}\n\t}\n\treturn config, nil\n}\n\nfunc (e *RealityClientConfig) ServerName() string {\n\treturn e.uClient.ServerName()\n}\n\nfunc (e *RealityClientConfig) SetServerName(serverName string) {\n\te.uClient.SetServerName(serverName)\n}\n\nfunc (e *RealityClientConfig) NextProtos() []string {\n\treturn e.uClient.NextProtos()\n}\n\nfunc (e *RealityClientConfig) SetNextProtos(nextProto []string) {\n\te.uClient.SetNextProtos(nextProto)\n}\n\nfunc (e *RealityClientConfig) STDConfig() (*STDConfig, error) {\n\treturn nil, E.New(\"unsupported usage for reality\")\n}\n\nfunc (e *RealityClientConfig) Client(conn net.Conn) (Conn, error) {\n\treturn ClientHandshake(context.Background(), conn, e)\n}\n\nfunc (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {\n\tverifier := &realityVerifier{\n\t\tserverName: e.uClient.ServerName(),\n\t}\n\tuConfig := e.uClient.config.Clone()\n\tuConfig.InsecureSkipVerify = true\n\tuConfig.SessionTicketsDisabled = true\n\tuConfig.VerifyPeerCertificate = verifier.VerifyPeerCertificate\n\tuConn := utls.UClient(conn, uConfig, e.uClient.id)\n\tverifier.UConn = uConn\n\terr := uConn.BuildHandshakeState()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, extension := range uConn.Extensions {\n\t\tif ce, ok := extension.(*utls.SupportedCurvesExtension); ok {\n\t\t\tce.Curves = common.Filter(ce.Curves, func(curveID utls.CurveID) bool {\n\t\t\t\treturn curveID != utls.X25519MLKEM768\n\t\t\t})\n\t\t}\n\t\tif ks, ok := extension.(*utls.KeyShareExtension); ok {\n\t\t\tks.KeyShares = common.Filter(ks.KeyShares, func(share utls.KeyShare) bool {\n\t\t\t\treturn share.Group != utls.X25519MLKEM768\n\t\t\t})\n\t\t}\n\t}\n\terr = uConn.BuildHandshakeState()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(uConfig.NextProtos) > 0 {\n\t\tfor _, extension := range uConn.Extensions {\n\t\t\tif alpnExtension, isALPN := extension.(*utls.ALPNExtension); isALPN {\n\t\t\t\talpnExtension.AlpnProtocols = uConfig.NextProtos\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\thello := uConn.HandshakeState.Hello\n\thello.SessionId = make([]byte, 32)\n\tcopy(hello.Raw[39:], hello.SessionId)\n\n\tvar nowTime time.Time\n\tif uConfig.Time != nil {\n\t\tnowTime = uConfig.Time()\n\t} else {\n\t\tnowTime = time.Now()\n\t}\n\tbinary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))\n\n\thello.SessionId[0] = 1\n\thello.SessionId[1] = 8\n\thello.SessionId[2] = 1\n\tbinary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))\n\tcopy(hello.SessionId[8:], e.shortID[:])\n\tif debug.Enabled {\n\t\tfmt.Printf(\"REALITY hello.sessionId[:16]: %v\\n\", hello.SessionId[:16])\n\t}\n\tpublicKey, err := ecdh.X25519().NewPublicKey(e.publicKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkeyShareKeys := uConn.HandshakeState.State13.KeyShareKeys\n\tif keyShareKeys == nil {\n\t\treturn nil, E.New(\"nil KeyShareKeys\")\n\t}\n\tecdheKey := keyShareKeys.Ecdhe\n\tif ecdheKey == nil {\n\t\treturn nil, E.New(\"nil ecdheKey\")\n\t}\n\tauthKey, err := ecdheKey.ECDH(publicKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif authKey == nil {\n\t\treturn nil, E.New(\"nil auth_key\")\n\t}\n\tverifier.authKey = authKey\n\t_, err = hkdf.New(sha256.New, authKey, hello.Random[:20], []byte(\"REALITY\")).Read(authKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\taesBlock, _ := aes.NewCipher(authKey)\n\taesGcmCipher, _ := cipher.NewGCM(aesBlock)\n\taesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)\n\tcopy(hello.Raw[39:], hello.SessionId)\n\tif debug.Enabled {\n\t\tfmt.Printf(\"REALITY hello.sessionId: %v\\n\", hello.SessionId)\n\t\tfmt.Printf(\"REALITY uConn.AuthKey: %v\\n\", authKey)\n\t}\n\n\terr = uConn.HandshakeContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif debug.Enabled {\n\t\tfmt.Printf(\"REALITY Conn.Verified: %v\\n\", verifier.verified)\n\t}\n\n\tif !verifier.verified {\n\t\tgo realityClientFallback(e.ctx, uConn, e.uClient.ServerName(), e.uClient.id)\n\t\treturn nil, E.New(\"reality verification failed\")\n\t}\n\n\treturn &realityClientConnWrapper{uConn}, nil\n}\n\nfunc realityClientFallback(ctx context.Context, uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {\n\tdefer uConn.Close()\n\tclient := &http.Client{\n\t\tTransport: &http2.Transport{\n\t\t\tDialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {\n\t\t\t\treturn uConn, nil\n\t\t\t},\n\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\tTime:    ntp.TimeFuncFromContext(ctx),\n\t\t\t\tRootCAs: adapter.RootPoolFromContext(ctx),\n\t\t\t},\n\t\t},\n\t}\n\trequest, _ := http.NewRequest(\"GET\", \"https://\"+serverName, nil)\n\trequest.Header.Set(\"User-Agent\", fingerprint.Client)\n\trequest.AddCookie(&http.Cookie{Name: \"padding\", Value: strings.Repeat(\"0\", mRand.Intn(32)+30)})\n\tresponse, err := client.Do(request)\n\tif err != nil {\n\t\treturn\n\t}\n\t_, _ = io.Copy(io.Discard, response.Body)\n\tresponse.Body.Close()\n}\n\nfunc (e *RealityClientConfig) Clone() Config {\n\treturn &RealityClientConfig{\n\t\te.ctx,\n\t\te.uClient.Clone().(*UTLSClientConfig),\n\t\te.publicKey,\n\t\te.shortID,\n\t}\n}\n\ntype realityVerifier struct {\n\t*utls.UConn\n\tserverName string\n\tauthKey    []byte\n\tverified   bool\n}\n\nfunc (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {\n\tp, _ := reflect.TypeOf(c.Conn).Elem().FieldByName(\"peerCertificates\")\n\tcerts := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))\n\tif pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {\n\t\th := hmac.New(sha512.New, c.authKey)\n\t\th.Write(pub)\n\t\tif bytes.Equal(h.Sum(nil), certs[0].Signature) {\n\t\t\tc.verified = true\n\t\t\treturn nil\n\t\t}\n\t}\n\topts := x509.VerifyOptions{\n\t\tDNSName:       c.serverName,\n\t\tIntermediates: x509.NewCertPool(),\n\t}\n\tfor _, cert := range certs[1:] {\n\t\topts.Intermediates.AddCert(cert)\n\t}\n\tif _, err := certs[0].Verify(opts); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype realityClientConnWrapper struct {\n\t*utls.UConn\n}\n\nfunc (c *realityClientConnWrapper) ConnectionState() tls.ConnectionState {\n\tstate := c.Conn.ConnectionState()\n\t//nolint:staticcheck\n\treturn tls.ConnectionState{\n\t\tVersion:                     state.Version,\n\t\tHandshakeComplete:           state.HandshakeComplete,\n\t\tDidResume:                   state.DidResume,\n\t\tCipherSuite:                 state.CipherSuite,\n\t\tNegotiatedProtocol:          state.NegotiatedProtocol,\n\t\tNegotiatedProtocolIsMutual:  state.NegotiatedProtocolIsMutual,\n\t\tServerName:                  state.ServerName,\n\t\tPeerCertificates:            state.PeerCertificates,\n\t\tVerifiedChains:              state.VerifiedChains,\n\t\tSignedCertificateTimestamps: state.SignedCertificateTimestamps,\n\t\tOCSPResponse:                state.OCSPResponse,\n\t\tTLSUnique:                   state.TLSUnique,\n\t}\n}\n\nfunc (c *realityClientConnWrapper) Upstream() any {\n\treturn c.UConn\n}\n\n// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.\n// We fixed it by calling Close() directly.\nfunc (c *realityClientConnWrapper) CloseWrite() error {\n\treturn c.Close()\n}\n\nfunc (c *realityClientConnWrapper) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (c *realityClientConnWrapper) WriterReplaceable() bool {\n\treturn true\n}\n"
  },
  {
    "path": "common/tls/reality_server.go",
    "content": "//go:build with_utls\n\npackage tls\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\n\tutls \"github.com/metacubex/utls\"\n)\n\nvar _ ServerConfigCompat = (*RealityServerConfig)(nil)\n\ntype RealityServerConfig struct {\n\tconfig *utls.RealityConfig\n}\n\nfunc NewRealityServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {\n\tvar tlsConfig utls.RealityConfig\n\n\tif options.ACME != nil && len(options.ACME.Domain) > 0 {\n\t\treturn nil, E.New(\"acme is unavailable in reality\")\n\t}\n\ttlsConfig.Time = ntp.TimeFuncFromContext(ctx)\n\tif options.ServerName != \"\" {\n\t\ttlsConfig.ServerName = options.ServerName\n\t}\n\tif len(options.ALPN) > 0 {\n\t\ttlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)\n\t}\n\tif options.MinVersion != \"\" {\n\t\tminVersion, err := ParseTLSVersion(options.MinVersion)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse min_version\")\n\t\t}\n\t\ttlsConfig.MinVersion = minVersion\n\t}\n\tif options.MaxVersion != \"\" {\n\t\tmaxVersion, err := ParseTLSVersion(options.MaxVersion)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse max_version\")\n\t\t}\n\t\ttlsConfig.MaxVersion = maxVersion\n\t}\n\tif options.CipherSuites != nil {\n\tfind:\n\t\tfor _, cipherSuite := range options.CipherSuites {\n\t\t\tfor _, tlsCipherSuite := range tls.CipherSuites() {\n\t\t\t\tif cipherSuite == tlsCipherSuite.Name {\n\t\t\t\t\ttlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)\n\t\t\t\t\tcontinue find\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil, E.New(\"unknown cipher_suite: \", cipherSuite)\n\t\t}\n\t}\n\tif len(options.CurvePreferences) > 0 {\n\t\treturn nil, E.New(\"curve preferences is unavailable in reality\")\n\t}\n\tif len(options.Certificate) > 0 || options.CertificatePath != \"\" || len(options.ClientCertificatePublicKeySHA256) > 0 {\n\t\treturn nil, E.New(\"certificate is unavailable in reality\")\n\t}\n\tif len(options.Key) > 0 || options.KeyPath != \"\" {\n\t\treturn nil, E.New(\"key is unavailable in reality\")\n\t}\n\n\ttlsConfig.SessionTicketsDisabled = true\n\ttlsConfig.Log = func(format string, v ...any) {\n\t\tif logger != nil {\n\t\t\tlogger.Trace(fmt.Sprintf(format, v...))\n\t\t}\n\t}\n\ttlsConfig.Type = N.NetworkTCP\n\ttlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()\n\n\ttlsConfig.ServerNames = map[string]bool{options.ServerName: true}\n\tprivateKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PrivateKey)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"decode private key\")\n\t}\n\tif len(privateKey) != 32 {\n\t\treturn nil, E.New(\"invalid private key\")\n\t}\n\ttlsConfig.PrivateKey = privateKey\n\ttlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)\n\n\ttlsConfig.ShortIds = make(map[[8]byte]bool)\n\tif len(options.Reality.ShortID) == 0 {\n\t\ttlsConfig.ShortIds[[8]byte{0}] = true\n\t} else {\n\t\tfor i, shortIDString := range options.Reality.ShortID {\n\t\t\tvar shortID [8]byte\n\t\t\tdecodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, \"decode short_id[\", i, \"]: \", shortIDString)\n\t\t\t}\n\t\t\tif decodedLen > 8 {\n\t\t\t\treturn nil, E.New(\"invalid short_id[\", i, \"]: \", shortIDString)\n\t\t\t}\n\t\t\ttlsConfig.ShortIds[shortID] = true\n\t\t}\n\t}\n\n\thandshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttlsConfig.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\treturn handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t}\n\n\tif options.ECH != nil && options.ECH.Enabled {\n\t\treturn nil, E.New(\"Reality is conflict with ECH\")\n\t}\n\tvar config ServerConfig = &RealityServerConfig{&tlsConfig}\n\tif options.KernelTx || options.KernelRx {\n\t\tif !C.IsLinux {\n\t\t\treturn nil, E.New(\"kTLS is only supported on Linux\")\n\t\t}\n\t\tconfig = &KTlSServerConfig{\n\t\t\tServerConfig: config,\n\t\t\tlogger:       logger,\n\t\t\tkernelTx:     options.KernelTx,\n\t\t\tkernelRx:     options.KernelRx,\n\t\t}\n\t}\n\treturn config, nil\n}\n\nfunc (c *RealityServerConfig) ServerName() string {\n\treturn c.config.ServerName\n}\n\nfunc (c *RealityServerConfig) SetServerName(serverName string) {\n\tc.config.ServerName = serverName\n}\n\nfunc (c *RealityServerConfig) NextProtos() []string {\n\treturn c.config.NextProtos\n}\n\nfunc (c *RealityServerConfig) SetNextProtos(nextProto []string) {\n\tc.config.NextProtos = nextProto\n}\n\nfunc (c *RealityServerConfig) STDConfig() (*tls.Config, error) {\n\treturn nil, E.New(\"unsupported usage for reality\")\n}\n\nfunc (c *RealityServerConfig) Client(conn net.Conn) (Conn, error) {\n\treturn ClientHandshake(context.Background(), conn, c)\n}\n\nfunc (c *RealityServerConfig) Start() error {\n\treturn nil\n}\n\nfunc (c *RealityServerConfig) Close() error {\n\treturn nil\n}\n\nfunc (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {\n\treturn ServerHandshake(context.Background(), conn, c)\n}\n\nfunc (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {\n\ttlsConn, err := utls.RealityServer(ctx, conn, c.config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &realityConnWrapper{Conn: tlsConn}, nil\n}\n\nfunc (c *RealityServerConfig) Clone() Config {\n\treturn &RealityServerConfig{\n\t\tconfig: c.config.Clone(),\n\t}\n}\n\nvar _ Conn = (*realityConnWrapper)(nil)\n\ntype realityConnWrapper struct {\n\t*utls.Conn\n}\n\nfunc (c *realityConnWrapper) ConnectionState() ConnectionState {\n\tstate := c.Conn.ConnectionState()\n\t//nolint:staticcheck\n\treturn tls.ConnectionState{\n\t\tVersion:                     state.Version,\n\t\tHandshakeComplete:           state.HandshakeComplete,\n\t\tDidResume:                   state.DidResume,\n\t\tCipherSuite:                 state.CipherSuite,\n\t\tNegotiatedProtocol:          state.NegotiatedProtocol,\n\t\tNegotiatedProtocolIsMutual:  state.NegotiatedProtocolIsMutual,\n\t\tServerName:                  state.ServerName,\n\t\tPeerCertificates:            state.PeerCertificates,\n\t\tVerifiedChains:              state.VerifiedChains,\n\t\tSignedCertificateTimestamps: state.SignedCertificateTimestamps,\n\t\tOCSPResponse:                state.OCSPResponse,\n\t\tTLSUnique:                   state.TLSUnique,\n\t}\n}\n\nfunc (c *realityConnWrapper) Upstream() any {\n\treturn c.Conn\n}\n\n// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.\n// We fixed it by calling Close() directly.\nfunc (c *realityConnWrapper) CloseWrite() error {\n\treturn c.Close()\n}\n\nfunc (c *realityConnWrapper) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (c *realityConnWrapper) WriterReplaceable() bool {\n\treturn true\n}\n"
  },
  {
    "path": "common/tls/reality_stub.go",
    "content": "//go:build with_reality_server\n\npackage tls\n\nvar _ int = \"The separate `with_reality_server` build tag has been merged into `with_utls` and is no longer needed, please update your build configuration.\"\n"
  },
  {
    "path": "common/tls/server.go",
    "content": "package tls\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/common/badtls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n)\n\ntype ServerOptions struct {\n\tContext        context.Context\n\tLogger         log.ContextLogger\n\tOptions        option.InboundTLSOptions\n\tKTLSCompatible bool\n}\n\nfunc NewServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {\n\treturn NewServerWithOptions(ServerOptions{\n\t\tContext: ctx,\n\t\tLogger:  logger,\n\t\tOptions: options,\n\t})\n}\n\nfunc NewServerWithOptions(options ServerOptions) (ServerConfig, error) {\n\tif !options.Options.Enabled {\n\t\treturn nil, nil\n\t}\n\tif !options.KTLSCompatible {\n\t\tif options.Options.KernelTx {\n\t\t\toptions.Logger.Warn(\"enabling kTLS TX in current scenarios will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_tx\")\n\t\t}\n\t}\n\tif options.Options.KernelRx {\n\t\toptions.Logger.Warn(\"enabling kTLS RX will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_rx\")\n\t}\n\tif options.Options.Reality != nil && options.Options.Reality.Enabled {\n\t\treturn NewRealityServer(options.Context, options.Logger, options.Options)\n\t}\n\treturn NewSTDServer(options.Context, options.Logger, options.Options)\n}\n\nfunc ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {\n\tctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)\n\tdefer cancel()\n\ttlsConn, err := aTLS.ServerHandshake(ctx, conn, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treadWaitConn, err := badtls.NewReadWaitConn(tlsConn)\n\tif err == nil {\n\t\treturn readWaitConn, nil\n\t} else if err != os.ErrInvalid {\n\t\treturn nil, err\n\t}\n\treturn tlsConn, nil\n}\n"
  },
  {
    "path": "common/tls/std_client.go",
    "content": "package tls\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tlsfragment\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/sing/common/ntp\"\n)\n\ntype STDClientConfig struct {\n\tctx                   context.Context\n\tconfig                *tls.Config\n\tfragment              bool\n\tfragmentFallbackDelay time.Duration\n\trecordFragment        bool\n}\n\nfunc (c *STDClientConfig) ServerName() string {\n\treturn c.config.ServerName\n}\n\nfunc (c *STDClientConfig) SetServerName(serverName string) {\n\tc.config.ServerName = serverName\n}\n\nfunc (c *STDClientConfig) NextProtos() []string {\n\treturn c.config.NextProtos\n}\n\nfunc (c *STDClientConfig) SetNextProtos(nextProto []string) {\n\tc.config.NextProtos = nextProto\n}\n\nfunc (c *STDClientConfig) STDConfig() (*STDConfig, error) {\n\treturn c.config, nil\n}\n\nfunc (c *STDClientConfig) Client(conn net.Conn) (Conn, error) {\n\tif c.recordFragment {\n\t\tconn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)\n\t}\n\treturn tls.Client(conn, c.config), nil\n}\n\nfunc (c *STDClientConfig) Clone() Config {\n\treturn &STDClientConfig{\n\t\tctx:                   c.ctx,\n\t\tconfig:                c.config.Clone(),\n\t\tfragment:              c.fragment,\n\t\tfragmentFallbackDelay: c.fragmentFallbackDelay,\n\t\trecordFragment:        c.recordFragment,\n\t}\n}\n\nfunc (c *STDClientConfig) ECHConfigList() []byte {\n\treturn c.config.EncryptedClientHelloConfigList\n}\n\nfunc (c *STDClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {\n\tc.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList\n}\n\nfunc NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) {\n\tvar serverName string\n\tif options.ServerName != \"\" {\n\t\tserverName = options.ServerName\n\t} else if serverAddress != \"\" {\n\t\tserverName = serverAddress\n\t}\n\tif serverName == \"\" && !options.Insecure {\n\t\treturn nil, E.New(\"missing server_name or insecure=true\")\n\t}\n\n\tvar tlsConfig tls.Config\n\ttlsConfig.Time = ntp.TimeFuncFromContext(ctx)\n\ttlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)\n\tif !options.DisableSNI {\n\t\ttlsConfig.ServerName = serverName\n\t}\n\tif options.Insecure {\n\t\ttlsConfig.InsecureSkipVerify = options.Insecure\n\t} else if options.DisableSNI {\n\t\ttlsConfig.InsecureSkipVerify = true\n\t\ttlsConfig.VerifyConnection = func(state tls.ConnectionState) error {\n\t\t\tverifyOptions := x509.VerifyOptions{\n\t\t\t\tRoots:         tlsConfig.RootCAs,\n\t\t\t\tDNSName:       serverName,\n\t\t\t\tIntermediates: x509.NewCertPool(),\n\t\t\t}\n\t\t\tfor _, cert := range state.PeerCertificates[1:] {\n\t\t\t\tverifyOptions.Intermediates.AddCert(cert)\n\t\t\t}\n\t\t\tif tlsConfig.Time != nil {\n\t\t\t\tverifyOptions.CurrentTime = tlsConfig.Time()\n\t\t\t}\n\t\t\t_, err := state.PeerCertificates[0].Verify(verifyOptions)\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(options.CertificatePublicKeySHA256) > 0 {\n\t\tif len(options.Certificate) > 0 || options.CertificatePath != \"\" {\n\t\t\treturn nil, E.New(\"certificate_public_key_sha256 is conflict with certificate or certificate_path\")\n\t\t}\n\t\ttlsConfig.InsecureSkipVerify = true\n\t\ttlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {\n\t\t\treturn verifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts, tlsConfig.Time)\n\t\t}\n\t}\n\tif len(options.ALPN) > 0 {\n\t\ttlsConfig.NextProtos = options.ALPN\n\t}\n\tif options.MinVersion != \"\" {\n\t\tminVersion, err := ParseTLSVersion(options.MinVersion)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse min_version\")\n\t\t}\n\t\ttlsConfig.MinVersion = minVersion\n\t}\n\tif options.MaxVersion != \"\" {\n\t\tmaxVersion, err := ParseTLSVersion(options.MaxVersion)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse max_version\")\n\t\t}\n\t\ttlsConfig.MaxVersion = maxVersion\n\t}\n\tif options.CipherSuites != nil {\n\tfind:\n\t\tfor _, cipherSuite := range options.CipherSuites {\n\t\t\tfor _, tlsCipherSuite := range tls.CipherSuites() {\n\t\t\t\tif cipherSuite == tlsCipherSuite.Name {\n\t\t\t\t\ttlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)\n\t\t\t\t\tcontinue find\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil, E.New(\"unknown cipher_suite: \", cipherSuite)\n\t\t}\n\t}\n\tfor _, curve := range options.CurvePreferences {\n\t\ttlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curve))\n\t}\n\tvar certificate []byte\n\tif len(options.Certificate) > 0 {\n\t\tcertificate = []byte(strings.Join(options.Certificate, \"\\n\"))\n\t} else if options.CertificatePath != \"\" {\n\t\tcontent, err := os.ReadFile(options.CertificatePath)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"read certificate\")\n\t\t}\n\t\tcertificate = content\n\t}\n\tif len(certificate) > 0 {\n\t\tcertPool := x509.NewCertPool()\n\t\tif !certPool.AppendCertsFromPEM(certificate) {\n\t\t\treturn nil, E.New(\"failed to parse certificate:\\n\\n\", certificate)\n\t\t}\n\t\ttlsConfig.RootCAs = certPool\n\t}\n\tvar clientCertificate []byte\n\tif len(options.ClientCertificate) > 0 {\n\t\tclientCertificate = []byte(strings.Join(options.ClientCertificate, \"\\n\"))\n\t} else if options.ClientCertificatePath != \"\" {\n\t\tcontent, err := os.ReadFile(options.ClientCertificatePath)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"read client certificate\")\n\t\t}\n\t\tclientCertificate = content\n\t}\n\tvar clientKey []byte\n\tif len(options.ClientKey) > 0 {\n\t\tclientKey = []byte(strings.Join(options.ClientKey, \"\\n\"))\n\t} else if options.ClientKeyPath != \"\" {\n\t\tcontent, err := os.ReadFile(options.ClientKeyPath)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"read client key\")\n\t\t}\n\t\tclientKey = content\n\t}\n\tif len(clientCertificate) > 0 && len(clientKey) > 0 {\n\t\tkeyPair, err := tls.X509KeyPair(clientCertificate, clientKey)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse client x509 key pair\")\n\t\t}\n\t\ttlsConfig.Certificates = []tls.Certificate{keyPair}\n\t} else if len(clientCertificate) > 0 || len(clientKey) > 0 {\n\t\treturn nil, E.New(\"client certificate and client key must be provided together\")\n\t}\n\tvar config Config = &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}\n\tif options.ECH != nil && options.ECH.Enabled {\n\t\tvar err error\n\t\tconfig, err = parseECHClientConfig(ctx, config.(ECHCapableConfig), options)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif options.KernelRx || options.KernelTx {\n\t\tif !C.IsLinux {\n\t\t\treturn nil, E.New(\"kTLS is only supported on Linux\")\n\t\t}\n\t\tconfig = &KTLSClientConfig{\n\t\t\tConfig:   config,\n\t\t\tlogger:   logger,\n\t\t\tkernelTx: options.KernelTx,\n\t\t\tkernelRx: options.KernelRx,\n\t\t}\n\t}\n\treturn config, nil\n}\n\nfunc verifyPublicKeySHA256(knownHashValues [][]byte, rawCerts [][]byte, timeFunc func() time.Time) error {\n\tleafCertificate, err := x509.ParseCertificate(rawCerts[0])\n\tif err != nil {\n\t\treturn E.Cause(err, \"failed to parse leaf certificate\")\n\t}\n\n\tpubKeyBytes, err := x509.MarshalPKIXPublicKey(leafCertificate.PublicKey)\n\tif err != nil {\n\t\treturn E.Cause(err, \"failed to marshal public key\")\n\t}\n\thashValue := sha256.Sum256(pubKeyBytes)\n\tfor _, value := range knownHashValues {\n\t\tif bytes.Equal(value, hashValue[:]) {\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn E.New(\"unrecognized remote public key: \", base64.StdEncoding.EncodeToString(hashValue[:]))\n}\n"
  },
  {
    "path": "common/tls/std_server.go",
    "content": "package tls\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/fswatch\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/ntp\"\n)\n\nvar errInsecureUnused = E.New(\"tls: insecure unused\")\n\ntype STDServerConfig struct {\n\taccess                sync.RWMutex\n\tconfig                *tls.Config\n\tlogger                log.Logger\n\tacmeService           adapter.SimpleLifecycle\n\tcertificate           []byte\n\tkey                   []byte\n\tcertificatePath       string\n\tkeyPath               string\n\tclientCertificatePath []string\n\techKeyPath            string\n\twatcher               *fswatch.Watcher\n}\n\nfunc (c *STDServerConfig) ServerName() string {\n\tc.access.RLock()\n\tdefer c.access.RUnlock()\n\treturn c.config.ServerName\n}\n\nfunc (c *STDServerConfig) SetServerName(serverName string) {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tconfig := c.config.Clone()\n\tconfig.ServerName = serverName\n\tc.config = config\n}\n\nfunc (c *STDServerConfig) NextProtos() []string {\n\tc.access.RLock()\n\tdefer c.access.RUnlock()\n\tif c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {\n\t\treturn c.config.NextProtos[1:]\n\t} else {\n\t\treturn c.config.NextProtos\n\t}\n}\n\nfunc (c *STDServerConfig) SetNextProtos(nextProto []string) {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tconfig := c.config.Clone()\n\tif c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {\n\t\tconfig.NextProtos = append(c.config.NextProtos[:1], nextProto...)\n\t} else {\n\t\tconfig.NextProtos = nextProto\n\t}\n\tc.config = config\n}\n\nfunc (c *STDServerConfig) STDConfig() (*STDConfig, error) {\n\treturn c.config, nil\n}\n\nfunc (c *STDServerConfig) Client(conn net.Conn) (Conn, error) {\n\treturn tls.Client(conn, c.config), nil\n}\n\nfunc (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {\n\treturn tls.Server(conn, c.config), nil\n}\n\nfunc (c *STDServerConfig) Clone() Config {\n\treturn &STDServerConfig{\n\t\tconfig: c.config.Clone(),\n\t}\n}\n\nfunc (c *STDServerConfig) Start() error {\n\tif c.acmeService != nil {\n\t\treturn c.acmeService.Start()\n\t} else {\n\t\terr := c.startWatcher()\n\t\tif err != nil {\n\t\t\tc.logger.Warn(\"create fsnotify watcher: \", err)\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc (c *STDServerConfig) startWatcher() error {\n\tvar watchPath []string\n\tif c.certificatePath != \"\" {\n\t\twatchPath = append(watchPath, c.certificatePath)\n\t}\n\tif c.keyPath != \"\" {\n\t\twatchPath = append(watchPath, c.keyPath)\n\t}\n\tif c.echKeyPath != \"\" {\n\t\twatchPath = append(watchPath, c.echKeyPath)\n\t}\n\tif len(c.clientCertificatePath) > 0 {\n\t\twatchPath = append(watchPath, c.clientCertificatePath...)\n\t}\n\tif len(watchPath) == 0 {\n\t\treturn nil\n\t}\n\twatcher, err := fswatch.NewWatcher(fswatch.Options{\n\t\tPath: watchPath,\n\t\tCallback: func(path string) {\n\t\t\terr := c.certificateUpdated(path)\n\t\t\tif err != nil {\n\t\t\t\tc.logger.Error(E.Cause(err, \"reload certificate\"))\n\t\t\t}\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = watcher.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.watcher = watcher\n\treturn nil\n}\n\nfunc (c *STDServerConfig) certificateUpdated(path string) error {\n\tif path == c.certificatePath || path == c.keyPath {\n\t\tif path == c.certificatePath {\n\t\t\tcertificate, err := os.ReadFile(c.certificatePath)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"reload certificate from \", c.certificatePath)\n\t\t\t}\n\t\t\tc.certificate = certificate\n\t\t} else if path == c.keyPath {\n\t\t\tkey, err := os.ReadFile(c.keyPath)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"reload key from \", c.keyPath)\n\t\t\t}\n\t\t\tc.key = key\n\t\t}\n\t\tkeyPair, err := tls.X509KeyPair(c.certificate, c.key)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"reload key pair\")\n\t\t}\n\t\tc.access.Lock()\n\t\tconfig := c.config.Clone()\n\t\tconfig.Certificates = []tls.Certificate{keyPair}\n\t\tc.config = config\n\t\tc.access.Unlock()\n\t\tc.logger.Info(\"reloaded TLS certificate\")\n\t} else if common.Contains(c.clientCertificatePath, path) {\n\t\tclientCertificateCA := x509.NewCertPool()\n\t\tvar reloaded bool\n\t\tfor _, certPath := range c.clientCertificatePath {\n\t\t\tcontent, err := os.ReadFile(certPath)\n\t\t\tif err != nil {\n\t\t\t\tc.logger.Error(E.Cause(err, \"reload certificate from \", c.clientCertificatePath))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !clientCertificateCA.AppendCertsFromPEM(content) {\n\t\t\t\tc.logger.Error(E.New(\"invalid client certificate file: \", certPath))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treloaded = true\n\t\t}\n\t\tif !reloaded {\n\t\t\treturn E.New(\"client certificates is empty\")\n\t\t}\n\t\tc.access.Lock()\n\t\tconfig := c.config.Clone()\n\t\tconfig.ClientCAs = clientCertificateCA\n\t\tc.config = config\n\t\tc.access.Unlock()\n\t\tc.logger.Info(\"reloaded client certificates\")\n\t} else if path == c.echKeyPath {\n\t\techKey, err := os.ReadFile(c.echKeyPath)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"reload ECH keys from \", c.echKeyPath)\n\t\t}\n\t\terr = c.setECHServerConfig(echKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.logger.Info(\"reloaded ECH keys\")\n\t}\n\treturn nil\n}\n\nfunc (c *STDServerConfig) Close() error {\n\tif c.acmeService != nil {\n\t\treturn c.acmeService.Close()\n\t}\n\tif c.watcher != nil {\n\t\treturn c.watcher.Close()\n\t}\n\treturn nil\n}\n\nfunc NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {\n\tif !options.Enabled {\n\t\treturn nil, nil\n\t}\n\tvar tlsConfig *tls.Config\n\tvar acmeService adapter.SimpleLifecycle\n\tvar err error\n\tif options.ACME != nil && len(options.ACME.Domain) > 0 {\n\t\t//nolint:staticcheck\n\t\ttlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif options.Insecure {\n\t\t\treturn nil, errInsecureUnused\n\t\t}\n\t} else {\n\t\ttlsConfig = &tls.Config{}\n\t}\n\ttlsConfig.Time = ntp.TimeFuncFromContext(ctx)\n\tif options.ServerName != \"\" {\n\t\ttlsConfig.ServerName = options.ServerName\n\t}\n\tif len(options.ALPN) > 0 {\n\t\ttlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...)\n\t}\n\tif options.MinVersion != \"\" {\n\t\tminVersion, err := ParseTLSVersion(options.MinVersion)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse min_version\")\n\t\t}\n\t\ttlsConfig.MinVersion = minVersion\n\t}\n\tif options.MaxVersion != \"\" {\n\t\tmaxVersion, err := ParseTLSVersion(options.MaxVersion)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse max_version\")\n\t\t}\n\t\ttlsConfig.MaxVersion = maxVersion\n\t}\n\tif options.CipherSuites != nil {\n\tfind:\n\t\tfor _, cipherSuite := range options.CipherSuites {\n\t\t\tfor _, tlsCipherSuite := range tls.CipherSuites() {\n\t\t\t\tif cipherSuite == tlsCipherSuite.Name {\n\t\t\t\t\ttlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)\n\t\t\t\t\tcontinue find\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil, E.New(\"unknown cipher_suite: \", cipherSuite)\n\t\t}\n\t}\n\tfor _, curveID := range options.CurvePreferences {\n\t\ttlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curveID))\n\t}\n\ttlsConfig.ClientAuth = tls.ClientAuthType(options.ClientAuthentication)\n\tvar (\n\t\tcertificate []byte\n\t\tkey         []byte\n\t)\n\tif acmeService == nil {\n\t\tif len(options.Certificate) > 0 {\n\t\t\tcertificate = []byte(strings.Join(options.Certificate, \"\\n\"))\n\t\t} else if options.CertificatePath != \"\" {\n\t\t\tcontent, err := os.ReadFile(options.CertificatePath)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, \"read certificate\")\n\t\t\t}\n\t\t\tcertificate = content\n\t\t}\n\t\tif len(options.Key) > 0 {\n\t\t\tkey = []byte(strings.Join(options.Key, \"\\n\"))\n\t\t} else if options.KeyPath != \"\" {\n\t\t\tcontent, err := os.ReadFile(options.KeyPath)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, \"read key\")\n\t\t\t}\n\t\t\tkey = content\n\t\t}\n\t\tif certificate == nil && key == nil && options.Insecure {\n\t\t\ttimeFunc := ntp.TimeFuncFromContext(ctx)\n\t\t\tif timeFunc == nil {\n\t\t\t\ttimeFunc = time.Now\n\t\t\t}\n\t\t\ttlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {\n\t\t\t\treturn GenerateKeyPair(nil, nil, timeFunc, info.ServerName)\n\t\t\t}\n\t\t} else {\n\t\t\tif certificate == nil {\n\t\t\t\treturn nil, E.New(\"missing certificate\")\n\t\t\t} else if key == nil {\n\t\t\t\treturn nil, E.New(\"missing key\")\n\t\t\t}\n\n\t\t\tkeyPair, err := tls.X509KeyPair(certificate, key)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, \"parse x509 key pair\")\n\t\t\t}\n\t\t\ttlsConfig.Certificates = []tls.Certificate{keyPair}\n\t\t}\n\t}\n\tif len(options.ClientCertificate) > 0 || len(options.ClientCertificatePath) > 0 {\n\t\tif tlsConfig.ClientAuth == tls.NoClientCert {\n\t\t\ttlsConfig.ClientAuth = tls.RequireAndVerifyClientCert\n\t\t}\n\t}\n\tif tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {\n\t\tif len(options.ClientCertificate) > 0 {\n\t\t\tclientCertificateCA := x509.NewCertPool()\n\t\t\tif !clientCertificateCA.AppendCertsFromPEM([]byte(strings.Join(options.ClientCertificate, \"\\n\"))) {\n\t\t\t\treturn nil, E.New(\"invalid client certificate strings\")\n\t\t\t}\n\t\t\ttlsConfig.ClientCAs = clientCertificateCA\n\t\t} else if len(options.ClientCertificatePath) > 0 {\n\t\t\tclientCertificateCA := x509.NewCertPool()\n\t\t\tfor _, path := range options.ClientCertificatePath {\n\t\t\t\tcontent, err := os.ReadFile(path)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, E.Cause(err, \"read client certificate from \", path)\n\t\t\t\t}\n\t\t\t\tif !clientCertificateCA.AppendCertsFromPEM(content) {\n\t\t\t\t\treturn nil, E.New(\"invalid client certificate file: \", path)\n\t\t\t\t}\n\t\t\t}\n\t\t\ttlsConfig.ClientCAs = clientCertificateCA\n\t\t} else if len(options.ClientCertificatePublicKeySHA256) > 0 {\n\t\t\tif tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {\n\t\t\t\ttlsConfig.ClientAuth = tls.RequireAnyClientCert\n\t\t\t} else if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven {\n\t\t\t\ttlsConfig.ClientAuth = tls.RequestClientCert\n\t\t\t}\n\t\t\ttlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {\n\t\t\t\treturn verifyPublicKeySHA256(options.ClientCertificatePublicKeySHA256, rawCerts, tlsConfig.Time)\n\t\t\t}\n\t\t} else {\n\t\t\treturn nil, E.New(\"missing client_certificate, client_certificate_path or client_certificate_public_key_sha256 for client authentication\")\n\t\t}\n\t}\n\tvar echKeyPath string\n\tif options.ECH != nil && options.ECH.Enabled {\n\t\terr = parseECHServerConfig(ctx, options, tlsConfig, &echKeyPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tserverConfig := &STDServerConfig{\n\t\tconfig:                tlsConfig,\n\t\tlogger:                logger,\n\t\tacmeService:           acmeService,\n\t\tcertificate:           certificate,\n\t\tkey:                   key,\n\t\tcertificatePath:       options.CertificatePath,\n\t\tclientCertificatePath: options.ClientCertificatePath,\n\t\tkeyPath:               options.KeyPath,\n\t\techKeyPath:            echKeyPath,\n\t}\n\tserverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {\n\t\tserverConfig.access.Lock()\n\t\tdefer serverConfig.access.Unlock()\n\t\treturn serverConfig.config, nil\n\t}\n\tvar config ServerConfig = serverConfig\n\tif options.KernelTx || options.KernelRx {\n\t\tif !C.IsLinux {\n\t\t\treturn nil, E.New(\"kTLS is only supported on Linux\")\n\t\t}\n\t\tconfig = &KTlSServerConfig{\n\t\t\tServerConfig: config,\n\t\t\tlogger:       logger,\n\t\t\tkernelTx:     options.KernelTx,\n\t\t\tkernelRx:     options.KernelRx,\n\t\t}\n\t}\n\treturn config, nil\n}\n"
  },
  {
    "path": "common/tls/time_wrapper.go",
    "content": "package tls\n\nimport (\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common/ntp\"\n)\n\ntype TimeServiceWrapper struct {\n\tntp.TimeService\n}\n\nfunc (w *TimeServiceWrapper) TimeFunc() func() time.Time {\n\treturn func() time.Time {\n\t\tif w.TimeService != nil {\n\t\t\treturn w.TimeService.TimeFunc()()\n\t\t} else {\n\t\t\treturn time.Now()\n\t\t}\n\t}\n}\n\nfunc (w *TimeServiceWrapper) Upstream() any {\n\treturn w.TimeService\n}\n"
  },
  {
    "path": "common/tls/utls_client.go",
    "content": "//go:build with_utls\n\npackage tls\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tlsfragment\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\n\tutls \"github.com/metacubex/utls\"\n\t\"golang.org/x/net/http2\"\n)\n\ntype UTLSClientConfig struct {\n\tctx                   context.Context\n\tconfig                *utls.Config\n\tid                    utls.ClientHelloID\n\tfragment              bool\n\tfragmentFallbackDelay time.Duration\n\trecordFragment        bool\n}\n\nfunc (c *UTLSClientConfig) ServerName() string {\n\treturn c.config.ServerName\n}\n\nfunc (c *UTLSClientConfig) SetServerName(serverName string) {\n\tc.config.ServerName = serverName\n}\n\nfunc (c *UTLSClientConfig) NextProtos() []string {\n\treturn c.config.NextProtos\n}\n\nfunc (c *UTLSClientConfig) SetNextProtos(nextProto []string) {\n\tif len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {\n\t\tnextProto = append(nextProto, \"http/1.1\")\n\t}\n\tc.config.NextProtos = nextProto\n}\n\nfunc (c *UTLSClientConfig) STDConfig() (*STDConfig, error) {\n\treturn nil, E.New(\"unsupported usage for uTLS\")\n}\n\nfunc (c *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {\n\tif c.recordFragment {\n\t\tconn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)\n\t}\n\treturn &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, c.config.Clone(), c.id)}, c.config.NextProtos}, nil\n}\n\nfunc (c *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {\n\tc.config.SessionIDGenerator = generator\n}\n\nfunc (c *UTLSClientConfig) Clone() Config {\n\treturn &UTLSClientConfig{\n\t\tc.ctx, c.config.Clone(), c.id, c.fragment, c.fragmentFallbackDelay, c.recordFragment,\n\t}\n}\n\nfunc (c *UTLSClientConfig) ECHConfigList() []byte {\n\treturn c.config.EncryptedClientHelloConfigList\n}\n\nfunc (c *UTLSClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {\n\tc.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList\n}\n\ntype utlsConnWrapper struct {\n\t*utls.UConn\n}\n\nfunc (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {\n\tstate := c.Conn.ConnectionState()\n\t//nolint:staticcheck\n\treturn tls.ConnectionState{\n\t\tVersion:                     state.Version,\n\t\tHandshakeComplete:           state.HandshakeComplete,\n\t\tDidResume:                   state.DidResume,\n\t\tCipherSuite:                 state.CipherSuite,\n\t\tNegotiatedProtocol:          state.NegotiatedProtocol,\n\t\tNegotiatedProtocolIsMutual:  state.NegotiatedProtocolIsMutual,\n\t\tServerName:                  state.ServerName,\n\t\tPeerCertificates:            state.PeerCertificates,\n\t\tVerifiedChains:              state.VerifiedChains,\n\t\tSignedCertificateTimestamps: state.SignedCertificateTimestamps,\n\t\tOCSPResponse:                state.OCSPResponse,\n\t\tTLSUnique:                   state.TLSUnique,\n\t}\n}\n\nfunc (c *utlsConnWrapper) Upstream() any {\n\treturn c.UConn\n}\n\nfunc (c *utlsConnWrapper) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (c *utlsConnWrapper) WriterReplaceable() bool {\n\treturn true\n}\n\ntype utlsALPNWrapper struct {\n\tutlsConnWrapper\n\tnextProtocols []string\n}\n\nfunc (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {\n\tif len(c.nextProtocols) > 0 {\n\t\terr := c.BuildHandshakeState()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, extension := range c.Extensions {\n\t\t\tif alpnExtension, isALPN := extension.(*utls.ALPNExtension); isALPN {\n\t\t\t\talpnExtension.AlpnProtocols = c.nextProtocols\n\t\t\t\terr = c.BuildHandshakeState()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn c.UConn.HandshakeContext(ctx)\n}\n\nfunc NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) {\n\tvar serverName string\n\tif options.ServerName != \"\" {\n\t\tserverName = options.ServerName\n\t} else if serverAddress != \"\" {\n\t\tserverName = serverAddress\n\t}\n\tif serverName == \"\" && !options.Insecure {\n\t\treturn nil, E.New(\"missing server_name or insecure=true\")\n\t}\n\n\tvar tlsConfig utls.Config\n\ttlsConfig.Time = ntp.TimeFuncFromContext(ctx)\n\ttlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)\n\tif !options.DisableSNI {\n\t\ttlsConfig.ServerName = serverName\n\t}\n\tif options.Insecure {\n\t\ttlsConfig.InsecureSkipVerify = options.Insecure\n\t} else if options.DisableSNI {\n\t\tif options.Reality != nil && options.Reality.Enabled {\n\t\t\treturn nil, E.New(\"disable_sni is unsupported in reality\")\n\t\t}\n\t\ttlsConfig.InsecureServerNameToVerify = serverName\n\t}\n\tif len(options.CertificatePublicKeySHA256) > 0 {\n\t\tif len(options.Certificate) > 0 || options.CertificatePath != \"\" {\n\t\t\treturn nil, E.New(\"certificate_public_key_sha256 is conflict with certificate or certificate_path\")\n\t\t}\n\t\ttlsConfig.InsecureSkipVerify = true\n\t\ttlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {\n\t\t\treturn verifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts, tlsConfig.Time)\n\t\t}\n\t}\n\tif len(options.ALPN) > 0 {\n\t\ttlsConfig.NextProtos = options.ALPN\n\t}\n\tif options.MinVersion != \"\" {\n\t\tminVersion, err := ParseTLSVersion(options.MinVersion)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse min_version\")\n\t\t}\n\t\ttlsConfig.MinVersion = minVersion\n\t}\n\tif options.MaxVersion != \"\" {\n\t\tmaxVersion, err := ParseTLSVersion(options.MaxVersion)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse max_version\")\n\t\t}\n\t\ttlsConfig.MaxVersion = maxVersion\n\t}\n\tif options.CipherSuites != nil {\n\tfind:\n\t\tfor _, cipherSuite := range options.CipherSuites {\n\t\t\tfor _, tlsCipherSuite := range tls.CipherSuites() {\n\t\t\t\tif cipherSuite == tlsCipherSuite.Name {\n\t\t\t\t\ttlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)\n\t\t\t\t\tcontinue find\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil, E.New(\"unknown cipher_suite: \", cipherSuite)\n\t\t}\n\t}\n\tvar certificate []byte\n\tif len(options.Certificate) > 0 {\n\t\tcertificate = []byte(strings.Join(options.Certificate, \"\\n\"))\n\t} else if options.CertificatePath != \"\" {\n\t\tcontent, err := os.ReadFile(options.CertificatePath)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"read certificate\")\n\t\t}\n\t\tcertificate = content\n\t}\n\tif len(certificate) > 0 {\n\t\tcertPool := x509.NewCertPool()\n\t\tif !certPool.AppendCertsFromPEM(certificate) {\n\t\t\treturn nil, E.New(\"failed to parse certificate:\\n\\n\", certificate)\n\t\t}\n\t\ttlsConfig.RootCAs = certPool\n\t}\n\tvar clientCertificate []byte\n\tif len(options.ClientCertificate) > 0 {\n\t\tclientCertificate = []byte(strings.Join(options.ClientCertificate, \"\\n\"))\n\t} else if options.ClientCertificatePath != \"\" {\n\t\tcontent, err := os.ReadFile(options.ClientCertificatePath)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"read client certificate\")\n\t\t}\n\t\tclientCertificate = content\n\t}\n\tvar clientKey []byte\n\tif len(options.ClientKey) > 0 {\n\t\tclientKey = []byte(strings.Join(options.ClientKey, \"\\n\"))\n\t} else if options.ClientKeyPath != \"\" {\n\t\tcontent, err := os.ReadFile(options.ClientKeyPath)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"read client key\")\n\t\t}\n\t\tclientKey = content\n\t}\n\tif len(clientCertificate) > 0 && len(clientKey) > 0 {\n\t\tkeyPair, err := utls.X509KeyPair(clientCertificate, clientKey)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse client x509 key pair\")\n\t\t}\n\t\ttlsConfig.Certificates = []utls.Certificate{keyPair}\n\t} else if len(clientCertificate) > 0 || len(clientKey) > 0 {\n\t\treturn nil, E.New(\"client certificate and client key must be provided together\")\n\t}\n\tid, err := uTLSClientHelloID(options.UTLS.Fingerprint)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar config Config = &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}\n\tif options.ECH != nil && options.ECH.Enabled {\n\t\tif options.Reality != nil && options.Reality.Enabled {\n\t\t\treturn nil, E.New(\"Reality is conflict with ECH\")\n\t\t}\n\t\tconfig, err = parseECHClientConfig(ctx, config.(ECHCapableConfig), options)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif (options.KernelRx || options.KernelTx) && !common.PtrValueOrDefault(options.Reality).Enabled {\n\t\tif !C.IsLinux {\n\t\t\treturn nil, E.New(\"kTLS is only supported on Linux\")\n\t\t}\n\t\tconfig = &KTLSClientConfig{\n\t\t\tConfig:   config,\n\t\t\tlogger:   logger,\n\t\t\tkernelTx: options.KernelTx,\n\t\t\tkernelRx: options.KernelRx,\n\t\t}\n\t}\n\treturn config, nil\n}\n\nvar (\n\trandomFingerprint     utls.ClientHelloID\n\trandomizedFingerprint utls.ClientHelloID\n)\n\nfunc init() {\n\tmodernFingerprints := []utls.ClientHelloID{\n\t\tutls.HelloChrome_Auto,\n\t\tutls.HelloFirefox_Auto,\n\t\tutls.HelloEdge_Auto,\n\t\tutls.HelloSafari_Auto,\n\t\tutls.HelloIOS_Auto,\n\t}\n\trandomFingerprint = modernFingerprints[rand.Intn(len(modernFingerprints))]\n\n\tweights := utls.DefaultWeights\n\tweights.TLSVersMax_Set_VersionTLS13 = 1\n\tweights.FirstKeyShare_Set_CurveP256 = 0\n\trandomizedFingerprint = utls.HelloRandomized\n\trandomizedFingerprint.Seed, _ = utls.NewPRNGSeed()\n\trandomizedFingerprint.Weights = &weights\n}\n\nfunc uTLSClientHelloID(name string) (utls.ClientHelloID, error) {\n\tswitch name {\n\tcase \"chrome_psk\", \"chrome_psk_shuffle\", \"chrome_padding_psk_shuffle\", \"chrome_pq\", \"chrome_pq_psk\":\n\t\tfallthrough\n\tcase \"chrome\", \"\":\n\t\treturn utls.HelloChrome_Auto, nil\n\tcase \"firefox\":\n\t\treturn utls.HelloFirefox_Auto, nil\n\tcase \"edge\":\n\t\treturn utls.HelloEdge_Auto, nil\n\tcase \"safari\":\n\t\treturn utls.HelloSafari_Auto, nil\n\tcase \"360\":\n\t\treturn utls.Hello360_Auto, nil\n\tcase \"qq\":\n\t\treturn utls.HelloQQ_Auto, nil\n\tcase \"ios\":\n\t\treturn utls.HelloIOS_Auto, nil\n\tcase \"android\":\n\t\treturn utls.HelloAndroid_11_OkHttp, nil\n\tcase \"random\":\n\t\treturn randomFingerprint, nil\n\tcase \"randomized\":\n\t\treturn randomizedFingerprint, nil\n\tdefault:\n\t\treturn utls.ClientHelloID{}, E.New(\"unknown uTLS fingerprint: \", name)\n\t}\n}\n"
  },
  {
    "path": "common/tls/utls_stub.go",
    "content": "//go:build !with_utls\n\npackage tls\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\nfunc NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) {\n\treturn nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)\n}\n\nfunc NewRealityClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) {\n\treturn nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)\n}\n\nfunc NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {\n\treturn nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)\n}\n"
  },
  {
    "path": "common/tlsfragment/conn.go",
    "content": "package tf\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"math/rand\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"golang.org/x/net/publicsuffix\"\n)\n\ntype Conn struct {\n\tnet.Conn\n\ttcpConn            *net.TCPConn\n\tctx                context.Context\n\tfirstPacketWritten bool\n\tsplitPacket        bool\n\tsplitRecord        bool\n\tfallbackDelay      time.Duration\n}\n\nfunc NewConn(conn net.Conn, ctx context.Context, splitPacket bool, splitRecord bool, fallbackDelay time.Duration) *Conn {\n\tif fallbackDelay == 0 {\n\t\tfallbackDelay = C.TLSFragmentFallbackDelay\n\t}\n\ttcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)\n\treturn &Conn{\n\t\tConn:          conn,\n\t\ttcpConn:       tcpConn,\n\t\tctx:           ctx,\n\t\tsplitPacket:   splitPacket,\n\t\tsplitRecord:   splitRecord,\n\t\tfallbackDelay: fallbackDelay,\n\t}\n}\n\nfunc (c *Conn) Write(b []byte) (n int, err error) {\n\tif !c.firstPacketWritten {\n\t\tdefer func() {\n\t\t\tc.firstPacketWritten = true\n\t\t}()\n\t\tserverName := IndexTLSServerName(b)\n\t\tif serverName != nil {\n\t\t\tif c.splitPacket {\n\t\t\t\tif c.tcpConn != nil {\n\t\t\t\t\terr = c.tcpConn.SetNoDelay(true)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tsplits := strings.Split(serverName.ServerName, \".\")\n\t\t\tcurrentIndex := serverName.Index\n\t\t\tif publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != \"\" {\n\t\t\t\tsplits = splits[:len(splits)-strings.Count(serverName.ServerName, \".\")]\n\t\t\t}\n\t\t\tif len(splits) > 1 && splits[0] == \"...\" {\n\t\t\t\tcurrentIndex += len(splits[0]) + 1\n\t\t\t\tsplits = splits[1:]\n\t\t\t}\n\t\t\tvar splitIndexes []int\n\t\t\tfor i, split := range splits {\n\t\t\t\tsplitAt := rand.Intn(len(split))\n\t\t\t\tsplitIndexes = append(splitIndexes, currentIndex+splitAt)\n\t\t\t\tcurrentIndex += len(split)\n\t\t\t\tif i != len(splits)-1 {\n\t\t\t\t\tcurrentIndex++\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar buffer bytes.Buffer\n\t\t\tfor i := 0; i <= len(splitIndexes); i++ {\n\t\t\t\tvar payload []byte\n\t\t\t\tif i == 0 {\n\t\t\t\t\tpayload = b[:splitIndexes[i]]\n\t\t\t\t\tif c.splitRecord {\n\t\t\t\t\t\tpayload = payload[recordLayerHeaderLen:]\n\t\t\t\t\t}\n\t\t\t\t} else if i == len(splitIndexes) {\n\t\t\t\t\tpayload = b[splitIndexes[i-1]:]\n\t\t\t\t} else {\n\t\t\t\t\tpayload = b[splitIndexes[i-1]:splitIndexes[i]]\n\t\t\t\t}\n\t\t\t\tif c.splitRecord {\n\t\t\t\t\tif c.splitPacket {\n\t\t\t\t\t\tbuffer.Reset()\n\t\t\t\t\t}\n\t\t\t\t\tpayloadLen := uint16(len(payload))\n\t\t\t\t\tbuffer.Write(b[:3])\n\t\t\t\t\tbinary.Write(&buffer, binary.BigEndian, payloadLen)\n\t\t\t\t\tbuffer.Write(payload)\n\t\t\t\t\tif c.splitPacket {\n\t\t\t\t\t\tpayload = buffer.Bytes()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif c.splitPacket {\n\t\t\t\t\tif c.tcpConn != nil && i != len(splitIndexes) {\n\t\t\t\t\t\terr = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t_, err = c.Conn.Write(payload)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif i != len(splitIndexes) {\n\t\t\t\t\t\t\ttime.Sleep(c.fallbackDelay)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif c.splitRecord && !c.splitPacket {\n\t\t\t\t_, err = c.Conn.Write(buffer.Bytes())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tif c.tcpConn != nil {\n\t\t\t\terr = c.tcpConn.SetNoDelay(false)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn len(b), nil\n\t\t}\n\t}\n\treturn c.Conn.Write(b)\n}\n\nfunc (c *Conn) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (c *Conn) WriterReplaceable() bool {\n\treturn c.firstPacketWritten\n}\n\nfunc (c *Conn) Upstream() any {\n\treturn c.Conn\n}\n"
  },
  {
    "path": "common/tlsfragment/conn_test.go",
    "content": "package tf_test\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net\"\n\t\"testing\"\n\n\ttf \"github.com/sagernet/sing-box/common/tlsfragment\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTLSFragment(t *testing.T) {\n\tt.Parallel()\n\ttcpConn, err := net.Dial(\"tcp\", \"1.1.1.1:443\")\n\trequire.NoError(t, err)\n\ttlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, false, 0), &tls.Config{\n\t\tServerName: \"www.cloudflare.com\",\n\t})\n\trequire.NoError(t, tlsConn.Handshake())\n}\n\nfunc TestTLSRecordFragment(t *testing.T) {\n\tt.Parallel()\n\ttcpConn, err := net.Dial(\"tcp\", \"1.1.1.1:443\")\n\trequire.NoError(t, err)\n\ttlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, true, 0), &tls.Config{\n\t\tServerName: \"www.cloudflare.com\",\n\t})\n\trequire.NoError(t, tlsConn.Handshake())\n}\n\nfunc TestTLS2Fragment(t *testing.T) {\n\tt.Parallel()\n\ttcpConn, err := net.Dial(\"tcp\", \"1.1.1.1:443\")\n\trequire.NoError(t, err)\n\ttlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, true, 0), &tls.Config{\n\t\tServerName: \"www.cloudflare.com\",\n\t})\n\trequire.NoError(t, tlsConn.Handshake())\n}\n"
  },
  {
    "path": "common/tlsfragment/index.go",
    "content": "package tf\n\nimport (\n\t\"encoding/binary\"\n)\n\nconst (\n\trecordLayerHeaderLen    int    = 5\n\thandshakeHeaderLen      int    = 6\n\trandomDataLen           int    = 32\n\tsessionIDHeaderLen      int    = 1\n\tcipherSuiteHeaderLen    int    = 2\n\tcompressMethodHeaderLen int    = 1\n\textensionsHeaderLen     int    = 2\n\textensionHeaderLen      int    = 4\n\tsniExtensionHeaderLen   int    = 5\n\tcontentType             uint8  = 22\n\thandshakeType           uint8  = 1\n\tsniExtensionType        uint16 = 0\n\tsniNameDNSHostnameType  uint8  = 0\n\ttlsVersionBitmask       uint16 = 0xFFFC\n\ttls13                   uint16 = 0x0304\n)\n\ntype MyServerName struct {\n\tIndex      int\n\tLength     int\n\tServerName string\n}\n\nfunc IndexTLSServerName(payload []byte) *MyServerName {\n\tif len(payload) < recordLayerHeaderLen || payload[0] != contentType {\n\t\treturn nil\n\t}\n\tsegmentLen := binary.BigEndian.Uint16(payload[3:5])\n\tif len(payload) < recordLayerHeaderLen+int(segmentLen) {\n\t\treturn nil\n\t}\n\tserverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen:])\n\tif serverName == nil {\n\t\treturn nil\n\t}\n\tserverName.Index += recordLayerHeaderLen\n\treturn serverName\n}\n\nfunc indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {\n\tif len(handshake) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {\n\t\treturn nil\n\t}\n\tif handshake[0] != handshakeType {\n\t\treturn nil\n\t}\n\thandshakeLen := uint32(handshake[1])<<16 | uint32(handshake[2])<<8 | uint32(handshake[3])\n\tif len(handshake[4:]) != int(handshakeLen) {\n\t\treturn nil\n\t}\n\ttlsVersion := uint16(handshake[4])<<8 | uint16(handshake[5])\n\tif tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {\n\t\treturn nil\n\t}\n\tsessionIDLen := handshake[38]\n\tcurrentIndex := handshakeHeaderLen + randomDataLen + sessionIDHeaderLen + int(sessionIDLen)\n\tif len(handshake) < currentIndex {\n\t\treturn nil\n\t}\n\tcipherSuites := handshake[currentIndex:]\n\tif len(cipherSuites) < cipherSuiteHeaderLen {\n\t\treturn nil\n\t}\n\tcsLen := uint16(cipherSuites[0])<<8 | uint16(cipherSuites[1])\n\tif len(cipherSuites) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {\n\t\treturn nil\n\t}\n\tcompressMethodLen := uint16(cipherSuites[cipherSuiteHeaderLen+int(csLen)])\n\tcurrentIndex += cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)\n\tif len(handshake) < currentIndex {\n\t\treturn nil\n\t}\n\tserverName := indexTLSServerNameFromExtensions(handshake[currentIndex:])\n\tif serverName == nil {\n\t\treturn nil\n\t}\n\tserverName.Index += currentIndex\n\treturn serverName\n}\n\nfunc indexTLSServerNameFromExtensions(exs []byte) *MyServerName {\n\tif len(exs) == 0 {\n\t\treturn nil\n\t}\n\tif len(exs) < extensionsHeaderLen {\n\t\treturn nil\n\t}\n\texsLen := uint16(exs[0])<<8 | uint16(exs[1])\n\texs = exs[extensionsHeaderLen:]\n\tif len(exs) < int(exsLen) {\n\t\treturn nil\n\t}\n\tfor currentIndex := extensionsHeaderLen; len(exs) > 0; {\n\t\tif len(exs) < extensionHeaderLen {\n\t\t\treturn nil\n\t\t}\n\t\texType := uint16(exs[0])<<8 | uint16(exs[1])\n\t\texLen := uint16(exs[2])<<8 | uint16(exs[3])\n\t\tif len(exs) < extensionHeaderLen+int(exLen) {\n\t\t\treturn nil\n\t\t}\n\t\tsex := exs[extensionHeaderLen : extensionHeaderLen+int(exLen)]\n\n\t\tswitch exType {\n\t\tcase sniExtensionType:\n\t\t\tif len(sex) < sniExtensionHeaderLen {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tsniType := sex[2]\n\t\t\tif sniType != sniNameDNSHostnameType {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tsniLen := uint16(sex[3])<<8 | uint16(sex[4])\n\t\t\tsex = sex[sniExtensionHeaderLen:]\n\n\t\t\treturn &MyServerName{\n\t\t\t\tIndex:      currentIndex + extensionHeaderLen + sniExtensionHeaderLen,\n\t\t\t\tLength:     int(sniLen),\n\t\t\t\tServerName: string(sex),\n\t\t\t}\n\t\t}\n\t\texs = exs[4+exLen:]\n\t\tcurrentIndex += 4 + int(exLen)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/tlsfragment/index_test.go",
    "content": "package tf_test\n\nimport (\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/common/tlsfragment\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestIndexTLSServerName(t *testing.T) {\n\tt.Parallel()\n\tpayload, err := hex.DecodeString(\"16030105f8010005f403036e35de7389a679c54029cf452611f2211c70d9ac3897271de589ab6155f8e4ab20637d225f1ef969ad87ed78bfb9d171300bcb1703b6f314ccefb964f79b7d0961002a0a0a130213031301c02cc02bcca9c030c02fcca8c00ac009c014c013009d009c0035002fc008c012000a01000581baba00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000e000c3a3a11ec001d001700180019000b000201000010000e000c02683208687474702f312e31000500050100000000000d00160014040308040401050308050805050108060601020100120000003304ef04ed3a3a00010011ec04c0aeb2250c092a3463161cccb29d9183331a424964248579507ed23a180b0ceab2a5f5d9ce41547e497a89055471ea572867ba3a1fc3c9e45025274a20f60c6b60e62476b6afed0403af59ab83660ef4112ae20386a602010d0a5d454c0ed34c84ed4423e750213e6a2baab1bf9c4367a6007ab40a33d95220c2dcaa44f257024a5626b545db0510f4311b1a60714154909c6a61fdfca011fb2626d657aeb6070bf078508babe3b584555013e34acc56198ed4663742b3155a664a9901794c4586820a7dc162c01827291f3792e1237f801a8d1ef096013c181c4a58d2f6859ba75022d18cc4418bd4f351d5c18f83a58857d05af860c4b9ac018a5b63f17184e591532c6bc2cf2215d4a282c8a8a4f6f7aee110422c8bc9ebd3b1d609c568523aaae555db320e6c269473d87af38c256cbb9febc20aea6380c32a8916f7a373c8b1e37554e3260bf6621f6b804ee80b3c516b1d01985bf4c603b6daa9a5991de6a7a29f3a7122b8afb843a7660110fce62b43c615f5bcc2db688ba012649c0952b0a2c031e732d2b454c6b2968683cb8d244be2c9a7fa163222979eaf92722b92b862d81a3d94450c2b60c318421ebb4307c42d1f0473592a5c30e42039cc68cda9721e61aa63f49def17c15221680ed444896340133bbee67556f56b9f9d78a4df715f926a12add0cc9c862e46ea8b7316ae468282c18601b2771c9c9322f982228cf93effaacd3f80cbd12bce5fc36f56e2a3caf91e578a5fae00c9b23a8ed1a66764f4433c3628a70b8f0a6196adc60a4cb4226f07ba4c6b363fe9065563bfc1347452946386bab488686e837ab979c64f9047417fca635fe1bb4f074f256cc8af837c7b455e280426547755af90a61640169ef180aea3a77e662bb6dac1b6c3696027129b1a5edf495314e9c7f4b6110e16378ec893fa24642330a40aba1a85326101acb97c620fd8d71389e69eaed7bdb01bbe1fd428d66191150c7b2cd1ad4257391676a82ba8ce07fb2667c3b289f159003a7c7bc31d361b7b7f49a802961739d950dfcc0fa1c7abce5abdd2245101da391151490862028110465950b9e9c03d08a90998ab83267838d2e74a0593bc81f74cdf734519a05b351c0e5488c68dd810e6e9142ccc1e2f4a7f464297eb340e27acc6b9d64e12e38cce8492b3d939140b5a9e149a75597f10a23874c84323a07cdd657274378f887c85c4259b9c04cd33ba58ed630ef2a744f8e19dd34843dff331d2a6be7e2332c599289cd248a611c73d7481cd4a9bd43449a3836f14b2af18a1739e17999e4c67e85cc5bcecabb14185e5bcaff3c96098f03dc5aba819f29587758f49f940585354a2a780830528d68ccd166920dadcaa25cab5fc1907272a826aba3f08bc6b88757776812ecb6c7cec69a223ec0a13a7b62a2349a0f63ed7a27a3b15ba21d71fe6864ec6e089ae17cadd433fa3138f7ee24353c11365818f8fc34f43a05542d18efaac24bfccc1f748a0cc1a67ad379468b76fd34973dba785f5c91d618333cd810fe0700d1bbc8422029782628070a624c52c5309a4a64d625b11f8033ab28df34a1add297517fcc06b92b6817b3c5144438cf260867c57bde68c8c4b82e6a135ef676a52fbae5708002a404e6189a60e2836de565ad1b29e3819e5ed49f6810bcb28e1bd6de57306f94b79d9dae1cc4624d2a068499beef81cd5fe4b76dcbfff2a2008001d002001976128c6d5a934533f28b9914d2480aab2a8c1ab03d212529ce8b27640a716002d00020101002b000706caca03040303001b00030200015a5a000100\")\n\trequire.NoError(t, err)\n\tserverName := tf.IndexTLSServerName(payload)\n\trequire.NotNil(t, serverName)\n\trequire.Equal(t, serverName.ServerName, string(payload[serverName.Index:serverName.Index+serverName.Length]))\n\trequire.Equal(t, \"github.com\", serverName.ServerName)\n}\n"
  },
  {
    "path": "common/tlsfragment/wait_darwin.go",
    "content": "package tf\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common/control\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n/*\nconst tcpMaxNotifyAck = 10\n\ntype tcpNotifyAckID uint32\n\n\ttype tcpNotifyAckComplete struct {\n\t\tNotifyPending       uint32\n\t\tNotifyCompleteCount uint32\n\t\tNotifyCompleteID    [tcpMaxNotifyAck]tcpNotifyAckID\n\t}\n\nvar sizeOfTCPNotifyAckComplete = int(unsafe.Sizeof(tcpNotifyAckComplete{}))\n\n\tfunc getsockoptTCPNotifyAckComplete(fd, level, opt int) (*tcpNotifyAckComplete, error) {\n\t\tvar value tcpNotifyAckComplete\n\t\tvallen := uint32(sizeOfTCPNotifyAckComplete)\n\t\terr := getsockopt(fd, level, opt, unsafe.Pointer(&value), &vallen)\n\t\treturn &value, err\n\t}\n\n//go:linkname getsockopt golang.org/x/sys/unix.getsockopt\nfunc getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *uint32) error\n\n\tfunc waitAck(ctx context.Context, conn *net.TCPConn, _ time.Duration) error {\n\t\tconst TCP_NOTIFY_ACKNOWLEDGEMENT = 0x212\n\t\treturn control.Conn(conn, func(fd uintptr) error {\n\t\t\terr := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, TCP_NOTIFY_ACKNOWLEDGEMENT, 1)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, unix.EINVAL) {\n\t\t\t\t\treturn waitAckFallback(ctx, conn, 0)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn ctx.Err()\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t\tvar ackComplete *tcpNotifyAckComplete\n\t\t\t\tackComplete, err = getsockoptTCPNotifyAckComplete(int(fd), unix.IPPROTO_TCP, TCP_NOTIFY_ACKNOWLEDGEMENT)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif ackComplete.NotifyPending == 0 {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t}\n\t\t})\n\t}\n*/\n\nfunc writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {\n\t_, err := conn.Write(payload)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn control.Conn(conn, func(fd uintptr) error {\n\t\tstart := time.Now()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn ctx.Err()\n\t\t\tdefault:\n\t\t\t}\n\t\t\tunacked, err := unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_NWRITE)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif unacked == 0 {\n\t\t\t\tif time.Since(start) <= 20*time.Millisecond {\n\t\t\t\t\t// under transparent proxy\n\t\t\t\t\ttime.Sleep(fallbackDelay)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "common/tlsfragment/wait_linux.go",
    "content": "package tf\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common/control\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {\n\t_, err := conn.Write(payload)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn control.Conn(conn, func(fd uintptr) error {\n\t\tstart := time.Now()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn ctx.Err()\n\t\t\tdefault:\n\t\t\t}\n\t\t\ttcpInfo, err := unix.GetsockoptTCPInfo(int(fd), unix.IPPROTO_TCP, unix.TCP_INFO)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif tcpInfo.Unacked == 0 {\n\t\t\t\tif time.Since(start) <= 20*time.Millisecond {\n\t\t\t\t\t// under transparent proxy\n\t\t\t\t\ttime.Sleep(fallbackDelay)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "common/tlsfragment/wait_stub.go",
    "content": "//go:build !(linux || darwin || windows)\n\npackage tf\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n)\n\nfunc writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {\n\t_, err := conn.Write(payload)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttime.Sleep(fallbackDelay)\n\treturn nil\n}\n"
  },
  {
    "path": "common/tlsfragment/wait_windows.go",
    "content": "package tf\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common/winiphlpapi\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\nfunc writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {\n\tstart := time.Now()\n\terr := winiphlpapi.WriteAndWaitAck(ctx, conn, payload)\n\tif err != nil {\n\t\tif errors.Is(err, windows.ERROR_ACCESS_DENIED) {\n\t\t\tif _, err := conn.Write(payload); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ttime.Sleep(fallbackDelay)\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\tif time.Since(start) <= 20*time.Millisecond {\n\t\ttime.Sleep(fallbackDelay)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/uot/router.go",
    "content": "package uot\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/uot\"\n)\n\nvar _ adapter.ConnectionRouterEx = (*Router)(nil)\n\ntype Router struct {\n\trouter adapter.ConnectionRouterEx\n\tlogger logger.ContextLogger\n}\n\nfunc NewRouter(router adapter.ConnectionRouterEx, logger logger.ContextLogger) *Router {\n\treturn &Router{router, logger}\n}\n\nfunc (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {\n\tswitch metadata.Destination.Fqdn {\n\tcase uot.MagicAddress:\n\t\trequest, err := uot.ReadRequest(conn)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"read UoT request\")\n\t\t}\n\t\tif request.IsConnect {\n\t\t\tr.logger.InfoContext(ctx, \"inbound UoT connect connection to \", request.Destination)\n\t\t} else {\n\t\t\tr.logger.InfoContext(ctx, \"inbound UoT connection to \", request.Destination)\n\t\t}\n\t\tmetadata.Domain = metadata.Destination.Fqdn\n\t\tmetadata.Destination = request.Destination\n\t\treturn r.router.RoutePacketConnection(ctx, uot.NewConn(conn, *request), metadata)\n\tcase uot.LegacyMagicAddress:\n\t\tr.logger.InfoContext(ctx, \"inbound legacy UoT connection\")\n\t\tmetadata.Domain = metadata.Destination.Fqdn\n\t\tmetadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}\n\t\treturn r.RoutePacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)\n\t}\n\treturn r.router.RouteConnection(ctx, conn, metadata)\n}\n\nfunc (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {\n\treturn r.router.RoutePacketConnection(ctx, conn, metadata)\n}\n\nfunc (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tswitch metadata.Destination.Fqdn {\n\tcase uot.MagicAddress:\n\t\trequest, err := uot.ReadRequest(conn)\n\t\tif err != nil {\n\t\t\terr = E.Cause(err, \"UoT read request\")\n\t\t\tr.logger.ErrorContext(ctx, \"process connection from \", metadata.Source, \": \", err)\n\t\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\t\treturn\n\t\t}\n\t\tif request.IsConnect {\n\t\t\tr.logger.InfoContext(ctx, \"inbound UoT connect connection to \", request.Destination)\n\t\t} else {\n\t\t\tr.logger.InfoContext(ctx, \"inbound UoT connection to \", request.Destination)\n\t\t}\n\t\tmetadata.Domain = metadata.Destination.Fqdn\n\t\tmetadata.Destination = request.Destination\n\t\tr.router.RoutePacketConnectionEx(ctx, uot.NewConn(conn, *request), metadata, onClose)\n\t\treturn\n\tcase uot.LegacyMagicAddress:\n\t\tr.logger.InfoContext(ctx, \"inbound legacy UoT connection\")\n\t\tmetadata.Domain = metadata.Destination.Fqdn\n\t\tmetadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}\n\t\tr.RoutePacketConnectionEx(ctx, uot.NewConn(conn, uot.Request{}), metadata, onClose)\n\t\treturn\n\t}\n\tr.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tr.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "common/urltest/urltest.go",
    "content": "package urltest\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\t\"github.com/sagernet/sing/common/observable\"\n)\n\nvar _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)\n\ntype HistoryStorage struct {\n\taccess       sync.RWMutex\n\tdelayHistory map[string]*adapter.URLTestHistory\n\tupdateHook   *observable.Subscriber[struct{}]\n}\n\nfunc NewHistoryStorage() *HistoryStorage {\n\treturn &HistoryStorage{\n\t\tdelayHistory: make(map[string]*adapter.URLTestHistory),\n\t}\n}\n\nfunc (s *HistoryStorage) SetHook(hook *observable.Subscriber[struct{}]) {\n\ts.updateHook = hook\n}\n\nfunc (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory {\n\tif s == nil {\n\t\treturn nil\n\t}\n\ts.access.RLock()\n\tdefer s.access.RUnlock()\n\treturn s.delayHistory[tag]\n}\n\nfunc (s *HistoryStorage) DeleteURLTestHistory(tag string) {\n\ts.access.Lock()\n\tdelete(s.delayHistory, tag)\n\ts.notifyUpdated()\n\ts.access.Unlock()\n}\n\nfunc (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {\n\ts.access.Lock()\n\ts.delayHistory[tag] = history\n\ts.notifyUpdated()\n\ts.access.Unlock()\n}\n\nfunc (s *HistoryStorage) notifyUpdated() {\n\tupdateHook := s.updateHook\n\tif updateHook != nil {\n\t\tupdateHook.Emit(struct{}{})\n\t}\n}\n\nfunc (s *HistoryStorage) Close() error {\n\ts.access.Lock()\n\tdefer s.access.Unlock()\n\ts.updateHook = nil\n\treturn nil\n}\n\nfunc URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {\n\tif link == \"\" {\n\t\tlink = \"https://www.gstatic.com/generate_204\"\n\t}\n\tlinkURL, err := url.Parse(link)\n\tif err != nil {\n\t\treturn\n\t}\n\thostname := linkURL.Hostname()\n\tport := linkURL.Port()\n\tif port == \"\" {\n\t\tswitch linkURL.Scheme {\n\t\tcase \"http\":\n\t\t\tport = \"80\"\n\t\tcase \"https\":\n\t\t\tport = \"443\"\n\t\t}\n\t}\n\n\tstart := time.Now()\n\tinstance, err := detour.DialContext(ctx, \"tcp\", M.ParseSocksaddrHostPortStr(hostname, port))\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer instance.Close()\n\tif N.NeedHandshakeForWrite(instance) {\n\t\tstart = time.Now()\n\t}\n\treq, err := http.NewRequest(http.MethodHead, link, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\tclient := http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\treturn instance, nil\n\t\t\t},\n\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\tTime:    ntp.TimeFuncFromContext(ctx),\n\t\t\t\tRootCAs: adapter.RootPoolFromContext(ctx),\n\t\t\t},\n\t\t},\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\treturn http.ErrUseLastResponse\n\t\t},\n\t\tTimeout: C.TCPTimeout,\n\t}\n\tdefer client.CloseIdleConnections()\n\tresp, err := client.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\treturn\n\t}\n\tresp.Body.Close()\n\tt = uint16(time.Since(start) / time.Millisecond)\n\treturn\n}\n"
  },
  {
    "path": "constant/certificate.go",
    "content": "package constant\n\nconst (\n\tCertificateStoreSystem  = \"system\"\n\tCertificateStoreMozilla = \"mozilla\"\n\tCertificateStoreChrome  = \"chrome\"\n\tCertificateStoreNone    = \"none\"\n)\n"
  },
  {
    "path": "constant/cgo.go",
    "content": "//go:build cgo\n\npackage constant\n\nconst CGO_ENABLED = true\n"
  },
  {
    "path": "constant/cgo_disabled.go",
    "content": "//go:build !cgo\n\npackage constant\n\nconst CGO_ENABLED = false\n"
  },
  {
    "path": "constant/dhcp.go",
    "content": "package constant\n\nimport \"time\"\n\nconst (\n\tDHCPTTL     = time.Hour\n\tDHCPTimeout = 5 * time.Second\n)\n"
  },
  {
    "path": "constant/dns.go",
    "content": "package constant\n\nconst (\n\tDefaultDNSTTL = 600\n)\n\ntype DomainStrategy = uint8\n\nconst (\n\tDomainStrategyAsIS DomainStrategy = iota\n\tDomainStrategyPreferIPv4\n\tDomainStrategyPreferIPv6\n\tDomainStrategyIPv4Only\n\tDomainStrategyIPv6Only\n)\n\nconst (\n\tDNSTypeLegacy      = \"legacy\"\n\tDNSTypeLegacyRcode = \"legacy_rcode\"\n\tDNSTypeUDP         = \"udp\"\n\tDNSTypeTCP         = \"tcp\"\n\tDNSTypeTLS         = \"tls\"\n\tDNSTypeHTTPS       = \"https\"\n\tDNSTypeQUIC        = \"quic\"\n\tDNSTypeHTTP3       = \"h3\"\n\tDNSTypeLocal       = \"local\"\n\tDNSTypeHosts       = \"hosts\"\n\tDNSTypeFakeIP      = \"fakeip\"\n\tDNSTypeDHCP        = \"dhcp\"\n\tDNSTypeTailscale   = \"tailscale\"\n)\n\nconst (\n\tDNSProviderAliDNS     = \"alidns\"\n\tDNSProviderCloudflare = \"cloudflare\"\n\tDNSProviderACMEDNS    = \"acmedns\"\n)\n"
  },
  {
    "path": "constant/err.go",
    "content": "package constant\n\nimport E \"github.com/sagernet/sing/common/exceptions\"\n\nvar ErrTLSRequired = E.New(\"TLS required\")\n\nvar ErrQUICNotIncluded = E.New(`QUIC is not included in this build, rebuild with -tags with_quic`)\n"
  },
  {
    "path": "constant/goos/gengoos.go",
    "content": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build ignore\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar gooses []string\n\nfunc main() {\n\tdata, err := os.ReadFile(\"../../go/build/syslist.go\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tconst goosPrefix = `const goosList = `\n\tfor _, line := range strings.Split(string(data), \"\\n\") {\n\t\tif strings.HasPrefix(line, goosPrefix) {\n\t\t\ttext, err := strconv.Unquote(strings.TrimPrefix(line, goosPrefix))\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"parsing goosList: %v\", err)\n\t\t\t}\n\t\t\tgooses = strings.Fields(text)\n\t\t}\n\t}\n\n\tfor _, target := range gooses {\n\t\tif target == \"nacl\" {\n\t\t\tcontinue\n\t\t}\n\t\tvar tags []string\n\t\tif target == \"linux\" {\n\t\t\ttags = append(tags, \"!android\") // must explicitly exclude android for linux\n\t\t}\n\t\tif target == \"solaris\" {\n\t\t\ttags = append(tags, \"!illumos\") // must explicitly exclude illumos for solaris\n\t\t}\n\t\tif target == \"darwin\" {\n\t\t\ttags = append(tags, \"!ios\") // must explicitly exclude ios for darwin\n\t\t}\n\t\ttags = append(tags, target) // must explicitly include target for bootstrapping purposes\n\t\tvar buf bytes.Buffer\n\t\tfmt.Fprintf(&buf, \"// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\\n\\n\")\n\t\tfmt.Fprintf(&buf, \"//go:build %s\\n\", strings.Join(tags, \" && \"))\n\t\tfmt.Fprintf(&buf, \"package goos\\n\\n\")\n\t\tfmt.Fprintf(&buf, \"const GOOS = `%s`\\n\\n\", target)\n\t\tfor _, goos := range gooses {\n\t\t\tvalue := 0\n\t\t\tif goos == target {\n\t\t\t\tvalue = 1\n\t\t\t}\n\t\t\tfmt.Fprintf(&buf, \"const Is%s = %d\\n\", strings.Title(goos), value)\n\t\t}\n\t\terr := os.WriteFile(\"zgoos_\"+target+\".go\", buf.Bytes(), 0o666)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "constant/goos/goos.go",
    "content": "// Copyright 2015 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// package goos contains GOOS-specific constants.\npackage goos\n\n// The next line makes 'go generate' write the zgoos*.go files with\n// per-OS information, including constants named Is$GOOS for every\n// known GOOS. The constant is 1 on the current system, 0 otherwise;\n// multiplying by them is useful for defining GOOS-specific constants.\n//go:generate go run gengoos.go\n"
  },
  {
    "path": "constant/goos/zgoos_aix.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build aix\n\npackage goos\n\nconst GOOS = `aix`\n\nconst IsAix = 1\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_android.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build android\n\npackage goos\n\nconst GOOS = `android`\n\nconst IsAix = 0\nconst IsAndroid = 1\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_darwin.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build !ios && darwin\n\npackage goos\n\nconst GOOS = `darwin`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 1\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_dragonfly.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build dragonfly\n\npackage goos\n\nconst GOOS = `dragonfly`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 1\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_freebsd.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build freebsd\n\npackage goos\n\nconst GOOS = `freebsd`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 1\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_hurd.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build hurd\n\npackage goos\n\nconst GOOS = `hurd`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 1\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_illumos.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build illumos\n\npackage goos\n\nconst GOOS = `illumos`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 1\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_ios.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build ios\n\npackage goos\n\nconst GOOS = `ios`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 1\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_js.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build js\n\npackage goos\n\nconst GOOS = `js`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 1\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_linux.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build !android && linux\n\npackage goos\n\nconst GOOS = `linux`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 1\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_netbsd.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build netbsd\n\npackage goos\n\nconst GOOS = `netbsd`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 1\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_openbsd.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build openbsd\n\npackage goos\n\nconst GOOS = `openbsd`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 1\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_plan9.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build plan9\n\npackage goos\n\nconst GOOS = `plan9`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 1\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_solaris.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build !illumos && solaris\n\npackage goos\n\nconst GOOS = `solaris`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 1\nconst IsWindows = 0\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_windows.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build windows\n\npackage goos\n\nconst GOOS = `windows`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 1\nconst IsZos = 0\n"
  },
  {
    "path": "constant/goos/zgoos_zos.go",
    "content": "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n//go:build zos\n\npackage goos\n\nconst GOOS = `zos`\n\nconst IsAix = 0\nconst IsAndroid = 0\nconst IsDarwin = 0\nconst IsDragonfly = 0\nconst IsFreebsd = 0\nconst IsHurd = 0\nconst IsIllumos = 0\nconst IsIos = 0\nconst IsJs = 0\nconst IsLinux = 0\nconst IsNacl = 0\nconst IsNetbsd = 0\nconst IsOpenbsd = 0\nconst IsPlan9 = 0\nconst IsSolaris = 0\nconst IsWindows = 0\nconst IsZos = 1\n"
  },
  {
    "path": "constant/hysteria2.go",
    "content": "package constant\n\nconst (\n\tHysterai2MasqueradeTypeFile   = \"file\"\n\tHysterai2MasqueradeTypeProxy  = \"proxy\"\n\tHysterai2MasqueradeTypeString = \"string\"\n)\n"
  },
  {
    "path": "constant/network.go",
    "content": "package constant\n\nimport (\n\t\"github.com/sagernet/sing/common\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\ntype InterfaceType uint8\n\nconst (\n\tInterfaceTypeWIFI InterfaceType = iota\n\tInterfaceTypeCellular\n\tInterfaceTypeEthernet\n\tInterfaceTypeOther\n)\n\nvar (\n\tinterfaceTypeToString = map[InterfaceType]string{\n\t\tInterfaceTypeWIFI:     \"wifi\",\n\t\tInterfaceTypeCellular: \"cellular\",\n\t\tInterfaceTypeEthernet: \"ethernet\",\n\t\tInterfaceTypeOther:    \"other\",\n\t}\n\tStringToInterfaceType = common.ReverseMap(interfaceTypeToString)\n)\n\nfunc (t InterfaceType) String() string {\n\tname, loaded := interfaceTypeToString[t]\n\tif !loaded {\n\t\treturn F.ToString(int(t))\n\t}\n\treturn name\n}\n\ntype NetworkStrategy uint8\n\nconst (\n\tNetworkStrategyDefault NetworkStrategy = iota\n\tNetworkStrategyFallback\n\tNetworkStrategyHybrid\n)\n\nvar (\n\tnetworkStrategyToString = map[NetworkStrategy]string{\n\t\tNetworkStrategyDefault:  \"default\",\n\t\tNetworkStrategyFallback: \"fallback\",\n\t\tNetworkStrategyHybrid:   \"hybrid\",\n\t}\n\tStringToNetworkStrategy = common.ReverseMap(networkStrategyToString)\n)\n\nfunc (s NetworkStrategy) String() string {\n\tname, loaded := networkStrategyToString[s]\n\tif !loaded {\n\t\treturn F.ToString(int(s))\n\t}\n\treturn name\n}\n"
  },
  {
    "path": "constant/os.go",
    "content": "package constant\n\nimport (\n\t\"github.com/sagernet/sing-box/constant/goos\"\n)\n\nconst IsAndroid = goos.IsAndroid == 1\n\nconst IsDarwin = goos.IsDarwin == 1 || goos.IsIos == 1\n\nconst IsDragonfly = goos.IsDragonfly == 1\n\nconst IsFreebsd = goos.IsFreebsd == 1\n\nconst IsHurd = goos.IsHurd == 1\n\nconst IsIllumos = goos.IsIllumos == 1\n\nconst IsIos = goos.IsIos == 1\n\nconst IsJs = goos.IsJs == 1\n\nconst IsLinux = goos.IsLinux == 1 || goos.IsAndroid == 1\n\nconst IsNacl = goos.IsNacl == 1\n\nconst IsNetbsd = goos.IsNetbsd == 1\n\nconst IsOpenbsd = goos.IsOpenbsd == 1\n\nconst IsPlan9 = goos.IsPlan9 == 1\n\nconst IsSolaris = goos.IsSolaris == 1\n\nconst IsWindows = goos.IsWindows == 1\n\nconst IsZos = goos.IsZos == 1\n"
  },
  {
    "path": "constant/path.go",
    "content": "package constant\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/sagernet/sing/common/rw\"\n)\n\nconst dirName = \"sing-box\"\n\nvar resourcePaths []string\n\nfunc FindPath(name string) (string, bool) {\n\tname = os.ExpandEnv(name)\n\tif rw.IsFile(name) {\n\t\treturn name, true\n\t}\n\tfor _, dir := range resourcePaths {\n\t\tif path := filepath.Join(dir, dirName, name); rw.IsFile(path) {\n\t\t\treturn path, true\n\t\t}\n\t\tif path := filepath.Join(dir, name); rw.IsFile(path) {\n\t\t\treturn path, true\n\t\t}\n\t}\n\treturn name, false\n}\n\nfunc init() {\n\tresourcePaths = append(resourcePaths, \".\")\n\tif home := os.Getenv(\"HOME\"); home != \"\" {\n\t\tresourcePaths = append(resourcePaths, home)\n\t}\n\tif userConfigDir, err := os.UserConfigDir(); err == nil {\n\t\tresourcePaths = append(resourcePaths, userConfigDir)\n\t}\n\tif userCacheDir, err := os.UserCacheDir(); err == nil {\n\t\tresourcePaths = append(resourcePaths, userCacheDir)\n\t}\n}\n"
  },
  {
    "path": "constant/path_unix.go",
    "content": "//go:build unix || linux\n\npackage constant\n\nimport (\n\t\"os\"\n)\n\nfunc init() {\n\tresourcePaths = append(resourcePaths, \"/etc\")\n\tresourcePaths = append(resourcePaths, \"/usr/share\")\n\tresourcePaths = append(resourcePaths, \"/usr/local/etc\")\n\tresourcePaths = append(resourcePaths, \"/usr/local/share\")\n\tif homeDir := os.Getenv(\"HOME\"); homeDir != \"\" {\n\t\tresourcePaths = append(resourcePaths, homeDir+\"/.local/share\")\n\t}\n}\n"
  },
  {
    "path": "constant/protocol.go",
    "content": "package constant\n\nconst (\n\tProtocolTLS        = \"tls\"\n\tProtocolHTTP       = \"http\"\n\tProtocolQUIC       = \"quic\"\n\tProtocolDNS        = \"dns\"\n\tProtocolSTUN       = \"stun\"\n\tProtocolBitTorrent = \"bittorrent\"\n\tProtocolDTLS       = \"dtls\"\n\tProtocolSSH        = \"ssh\"\n\tProtocolRDP        = \"rdp\"\n\tProtocolNTP        = \"ntp\"\n)\n\nconst (\n\tClientChromium = \"chromium\"\n\tClientSafari   = \"safari\"\n\tClientFirefox  = \"firefox\"\n\tClientQUICGo   = \"quic-go\"\n\tClientUnknown  = \"unknown\"\n)\n"
  },
  {
    "path": "constant/proxy.go",
    "content": "package constant\n\nconst (\n\tTypeTun          = \"tun\"\n\tTypeRedirect     = \"redirect\"\n\tTypeTProxy       = \"tproxy\"\n\tTypeDirect       = \"direct\"\n\tTypeBlock        = \"block\"\n\tTypeDNS          = \"dns\"\n\tTypeSOCKS        = \"socks\"\n\tTypeHTTP         = \"http\"\n\tTypeMixed        = \"mixed\"\n\tTypeShadowsocks  = \"shadowsocks\"\n\tTypeVMess        = \"vmess\"\n\tTypeTrojan       = \"trojan\"\n\tTypeNaive        = \"naive\"\n\tTypeWireGuard    = \"wireguard\"\n\tTypeHysteria     = \"hysteria\"\n\tTypeTor          = \"tor\"\n\tTypeSSH          = \"ssh\"\n\tTypeShadowTLS    = \"shadowtls\"\n\tTypeAnyTLS       = \"anytls\"\n\tTypeShadowsocksR = \"shadowsocksr\"\n\tTypeVLESS        = \"vless\"\n\tTypeTUIC         = \"tuic\"\n\tTypeHysteria2    = \"hysteria2\"\n\tTypeTailscale    = \"tailscale\"\n\tTypeDERP         = \"derp\"\n\tTypeResolved     = \"resolved\"\n\tTypeSSMAPI       = \"ssm-api\"\n\tTypeCCM          = \"ccm\"\n\tTypeOCM          = \"ocm\"\n\tTypeOOMKiller    = \"oom-killer\"\n)\n\nconst (\n\tTypeSelector = \"selector\"\n\tTypeURLTest  = \"urltest\"\n)\n\nfunc ProxyDisplayName(proxyType string) string {\n\tswitch proxyType {\n\tcase TypeTun:\n\t\treturn \"TUN\"\n\tcase TypeRedirect:\n\t\treturn \"Redirect\"\n\tcase TypeTProxy:\n\t\treturn \"TProxy\"\n\tcase TypeDirect:\n\t\treturn \"Direct\"\n\tcase TypeBlock:\n\t\treturn \"Block\"\n\tcase TypeDNS:\n\t\treturn \"DNS\"\n\tcase TypeSOCKS:\n\t\treturn \"SOCKS\"\n\tcase TypeHTTP:\n\t\treturn \"HTTP\"\n\tcase TypeMixed:\n\t\treturn \"Mixed\"\n\tcase TypeShadowsocks:\n\t\treturn \"Shadowsocks\"\n\tcase TypeVMess:\n\t\treturn \"VMess\"\n\tcase TypeTrojan:\n\t\treturn \"Trojan\"\n\tcase TypeNaive:\n\t\treturn \"Naive\"\n\tcase TypeWireGuard:\n\t\treturn \"WireGuard\"\n\tcase TypeHysteria:\n\t\treturn \"Hysteria\"\n\tcase TypeTor:\n\t\treturn \"Tor\"\n\tcase TypeSSH:\n\t\treturn \"SSH\"\n\tcase TypeShadowTLS:\n\t\treturn \"ShadowTLS\"\n\tcase TypeShadowsocksR:\n\t\treturn \"ShadowsocksR\"\n\tcase TypeVLESS:\n\t\treturn \"VLESS\"\n\tcase TypeTUIC:\n\t\treturn \"TUIC\"\n\tcase TypeHysteria2:\n\t\treturn \"Hysteria2\"\n\tcase TypeAnyTLS:\n\t\treturn \"AnyTLS\"\n\tcase TypeTailscale:\n\t\treturn \"Tailscale\"\n\tcase TypeSelector:\n\t\treturn \"Selector\"\n\tcase TypeURLTest:\n\t\treturn \"URLTest\"\n\tdefault:\n\t\treturn \"Unknown\"\n\t}\n}\n"
  },
  {
    "path": "constant/quic.go",
    "content": "//go:build with_quic\n\npackage constant\n\nconst WithQUIC = true\n"
  },
  {
    "path": "constant/quic_stub.go",
    "content": "//go:build !with_quic\n\npackage constant\n\nconst WithQUIC = false\n"
  },
  {
    "path": "constant/rule.go",
    "content": "package constant\n\nconst (\n\tRuleTypeDefault = \"default\"\n\tRuleTypeLogical = \"logical\"\n)\n\nconst (\n\tLogicalTypeAnd = \"and\"\n\tLogicalTypeOr  = \"or\"\n)\n\nconst (\n\tRuleSetTypeInline   = \"inline\"\n\tRuleSetTypeLocal    = \"local\"\n\tRuleSetTypeRemote   = \"remote\"\n\tRuleSetFormatSource = \"source\"\n\tRuleSetFormatBinary = \"binary\"\n)\n\nconst (\n\tRuleSetVersion1 = 1 + iota\n\tRuleSetVersion2\n\tRuleSetVersion3\n\tRuleSetVersion4\n\tRuleSetVersionCurrent = RuleSetVersion4\n)\n\nconst (\n\tRuleActionTypeRoute        = \"route\"\n\tRuleActionTypeRouteOptions = \"route-options\"\n\tRuleActionTypeDirect       = \"direct\"\n\tRuleActionTypeBypass       = \"bypass\"\n\tRuleActionTypeReject       = \"reject\"\n\tRuleActionTypeHijackDNS    = \"hijack-dns\"\n\tRuleActionTypeSniff        = \"sniff\"\n\tRuleActionTypeResolve      = \"resolve\"\n\tRuleActionTypePredefined   = \"predefined\"\n)\n\nconst (\n\tRuleActionRejectMethodDefault = \"default\"\n\tRuleActionRejectMethodDrop    = \"drop\"\n\tRuleActionRejectMethodReply   = \"reply\"\n)\n"
  },
  {
    "path": "constant/speed.go",
    "content": "package constant\n\nconst MbpsToBps = 125000\n"
  },
  {
    "path": "constant/time.go",
    "content": "package constant\n\nconst TimeLayout = \"2006-01-02 15:04:05 -0700\"\n"
  },
  {
    "path": "constant/timeout.go",
    "content": "package constant\n\nimport \"time\"\n\nconst (\n\tTCPKeepAliveInitial        = 5 * time.Minute\n\tTCPKeepAliveInterval       = 75 * time.Second\n\tTCPConnectTimeout          = 5 * time.Second\n\tTCPTimeout                 = 15 * time.Second\n\tReadPayloadTimeout         = 300 * time.Millisecond\n\tDNSTimeout                 = 10 * time.Second\n\tUDPTimeout                 = 5 * time.Minute\n\tDefaultURLTestInterval     = 3 * time.Minute\n\tDefaultURLTestIdleTimeout  = 30 * time.Minute\n\tStartTimeout               = 10 * time.Second\n\tStopTimeout                = 5 * time.Second\n\tFatalStopTimeout           = 10 * time.Second\n\tFakeIPMetadataSaveInterval = 10 * time.Second\n\tTLSFragmentFallbackDelay   = 500 * time.Millisecond\n)\n\nvar PortProtocols = map[uint16]string{\n\t53:   ProtocolDNS,\n\t123:  ProtocolNTP,\n\t3478: ProtocolSTUN,\n\t443:  ProtocolQUIC,\n}\n\nvar ProtocolTimeouts = map[string]time.Duration{\n\tProtocolDNS:  10 * time.Second,\n\tProtocolNTP:  10 * time.Second,\n\tProtocolSTUN: 10 * time.Second,\n\tProtocolQUIC: 30 * time.Second,\n\tProtocolDTLS: 30 * time.Second,\n}\n"
  },
  {
    "path": "constant/v2ray.go",
    "content": "package constant\n\nconst (\n\tV2RayTransportTypeHTTP        = \"http\"\n\tV2RayTransportTypeWebsocket   = \"ws\"\n\tV2RayTransportTypeQUIC        = \"quic\"\n\tV2RayTransportTypeGRPC        = \"grpc\"\n\tV2RayTransportTypeHTTPUpgrade = \"httpupgrade\"\n)\n"
  },
  {
    "path": "constant/version.go",
    "content": "package constant\n\nvar Version = \"unknown\"\n"
  },
  {
    "path": "daemon/deprecated.go",
    "content": "package daemon\n\nimport (\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/experimental/deprecated\"\n\t\"github.com/sagernet/sing/common\"\n)\n\nvar _ deprecated.Manager = (*deprecatedManager)(nil)\n\ntype deprecatedManager struct {\n\taccess sync.Mutex\n\tnotes  []deprecated.Note\n}\n\nfunc (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tm.notes = common.Uniq(append(m.notes, feature))\n}\n\nfunc (m *deprecatedManager) Get() []deprecated.Note {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tnotes := m.notes\n\tm.notes = nil\n\treturn notes\n}\n"
  },
  {
    "path": "daemon/instance.go",
    "content": "package daemon\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/urltest\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/experimental/deprecated\"\n\t\"github.com/sagernet/sing-box/include\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/pause\"\n)\n\ntype Instance struct {\n\tctx                   context.Context\n\tcancel                context.CancelFunc\n\tinstance              *box.Box\n\tconnectionManager     adapter.ConnectionManager\n\tclashServer           adapter.ClashServer\n\tcacheFile             adapter.CacheFile\n\tpauseManager          pause.Manager\n\turlTestHistoryStorage *urltest.HistoryStorage\n}\n\nfunc (s *StartedService) CheckConfig(configContent string) error {\n\toptions, err := parseConfig(s.ctx, configContent)\n\tif err != nil {\n\t\treturn err\n\t}\n\tctx, cancel := context.WithCancel(s.ctx)\n\tdefer cancel()\n\tinstance, err := box.New(box.Options{\n\t\tContext: ctx,\n\t\tOptions: options,\n\t})\n\tif err == nil {\n\t\tinstance.Close()\n\t}\n\treturn err\n}\n\nfunc (s *StartedService) FormatConfig(configContent string) (string, error) {\n\toptions, err := parseConfig(s.ctx, configContent)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tvar buffer bytes.Buffer\n\tencoder := json.NewEncoder(&buffer)\n\tencoder.SetIndent(\"\", \"  \")\n\terr = encoder.Encode(options)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn buffer.String(), nil\n}\n\ntype OverrideOptions struct {\n\tAutoRedirect   bool\n\tIncludePackage []string\n\tExcludePackage []string\n}\n\nfunc (s *StartedService) newInstance(profileContent string, overrideOptions *OverrideOptions) (*Instance, error) {\n\tctx := s.ctx\n\tservice.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))\n\tctx, cancel := context.WithCancel(include.Context(ctx))\n\toptions, err := parseConfig(ctx, profileContent)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, err\n\t}\n\tif overrideOptions != nil {\n\t\tfor _, inbound := range options.Inbounds {\n\t\t\tif tunInboundOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN {\n\t\t\t\ttunInboundOptions.AutoRedirect = overrideOptions.AutoRedirect\n\t\t\t\ttunInboundOptions.IncludePackage = append(tunInboundOptions.IncludePackage, overrideOptions.IncludePackage...)\n\t\t\t\ttunInboundOptions.ExcludePackage = append(tunInboundOptions.ExcludePackage, overrideOptions.ExcludePackage...)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif s.oomKiller && C.IsIos {\n\t\tif !common.Any(options.Services, func(it option.Service) bool {\n\t\t\treturn it.Type == C.TypeOOMKiller\n\t\t}) {\n\t\t\toptions.Services = append(options.Services, option.Service{\n\t\t\t\tType: C.TypeOOMKiller,\n\t\t\t})\n\t\t}\n\t}\n\turlTestHistoryStorage := urltest.NewHistoryStorage()\n\tctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)\n\ti := &Instance{\n\t\tctx:                   ctx,\n\t\tcancel:                cancel,\n\t\turlTestHistoryStorage: urlTestHistoryStorage,\n\t}\n\tboxInstance, err := box.New(box.Options{\n\t\tContext:           ctx,\n\t\tOptions:           options,\n\t\tPlatformLogWriter: s,\n\t})\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, err\n\t}\n\ti.instance = boxInstance\n\ti.connectionManager = service.FromContext[adapter.ConnectionManager](ctx)\n\ti.clashServer = service.FromContext[adapter.ClashServer](ctx)\n\ti.pauseManager = service.FromContext[pause.Manager](ctx)\n\ti.cacheFile = service.FromContext[adapter.CacheFile](ctx)\n\tlog.SetStdLogger(boxInstance.LogFactory().Logger())\n\treturn i, nil\n}\n\nfunc (i *Instance) Start() error {\n\treturn i.instance.Start()\n}\n\nfunc (i *Instance) Close() error {\n\ti.cancel()\n\ti.urlTestHistoryStorage.Close()\n\treturn i.instance.Close()\n}\n\nfunc (i *Instance) Box() *box.Box {\n\treturn i.instance\n}\n\nfunc (i *Instance) PauseManager() pause.Manager {\n\treturn i.pauseManager\n}\n\nfunc parseConfig(ctx context.Context, configContent string) (option.Options, error) {\n\toptions, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))\n\tif err != nil {\n\t\treturn option.Options{}, E.Cause(err, \"decode config\")\n\t}\n\treturn options, nil\n}\n"
  },
  {
    "path": "daemon/platform.go",
    "content": "package daemon\n\ntype PlatformHandler interface {\n\tServiceStop() error\n\tServiceReload() error\n\tSystemProxyStatus() (*SystemProxyStatus, error)\n\tSetSystemProxyEnabled(enabled bool) error\n\tWriteDebugMessage(message string)\n}\n"
  },
  {
    "path": "daemon/started_service.go",
    "content": "package daemon\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"runtime\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/urltest\"\n\t\"github.com/sagernet/sing-box/experimental/clashapi\"\n\t\"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol\"\n\t\"github.com/sagernet/sing-box/experimental/deprecated\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/protocol/group\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/batch\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/memory\"\n\t\"github.com/sagernet/sing/common/observable\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"github.com/gofrs/uuid/v5\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nvar _ StartedServiceServer = (*StartedService)(nil)\n\ntype StartedService struct {\n\tctx context.Context\n\t// platform adapter.PlatformInterface\n\thandler     PlatformHandler\n\tdebug       bool\n\tlogMaxLines int\n\toomKiller   bool\n\t// workingDirectory string\n\t// tempDirectory    string\n\t// userID           int\n\t// groupID          int\n\t// systemProxyEnabled      bool\n\tserviceAccess           sync.RWMutex\n\tserviceStatus           *ServiceStatus\n\tserviceStatusSubscriber *observable.Subscriber[*ServiceStatus]\n\tserviceStatusObserver   *observable.Observer[*ServiceStatus]\n\tlogAccess               sync.RWMutex\n\tlogLines                list.List[*log.Entry]\n\tlogSubscriber           *observable.Subscriber[*log.Entry]\n\tlogObserver             *observable.Observer[*log.Entry]\n\tinstance                *Instance\n\tstartedAt               time.Time\n\turlTestSubscriber       *observable.Subscriber[struct{}]\n\turlTestObserver         *observable.Observer[struct{}]\n\turlTestHistoryStorage   *urltest.HistoryStorage\n\tclashModeSubscriber     *observable.Subscriber[struct{}]\n\tclashModeObserver       *observable.Observer[struct{}]\n\n\tconnectionEventSubscriber *observable.Subscriber[trafficontrol.ConnectionEvent]\n\tconnectionEventObserver   *observable.Observer[trafficontrol.ConnectionEvent]\n}\n\ntype ServiceOptions struct {\n\tContext context.Context\n\t// Platform           adapter.PlatformInterface\n\tHandler     PlatformHandler\n\tDebug       bool\n\tLogMaxLines int\n\tOOMKiller   bool\n\t// WorkingDirectory   string\n\t// TempDirectory      string\n\t// UserID             int\n\t// GroupID            int\n\t// SystemProxyEnabled bool\n}\n\nfunc NewStartedService(options ServiceOptions) *StartedService {\n\ts := &StartedService{\n\t\tctx: options.Context,\n\t\t// platform:                options.Platform,\n\t\thandler:     options.Handler,\n\t\tdebug:       options.Debug,\n\t\tlogMaxLines: options.LogMaxLines,\n\t\toomKiller:   options.OOMKiller,\n\t\t// workingDirectory: options.WorkingDirectory,\n\t\t// tempDirectory:    options.TempDirectory,\n\t\t// userID:           options.UserID,\n\t\t// groupID:          options.GroupID,\n\t\t// systemProxyEnabled:      options.SystemProxyEnabled,\n\t\tserviceStatus:             &ServiceStatus{Status: ServiceStatus_IDLE},\n\t\tserviceStatusSubscriber:   observable.NewSubscriber[*ServiceStatus](4),\n\t\tlogSubscriber:             observable.NewSubscriber[*log.Entry](128),\n\t\turlTestSubscriber:         observable.NewSubscriber[struct{}](1),\n\t\turlTestHistoryStorage:     urltest.NewHistoryStorage(),\n\t\tclashModeSubscriber:       observable.NewSubscriber[struct{}](1),\n\t\tconnectionEventSubscriber: observable.NewSubscriber[trafficontrol.ConnectionEvent](256),\n\t}\n\ts.serviceStatusObserver = observable.NewObserver(s.serviceStatusSubscriber, 2)\n\ts.logObserver = observable.NewObserver(s.logSubscriber, 64)\n\ts.urlTestObserver = observable.NewObserver(s.urlTestSubscriber, 1)\n\ts.clashModeObserver = observable.NewObserver(s.clashModeSubscriber, 1)\n\ts.connectionEventObserver = observable.NewObserver(s.connectionEventSubscriber, 64)\n\treturn s\n}\n\nfunc (s *StartedService) resetLogs() {\n\ts.logAccess.Lock()\n\ts.logLines = list.List[*log.Entry]{}\n\ts.logAccess.Unlock()\n\ts.logSubscriber.Emit(nil)\n}\n\nfunc (s *StartedService) updateStatus(newStatus ServiceStatus_Type) {\n\tstatusObject := &ServiceStatus{Status: newStatus}\n\ts.serviceStatusSubscriber.Emit(statusObject)\n\ts.serviceStatus = statusObject\n}\n\nfunc (s *StartedService) updateStatusError(err error) error {\n\tstatusObject := &ServiceStatus{Status: ServiceStatus_FATAL, ErrorMessage: err.Error()}\n\ts.serviceStatusSubscriber.Emit(statusObject)\n\ts.serviceStatus = statusObject\n\ts.serviceAccess.Unlock()\n\treturn err\n}\n\nfunc (s *StartedService) waitForStarted(ctx context.Context) error {\n\ts.serviceAccess.RLock()\n\tcurrentStatus := s.serviceStatus.Status\n\ts.serviceAccess.RUnlock()\n\n\tswitch currentStatus {\n\tcase ServiceStatus_STARTED:\n\t\treturn nil\n\tcase ServiceStatus_STARTING:\n\tdefault:\n\t\treturn os.ErrInvalid\n\t}\n\n\tsubscription, done, err := s.serviceStatusObserver.Subscribe()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer s.serviceStatusObserver.UnSubscribe(subscription)\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tcase <-s.ctx.Done():\n\t\t\treturn s.ctx.Err()\n\t\tcase status := <-subscription:\n\t\t\tswitch status.Status {\n\t\t\tcase ServiceStatus_STARTED:\n\t\t\t\treturn nil\n\t\t\tcase ServiceStatus_FATAL:\n\t\t\t\treturn E.New(status.ErrorMessage)\n\t\t\tcase ServiceStatus_IDLE, ServiceStatus_STOPPING:\n\t\t\t\treturn os.ErrInvalid\n\t\t\t}\n\t\tcase <-done:\n\t\t\treturn os.ErrClosed\n\t\t}\n\t}\n}\n\nfunc (s *StartedService) StartOrReloadService(profileContent string, options *OverrideOptions) error {\n\ts.serviceAccess.Lock()\n\tswitch s.serviceStatus.Status {\n\tcase ServiceStatus_IDLE, ServiceStatus_STARTED, ServiceStatus_STARTING:\n\tdefault:\n\t\ts.serviceAccess.Unlock()\n\t\treturn os.ErrInvalid\n\t}\n\toldInstance := s.instance\n\tif oldInstance != nil {\n\t\ts.updateStatus(ServiceStatus_STOPPING)\n\t\ts.serviceAccess.Unlock()\n\t\t_ = oldInstance.Close()\n\t\ts.serviceAccess.Lock()\n\t}\n\ts.updateStatus(ServiceStatus_STARTING)\n\ts.resetLogs()\n\tinstance, err := s.newInstance(profileContent, options)\n\tif err != nil {\n\t\treturn s.updateStatusError(err)\n\t}\n\ts.instance = instance\n\tinstance.urlTestHistoryStorage.SetHook(s.urlTestSubscriber)\n\tif instance.clashServer != nil {\n\t\tinstance.clashServer.SetModeUpdateHook(s.clashModeSubscriber)\n\t\tinstance.clashServer.(*clashapi.Server).TrafficManager().SetEventHook(s.connectionEventSubscriber)\n\t}\n\ts.serviceAccess.Unlock()\n\terr = instance.Start()\n\ts.serviceAccess.Lock()\n\tif s.serviceStatus.Status != ServiceStatus_STARTING {\n\t\ts.serviceAccess.Unlock()\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\treturn s.updateStatusError(err)\n\t}\n\ts.startedAt = time.Now()\n\ts.updateStatus(ServiceStatus_STARTED)\n\ts.serviceAccess.Unlock()\n\truntime.GC()\n\treturn nil\n}\n\nfunc (s *StartedService) Close() {\n\ts.serviceStatusSubscriber.Close()\n\ts.logSubscriber.Close()\n\ts.urlTestSubscriber.Close()\n\ts.clashModeSubscriber.Close()\n\ts.connectionEventSubscriber.Close()\n}\n\nfunc (s *StartedService) CloseService() error {\n\ts.serviceAccess.Lock()\n\tswitch s.serviceStatus.Status {\n\tcase ServiceStatus_STARTING, ServiceStatus_STARTED:\n\tdefault:\n\t\ts.serviceAccess.Unlock()\n\t\treturn os.ErrInvalid\n\t}\n\ts.updateStatus(ServiceStatus_STOPPING)\n\tif s.instance != nil {\n\t\terr := s.instance.Close()\n\t\tif err != nil {\n\t\t\treturn s.updateStatusError(err)\n\t\t}\n\t}\n\ts.instance = nil\n\ts.startedAt = time.Time{}\n\ts.updateStatus(ServiceStatus_IDLE)\n\ts.serviceAccess.Unlock()\n\truntime.GC()\n\treturn nil\n}\n\nfunc (s *StartedService) SetError(err error) {\n\ts.serviceAccess.Lock()\n\ts.updateStatusError(err)\n\ts.WriteMessage(log.LevelError, err.Error())\n}\n\nfunc (s *StartedService) StopService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {\n\terr := s.handler.ServiceStop()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *StartedService) ReloadService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {\n\terr := s.handler.ServiceReload()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *StartedService) SubscribeServiceStatus(empty *emptypb.Empty, server grpc.ServerStreamingServer[ServiceStatus]) error {\n\tsubscription, done, err := s.serviceStatusObserver.Subscribe()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer s.serviceStatusObserver.UnSubscribe(subscription)\n\terr = server.Send(s.serviceStatus)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor {\n\t\tselect {\n\t\tcase <-s.ctx.Done():\n\t\t\treturn s.ctx.Err()\n\t\tcase <-server.Context().Done():\n\t\t\treturn server.Context().Err()\n\t\tcase newStatus := <-subscription:\n\t\t\terr = server.Send(newStatus)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase <-done:\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (s *StartedService) SubscribeLog(empty *emptypb.Empty, server grpc.ServerStreamingServer[Log]) error {\n\tvar savedLines []*log.Entry\n\ts.logAccess.Lock()\n\tsavedLines = make([]*log.Entry, 0, s.logLines.Len())\n\tfor element := s.logLines.Front(); element != nil; element = element.Next() {\n\t\tsavedLines = append(savedLines, element.Value)\n\t}\n\tsubscription, done, err := s.logObserver.Subscribe()\n\ts.logAccess.Unlock()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer s.logObserver.UnSubscribe(subscription)\n\terr = server.Send(&Log{\n\t\tMessages: common.Map(savedLines, func(it *log.Entry) *Log_Message {\n\t\t\treturn &Log_Message{\n\t\t\t\tLevel:   LogLevel(it.Level),\n\t\t\t\tMessage: it.Message,\n\t\t\t}\n\t\t}),\n\t\tReset_: true,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor {\n\t\tselect {\n\t\tcase <-s.ctx.Done():\n\t\t\treturn s.ctx.Err()\n\t\tcase <-server.Context().Done():\n\t\t\treturn server.Context().Err()\n\t\tcase message := <-subscription:\n\t\t\tvar rawMessage Log\n\t\t\tif message == nil {\n\t\t\t\trawMessage.Reset_ = true\n\t\t\t} else {\n\t\t\t\trawMessage.Messages = append(rawMessage.Messages, &Log_Message{\n\t\t\t\t\tLevel:   LogLevel(message.Level),\n\t\t\t\t\tMessage: message.Message,\n\t\t\t\t})\n\t\t\t}\n\t\tfetch:\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase message = <-subscription:\n\t\t\t\t\tif message == nil {\n\t\t\t\t\t\trawMessage.Messages = nil\n\t\t\t\t\t\trawMessage.Reset_ = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\trawMessage.Messages = append(rawMessage.Messages, &Log_Message{\n\t\t\t\t\t\t\tLevel:   LogLevel(message.Level),\n\t\t\t\t\t\t\tMessage: message.Message,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tbreak fetch\n\t\t\t\t}\n\t\t\t}\n\t\t\terr = server.Send(&rawMessage)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase <-done:\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (s *StartedService) GetDefaultLogLevel(ctx context.Context, empty *emptypb.Empty) (*DefaultLogLevel, error) {\n\ts.serviceAccess.RLock()\n\tswitch s.serviceStatus.Status {\n\tcase ServiceStatus_STARTING, ServiceStatus_STARTED:\n\tdefault:\n\t\ts.serviceAccess.RUnlock()\n\t\treturn nil, os.ErrInvalid\n\t}\n\tlogLevel := s.instance.instance.LogFactory().Level()\n\ts.serviceAccess.RUnlock()\n\treturn &DefaultLogLevel{Level: LogLevel(logLevel)}, nil\n}\n\nfunc (s *StartedService) ClearLogs(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {\n\ts.resetLogs()\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server grpc.ServerStreamingServer[Status]) error {\n\tinterval := time.Duration(request.Interval)\n\tif interval <= 0 {\n\t\tinterval = time.Second // Default to 1 second\n\t}\n\tticker := time.NewTicker(interval)\n\tdefer ticker.Stop()\n\tstatus := s.readStatus()\n\tuploadTotal := status.UplinkTotal\n\tdownloadTotal := status.DownlinkTotal\n\tfor {\n\t\terr := server.Send(status)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tselect {\n\t\tcase <-s.ctx.Done():\n\t\t\treturn s.ctx.Err()\n\t\tcase <-server.Context().Done():\n\t\t\treturn server.Context().Err()\n\t\tcase <-ticker.C:\n\t\t}\n\t\tstatus = s.readStatus()\n\t\tupload := status.UplinkTotal - uploadTotal\n\t\tdownload := status.DownlinkTotal - downloadTotal\n\t\tuploadTotal = status.UplinkTotal\n\t\tdownloadTotal = status.DownlinkTotal\n\t\tstatus.Uplink = upload\n\t\tstatus.Downlink = download\n\t}\n}\n\nfunc (s *StartedService) readStatus() *Status {\n\tvar status Status\n\tstatus.Memory = memory.Total()\n\tstatus.Goroutines = int32(runtime.NumGoroutine())\n\ts.serviceAccess.RLock()\n\tnowService := s.instance\n\ts.serviceAccess.RUnlock()\n\tif nowService != nil && nowService.connectionManager != nil {\n\t\tstatus.ConnectionsOut = int32(nowService.connectionManager.Count())\n\t}\n\tif nowService != nil {\n\t\tif clashServer := nowService.clashServer; clashServer != nil {\n\t\t\tstatus.TrafficAvailable = true\n\t\t\ttrafficManager := clashServer.(*clashapi.Server).TrafficManager()\n\t\t\tstatus.UplinkTotal, status.DownlinkTotal = trafficManager.Total()\n\t\t\tstatus.ConnectionsIn = int32(trafficManager.ConnectionsLen())\n\t\t}\n\t}\n\treturn &status\n}\n\nfunc (s *StartedService) SubscribeGroups(empty *emptypb.Empty, server grpc.ServerStreamingServer[Groups]) error {\n\terr := s.waitForStarted(server.Context())\n\tif err != nil {\n\t\treturn err\n\t}\n\tsubscription, done, err := s.urlTestObserver.Subscribe()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer s.urlTestObserver.UnSubscribe(subscription)\n\tfor {\n\t\ts.serviceAccess.RLock()\n\t\tif s.serviceStatus.Status != ServiceStatus_STARTED {\n\t\t\ts.serviceAccess.RUnlock()\n\t\t\treturn os.ErrInvalid\n\t\t}\n\t\tgroups := s.readGroups()\n\t\ts.serviceAccess.RUnlock()\n\t\terr = server.Send(groups)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tselect {\n\t\tcase <-subscription:\n\t\tcase <-s.ctx.Done():\n\t\t\treturn s.ctx.Err()\n\t\tcase <-server.Context().Done():\n\t\t\treturn server.Context().Err()\n\t\tcase <-done:\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (s *StartedService) readGroups() *Groups {\n\thistoryStorage := s.instance.urlTestHistoryStorage\n\tboxService := s.instance\n\toutbounds := boxService.instance.Outbound().Outbounds()\n\tvar iGroups []adapter.OutboundGroup\n\tfor _, it := range outbounds {\n\t\tif group, isGroup := it.(adapter.OutboundGroup); isGroup {\n\t\t\tiGroups = append(iGroups, group)\n\t\t}\n\t}\n\tvar gs Groups\n\tfor _, iGroup := range iGroups {\n\t\tvar g Group\n\t\tg.Tag = iGroup.Tag()\n\t\tg.Type = iGroup.Type()\n\t\t_, g.Selectable = iGroup.(*group.Selector)\n\t\tg.Selected = iGroup.Now()\n\t\tif boxService.cacheFile != nil {\n\t\t\tif isExpand, loaded := boxService.cacheFile.LoadGroupExpand(g.Tag); loaded {\n\t\t\t\tg.IsExpand = isExpand\n\t\t\t}\n\t\t}\n\n\t\tfor _, itemTag := range iGroup.All() {\n\t\t\titemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag)\n\t\t\tif !isLoaded {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar item GroupItem\n\t\t\titem.Tag = itemTag\n\t\t\titem.Type = itemOutbound.Type()\n\t\t\tif history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil {\n\t\t\t\titem.UrlTestTime = history.Time.Unix()\n\t\t\t\titem.UrlTestDelay = int32(history.Delay)\n\t\t\t}\n\t\t\tg.Items = append(g.Items, &item)\n\t\t}\n\t\tif len(g.Items) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tgs.Group = append(gs.Group, &g)\n\t}\n\treturn &gs\n}\n\nfunc (s *StartedService) GetClashModeStatus(ctx context.Context, empty *emptypb.Empty) (*ClashModeStatus, error) {\n\ts.serviceAccess.RLock()\n\tif s.serviceStatus.Status != ServiceStatus_STARTED {\n\t\ts.serviceAccess.RUnlock()\n\t\treturn nil, os.ErrInvalid\n\t}\n\tclashServer := s.instance.clashServer\n\ts.serviceAccess.RUnlock()\n\tif clashServer == nil {\n\t\treturn nil, os.ErrInvalid\n\t}\n\treturn &ClashModeStatus{\n\t\tModeList:    clashServer.ModeList(),\n\t\tCurrentMode: clashServer.Mode(),\n\t}, nil\n}\n\nfunc (s *StartedService) SubscribeClashMode(empty *emptypb.Empty, server grpc.ServerStreamingServer[ClashMode]) error {\n\terr := s.waitForStarted(server.Context())\n\tif err != nil {\n\t\treturn err\n\t}\n\tsubscription, done, err := s.clashModeObserver.Subscribe()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer s.clashModeObserver.UnSubscribe(subscription)\n\tfor {\n\t\ts.serviceAccess.RLock()\n\t\tif s.serviceStatus.Status != ServiceStatus_STARTED {\n\t\t\ts.serviceAccess.RUnlock()\n\t\t\treturn os.ErrInvalid\n\t\t}\n\t\tmessage := &ClashMode{Mode: s.instance.clashServer.Mode()}\n\t\ts.serviceAccess.RUnlock()\n\t\terr = server.Send(message)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tselect {\n\t\tcase <-subscription:\n\t\tcase <-s.ctx.Done():\n\t\t\treturn s.ctx.Err()\n\t\tcase <-server.Context().Done():\n\t\t\treturn server.Context().Err()\n\t\tcase <-done:\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (s *StartedService) SetClashMode(ctx context.Context, request *ClashMode) (*emptypb.Empty, error) {\n\ts.serviceAccess.RLock()\n\tif s.serviceStatus.Status != ServiceStatus_STARTED {\n\t\ts.serviceAccess.RUnlock()\n\t\treturn nil, os.ErrInvalid\n\t}\n\tclashServer := s.instance.clashServer\n\ts.serviceAccess.RUnlock()\n\tclashServer.(*clashapi.Server).SetMode(request.Mode)\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *StartedService) URLTest(ctx context.Context, request *URLTestRequest) (*emptypb.Empty, error) {\n\ts.serviceAccess.RLock()\n\tif s.serviceStatus.Status != ServiceStatus_STARTED {\n\t\ts.serviceAccess.RUnlock()\n\t\treturn nil, os.ErrInvalid\n\t}\n\tboxService := s.instance\n\ts.serviceAccess.RUnlock()\n\tgroupTag := request.OutboundTag\n\tabstractOutboundGroup, isLoaded := boxService.instance.Outbound().Outbound(groupTag)\n\tif !isLoaded {\n\t\treturn nil, E.New(\"outbound group not found: \", groupTag)\n\t}\n\toutboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup)\n\tif !isOutboundGroup {\n\t\treturn nil, E.New(\"outbound is not a group: \", groupTag)\n\t}\n\turlTest, isURLTest := abstractOutboundGroup.(*group.URLTest)\n\tif isURLTest {\n\t\tgo urlTest.CheckOutbounds()\n\t} else {\n\t\thistoryStorage := boxService.urlTestHistoryStorage\n\n\t\toutbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {\n\t\t\titOutbound, _ := boxService.instance.Outbound().Outbound(it)\n\t\t\treturn itOutbound\n\t\t}), func(it adapter.Outbound) bool {\n\t\t\tif it == nil {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t_, isGroup := it.(adapter.OutboundGroup)\n\t\t\tif isGroup {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tb, _ := batch.New(boxService.ctx, batch.WithConcurrencyNum[any](10))\n\t\tfor _, detour := range outbounds {\n\t\t\toutboundToTest := detour\n\t\t\toutboundTag := outboundToTest.Tag()\n\t\t\tb.Go(outboundTag, func() (any, error) {\n\t\t\t\tt, err := urltest.URLTest(boxService.ctx, \"\", outboundToTest)\n\t\t\t\tif err != nil {\n\t\t\t\t\thistoryStorage.DeleteURLTestHistory(outboundTag)\n\t\t\t\t} else {\n\t\t\t\t\thistoryStorage.StoreURLTestHistory(outboundTag, &adapter.URLTestHistory{\n\t\t\t\t\t\tTime:  time.Now(),\n\t\t\t\t\t\tDelay: t,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\treturn nil, nil\n\t\t\t})\n\t\t}\n\t}\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *StartedService) SelectOutbound(ctx context.Context, request *SelectOutboundRequest) (*emptypb.Empty, error) {\n\ts.serviceAccess.RLock()\n\tswitch s.serviceStatus.Status {\n\tcase ServiceStatus_STARTING, ServiceStatus_STARTED:\n\tdefault:\n\t\ts.serviceAccess.RUnlock()\n\t\treturn nil, os.ErrInvalid\n\t}\n\tboxService := s.instance.instance\n\ts.serviceAccess.RUnlock()\n\toutboundGroup, isLoaded := boxService.Outbound().Outbound(request.GroupTag)\n\tif !isLoaded {\n\t\treturn nil, E.New(\"selector not found: \", request.GroupTag)\n\t}\n\tselector, isSelector := outboundGroup.(*group.Selector)\n\tif !isSelector {\n\t\treturn nil, E.New(\"outbound is not a selector: \", request.GroupTag)\n\t}\n\tif !selector.SelectOutbound(request.OutboundTag) {\n\t\treturn nil, E.New(\"outbound not found in selector: \", request.OutboundTag)\n\t}\n\ts.urlTestObserver.Emit(struct{}{})\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *StartedService) SetGroupExpand(ctx context.Context, request *SetGroupExpandRequest) (*emptypb.Empty, error) {\n\ts.serviceAccess.RLock()\n\tswitch s.serviceStatus.Status {\n\tcase ServiceStatus_STARTING, ServiceStatus_STARTED:\n\tdefault:\n\t\ts.serviceAccess.RUnlock()\n\t\treturn nil, os.ErrInvalid\n\t}\n\tboxService := s.instance\n\ts.serviceAccess.RUnlock()\n\tif boxService.cacheFile != nil {\n\t\terr := boxService.cacheFile.StoreGroupExpand(request.GroupTag, request.IsExpand)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *StartedService) GetSystemProxyStatus(ctx context.Context, empty *emptypb.Empty) (*SystemProxyStatus, error) {\n\treturn s.handler.SystemProxyStatus()\n}\n\nfunc (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {\n\terr := s.handler.SetSystemProxyEnabled(request.Enabled)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn nil, err\n}\n\nfunc (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error {\n\terr := s.waitForStarted(server.Context())\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.serviceAccess.RLock()\n\tboxService := s.instance\n\ts.serviceAccess.RUnlock()\n\n\tif boxService.clashServer == nil {\n\t\treturn E.New(\"clash server not available\")\n\t}\n\n\ttrafficManager := boxService.clashServer.(*clashapi.Server).TrafficManager()\n\n\tsubscription, done, err := s.connectionEventObserver.Subscribe()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer s.connectionEventObserver.UnSubscribe(subscription)\n\n\tconnectionSnapshots := make(map[uuid.UUID]connectionSnapshot)\n\tinitialEvents := s.buildInitialConnectionState(trafficManager, connectionSnapshots)\n\terr = server.Send(&ConnectionEvents{\n\t\tEvents: initialEvents,\n\t\tReset_: true,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinterval := time.Duration(request.Interval)\n\tif interval <= 0 {\n\t\tinterval = time.Second\n\t}\n\tticker := time.NewTicker(interval)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-s.ctx.Done():\n\t\t\treturn s.ctx.Err()\n\t\tcase <-server.Context().Done():\n\t\t\treturn server.Context().Err()\n\t\tcase <-done:\n\t\t\treturn nil\n\n\t\tcase event := <-subscription:\n\t\t\tvar pendingEvents []*ConnectionEvent\n\t\t\tif protoEvent := s.applyConnectionEvent(event, connectionSnapshots); protoEvent != nil {\n\t\t\t\tpendingEvents = append(pendingEvents, protoEvent)\n\t\t\t}\n\t\tdrain:\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase event = <-subscription:\n\t\t\t\t\tif protoEvent := s.applyConnectionEvent(event, connectionSnapshots); protoEvent != nil {\n\t\t\t\t\t\tpendingEvents = append(pendingEvents, protoEvent)\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tbreak drain\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(pendingEvents) > 0 {\n\t\t\t\terr = server.Send(&ConnectionEvents{Events: pendingEvents})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase <-ticker.C:\n\t\t\tprotoEvents := s.buildTrafficUpdates(trafficManager, connectionSnapshots)\n\t\t\tif len(protoEvents) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\terr = server.Send(&ConnectionEvents{Events: protoEvents})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype connectionSnapshot struct {\n\tuplink     int64\n\tdownlink   int64\n\thadTraffic bool\n}\n\nfunc (s *StartedService) buildInitialConnectionState(manager *trafficontrol.Manager, snapshots map[uuid.UUID]connectionSnapshot) []*ConnectionEvent {\n\tvar events []*ConnectionEvent\n\n\tfor _, metadata := range manager.Connections() {\n\t\tevents = append(events, &ConnectionEvent{\n\t\t\tType:       ConnectionEventType_CONNECTION_EVENT_NEW,\n\t\t\tId:         metadata.ID.String(),\n\t\t\tConnection: buildConnectionProto(metadata),\n\t\t})\n\t\tsnapshots[metadata.ID] = connectionSnapshot{\n\t\t\tuplink:   metadata.Upload.Load(),\n\t\t\tdownlink: metadata.Download.Load(),\n\t\t}\n\t}\n\n\tfor _, metadata := range manager.ClosedConnections() {\n\t\tconn := buildConnectionProto(metadata)\n\t\tconn.ClosedAt = metadata.ClosedAt.UnixMilli()\n\t\tevents = append(events, &ConnectionEvent{\n\t\t\tType:       ConnectionEventType_CONNECTION_EVENT_NEW,\n\t\t\tId:         metadata.ID.String(),\n\t\t\tConnection: conn,\n\t\t})\n\t}\n\n\treturn events\n}\n\nfunc (s *StartedService) applyConnectionEvent(event trafficontrol.ConnectionEvent, snapshots map[uuid.UUID]connectionSnapshot) *ConnectionEvent {\n\tswitch event.Type {\n\tcase trafficontrol.ConnectionEventNew:\n\t\tif _, exists := snapshots[event.ID]; exists {\n\t\t\treturn nil\n\t\t}\n\t\tsnapshots[event.ID] = connectionSnapshot{\n\t\t\tuplink:   event.Metadata.Upload.Load(),\n\t\t\tdownlink: event.Metadata.Download.Load(),\n\t\t}\n\t\treturn &ConnectionEvent{\n\t\t\tType:       ConnectionEventType_CONNECTION_EVENT_NEW,\n\t\t\tId:         event.ID.String(),\n\t\t\tConnection: buildConnectionProto(event.Metadata),\n\t\t}\n\tcase trafficontrol.ConnectionEventClosed:\n\t\tdelete(snapshots, event.ID)\n\t\tprotoEvent := &ConnectionEvent{\n\t\t\tType: ConnectionEventType_CONNECTION_EVENT_CLOSED,\n\t\t\tId:   event.ID.String(),\n\t\t}\n\t\tclosedAt := event.ClosedAt\n\t\tif closedAt.IsZero() && !event.Metadata.ClosedAt.IsZero() {\n\t\t\tclosedAt = event.Metadata.ClosedAt\n\t\t}\n\t\tif closedAt.IsZero() {\n\t\t\tclosedAt = time.Now()\n\t\t}\n\t\tprotoEvent.ClosedAt = closedAt.UnixMilli()\n\t\tif event.Metadata.ID != uuid.Nil {\n\t\t\tconn := buildConnectionProto(event.Metadata)\n\t\t\tconn.ClosedAt = protoEvent.ClosedAt\n\t\t\tprotoEvent.Connection = conn\n\t\t}\n\t\treturn protoEvent\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc (s *StartedService) buildTrafficUpdates(manager *trafficontrol.Manager, snapshots map[uuid.UUID]connectionSnapshot) []*ConnectionEvent {\n\tactiveConnections := manager.Connections()\n\tactiveIndex := make(map[uuid.UUID]*trafficontrol.TrackerMetadata, len(activeConnections))\n\tvar events []*ConnectionEvent\n\n\tfor _, metadata := range activeConnections {\n\t\tactiveIndex[metadata.ID] = metadata\n\t\tcurrentUpload := metadata.Upload.Load()\n\t\tcurrentDownload := metadata.Download.Load()\n\t\tsnapshot, exists := snapshots[metadata.ID]\n\t\tif !exists {\n\t\t\tsnapshots[metadata.ID] = connectionSnapshot{\n\t\t\t\tuplink:   currentUpload,\n\t\t\t\tdownlink: currentDownload,\n\t\t\t}\n\t\t\tevents = append(events, &ConnectionEvent{\n\t\t\t\tType:       ConnectionEventType_CONNECTION_EVENT_NEW,\n\t\t\t\tId:         metadata.ID.String(),\n\t\t\t\tConnection: buildConnectionProto(metadata),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\t\tuplinkDelta := currentUpload - snapshot.uplink\n\t\tdownlinkDelta := currentDownload - snapshot.downlink\n\t\tif uplinkDelta < 0 || downlinkDelta < 0 {\n\t\t\tif snapshot.hadTraffic {\n\t\t\t\tevents = append(events, &ConnectionEvent{\n\t\t\t\t\tType:          ConnectionEventType_CONNECTION_EVENT_UPDATE,\n\t\t\t\t\tId:            metadata.ID.String(),\n\t\t\t\t\tUplinkDelta:   0,\n\t\t\t\t\tDownlinkDelta: 0,\n\t\t\t\t})\n\t\t\t}\n\t\t\tsnapshot.uplink = currentUpload\n\t\t\tsnapshot.downlink = currentDownload\n\t\t\tsnapshot.hadTraffic = false\n\t\t\tsnapshots[metadata.ID] = snapshot\n\t\t\tcontinue\n\t\t}\n\t\tif uplinkDelta > 0 || downlinkDelta > 0 {\n\t\t\tsnapshot.uplink = currentUpload\n\t\t\tsnapshot.downlink = currentDownload\n\t\t\tsnapshot.hadTraffic = true\n\t\t\tsnapshots[metadata.ID] = snapshot\n\t\t\tevents = append(events, &ConnectionEvent{\n\t\t\t\tType:          ConnectionEventType_CONNECTION_EVENT_UPDATE,\n\t\t\t\tId:            metadata.ID.String(),\n\t\t\t\tUplinkDelta:   uplinkDelta,\n\t\t\t\tDownlinkDelta: downlinkDelta,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\t\tif snapshot.hadTraffic {\n\t\t\tsnapshot.uplink = currentUpload\n\t\t\tsnapshot.downlink = currentDownload\n\t\t\tsnapshot.hadTraffic = false\n\t\t\tsnapshots[metadata.ID] = snapshot\n\t\t\tevents = append(events, &ConnectionEvent{\n\t\t\t\tType:          ConnectionEventType_CONNECTION_EVENT_UPDATE,\n\t\t\t\tId:            metadata.ID.String(),\n\t\t\t\tUplinkDelta:   0,\n\t\t\t\tDownlinkDelta: 0,\n\t\t\t})\n\t\t}\n\t}\n\n\tvar closedIndex map[uuid.UUID]*trafficontrol.TrackerMetadata\n\tfor id := range snapshots {\n\t\tif _, exists := activeIndex[id]; exists {\n\t\t\tcontinue\n\t\t}\n\t\tif closedIndex == nil {\n\t\t\tclosedIndex = make(map[uuid.UUID]*trafficontrol.TrackerMetadata)\n\t\t\tfor _, metadata := range manager.ClosedConnections() {\n\t\t\t\tclosedIndex[metadata.ID] = metadata\n\t\t\t}\n\t\t}\n\t\tclosedAt := time.Now()\n\t\tvar conn *Connection\n\t\tif metadata, ok := closedIndex[id]; ok {\n\t\t\tif !metadata.ClosedAt.IsZero() {\n\t\t\t\tclosedAt = metadata.ClosedAt\n\t\t\t}\n\t\t\tconn = buildConnectionProto(metadata)\n\t\t\tconn.ClosedAt = closedAt.UnixMilli()\n\t\t}\n\t\tevents = append(events, &ConnectionEvent{\n\t\t\tType:       ConnectionEventType_CONNECTION_EVENT_CLOSED,\n\t\t\tId:         id.String(),\n\t\t\tClosedAt:   closedAt.UnixMilli(),\n\t\t\tConnection: conn,\n\t\t})\n\t\tdelete(snapshots, id)\n\t}\n\n\treturn events\n}\n\nfunc buildConnectionProto(metadata *trafficontrol.TrackerMetadata) *Connection {\n\tvar rule string\n\tif metadata.Rule != nil {\n\t\trule = metadata.Rule.String()\n\t}\n\tuplinkTotal := metadata.Upload.Load()\n\tdownlinkTotal := metadata.Download.Load()\n\tvar processInfo *ProcessInfo\n\tif metadata.Metadata.ProcessInfo != nil {\n\t\tprocessInfo = &ProcessInfo{\n\t\t\tProcessId:   metadata.Metadata.ProcessInfo.ProcessID,\n\t\t\tUserId:      metadata.Metadata.ProcessInfo.UserId,\n\t\t\tUserName:    metadata.Metadata.ProcessInfo.UserName,\n\t\t\tProcessPath: metadata.Metadata.ProcessInfo.ProcessPath,\n\t\t\tPackageName: metadata.Metadata.ProcessInfo.AndroidPackageName,\n\t\t}\n\t}\n\treturn &Connection{\n\t\tId:            metadata.ID.String(),\n\t\tInbound:       metadata.Metadata.Inbound,\n\t\tInboundType:   metadata.Metadata.InboundType,\n\t\tIpVersion:     int32(metadata.Metadata.IPVersion),\n\t\tNetwork:       metadata.Metadata.Network,\n\t\tSource:        metadata.Metadata.Source.String(),\n\t\tDestination:   metadata.Metadata.Destination.String(),\n\t\tDomain:        metadata.Metadata.Domain,\n\t\tProtocol:      metadata.Metadata.Protocol,\n\t\tUser:          metadata.Metadata.User,\n\t\tFromOutbound:  metadata.Metadata.Outbound,\n\t\tCreatedAt:     metadata.CreatedAt.UnixMilli(),\n\t\tUplinkTotal:   uplinkTotal,\n\t\tDownlinkTotal: downlinkTotal,\n\t\tRule:          rule,\n\t\tOutbound:      metadata.Outbound,\n\t\tOutboundType:  metadata.OutboundType,\n\t\tChainList:     metadata.Chain,\n\t\tProcessInfo:   processInfo,\n\t}\n}\n\nfunc (s *StartedService) CloseConnection(ctx context.Context, request *CloseConnectionRequest) (*emptypb.Empty, error) {\n\ts.serviceAccess.RLock()\n\tswitch s.serviceStatus.Status {\n\tcase ServiceStatus_STARTING, ServiceStatus_STARTED:\n\tdefault:\n\t\ts.serviceAccess.RUnlock()\n\t\treturn nil, os.ErrInvalid\n\t}\n\tboxService := s.instance\n\ts.serviceAccess.RUnlock()\n\ttargetConn := boxService.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(request.Id))\n\tif targetConn != nil {\n\t\ttargetConn.Close()\n\t}\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {\n\ts.serviceAccess.RLock()\n\tnowService := s.instance\n\ts.serviceAccess.RUnlock()\n\tif nowService != nil && nowService.connectionManager != nil {\n\t\tnowService.connectionManager.CloseAll()\n\t}\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *StartedService) GetDeprecatedWarnings(ctx context.Context, empty *emptypb.Empty) (*DeprecatedWarnings, error) {\n\ts.serviceAccess.RLock()\n\tif s.serviceStatus.Status != ServiceStatus_STARTED {\n\t\ts.serviceAccess.RUnlock()\n\t\treturn nil, os.ErrInvalid\n\t}\n\tboxService := s.instance\n\ts.serviceAccess.RUnlock()\n\tnotes := service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get()\n\treturn &DeprecatedWarnings{\n\t\tWarnings: common.Map(notes, func(it deprecated.Note) *DeprecatedWarning {\n\t\t\treturn &DeprecatedWarning{\n\t\t\t\tMessage:       it.Message(),\n\t\t\t\tImpending:     it.Impending(),\n\t\t\t\tMigrationLink: it.MigrationLink,\n\t\t\t}\n\t\t}),\n\t}, nil\n}\n\nfunc (s *StartedService) GetStartedAt(ctx context.Context, empty *emptypb.Empty) (*StartedAt, error) {\n\ts.serviceAccess.RLock()\n\tdefer s.serviceAccess.RUnlock()\n\treturn &StartedAt{StartedAt: s.startedAt.UnixMilli()}, nil\n}\n\nfunc (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {\n}\n\nfunc (s *StartedService) WriteMessage(level log.Level, message string) {\n\titem := &log.Entry{Level: level, Message: message}\n\ts.logAccess.Lock()\n\ts.logLines.PushBack(item)\n\tif s.logLines.Len() > s.logMaxLines {\n\t\ts.logLines.Remove(s.logLines.Front())\n\t}\n\ts.logAccess.Unlock()\n\ts.logSubscriber.Emit(item)\n\tif s.debug {\n\t\ts.handler.WriteDebugMessage(message)\n\t}\n}\n\nfunc (s *StartedService) Instance() *Instance {\n\ts.serviceAccess.RLock()\n\tdefer s.serviceAccess.RUnlock()\n\treturn s.instance\n}\n"
  },
  {
    "path": "daemon/started_service.pb.go",
    "content": "package daemon\n\nimport (\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype LogLevel int32\n\nconst (\n\tLogLevel_PANIC LogLevel = 0\n\tLogLevel_FATAL LogLevel = 1\n\tLogLevel_ERROR LogLevel = 2\n\tLogLevel_WARN  LogLevel = 3\n\tLogLevel_INFO  LogLevel = 4\n\tLogLevel_DEBUG LogLevel = 5\n\tLogLevel_TRACE LogLevel = 6\n)\n\n// Enum value maps for LogLevel.\nvar (\n\tLogLevel_name = map[int32]string{\n\t\t0: \"PANIC\",\n\t\t1: \"FATAL\",\n\t\t2: \"ERROR\",\n\t\t3: \"WARN\",\n\t\t4: \"INFO\",\n\t\t5: \"DEBUG\",\n\t\t6: \"TRACE\",\n\t}\n\tLogLevel_value = map[string]int32{\n\t\t\"PANIC\": 0,\n\t\t\"FATAL\": 1,\n\t\t\"ERROR\": 2,\n\t\t\"WARN\":  3,\n\t\t\"INFO\":  4,\n\t\t\"DEBUG\": 5,\n\t\t\"TRACE\": 6,\n\t}\n)\n\nfunc (x LogLevel) Enum() *LogLevel {\n\tp := new(LogLevel)\n\t*p = x\n\treturn p\n}\n\nfunc (x LogLevel) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (LogLevel) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_daemon_started_service_proto_enumTypes[0].Descriptor()\n}\n\nfunc (LogLevel) Type() protoreflect.EnumType {\n\treturn &file_daemon_started_service_proto_enumTypes[0]\n}\n\nfunc (x LogLevel) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use LogLevel.Descriptor instead.\nfunc (LogLevel) EnumDescriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{0}\n}\n\ntype ConnectionEventType int32\n\nconst (\n\tConnectionEventType_CONNECTION_EVENT_NEW    ConnectionEventType = 0\n\tConnectionEventType_CONNECTION_EVENT_UPDATE ConnectionEventType = 1\n\tConnectionEventType_CONNECTION_EVENT_CLOSED ConnectionEventType = 2\n)\n\n// Enum value maps for ConnectionEventType.\nvar (\n\tConnectionEventType_name = map[int32]string{\n\t\t0: \"CONNECTION_EVENT_NEW\",\n\t\t1: \"CONNECTION_EVENT_UPDATE\",\n\t\t2: \"CONNECTION_EVENT_CLOSED\",\n\t}\n\tConnectionEventType_value = map[string]int32{\n\t\t\"CONNECTION_EVENT_NEW\":    0,\n\t\t\"CONNECTION_EVENT_UPDATE\": 1,\n\t\t\"CONNECTION_EVENT_CLOSED\": 2,\n\t}\n)\n\nfunc (x ConnectionEventType) Enum() *ConnectionEventType {\n\tp := new(ConnectionEventType)\n\t*p = x\n\treturn p\n}\n\nfunc (x ConnectionEventType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (ConnectionEventType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_daemon_started_service_proto_enumTypes[1].Descriptor()\n}\n\nfunc (ConnectionEventType) Type() protoreflect.EnumType {\n\treturn &file_daemon_started_service_proto_enumTypes[1]\n}\n\nfunc (x ConnectionEventType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use ConnectionEventType.Descriptor instead.\nfunc (ConnectionEventType) EnumDescriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{1}\n}\n\ntype ServiceStatus_Type int32\n\nconst (\n\tServiceStatus_IDLE     ServiceStatus_Type = 0\n\tServiceStatus_STARTING ServiceStatus_Type = 1\n\tServiceStatus_STARTED  ServiceStatus_Type = 2\n\tServiceStatus_STOPPING ServiceStatus_Type = 3\n\tServiceStatus_FATAL    ServiceStatus_Type = 4\n)\n\n// Enum value maps for ServiceStatus_Type.\nvar (\n\tServiceStatus_Type_name = map[int32]string{\n\t\t0: \"IDLE\",\n\t\t1: \"STARTING\",\n\t\t2: \"STARTED\",\n\t\t3: \"STOPPING\",\n\t\t4: \"FATAL\",\n\t}\n\tServiceStatus_Type_value = map[string]int32{\n\t\t\"IDLE\":     0,\n\t\t\"STARTING\": 1,\n\t\t\"STARTED\":  2,\n\t\t\"STOPPING\": 3,\n\t\t\"FATAL\":    4,\n\t}\n)\n\nfunc (x ServiceStatus_Type) Enum() *ServiceStatus_Type {\n\tp := new(ServiceStatus_Type)\n\t*p = x\n\treturn p\n}\n\nfunc (x ServiceStatus_Type) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (ServiceStatus_Type) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_daemon_started_service_proto_enumTypes[2].Descriptor()\n}\n\nfunc (ServiceStatus_Type) Type() protoreflect.EnumType {\n\treturn &file_daemon_started_service_proto_enumTypes[2]\n}\n\nfunc (x ServiceStatus_Type) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use ServiceStatus_Type.Descriptor instead.\nfunc (ServiceStatus_Type) EnumDescriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{0, 0}\n}\n\ntype ServiceStatus struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tStatus        ServiceStatus_Type     `protobuf:\"varint,1,opt,name=status,proto3,enum=daemon.ServiceStatus_Type\" json:\"status,omitempty\"`\n\tErrorMessage  string                 `protobuf:\"bytes,2,opt,name=errorMessage,proto3\" json:\"errorMessage,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ServiceStatus) Reset() {\n\t*x = ServiceStatus{}\n\tmi := &file_daemon_started_service_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ServiceStatus) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServiceStatus) ProtoMessage() {}\n\nfunc (x *ServiceStatus) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ServiceStatus.ProtoReflect.Descriptor instead.\nfunc (*ServiceStatus) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *ServiceStatus) GetStatus() ServiceStatus_Type {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn ServiceStatus_IDLE\n}\n\nfunc (x *ServiceStatus) GetErrorMessage() string {\n\tif x != nil {\n\t\treturn x.ErrorMessage\n\t}\n\treturn \"\"\n}\n\ntype ReloadServiceRequest struct {\n\tstate             protoimpl.MessageState `protogen:\"open.v1\"`\n\tNewProfileContent string                 `protobuf:\"bytes,1,opt,name=newProfileContent,proto3\" json:\"newProfileContent,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *ReloadServiceRequest) Reset() {\n\t*x = ReloadServiceRequest{}\n\tmi := &file_daemon_started_service_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ReloadServiceRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReloadServiceRequest) ProtoMessage() {}\n\nfunc (x *ReloadServiceRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReloadServiceRequest.ProtoReflect.Descriptor instead.\nfunc (*ReloadServiceRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *ReloadServiceRequest) GetNewProfileContent() string {\n\tif x != nil {\n\t\treturn x.NewProfileContent\n\t}\n\treturn \"\"\n}\n\ntype SubscribeStatusRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tInterval      int64                  `protobuf:\"varint,1,opt,name=interval,proto3\" json:\"interval,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SubscribeStatusRequest) Reset() {\n\t*x = SubscribeStatusRequest{}\n\tmi := &file_daemon_started_service_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SubscribeStatusRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SubscribeStatusRequest) ProtoMessage() {}\n\nfunc (x *SubscribeStatusRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SubscribeStatusRequest.ProtoReflect.Descriptor instead.\nfunc (*SubscribeStatusRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *SubscribeStatusRequest) GetInterval() int64 {\n\tif x != nil {\n\t\treturn x.Interval\n\t}\n\treturn 0\n}\n\ntype Log struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tMessages      []*Log_Message         `protobuf:\"bytes,1,rep,name=messages,proto3\" json:\"messages,omitempty\"`\n\tReset_        bool                   `protobuf:\"varint,2,opt,name=reset,proto3\" json:\"reset,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Log) Reset() {\n\t*x = Log{}\n\tmi := &file_daemon_started_service_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Log) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Log) ProtoMessage() {}\n\nfunc (x *Log) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Log.ProtoReflect.Descriptor instead.\nfunc (*Log) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *Log) GetMessages() []*Log_Message {\n\tif x != nil {\n\t\treturn x.Messages\n\t}\n\treturn nil\n}\n\nfunc (x *Log) GetReset_() bool {\n\tif x != nil {\n\t\treturn x.Reset_\n\t}\n\treturn false\n}\n\ntype DefaultLogLevel struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tLevel         LogLevel               `protobuf:\"varint,1,opt,name=level,proto3,enum=daemon.LogLevel\" json:\"level,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DefaultLogLevel) Reset() {\n\t*x = DefaultLogLevel{}\n\tmi := &file_daemon_started_service_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DefaultLogLevel) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DefaultLogLevel) ProtoMessage() {}\n\nfunc (x *DefaultLogLevel) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DefaultLogLevel.ProtoReflect.Descriptor instead.\nfunc (*DefaultLogLevel) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *DefaultLogLevel) GetLevel() LogLevel {\n\tif x != nil {\n\t\treturn x.Level\n\t}\n\treturn LogLevel_PANIC\n}\n\ntype Status struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tMemory           uint64                 `protobuf:\"varint,1,opt,name=memory,proto3\" json:\"memory,omitempty\"`\n\tGoroutines       int32                  `protobuf:\"varint,2,opt,name=goroutines,proto3\" json:\"goroutines,omitempty\"`\n\tConnectionsIn    int32                  `protobuf:\"varint,3,opt,name=connectionsIn,proto3\" json:\"connectionsIn,omitempty\"`\n\tConnectionsOut   int32                  `protobuf:\"varint,4,opt,name=connectionsOut,proto3\" json:\"connectionsOut,omitempty\"`\n\tTrafficAvailable bool                   `protobuf:\"varint,5,opt,name=trafficAvailable,proto3\" json:\"trafficAvailable,omitempty\"`\n\tUplink           int64                  `protobuf:\"varint,6,opt,name=uplink,proto3\" json:\"uplink,omitempty\"`\n\tDownlink         int64                  `protobuf:\"varint,7,opt,name=downlink,proto3\" json:\"downlink,omitempty\"`\n\tUplinkTotal      int64                  `protobuf:\"varint,8,opt,name=uplinkTotal,proto3\" json:\"uplinkTotal,omitempty\"`\n\tDownlinkTotal    int64                  `protobuf:\"varint,9,opt,name=downlinkTotal,proto3\" json:\"downlinkTotal,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *Status) Reset() {\n\t*x = Status{}\n\tmi := &file_daemon_started_service_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Status) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Status) ProtoMessage() {}\n\nfunc (x *Status) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Status.ProtoReflect.Descriptor instead.\nfunc (*Status) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *Status) GetMemory() uint64 {\n\tif x != nil {\n\t\treturn x.Memory\n\t}\n\treturn 0\n}\n\nfunc (x *Status) GetGoroutines() int32 {\n\tif x != nil {\n\t\treturn x.Goroutines\n\t}\n\treturn 0\n}\n\nfunc (x *Status) GetConnectionsIn() int32 {\n\tif x != nil {\n\t\treturn x.ConnectionsIn\n\t}\n\treturn 0\n}\n\nfunc (x *Status) GetConnectionsOut() int32 {\n\tif x != nil {\n\t\treturn x.ConnectionsOut\n\t}\n\treturn 0\n}\n\nfunc (x *Status) GetTrafficAvailable() bool {\n\tif x != nil {\n\t\treturn x.TrafficAvailable\n\t}\n\treturn false\n}\n\nfunc (x *Status) GetUplink() int64 {\n\tif x != nil {\n\t\treturn x.Uplink\n\t}\n\treturn 0\n}\n\nfunc (x *Status) GetDownlink() int64 {\n\tif x != nil {\n\t\treturn x.Downlink\n\t}\n\treturn 0\n}\n\nfunc (x *Status) GetUplinkTotal() int64 {\n\tif x != nil {\n\t\treturn x.UplinkTotal\n\t}\n\treturn 0\n}\n\nfunc (x *Status) GetDownlinkTotal() int64 {\n\tif x != nil {\n\t\treturn x.DownlinkTotal\n\t}\n\treturn 0\n}\n\ntype Groups struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tGroup         []*Group               `protobuf:\"bytes,1,rep,name=group,proto3\" json:\"group,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Groups) Reset() {\n\t*x = Groups{}\n\tmi := &file_daemon_started_service_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Groups) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Groups) ProtoMessage() {}\n\nfunc (x *Groups) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Groups.ProtoReflect.Descriptor instead.\nfunc (*Groups) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *Groups) GetGroup() []*Group {\n\tif x != nil {\n\t\treturn x.Group\n\t}\n\treturn nil\n}\n\ntype Group struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tType          string                 `protobuf:\"bytes,2,opt,name=type,proto3\" json:\"type,omitempty\"`\n\tSelectable    bool                   `protobuf:\"varint,3,opt,name=selectable,proto3\" json:\"selectable,omitempty\"`\n\tSelected      string                 `protobuf:\"bytes,4,opt,name=selected,proto3\" json:\"selected,omitempty\"`\n\tIsExpand      bool                   `protobuf:\"varint,5,opt,name=isExpand,proto3\" json:\"isExpand,omitempty\"`\n\tItems         []*GroupItem           `protobuf:\"bytes,6,rep,name=items,proto3\" json:\"items,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Group) Reset() {\n\t*x = Group{}\n\tmi := &file_daemon_started_service_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Group) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Group) ProtoMessage() {}\n\nfunc (x *Group) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Group.ProtoReflect.Descriptor instead.\nfunc (*Group) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *Group) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *Group) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\nfunc (x *Group) GetSelectable() bool {\n\tif x != nil {\n\t\treturn x.Selectable\n\t}\n\treturn false\n}\n\nfunc (x *Group) GetSelected() string {\n\tif x != nil {\n\t\treturn x.Selected\n\t}\n\treturn \"\"\n}\n\nfunc (x *Group) GetIsExpand() bool {\n\tif x != nil {\n\t\treturn x.IsExpand\n\t}\n\treturn false\n}\n\nfunc (x *Group) GetItems() []*GroupItem {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\ntype GroupItem struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tType          string                 `protobuf:\"bytes,2,opt,name=type,proto3\" json:\"type,omitempty\"`\n\tUrlTestTime   int64                  `protobuf:\"varint,3,opt,name=urlTestTime,proto3\" json:\"urlTestTime,omitempty\"`\n\tUrlTestDelay  int32                  `protobuf:\"varint,4,opt,name=urlTestDelay,proto3\" json:\"urlTestDelay,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GroupItem) Reset() {\n\t*x = GroupItem{}\n\tmi := &file_daemon_started_service_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GroupItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GroupItem) ProtoMessage() {}\n\nfunc (x *GroupItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[8]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GroupItem.ProtoReflect.Descriptor instead.\nfunc (*GroupItem) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *GroupItem) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *GroupItem) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\nfunc (x *GroupItem) GetUrlTestTime() int64 {\n\tif x != nil {\n\t\treturn x.UrlTestTime\n\t}\n\treturn 0\n}\n\nfunc (x *GroupItem) GetUrlTestDelay() int32 {\n\tif x != nil {\n\t\treturn x.UrlTestDelay\n\t}\n\treturn 0\n}\n\ntype URLTestRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tOutboundTag   string                 `protobuf:\"bytes,1,opt,name=outboundTag,proto3\" json:\"outboundTag,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *URLTestRequest) Reset() {\n\t*x = URLTestRequest{}\n\tmi := &file_daemon_started_service_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *URLTestRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*URLTestRequest) ProtoMessage() {}\n\nfunc (x *URLTestRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[9]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use URLTestRequest.ProtoReflect.Descriptor instead.\nfunc (*URLTestRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *URLTestRequest) GetOutboundTag() string {\n\tif x != nil {\n\t\treturn x.OutboundTag\n\t}\n\treturn \"\"\n}\n\ntype SelectOutboundRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tGroupTag      string                 `protobuf:\"bytes,1,opt,name=groupTag,proto3\" json:\"groupTag,omitempty\"`\n\tOutboundTag   string                 `protobuf:\"bytes,2,opt,name=outboundTag,proto3\" json:\"outboundTag,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SelectOutboundRequest) Reset() {\n\t*x = SelectOutboundRequest{}\n\tmi := &file_daemon_started_service_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SelectOutboundRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SelectOutboundRequest) ProtoMessage() {}\n\nfunc (x *SelectOutboundRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[10]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SelectOutboundRequest.ProtoReflect.Descriptor instead.\nfunc (*SelectOutboundRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *SelectOutboundRequest) GetGroupTag() string {\n\tif x != nil {\n\t\treturn x.GroupTag\n\t}\n\treturn \"\"\n}\n\nfunc (x *SelectOutboundRequest) GetOutboundTag() string {\n\tif x != nil {\n\t\treturn x.OutboundTag\n\t}\n\treturn \"\"\n}\n\ntype SetGroupExpandRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tGroupTag      string                 `protobuf:\"bytes,1,opt,name=groupTag,proto3\" json:\"groupTag,omitempty\"`\n\tIsExpand      bool                   `protobuf:\"varint,2,opt,name=isExpand,proto3\" json:\"isExpand,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SetGroupExpandRequest) Reset() {\n\t*x = SetGroupExpandRequest{}\n\tmi := &file_daemon_started_service_proto_msgTypes[11]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SetGroupExpandRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SetGroupExpandRequest) ProtoMessage() {}\n\nfunc (x *SetGroupExpandRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[11]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SetGroupExpandRequest.ProtoReflect.Descriptor instead.\nfunc (*SetGroupExpandRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *SetGroupExpandRequest) GetGroupTag() string {\n\tif x != nil {\n\t\treturn x.GroupTag\n\t}\n\treturn \"\"\n}\n\nfunc (x *SetGroupExpandRequest) GetIsExpand() bool {\n\tif x != nil {\n\t\treturn x.IsExpand\n\t}\n\treturn false\n}\n\ntype ClashMode struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tMode          string                 `protobuf:\"bytes,3,opt,name=mode,proto3\" json:\"mode,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ClashMode) Reset() {\n\t*x = ClashMode{}\n\tmi := &file_daemon_started_service_proto_msgTypes[12]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClashMode) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClashMode) ProtoMessage() {}\n\nfunc (x *ClashMode) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[12]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClashMode.ProtoReflect.Descriptor instead.\nfunc (*ClashMode) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *ClashMode) GetMode() string {\n\tif x != nil {\n\t\treturn x.Mode\n\t}\n\treturn \"\"\n}\n\ntype ClashModeStatus struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tModeList      []string               `protobuf:\"bytes,1,rep,name=modeList,proto3\" json:\"modeList,omitempty\"`\n\tCurrentMode   string                 `protobuf:\"bytes,2,opt,name=currentMode,proto3\" json:\"currentMode,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ClashModeStatus) Reset() {\n\t*x = ClashModeStatus{}\n\tmi := &file_daemon_started_service_proto_msgTypes[13]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClashModeStatus) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClashModeStatus) ProtoMessage() {}\n\nfunc (x *ClashModeStatus) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[13]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClashModeStatus.ProtoReflect.Descriptor instead.\nfunc (*ClashModeStatus) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *ClashModeStatus) GetModeList() []string {\n\tif x != nil {\n\t\treturn x.ModeList\n\t}\n\treturn nil\n}\n\nfunc (x *ClashModeStatus) GetCurrentMode() string {\n\tif x != nil {\n\t\treturn x.CurrentMode\n\t}\n\treturn \"\"\n}\n\ntype SystemProxyStatus struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAvailable     bool                   `protobuf:\"varint,1,opt,name=available,proto3\" json:\"available,omitempty\"`\n\tEnabled       bool                   `protobuf:\"varint,2,opt,name=enabled,proto3\" json:\"enabled,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SystemProxyStatus) Reset() {\n\t*x = SystemProxyStatus{}\n\tmi := &file_daemon_started_service_proto_msgTypes[14]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SystemProxyStatus) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SystemProxyStatus) ProtoMessage() {}\n\nfunc (x *SystemProxyStatus) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[14]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SystemProxyStatus.ProtoReflect.Descriptor instead.\nfunc (*SystemProxyStatus) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *SystemProxyStatus) GetAvailable() bool {\n\tif x != nil {\n\t\treturn x.Available\n\t}\n\treturn false\n}\n\nfunc (x *SystemProxyStatus) GetEnabled() bool {\n\tif x != nil {\n\t\treturn x.Enabled\n\t}\n\treturn false\n}\n\ntype SetSystemProxyEnabledRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tEnabled       bool                   `protobuf:\"varint,1,opt,name=enabled,proto3\" json:\"enabled,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SetSystemProxyEnabledRequest) Reset() {\n\t*x = SetSystemProxyEnabledRequest{}\n\tmi := &file_daemon_started_service_proto_msgTypes[15]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SetSystemProxyEnabledRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SetSystemProxyEnabledRequest) ProtoMessage() {}\n\nfunc (x *SetSystemProxyEnabledRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[15]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SetSystemProxyEnabledRequest.ProtoReflect.Descriptor instead.\nfunc (*SetSystemProxyEnabledRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *SetSystemProxyEnabledRequest) GetEnabled() bool {\n\tif x != nil {\n\t\treturn x.Enabled\n\t}\n\treturn false\n}\n\ntype SubscribeConnectionsRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tInterval      int64                  `protobuf:\"varint,1,opt,name=interval,proto3\" json:\"interval,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SubscribeConnectionsRequest) Reset() {\n\t*x = SubscribeConnectionsRequest{}\n\tmi := &file_daemon_started_service_proto_msgTypes[16]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SubscribeConnectionsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SubscribeConnectionsRequest) ProtoMessage() {}\n\nfunc (x *SubscribeConnectionsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[16]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SubscribeConnectionsRequest.ProtoReflect.Descriptor instead.\nfunc (*SubscribeConnectionsRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *SubscribeConnectionsRequest) GetInterval() int64 {\n\tif x != nil {\n\t\treturn x.Interval\n\t}\n\treturn 0\n}\n\ntype ConnectionEvent struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tType          ConnectionEventType    `protobuf:\"varint,1,opt,name=type,proto3,enum=daemon.ConnectionEventType\" json:\"type,omitempty\"`\n\tId            string                 `protobuf:\"bytes,2,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tConnection    *Connection            `protobuf:\"bytes,3,opt,name=connection,proto3\" json:\"connection,omitempty\"`\n\tUplinkDelta   int64                  `protobuf:\"varint,4,opt,name=uplinkDelta,proto3\" json:\"uplinkDelta,omitempty\"`\n\tDownlinkDelta int64                  `protobuf:\"varint,5,opt,name=downlinkDelta,proto3\" json:\"downlinkDelta,omitempty\"`\n\tClosedAt      int64                  `protobuf:\"varint,6,opt,name=closedAt,proto3\" json:\"closedAt,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ConnectionEvent) Reset() {\n\t*x = ConnectionEvent{}\n\tmi := &file_daemon_started_service_proto_msgTypes[17]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ConnectionEvent) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ConnectionEvent) ProtoMessage() {}\n\nfunc (x *ConnectionEvent) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[17]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ConnectionEvent.ProtoReflect.Descriptor instead.\nfunc (*ConnectionEvent) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *ConnectionEvent) GetType() ConnectionEventType {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn ConnectionEventType_CONNECTION_EVENT_NEW\n}\n\nfunc (x *ConnectionEvent) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *ConnectionEvent) GetConnection() *Connection {\n\tif x != nil {\n\t\treturn x.Connection\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectionEvent) GetUplinkDelta() int64 {\n\tif x != nil {\n\t\treturn x.UplinkDelta\n\t}\n\treturn 0\n}\n\nfunc (x *ConnectionEvent) GetDownlinkDelta() int64 {\n\tif x != nil {\n\t\treturn x.DownlinkDelta\n\t}\n\treturn 0\n}\n\nfunc (x *ConnectionEvent) GetClosedAt() int64 {\n\tif x != nil {\n\t\treturn x.ClosedAt\n\t}\n\treturn 0\n}\n\ntype ConnectionEvents struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tEvents        []*ConnectionEvent     `protobuf:\"bytes,1,rep,name=events,proto3\" json:\"events,omitempty\"`\n\tReset_        bool                   `protobuf:\"varint,2,opt,name=reset,proto3\" json:\"reset,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ConnectionEvents) Reset() {\n\t*x = ConnectionEvents{}\n\tmi := &file_daemon_started_service_proto_msgTypes[18]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ConnectionEvents) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ConnectionEvents) ProtoMessage() {}\n\nfunc (x *ConnectionEvents) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[18]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ConnectionEvents.ProtoReflect.Descriptor instead.\nfunc (*ConnectionEvents) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *ConnectionEvents) GetEvents() []*ConnectionEvent {\n\tif x != nil {\n\t\treturn x.Events\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectionEvents) GetReset_() bool {\n\tif x != nil {\n\t\treturn x.Reset_\n\t}\n\treturn false\n}\n\ntype Connection struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tId            string                 `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tInbound       string                 `protobuf:\"bytes,2,opt,name=inbound,proto3\" json:\"inbound,omitempty\"`\n\tInboundType   string                 `protobuf:\"bytes,3,opt,name=inboundType,proto3\" json:\"inboundType,omitempty\"`\n\tIpVersion     int32                  `protobuf:\"varint,4,opt,name=ipVersion,proto3\" json:\"ipVersion,omitempty\"`\n\tNetwork       string                 `protobuf:\"bytes,5,opt,name=network,proto3\" json:\"network,omitempty\"`\n\tSource        string                 `protobuf:\"bytes,6,opt,name=source,proto3\" json:\"source,omitempty\"`\n\tDestination   string                 `protobuf:\"bytes,7,opt,name=destination,proto3\" json:\"destination,omitempty\"`\n\tDomain        string                 `protobuf:\"bytes,8,opt,name=domain,proto3\" json:\"domain,omitempty\"`\n\tProtocol      string                 `protobuf:\"bytes,9,opt,name=protocol,proto3\" json:\"protocol,omitempty\"`\n\tUser          string                 `protobuf:\"bytes,10,opt,name=user,proto3\" json:\"user,omitempty\"`\n\tFromOutbound  string                 `protobuf:\"bytes,11,opt,name=fromOutbound,proto3\" json:\"fromOutbound,omitempty\"`\n\tCreatedAt     int64                  `protobuf:\"varint,12,opt,name=createdAt,proto3\" json:\"createdAt,omitempty\"`\n\tClosedAt      int64                  `protobuf:\"varint,13,opt,name=closedAt,proto3\" json:\"closedAt,omitempty\"`\n\tUplink        int64                  `protobuf:\"varint,14,opt,name=uplink,proto3\" json:\"uplink,omitempty\"`\n\tDownlink      int64                  `protobuf:\"varint,15,opt,name=downlink,proto3\" json:\"downlink,omitempty\"`\n\tUplinkTotal   int64                  `protobuf:\"varint,16,opt,name=uplinkTotal,proto3\" json:\"uplinkTotal,omitempty\"`\n\tDownlinkTotal int64                  `protobuf:\"varint,17,opt,name=downlinkTotal,proto3\" json:\"downlinkTotal,omitempty\"`\n\tRule          string                 `protobuf:\"bytes,18,opt,name=rule,proto3\" json:\"rule,omitempty\"`\n\tOutbound      string                 `protobuf:\"bytes,19,opt,name=outbound,proto3\" json:\"outbound,omitempty\"`\n\tOutboundType  string                 `protobuf:\"bytes,20,opt,name=outboundType,proto3\" json:\"outboundType,omitempty\"`\n\tChainList     []string               `protobuf:\"bytes,21,rep,name=chainList,proto3\" json:\"chainList,omitempty\"`\n\tProcessInfo   *ProcessInfo           `protobuf:\"bytes,22,opt,name=processInfo,proto3\" json:\"processInfo,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Connection) Reset() {\n\t*x = Connection{}\n\tmi := &file_daemon_started_service_proto_msgTypes[19]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Connection) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Connection) ProtoMessage() {}\n\nfunc (x *Connection) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[19]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Connection.ProtoReflect.Descriptor instead.\nfunc (*Connection) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *Connection) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetInbound() string {\n\tif x != nil {\n\t\treturn x.Inbound\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetInboundType() string {\n\tif x != nil {\n\t\treturn x.InboundType\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetIpVersion() int32 {\n\tif x != nil {\n\t\treturn x.IpVersion\n\t}\n\treturn 0\n}\n\nfunc (x *Connection) GetNetwork() string {\n\tif x != nil {\n\t\treturn x.Network\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetSource() string {\n\tif x != nil {\n\t\treturn x.Source\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetDestination() string {\n\tif x != nil {\n\t\treturn x.Destination\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetDomain() string {\n\tif x != nil {\n\t\treturn x.Domain\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetProtocol() string {\n\tif x != nil {\n\t\treturn x.Protocol\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetUser() string {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetFromOutbound() string {\n\tif x != nil {\n\t\treturn x.FromOutbound\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetCreatedAt() int64 {\n\tif x != nil {\n\t\treturn x.CreatedAt\n\t}\n\treturn 0\n}\n\nfunc (x *Connection) GetClosedAt() int64 {\n\tif x != nil {\n\t\treturn x.ClosedAt\n\t}\n\treturn 0\n}\n\nfunc (x *Connection) GetUplink() int64 {\n\tif x != nil {\n\t\treturn x.Uplink\n\t}\n\treturn 0\n}\n\nfunc (x *Connection) GetDownlink() int64 {\n\tif x != nil {\n\t\treturn x.Downlink\n\t}\n\treturn 0\n}\n\nfunc (x *Connection) GetUplinkTotal() int64 {\n\tif x != nil {\n\t\treturn x.UplinkTotal\n\t}\n\treturn 0\n}\n\nfunc (x *Connection) GetDownlinkTotal() int64 {\n\tif x != nil {\n\t\treturn x.DownlinkTotal\n\t}\n\treturn 0\n}\n\nfunc (x *Connection) GetRule() string {\n\tif x != nil {\n\t\treturn x.Rule\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetOutbound() string {\n\tif x != nil {\n\t\treturn x.Outbound\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetOutboundType() string {\n\tif x != nil {\n\t\treturn x.OutboundType\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connection) GetChainList() []string {\n\tif x != nil {\n\t\treturn x.ChainList\n\t}\n\treturn nil\n}\n\nfunc (x *Connection) GetProcessInfo() *ProcessInfo {\n\tif x != nil {\n\t\treturn x.ProcessInfo\n\t}\n\treturn nil\n}\n\ntype ProcessInfo struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tProcessId     uint32                 `protobuf:\"varint,1,opt,name=processId,proto3\" json:\"processId,omitempty\"`\n\tUserId        int32                  `protobuf:\"varint,2,opt,name=userId,proto3\" json:\"userId,omitempty\"`\n\tUserName      string                 `protobuf:\"bytes,3,opt,name=userName,proto3\" json:\"userName,omitempty\"`\n\tProcessPath   string                 `protobuf:\"bytes,4,opt,name=processPath,proto3\" json:\"processPath,omitempty\"`\n\tPackageName   string                 `protobuf:\"bytes,5,opt,name=packageName,proto3\" json:\"packageName,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ProcessInfo) Reset() {\n\t*x = ProcessInfo{}\n\tmi := &file_daemon_started_service_proto_msgTypes[20]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ProcessInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ProcessInfo) ProtoMessage() {}\n\nfunc (x *ProcessInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[20]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ProcessInfo.ProtoReflect.Descriptor instead.\nfunc (*ProcessInfo) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *ProcessInfo) GetProcessId() uint32 {\n\tif x != nil {\n\t\treturn x.ProcessId\n\t}\n\treturn 0\n}\n\nfunc (x *ProcessInfo) GetUserId() int32 {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn 0\n}\n\nfunc (x *ProcessInfo) GetUserName() string {\n\tif x != nil {\n\t\treturn x.UserName\n\t}\n\treturn \"\"\n}\n\nfunc (x *ProcessInfo) GetProcessPath() string {\n\tif x != nil {\n\t\treturn x.ProcessPath\n\t}\n\treturn \"\"\n}\n\nfunc (x *ProcessInfo) GetPackageName() string {\n\tif x != nil {\n\t\treturn x.PackageName\n\t}\n\treturn \"\"\n}\n\ntype CloseConnectionRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tId            string                 `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CloseConnectionRequest) Reset() {\n\t*x = CloseConnectionRequest{}\n\tmi := &file_daemon_started_service_proto_msgTypes[21]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CloseConnectionRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CloseConnectionRequest) ProtoMessage() {}\n\nfunc (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[21]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CloseConnectionRequest.ProtoReflect.Descriptor instead.\nfunc (*CloseConnectionRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *CloseConnectionRequest) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\ntype DeprecatedWarnings struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tWarnings      []*DeprecatedWarning   `protobuf:\"bytes,1,rep,name=warnings,proto3\" json:\"warnings,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeprecatedWarnings) Reset() {\n\t*x = DeprecatedWarnings{}\n\tmi := &file_daemon_started_service_proto_msgTypes[22]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeprecatedWarnings) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeprecatedWarnings) ProtoMessage() {}\n\nfunc (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[22]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeprecatedWarnings.ProtoReflect.Descriptor instead.\nfunc (*DeprecatedWarnings) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *DeprecatedWarnings) GetWarnings() []*DeprecatedWarning {\n\tif x != nil {\n\t\treturn x.Warnings\n\t}\n\treturn nil\n}\n\ntype DeprecatedWarning struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tMessage       string                 `protobuf:\"bytes,1,opt,name=message,proto3\" json:\"message,omitempty\"`\n\tImpending     bool                   `protobuf:\"varint,2,opt,name=impending,proto3\" json:\"impending,omitempty\"`\n\tMigrationLink string                 `protobuf:\"bytes,3,opt,name=migrationLink,proto3\" json:\"migrationLink,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeprecatedWarning) Reset() {\n\t*x = DeprecatedWarning{}\n\tmi := &file_daemon_started_service_proto_msgTypes[23]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeprecatedWarning) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeprecatedWarning) ProtoMessage() {}\n\nfunc (x *DeprecatedWarning) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[23]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeprecatedWarning.ProtoReflect.Descriptor instead.\nfunc (*DeprecatedWarning) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *DeprecatedWarning) GetMessage() string {\n\tif x != nil {\n\t\treturn x.Message\n\t}\n\treturn \"\"\n}\n\nfunc (x *DeprecatedWarning) GetImpending() bool {\n\tif x != nil {\n\t\treturn x.Impending\n\t}\n\treturn false\n}\n\nfunc (x *DeprecatedWarning) GetMigrationLink() string {\n\tif x != nil {\n\t\treturn x.MigrationLink\n\t}\n\treturn \"\"\n}\n\ntype StartedAt struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tStartedAt     int64                  `protobuf:\"varint,1,opt,name=startedAt,proto3\" json:\"startedAt,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *StartedAt) Reset() {\n\t*x = StartedAt{}\n\tmi := &file_daemon_started_service_proto_msgTypes[24]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *StartedAt) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StartedAt) ProtoMessage() {}\n\nfunc (x *StartedAt) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[24]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use StartedAt.ProtoReflect.Descriptor instead.\nfunc (*StartedAt) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *StartedAt) GetStartedAt() int64 {\n\tif x != nil {\n\t\treturn x.StartedAt\n\t}\n\treturn 0\n}\n\ntype Log_Message struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tLevel         LogLevel               `protobuf:\"varint,1,opt,name=level,proto3,enum=daemon.LogLevel\" json:\"level,omitempty\"`\n\tMessage       string                 `protobuf:\"bytes,2,opt,name=message,proto3\" json:\"message,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Log_Message) Reset() {\n\t*x = Log_Message{}\n\tmi := &file_daemon_started_service_proto_msgTypes[25]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Log_Message) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Log_Message) ProtoMessage() {}\n\nfunc (x *Log_Message) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_started_service_proto_msgTypes[25]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Log_Message.ProtoReflect.Descriptor instead.\nfunc (*Log_Message) Descriptor() ([]byte, []int) {\n\treturn file_daemon_started_service_proto_rawDescGZIP(), []int{3, 0}\n}\n\nfunc (x *Log_Message) GetLevel() LogLevel {\n\tif x != nil {\n\t\treturn x.Level\n\t}\n\treturn LogLevel_PANIC\n}\n\nfunc (x *Log_Message) GetMessage() string {\n\tif x != nil {\n\t\treturn x.Message\n\t}\n\treturn \"\"\n}\n\nvar File_daemon_started_service_proto protoreflect.FileDescriptor\n\nconst file_daemon_started_service_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1cdaemon/started_service.proto\\x12\\x06daemon\\x1a\\x1bgoogle/protobuf/empty.proto\\\"\\xad\\x01\\n\" +\n\t\"\\rServiceStatus\\x122\\n\" +\n\t\"\\x06status\\x18\\x01 \\x01(\\x0e2\\x1a.daemon.ServiceStatus.TypeR\\x06status\\x12\\\"\\n\" +\n\t\"\\ferrorMessage\\x18\\x02 \\x01(\\tR\\ferrorMessage\\\"D\\n\" +\n\t\"\\x04Type\\x12\\b\\n\" +\n\t\"\\x04IDLE\\x10\\x00\\x12\\f\\n\" +\n\t\"\\bSTARTING\\x10\\x01\\x12\\v\\n\" +\n\t\"\\aSTARTED\\x10\\x02\\x12\\f\\n\" +\n\t\"\\bSTOPPING\\x10\\x03\\x12\\t\\n\" +\n\t\"\\x05FATAL\\x10\\x04\\\"D\\n\" +\n\t\"\\x14ReloadServiceRequest\\x12,\\n\" +\n\t\"\\x11newProfileContent\\x18\\x01 \\x01(\\tR\\x11newProfileContent\\\"4\\n\" +\n\t\"\\x16SubscribeStatusRequest\\x12\\x1a\\n\" +\n\t\"\\binterval\\x18\\x01 \\x01(\\x03R\\binterval\\\"\\x99\\x01\\n\" +\n\t\"\\x03Log\\x12/\\n\" +\n\t\"\\bmessages\\x18\\x01 \\x03(\\v2\\x13.daemon.Log.MessageR\\bmessages\\x12\\x14\\n\" +\n\t\"\\x05reset\\x18\\x02 \\x01(\\bR\\x05reset\\x1aK\\n\" +\n\t\"\\aMessage\\x12&\\n\" +\n\t\"\\x05level\\x18\\x01 \\x01(\\x0e2\\x10.daemon.LogLevelR\\x05level\\x12\\x18\\n\" +\n\t\"\\amessage\\x18\\x02 \\x01(\\tR\\amessage\\\"9\\n\" +\n\t\"\\x0fDefaultLogLevel\\x12&\\n\" +\n\t\"\\x05level\\x18\\x01 \\x01(\\x0e2\\x10.daemon.LogLevelR\\x05level\\\"\\xb6\\x02\\n\" +\n\t\"\\x06Status\\x12\\x16\\n\" +\n\t\"\\x06memory\\x18\\x01 \\x01(\\x04R\\x06memory\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"goroutines\\x18\\x02 \\x01(\\x05R\\n\" +\n\t\"goroutines\\x12$\\n\" +\n\t\"\\rconnectionsIn\\x18\\x03 \\x01(\\x05R\\rconnectionsIn\\x12&\\n\" +\n\t\"\\x0econnectionsOut\\x18\\x04 \\x01(\\x05R\\x0econnectionsOut\\x12*\\n\" +\n\t\"\\x10trafficAvailable\\x18\\x05 \\x01(\\bR\\x10trafficAvailable\\x12\\x16\\n\" +\n\t\"\\x06uplink\\x18\\x06 \\x01(\\x03R\\x06uplink\\x12\\x1a\\n\" +\n\t\"\\bdownlink\\x18\\a \\x01(\\x03R\\bdownlink\\x12 \\n\" +\n\t\"\\vuplinkTotal\\x18\\b \\x01(\\x03R\\vuplinkTotal\\x12$\\n\" +\n\t\"\\rdownlinkTotal\\x18\\t \\x01(\\x03R\\rdownlinkTotal\\\"-\\n\" +\n\t\"\\x06Groups\\x12#\\n\" +\n\t\"\\x05group\\x18\\x01 \\x03(\\v2\\r.daemon.GroupR\\x05group\\\"\\xae\\x01\\n\" +\n\t\"\\x05Group\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12\\x12\\n\" +\n\t\"\\x04type\\x18\\x02 \\x01(\\tR\\x04type\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"selectable\\x18\\x03 \\x01(\\bR\\n\" +\n\t\"selectable\\x12\\x1a\\n\" +\n\t\"\\bselected\\x18\\x04 \\x01(\\tR\\bselected\\x12\\x1a\\n\" +\n\t\"\\bisExpand\\x18\\x05 \\x01(\\bR\\bisExpand\\x12'\\n\" +\n\t\"\\x05items\\x18\\x06 \\x03(\\v2\\x11.daemon.GroupItemR\\x05items\\\"w\\n\" +\n\t\"\\tGroupItem\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12\\x12\\n\" +\n\t\"\\x04type\\x18\\x02 \\x01(\\tR\\x04type\\x12 \\n\" +\n\t\"\\vurlTestTime\\x18\\x03 \\x01(\\x03R\\vurlTestTime\\x12\\\"\\n\" +\n\t\"\\furlTestDelay\\x18\\x04 \\x01(\\x05R\\furlTestDelay\\\"2\\n\" +\n\t\"\\x0eURLTestRequest\\x12 \\n\" +\n\t\"\\voutboundTag\\x18\\x01 \\x01(\\tR\\voutboundTag\\\"U\\n\" +\n\t\"\\x15SelectOutboundRequest\\x12\\x1a\\n\" +\n\t\"\\bgroupTag\\x18\\x01 \\x01(\\tR\\bgroupTag\\x12 \\n\" +\n\t\"\\voutboundTag\\x18\\x02 \\x01(\\tR\\voutboundTag\\\"O\\n\" +\n\t\"\\x15SetGroupExpandRequest\\x12\\x1a\\n\" +\n\t\"\\bgroupTag\\x18\\x01 \\x01(\\tR\\bgroupTag\\x12\\x1a\\n\" +\n\t\"\\bisExpand\\x18\\x02 \\x01(\\bR\\bisExpand\\\"\\x1f\\n\" +\n\t\"\\tClashMode\\x12\\x12\\n\" +\n\t\"\\x04mode\\x18\\x03 \\x01(\\tR\\x04mode\\\"O\\n\" +\n\t\"\\x0fClashModeStatus\\x12\\x1a\\n\" +\n\t\"\\bmodeList\\x18\\x01 \\x03(\\tR\\bmodeList\\x12 \\n\" +\n\t\"\\vcurrentMode\\x18\\x02 \\x01(\\tR\\vcurrentMode\\\"K\\n\" +\n\t\"\\x11SystemProxyStatus\\x12\\x1c\\n\" +\n\t\"\\tavailable\\x18\\x01 \\x01(\\bR\\tavailable\\x12\\x18\\n\" +\n\t\"\\aenabled\\x18\\x02 \\x01(\\bR\\aenabled\\\"8\\n\" +\n\t\"\\x1cSetSystemProxyEnabledRequest\\x12\\x18\\n\" +\n\t\"\\aenabled\\x18\\x01 \\x01(\\bR\\aenabled\\\"9\\n\" +\n\t\"\\x1bSubscribeConnectionsRequest\\x12\\x1a\\n\" +\n\t\"\\binterval\\x18\\x01 \\x01(\\x03R\\binterval\\\"\\xea\\x01\\n\" +\n\t\"\\x0fConnectionEvent\\x12/\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2\\x1b.daemon.ConnectionEventTypeR\\x04type\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x02 \\x01(\\tR\\x02id\\x122\\n\" +\n\t\"\\n\" +\n\t\"connection\\x18\\x03 \\x01(\\v2\\x12.daemon.ConnectionR\\n\" +\n\t\"connection\\x12 \\n\" +\n\t\"\\vuplinkDelta\\x18\\x04 \\x01(\\x03R\\vuplinkDelta\\x12$\\n\" +\n\t\"\\rdownlinkDelta\\x18\\x05 \\x01(\\x03R\\rdownlinkDelta\\x12\\x1a\\n\" +\n\t\"\\bclosedAt\\x18\\x06 \\x01(\\x03R\\bclosedAt\\\"Y\\n\" +\n\t\"\\x10ConnectionEvents\\x12/\\n\" +\n\t\"\\x06events\\x18\\x01 \\x03(\\v2\\x17.daemon.ConnectionEventR\\x06events\\x12\\x14\\n\" +\n\t\"\\x05reset\\x18\\x02 \\x01(\\bR\\x05reset\\\"\\x95\\x05\\n\" +\n\t\"\\n\" +\n\t\"Connection\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x01 \\x01(\\tR\\x02id\\x12\\x18\\n\" +\n\t\"\\ainbound\\x18\\x02 \\x01(\\tR\\ainbound\\x12 \\n\" +\n\t\"\\vinboundType\\x18\\x03 \\x01(\\tR\\vinboundType\\x12\\x1c\\n\" +\n\t\"\\tipVersion\\x18\\x04 \\x01(\\x05R\\tipVersion\\x12\\x18\\n\" +\n\t\"\\anetwork\\x18\\x05 \\x01(\\tR\\anetwork\\x12\\x16\\n\" +\n\t\"\\x06source\\x18\\x06 \\x01(\\tR\\x06source\\x12 \\n\" +\n\t\"\\vdestination\\x18\\a \\x01(\\tR\\vdestination\\x12\\x16\\n\" +\n\t\"\\x06domain\\x18\\b \\x01(\\tR\\x06domain\\x12\\x1a\\n\" +\n\t\"\\bprotocol\\x18\\t \\x01(\\tR\\bprotocol\\x12\\x12\\n\" +\n\t\"\\x04user\\x18\\n\" +\n\t\" \\x01(\\tR\\x04user\\x12\\\"\\n\" +\n\t\"\\ffromOutbound\\x18\\v \\x01(\\tR\\ffromOutbound\\x12\\x1c\\n\" +\n\t\"\\tcreatedAt\\x18\\f \\x01(\\x03R\\tcreatedAt\\x12\\x1a\\n\" +\n\t\"\\bclosedAt\\x18\\r \\x01(\\x03R\\bclosedAt\\x12\\x16\\n\" +\n\t\"\\x06uplink\\x18\\x0e \\x01(\\x03R\\x06uplink\\x12\\x1a\\n\" +\n\t\"\\bdownlink\\x18\\x0f \\x01(\\x03R\\bdownlink\\x12 \\n\" +\n\t\"\\vuplinkTotal\\x18\\x10 \\x01(\\x03R\\vuplinkTotal\\x12$\\n\" +\n\t\"\\rdownlinkTotal\\x18\\x11 \\x01(\\x03R\\rdownlinkTotal\\x12\\x12\\n\" +\n\t\"\\x04rule\\x18\\x12 \\x01(\\tR\\x04rule\\x12\\x1a\\n\" +\n\t\"\\boutbound\\x18\\x13 \\x01(\\tR\\boutbound\\x12\\\"\\n\" +\n\t\"\\foutboundType\\x18\\x14 \\x01(\\tR\\foutboundType\\x12\\x1c\\n\" +\n\t\"\\tchainList\\x18\\x15 \\x03(\\tR\\tchainList\\x125\\n\" +\n\t\"\\vprocessInfo\\x18\\x16 \\x01(\\v2\\x13.daemon.ProcessInfoR\\vprocessInfo\\\"\\xa3\\x01\\n\" +\n\t\"\\vProcessInfo\\x12\\x1c\\n\" +\n\t\"\\tprocessId\\x18\\x01 \\x01(\\rR\\tprocessId\\x12\\x16\\n\" +\n\t\"\\x06userId\\x18\\x02 \\x01(\\x05R\\x06userId\\x12\\x1a\\n\" +\n\t\"\\buserName\\x18\\x03 \\x01(\\tR\\buserName\\x12 \\n\" +\n\t\"\\vprocessPath\\x18\\x04 \\x01(\\tR\\vprocessPath\\x12 \\n\" +\n\t\"\\vpackageName\\x18\\x05 \\x01(\\tR\\vpackageName\\\"(\\n\" +\n\t\"\\x16CloseConnectionRequest\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x01 \\x01(\\tR\\x02id\\\"K\\n\" +\n\t\"\\x12DeprecatedWarnings\\x125\\n\" +\n\t\"\\bwarnings\\x18\\x01 \\x03(\\v2\\x19.daemon.DeprecatedWarningR\\bwarnings\\\"q\\n\" +\n\t\"\\x11DeprecatedWarning\\x12\\x18\\n\" +\n\t\"\\amessage\\x18\\x01 \\x01(\\tR\\amessage\\x12\\x1c\\n\" +\n\t\"\\timpending\\x18\\x02 \\x01(\\bR\\timpending\\x12$\\n\" +\n\t\"\\rmigrationLink\\x18\\x03 \\x01(\\tR\\rmigrationLink\\\")\\n\" +\n\t\"\\tStartedAt\\x12\\x1c\\n\" +\n\t\"\\tstartedAt\\x18\\x01 \\x01(\\x03R\\tstartedAt*U\\n\" +\n\t\"\\bLogLevel\\x12\\t\\n\" +\n\t\"\\x05PANIC\\x10\\x00\\x12\\t\\n\" +\n\t\"\\x05FATAL\\x10\\x01\\x12\\t\\n\" +\n\t\"\\x05ERROR\\x10\\x02\\x12\\b\\n\" +\n\t\"\\x04WARN\\x10\\x03\\x12\\b\\n\" +\n\t\"\\x04INFO\\x10\\x04\\x12\\t\\n\" +\n\t\"\\x05DEBUG\\x10\\x05\\x12\\t\\n\" +\n\t\"\\x05TRACE\\x10\\x06*i\\n\" +\n\t\"\\x13ConnectionEventType\\x12\\x18\\n\" +\n\t\"\\x14CONNECTION_EVENT_NEW\\x10\\x00\\x12\\x1b\\n\" +\n\t\"\\x17CONNECTION_EVENT_UPDATE\\x10\\x01\\x12\\x1b\\n\" +\n\t\"\\x17CONNECTION_EVENT_CLOSED\\x10\\x022\\xe5\\v\\n\" +\n\t\"\\x0eStartedService\\x12=\\n\" +\n\t\"\\vStopService\\x12\\x16.google.protobuf.Empty\\x1a\\x16.google.protobuf.Empty\\x12?\\n\" +\n\t\"\\rReloadService\\x12\\x16.google.protobuf.Empty\\x1a\\x16.google.protobuf.Empty\\x12K\\n\" +\n\t\"\\x16SubscribeServiceStatus\\x12\\x16.google.protobuf.Empty\\x1a\\x15.daemon.ServiceStatus\\\"\\x000\\x01\\x127\\n\" +\n\t\"\\fSubscribeLog\\x12\\x16.google.protobuf.Empty\\x1a\\v.daemon.Log\\\"\\x000\\x01\\x12G\\n\" +\n\t\"\\x12GetDefaultLogLevel\\x12\\x16.google.protobuf.Empty\\x1a\\x17.daemon.DefaultLogLevel\\\"\\x00\\x12=\\n\" +\n\t\"\\tClearLogs\\x12\\x16.google.protobuf.Empty\\x1a\\x16.google.protobuf.Empty\\\"\\x00\\x12E\\n\" +\n\t\"\\x0fSubscribeStatus\\x12\\x1e.daemon.SubscribeStatusRequest\\x1a\\x0e.daemon.Status\\\"\\x000\\x01\\x12=\\n\" +\n\t\"\\x0fSubscribeGroups\\x12\\x16.google.protobuf.Empty\\x1a\\x0e.daemon.Groups\\\"\\x000\\x01\\x12G\\n\" +\n\t\"\\x12GetClashModeStatus\\x12\\x16.google.protobuf.Empty\\x1a\\x17.daemon.ClashModeStatus\\\"\\x00\\x12C\\n\" +\n\t\"\\x12SubscribeClashMode\\x12\\x16.google.protobuf.Empty\\x1a\\x11.daemon.ClashMode\\\"\\x000\\x01\\x12;\\n\" +\n\t\"\\fSetClashMode\\x12\\x11.daemon.ClashMode\\x1a\\x16.google.protobuf.Empty\\\"\\x00\\x12;\\n\" +\n\t\"\\aURLTest\\x12\\x16.daemon.URLTestRequest\\x1a\\x16.google.protobuf.Empty\\\"\\x00\\x12I\\n\" +\n\t\"\\x0eSelectOutbound\\x12\\x1d.daemon.SelectOutboundRequest\\x1a\\x16.google.protobuf.Empty\\\"\\x00\\x12I\\n\" +\n\t\"\\x0eSetGroupExpand\\x12\\x1d.daemon.SetGroupExpandRequest\\x1a\\x16.google.protobuf.Empty\\\"\\x00\\x12K\\n\" +\n\t\"\\x14GetSystemProxyStatus\\x12\\x16.google.protobuf.Empty\\x1a\\x19.daemon.SystemProxyStatus\\\"\\x00\\x12W\\n\" +\n\t\"\\x15SetSystemProxyEnabled\\x12$.daemon.SetSystemProxyEnabledRequest\\x1a\\x16.google.protobuf.Empty\\\"\\x00\\x12Y\\n\" +\n\t\"\\x14SubscribeConnections\\x12#.daemon.SubscribeConnectionsRequest\\x1a\\x18.daemon.ConnectionEvents\\\"\\x000\\x01\\x12K\\n\" +\n\t\"\\x0fCloseConnection\\x12\\x1e.daemon.CloseConnectionRequest\\x1a\\x16.google.protobuf.Empty\\\"\\x00\\x12G\\n\" +\n\t\"\\x13CloseAllConnections\\x12\\x16.google.protobuf.Empty\\x1a\\x16.google.protobuf.Empty\\\"\\x00\\x12M\\n\" +\n\t\"\\x15GetDeprecatedWarnings\\x12\\x16.google.protobuf.Empty\\x1a\\x1a.daemon.DeprecatedWarnings\\\"\\x00\\x12;\\n\" +\n\t\"\\fGetStartedAt\\x12\\x16.google.protobuf.Empty\\x1a\\x11.daemon.StartedAt\\\"\\x00B%Z#github.com/sagernet/sing-box/daemonb\\x06proto3\"\n\nvar (\n\tfile_daemon_started_service_proto_rawDescOnce sync.Once\n\tfile_daemon_started_service_proto_rawDescData []byte\n)\n\nfunc file_daemon_started_service_proto_rawDescGZIP() []byte {\n\tfile_daemon_started_service_proto_rawDescOnce.Do(func() {\n\t\tfile_daemon_started_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)))\n\t})\n\treturn file_daemon_started_service_proto_rawDescData\n}\n\nvar (\n\tfile_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3)\n\tfile_daemon_started_service_proto_msgTypes  = make([]protoimpl.MessageInfo, 26)\n\tfile_daemon_started_service_proto_goTypes   = []any{\n\t\t(LogLevel)(0),                        // 0: daemon.LogLevel\n\t\t(ConnectionEventType)(0),             // 1: daemon.ConnectionEventType\n\t\t(ServiceStatus_Type)(0),              // 2: daemon.ServiceStatus.Type\n\t\t(*ServiceStatus)(nil),                // 3: daemon.ServiceStatus\n\t\t(*ReloadServiceRequest)(nil),         // 4: daemon.ReloadServiceRequest\n\t\t(*SubscribeStatusRequest)(nil),       // 5: daemon.SubscribeStatusRequest\n\t\t(*Log)(nil),                          // 6: daemon.Log\n\t\t(*DefaultLogLevel)(nil),              // 7: daemon.DefaultLogLevel\n\t\t(*Status)(nil),                       // 8: daemon.Status\n\t\t(*Groups)(nil),                       // 9: daemon.Groups\n\t\t(*Group)(nil),                        // 10: daemon.Group\n\t\t(*GroupItem)(nil),                    // 11: daemon.GroupItem\n\t\t(*URLTestRequest)(nil),               // 12: daemon.URLTestRequest\n\t\t(*SelectOutboundRequest)(nil),        // 13: daemon.SelectOutboundRequest\n\t\t(*SetGroupExpandRequest)(nil),        // 14: daemon.SetGroupExpandRequest\n\t\t(*ClashMode)(nil),                    // 15: daemon.ClashMode\n\t\t(*ClashModeStatus)(nil),              // 16: daemon.ClashModeStatus\n\t\t(*SystemProxyStatus)(nil),            // 17: daemon.SystemProxyStatus\n\t\t(*SetSystemProxyEnabledRequest)(nil), // 18: daemon.SetSystemProxyEnabledRequest\n\t\t(*SubscribeConnectionsRequest)(nil),  // 19: daemon.SubscribeConnectionsRequest\n\t\t(*ConnectionEvent)(nil),              // 20: daemon.ConnectionEvent\n\t\t(*ConnectionEvents)(nil),             // 21: daemon.ConnectionEvents\n\t\t(*Connection)(nil),                   // 22: daemon.Connection\n\t\t(*ProcessInfo)(nil),                  // 23: daemon.ProcessInfo\n\t\t(*CloseConnectionRequest)(nil),       // 24: daemon.CloseConnectionRequest\n\t\t(*DeprecatedWarnings)(nil),           // 25: daemon.DeprecatedWarnings\n\t\t(*DeprecatedWarning)(nil),            // 26: daemon.DeprecatedWarning\n\t\t(*StartedAt)(nil),                    // 27: daemon.StartedAt\n\t\t(*Log_Message)(nil),                  // 28: daemon.Log.Message\n\t\t(*emptypb.Empty)(nil),                // 29: google.protobuf.Empty\n\t}\n)\n\nvar file_daemon_started_service_proto_depIdxs = []int32{\n\t2,  // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type\n\t28, // 1: daemon.Log.messages:type_name -> daemon.Log.Message\n\t0,  // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel\n\t10, // 3: daemon.Groups.group:type_name -> daemon.Group\n\t11, // 4: daemon.Group.items:type_name -> daemon.GroupItem\n\t1,  // 5: daemon.ConnectionEvent.type:type_name -> daemon.ConnectionEventType\n\t22, // 6: daemon.ConnectionEvent.connection:type_name -> daemon.Connection\n\t20, // 7: daemon.ConnectionEvents.events:type_name -> daemon.ConnectionEvent\n\t23, // 8: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo\n\t26, // 9: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning\n\t0,  // 10: daemon.Log.Message.level:type_name -> daemon.LogLevel\n\t29, // 11: daemon.StartedService.StopService:input_type -> google.protobuf.Empty\n\t29, // 12: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty\n\t29, // 13: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty\n\t29, // 14: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty\n\t29, // 15: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty\n\t29, // 16: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty\n\t5,  // 17: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest\n\t29, // 18: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty\n\t29, // 19: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty\n\t29, // 20: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty\n\t15, // 21: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode\n\t12, // 22: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest\n\t13, // 23: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest\n\t14, // 24: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest\n\t29, // 25: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty\n\t18, // 26: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest\n\t19, // 27: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest\n\t24, // 28: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest\n\t29, // 29: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty\n\t29, // 30: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty\n\t29, // 31: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty\n\t29, // 32: daemon.StartedService.StopService:output_type -> google.protobuf.Empty\n\t29, // 33: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty\n\t3,  // 34: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus\n\t6,  // 35: daemon.StartedService.SubscribeLog:output_type -> daemon.Log\n\t7,  // 36: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel\n\t29, // 37: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty\n\t8,  // 38: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status\n\t9,  // 39: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups\n\t16, // 40: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus\n\t15, // 41: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode\n\t29, // 42: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty\n\t29, // 43: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty\n\t29, // 44: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty\n\t29, // 45: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty\n\t17, // 46: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus\n\t29, // 47: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty\n\t21, // 48: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents\n\t29, // 49: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty\n\t29, // 50: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty\n\t25, // 51: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings\n\t27, // 52: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt\n\t32, // [32:53] is the sub-list for method output_type\n\t11, // [11:32] is the sub-list for method input_type\n\t11, // [11:11] is the sub-list for extension type_name\n\t11, // [11:11] is the sub-list for extension extendee\n\t0,  // [0:11] is the sub-list for field type_name\n}\n\nfunc init() { file_daemon_started_service_proto_init() }\nfunc file_daemon_started_service_proto_init() {\n\tif File_daemon_started_service_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)),\n\t\t\tNumEnums:      3,\n\t\t\tNumMessages:   26,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_daemon_started_service_proto_goTypes,\n\t\tDependencyIndexes: file_daemon_started_service_proto_depIdxs,\n\t\tEnumInfos:         file_daemon_started_service_proto_enumTypes,\n\t\tMessageInfos:      file_daemon_started_service_proto_msgTypes,\n\t}.Build()\n\tFile_daemon_started_service_proto = out.File\n\tfile_daemon_started_service_proto_goTypes = nil\n\tfile_daemon_started_service_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "daemon/started_service.proto",
    "content": "syntax = \"proto3\";\n\npackage daemon;\noption go_package = \"github.com/sagernet/sing-box/daemon\";\n\nimport \"google/protobuf/empty.proto\";\n\nservice StartedService {\n  rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty);\n  rpc ReloadService(google.protobuf.Empty) returns (google.protobuf.Empty);\n\n  rpc SubscribeServiceStatus(google.protobuf.Empty) returns(stream ServiceStatus) {}\n  rpc SubscribeLog(google.protobuf.Empty) returns(stream Log) {}\n  rpc GetDefaultLogLevel(google.protobuf.Empty) returns(DefaultLogLevel) {}\n  rpc ClearLogs(google.protobuf.Empty) returns(google.protobuf.Empty) {}\n  rpc SubscribeStatus(SubscribeStatusRequest) returns(stream Status) {}\n  rpc SubscribeGroups(google.protobuf.Empty) returns(stream Groups) {}\n\n  rpc GetClashModeStatus(google.protobuf.Empty) returns(ClashModeStatus) {}\n  rpc SubscribeClashMode(google.protobuf.Empty) returns(stream ClashMode) {}\n  rpc SetClashMode(ClashMode) returns(google.protobuf.Empty) {}\n\n  rpc URLTest(URLTestRequest) returns(google.protobuf.Empty) {}\n  rpc SelectOutbound(SelectOutboundRequest) returns (google.protobuf.Empty) {}\n  rpc SetGroupExpand(SetGroupExpandRequest) returns (google.protobuf.Empty) {}\n\n  rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}\n  rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}\n\n  rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {}\n  rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}\n  rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}\n  rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}\n  rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {}\n}\n\nmessage ServiceStatus {\n  enum Type {\n    IDLE = 0;\n    STARTING = 1;\n    STARTED = 2;\n    STOPPING = 3;\n    FATAL = 4;\n  }\n  Type status = 1;\n  string errorMessage = 2;\n}\n\nmessage ReloadServiceRequest {\n  string newProfileContent = 1;\n}\n\nmessage SubscribeStatusRequest {\n  int64 interval = 1;\n}\n\nenum LogLevel {\n  PANIC = 0;\n  FATAL = 1;\n  ERROR = 2;\n  WARN = 3;\n  INFO = 4;\n  DEBUG = 5;\n  TRACE = 6;\n}\n\nmessage Log {\n  repeated Message messages = 1;\n  bool reset = 2;\n  message Message {\n    LogLevel level = 1;\n    string message = 2;\n  }\n}\n\nmessage DefaultLogLevel {\n  LogLevel level = 1;\n}\n\nmessage Status {\n  uint64 memory = 1;\n  int32 goroutines = 2;\n  int32 connectionsIn = 3;\n  int32 connectionsOut = 4;\n  bool trafficAvailable = 5;\n  int64 uplink = 6;\n  int64 downlink = 7;\n  int64 uplinkTotal = 8;\n  int64 downlinkTotal = 9;\n}\n\nmessage Groups {\n  repeated Group group = 1;\n}\n\nmessage Group {\n  string tag = 1;\n  string type = 2;\n  bool selectable = 3;\n  string selected = 4;\n  bool isExpand = 5;\n  repeated GroupItem items = 6;\n}\n\nmessage GroupItem {\n  string tag = 1;\n  string type = 2;\n  int64 urlTestTime = 3;\n  int32 urlTestDelay = 4;\n}\n\nmessage URLTestRequest {\n  string outboundTag = 1;\n}\n\nmessage SelectOutboundRequest {\n  string groupTag = 1;\n  string outboundTag = 2;\n}\n\nmessage SetGroupExpandRequest {\n  string groupTag = 1;\n  bool isExpand = 2;\n}\n\nmessage ClashMode {\n  string mode = 3;\n}\n\nmessage ClashModeStatus {\n  repeated string modeList = 1;\n  string currentMode = 2;\n}\n\nmessage SystemProxyStatus {\n  bool available = 1;\n  bool enabled = 2;\n}\n\nmessage SetSystemProxyEnabledRequest {\n  bool enabled = 1;\n}\n\nmessage SubscribeConnectionsRequest {\n  int64 interval = 1;\n}\n\nenum ConnectionEventType {\n  CONNECTION_EVENT_NEW = 0;\n  CONNECTION_EVENT_UPDATE = 1;\n  CONNECTION_EVENT_CLOSED = 2;\n}\n\nmessage ConnectionEvent {\n  ConnectionEventType type = 1;\n  string id = 2;\n  Connection connection = 3;\n  int64 uplinkDelta = 4;\n  int64 downlinkDelta = 5;\n  int64 closedAt = 6;\n}\n\nmessage ConnectionEvents {\n  repeated ConnectionEvent events = 1;\n  bool reset = 2;\n}\n\nmessage Connection {\n  string id = 1;\n  string inbound = 2;\n  string inboundType = 3;\n  int32 ipVersion = 4;\n  string network = 5;\n  string source = 6;\n  string destination = 7;\n  string domain = 8;\n  string protocol = 9;\n  string user = 10;\n  string fromOutbound = 11;\n  int64 createdAt = 12;\n  int64 closedAt = 13;\n  int64 uplink = 14;\n  int64 downlink = 15;\n  int64 uplinkTotal = 16;\n  int64 downlinkTotal = 17;\n  string rule = 18;\n  string outbound = 19;\n  string outboundType = 20;\n  repeated string chainList = 21;\n  ProcessInfo processInfo = 22;\n}\n\nmessage ProcessInfo {\n  uint32 processId = 1;\n  int32 userId = 2;\n  string userName = 3;\n  string processPath = 4;\n  string packageName = 5;\n}\n\nmessage CloseConnectionRequest {\n  string id = 1;\n}\n\nmessage DeprecatedWarnings {\n  repeated DeprecatedWarning warnings = 1;\n}\n\nmessage DeprecatedWarning {\n  string message = 1;\n  bool impending = 2;\n  string migrationLink = 3;\n}\n\nmessage StartedAt {\n  int64 startedAt = 1;\n}"
  },
  {
    "path": "daemon/started_service_grpc.pb.go",
    "content": "package daemon\n\nimport (\n\tcontext \"context\"\n\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tStartedService_StopService_FullMethodName            = \"/daemon.StartedService/StopService\"\n\tStartedService_ReloadService_FullMethodName          = \"/daemon.StartedService/ReloadService\"\n\tStartedService_SubscribeServiceStatus_FullMethodName = \"/daemon.StartedService/SubscribeServiceStatus\"\n\tStartedService_SubscribeLog_FullMethodName           = \"/daemon.StartedService/SubscribeLog\"\n\tStartedService_GetDefaultLogLevel_FullMethodName     = \"/daemon.StartedService/GetDefaultLogLevel\"\n\tStartedService_ClearLogs_FullMethodName              = \"/daemon.StartedService/ClearLogs\"\n\tStartedService_SubscribeStatus_FullMethodName        = \"/daemon.StartedService/SubscribeStatus\"\n\tStartedService_SubscribeGroups_FullMethodName        = \"/daemon.StartedService/SubscribeGroups\"\n\tStartedService_GetClashModeStatus_FullMethodName     = \"/daemon.StartedService/GetClashModeStatus\"\n\tStartedService_SubscribeClashMode_FullMethodName     = \"/daemon.StartedService/SubscribeClashMode\"\n\tStartedService_SetClashMode_FullMethodName           = \"/daemon.StartedService/SetClashMode\"\n\tStartedService_URLTest_FullMethodName                = \"/daemon.StartedService/URLTest\"\n\tStartedService_SelectOutbound_FullMethodName         = \"/daemon.StartedService/SelectOutbound\"\n\tStartedService_SetGroupExpand_FullMethodName         = \"/daemon.StartedService/SetGroupExpand\"\n\tStartedService_GetSystemProxyStatus_FullMethodName   = \"/daemon.StartedService/GetSystemProxyStatus\"\n\tStartedService_SetSystemProxyEnabled_FullMethodName  = \"/daemon.StartedService/SetSystemProxyEnabled\"\n\tStartedService_SubscribeConnections_FullMethodName   = \"/daemon.StartedService/SubscribeConnections\"\n\tStartedService_CloseConnection_FullMethodName        = \"/daemon.StartedService/CloseConnection\"\n\tStartedService_CloseAllConnections_FullMethodName    = \"/daemon.StartedService/CloseAllConnections\"\n\tStartedService_GetDeprecatedWarnings_FullMethodName  = \"/daemon.StartedService/GetDeprecatedWarnings\"\n\tStartedService_GetStartedAt_FullMethodName           = \"/daemon.StartedService/GetStartedAt\"\n)\n\n// StartedServiceClient is the client API for StartedService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype StartedServiceClient interface {\n\tStopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tSubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error)\n\tSubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error)\n\tGetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error)\n\tClearLogs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tSubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error)\n\tSubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error)\n\tGetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error)\n\tSubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error)\n\tSetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tURLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tSelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tSetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tGetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)\n\tSetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tSubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error)\n\tCloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tCloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tGetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)\n\tGetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error)\n}\n\ntype startedServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewStartedServiceClient(cc grpc.ClientConnInterface) StartedServiceClient {\n\treturn &startedServiceClient{cc}\n}\n\nfunc (c *startedServiceClient) StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, StartedService_StopService_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, StartedService_ReloadService_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[0], StartedService_SubscribeServiceStatus_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[emptypb.Empty, ServiceStatus]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeServiceStatusClient = grpc.ServerStreamingClient[ServiceStatus]\n\nfunc (c *startedServiceClient) SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[1], StartedService_SubscribeLog_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[emptypb.Empty, Log]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeLogClient = grpc.ServerStreamingClient[Log]\n\nfunc (c *startedServiceClient) GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(DefaultLogLevel)\n\terr := c.cc.Invoke(ctx, StartedService_GetDefaultLogLevel_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) ClearLogs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, StartedService_ClearLogs_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[2], StartedService_SubscribeStatus_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[SubscribeStatusRequest, Status]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeStatusClient = grpc.ServerStreamingClient[Status]\n\nfunc (c *startedServiceClient) SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[3], StartedService_SubscribeGroups_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[emptypb.Empty, Groups]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeGroupsClient = grpc.ServerStreamingClient[Groups]\n\nfunc (c *startedServiceClient) GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ClashModeStatus)\n\terr := c.cc.Invoke(ctx, StartedService_GetClashModeStatus_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[4], StartedService_SubscribeClashMode_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[emptypb.Empty, ClashMode]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeClashModeClient = grpc.ServerStreamingClient[ClashMode]\n\nfunc (c *startedServiceClient) SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, StartedService_SetClashMode_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, StartedService_URLTest_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, StartedService_SelectOutbound_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, StartedService_SetGroupExpand_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(SystemProxyStatus)\n\terr := c.cc.Invoke(ctx, StartedService_GetSystemProxyStatus_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, StartedService_SetSystemProxyEnabled_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[SubscribeConnectionsRequest, ConnectionEvents]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeConnectionsClient = grpc.ServerStreamingClient[ConnectionEvents]\n\nfunc (c *startedServiceClient) CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, StartedService_CloseConnection_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, StartedService_CloseAllConnections_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(DeprecatedWarnings)\n\terr := c.cc.Invoke(ctx, StartedService_GetDeprecatedWarnings_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *startedServiceClient) GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(StartedAt)\n\terr := c.cc.Invoke(ctx, StartedService_GetStartedAt_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// StartedServiceServer is the server API for StartedService service.\n// All implementations must embed UnimplementedStartedServiceServer\n// for forward compatibility.\ntype StartedServiceServer interface {\n\tStopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\tReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\tSubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error\n\tSubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error\n\tGetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error)\n\tClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\tSubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error\n\tSubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error\n\tGetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error)\n\tSubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error\n\tSetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error)\n\tURLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error)\n\tSelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error)\n\tSetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)\n\tGetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)\n\tSetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)\n\tSubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error\n\tCloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)\n\tCloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\tGetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)\n\tGetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error)\n\tmustEmbedUnimplementedStartedServiceServer()\n}\n\n// UnimplementedStartedServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedStartedServiceServer struct{}\n\nfunc (UnimplementedStartedServiceServer) StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method StopService not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ReloadService not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error {\n\treturn status.Error(codes.Unimplemented, \"method SubscribeServiceStatus not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error {\n\treturn status.Error(codes.Unimplemented, \"method SubscribeLog not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetDefaultLogLevel not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ClearLogs not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error {\n\treturn status.Error(codes.Unimplemented, \"method SubscribeStatus not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error {\n\treturn status.Error(codes.Unimplemented, \"method SubscribeGroups not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetClashModeStatus not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error {\n\treturn status.Error(codes.Unimplemented, \"method SubscribeClashMode not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SetClashMode not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method URLTest not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SelectOutbound not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SetGroupExpand not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetSystemProxyStatus not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SetSystemProxyEnabled not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error {\n\treturn status.Error(codes.Unimplemented, \"method SubscribeConnections not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method CloseConnection not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method CloseAllConnections not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetDeprecatedWarnings not implemented\")\n}\n\nfunc (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetStartedAt not implemented\")\n}\nfunc (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}\nfunc (UnimplementedStartedServiceServer) testEmbeddedByValue()                        {}\n\n// UnsafeStartedServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to StartedServiceServer will\n// result in compilation errors.\ntype UnsafeStartedServiceServer interface {\n\tmustEmbedUnimplementedStartedServiceServer()\n}\n\nfunc RegisterStartedServiceServer(s grpc.ServiceRegistrar, srv StartedServiceServer) {\n\t// If the following call panics, it indicates UnimplementedStartedServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&StartedService_ServiceDesc, srv)\n}\n\nfunc _StartedService_StopService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).StopService(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_StopService_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).StopService(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_ReloadService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).ReloadService(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_ReloadService_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).ReloadService(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_SubscribeServiceStatus_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(emptypb.Empty)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(StartedServiceServer).SubscribeServiceStatus(m, &grpc.GenericServerStream[emptypb.Empty, ServiceStatus]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeServiceStatusServer = grpc.ServerStreamingServer[ServiceStatus]\n\nfunc _StartedService_SubscribeLog_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(emptypb.Empty)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(StartedServiceServer).SubscribeLog(m, &grpc.GenericServerStream[emptypb.Empty, Log]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeLogServer = grpc.ServerStreamingServer[Log]\n\nfunc _StartedService_GetDefaultLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).GetDefaultLogLevel(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_GetDefaultLogLevel_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).GetDefaultLogLevel(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_ClearLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).ClearLogs(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_ClearLogs_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).ClearLogs(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_SubscribeStatus_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SubscribeStatusRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(StartedServiceServer).SubscribeStatus(m, &grpc.GenericServerStream[SubscribeStatusRequest, Status]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeStatusServer = grpc.ServerStreamingServer[Status]\n\nfunc _StartedService_SubscribeGroups_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(emptypb.Empty)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(StartedServiceServer).SubscribeGroups(m, &grpc.GenericServerStream[emptypb.Empty, Groups]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeGroupsServer = grpc.ServerStreamingServer[Groups]\n\nfunc _StartedService_GetClashModeStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).GetClashModeStatus(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_GetClashModeStatus_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).GetClashModeStatus(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_SubscribeClashMode_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(emptypb.Empty)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(StartedServiceServer).SubscribeClashMode(m, &grpc.GenericServerStream[emptypb.Empty, ClashMode]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeClashModeServer = grpc.ServerStreamingServer[ClashMode]\n\nfunc _StartedService_SetClashMode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ClashMode)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).SetClashMode(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_SetClashMode_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).SetClashMode(ctx, req.(*ClashMode))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_URLTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(URLTestRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).URLTest(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_URLTest_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).URLTest(ctx, req.(*URLTestRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_SelectOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SelectOutboundRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).SelectOutbound(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_SelectOutbound_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).SelectOutbound(ctx, req.(*SelectOutboundRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_SetGroupExpand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SetGroupExpandRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).SetGroupExpand(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_SetGroupExpand_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).SetGroupExpand(ctx, req.(*SetGroupExpandRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_GetSystemProxyStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).GetSystemProxyStatus(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_GetSystemProxyStatus_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).GetSystemProxyStatus(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SetSystemProxyEnabledRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_SetSystemProxyEnabled_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, req.(*SetSystemProxyEnabledRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SubscribeConnectionsRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(StartedServiceServer).SubscribeConnections(m, &grpc.GenericServerStream[SubscribeConnectionsRequest, ConnectionEvents]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype StartedService_SubscribeConnectionsServer = grpc.ServerStreamingServer[ConnectionEvents]\n\nfunc _StartedService_CloseConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CloseConnectionRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).CloseConnection(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_CloseConnection_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).CloseConnection(ctx, req.(*CloseConnectionRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_CloseAllConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).CloseAllConnections(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_CloseAllConnections_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).CloseAllConnections(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_GetDeprecatedWarnings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_GetDeprecatedWarnings_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StartedService_GetStartedAt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StartedServiceServer).GetStartedAt(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StartedService_GetStartedAt_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StartedServiceServer).GetStartedAt(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar StartedService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"daemon.StartedService\",\n\tHandlerType: (*StartedServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"StopService\",\n\t\t\tHandler:    _StartedService_StopService_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ReloadService\",\n\t\t\tHandler:    _StartedService_ReloadService_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetDefaultLogLevel\",\n\t\t\tHandler:    _StartedService_GetDefaultLogLevel_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ClearLogs\",\n\t\t\tHandler:    _StartedService_ClearLogs_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetClashModeStatus\",\n\t\t\tHandler:    _StartedService_GetClashModeStatus_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetClashMode\",\n\t\t\tHandler:    _StartedService_SetClashMode_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"URLTest\",\n\t\t\tHandler:    _StartedService_URLTest_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SelectOutbound\",\n\t\t\tHandler:    _StartedService_SelectOutbound_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetGroupExpand\",\n\t\t\tHandler:    _StartedService_SetGroupExpand_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetSystemProxyStatus\",\n\t\t\tHandler:    _StartedService_GetSystemProxyStatus_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetSystemProxyEnabled\",\n\t\t\tHandler:    _StartedService_SetSystemProxyEnabled_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CloseConnection\",\n\t\t\tHandler:    _StartedService_CloseConnection_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CloseAllConnections\",\n\t\t\tHandler:    _StartedService_CloseAllConnections_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetDeprecatedWarnings\",\n\t\t\tHandler:    _StartedService_GetDeprecatedWarnings_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetStartedAt\",\n\t\t\tHandler:    _StartedService_GetStartedAt_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"SubscribeServiceStatus\",\n\t\t\tHandler:       _StartedService_SubscribeServiceStatus_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"SubscribeLog\",\n\t\t\tHandler:       _StartedService_SubscribeLog_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"SubscribeStatus\",\n\t\t\tHandler:       _StartedService_SubscribeStatus_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"SubscribeGroups\",\n\t\t\tHandler:       _StartedService_SubscribeGroups_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"SubscribeClashMode\",\n\t\t\tHandler:       _StartedService_SubscribeClashMode_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"SubscribeConnections\",\n\t\t\tHandler:       _StartedService_SubscribeConnections_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"daemon/started_service.proto\",\n}\n"
  },
  {
    "path": "debug.go",
    "content": "package box\n\nimport (\n\t\"runtime/debug\"\n\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc applyDebugOptions(options option.DebugOptions) error {\n\tapplyDebugListenOption(options)\n\tif options.GCPercent != nil {\n\t\tdebug.SetGCPercent(*options.GCPercent)\n\t}\n\tif options.MaxStack != nil {\n\t\tdebug.SetMaxStack(*options.MaxStack)\n\t}\n\tif options.MaxThreads != nil {\n\t\tdebug.SetMaxThreads(*options.MaxThreads)\n\t}\n\tif options.PanicOnFault != nil {\n\t\tdebug.SetPanicOnFault(*options.PanicOnFault)\n\t}\n\tif options.TraceBack != \"\" {\n\t\tdebug.SetTraceback(options.TraceBack)\n\t}\n\tif options.MemoryLimit.Value() != 0 {\n\t\tdebug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))\n\t}\n\tif options.OOMKiller != nil {\n\t\treturn E.New(\"legacy oom_killer in debug options is removed, use oom-killer service instead\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "debug_http.go",
    "content": "package box\n\nimport (\n\t\"net/http\"\n\t\"net/http/pprof\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/byteformats\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nvar debugHTTPServer *http.Server\n\nfunc applyDebugListenOption(options option.DebugOptions) {\n\tif debugHTTPServer != nil {\n\t\tdebugHTTPServer.Close()\n\t\tdebugHTTPServer = nil\n\t}\n\tif options.Listen == \"\" {\n\t\treturn\n\t}\n\tr := chi.NewMux()\n\tr.Route(\"/debug\", func(r chi.Router) {\n\t\tr.Get(\"/gc\", func(writer http.ResponseWriter, request *http.Request) {\n\t\t\twriter.WriteHeader(http.StatusNoContent)\n\t\t\tgo debug.FreeOSMemory()\n\t\t})\n\t\tr.Get(\"/memory\", func(writer http.ResponseWriter, request *http.Request) {\n\t\t\tvar memStats runtime.MemStats\n\t\t\truntime.ReadMemStats(&memStats)\n\n\t\t\tvar memObject badjson.JSONObject\n\t\t\tmemObject.Put(\"heap\", byteformats.FormatMemoryBytes(memStats.HeapInuse))\n\t\t\tmemObject.Put(\"stack\", byteformats.FormatMemoryBytes(memStats.StackInuse))\n\t\t\tmemObject.Put(\"idle\", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))\n\t\t\tmemObject.Put(\"goroutines\", runtime.NumGoroutine())\n\t\t\tmemObject.Put(\"rss\", rusageMaxRSS())\n\n\t\t\tencoder := json.NewEncoder(writer)\n\t\t\tencoder.SetIndent(\"\", \"  \")\n\t\t\tencoder.Encode(&memObject)\n\t\t})\n\t\tr.Route(\"/pprof\", func(r chi.Router) {\n\t\t\tr.HandleFunc(\"/\", func(writer http.ResponseWriter, request *http.Request) {\n\t\t\t\tif !strings.HasSuffix(request.URL.Path, \"/\") {\n\t\t\t\t\thttp.Redirect(writer, request, request.URL.Path+\"/\", http.StatusMovedPermanently)\n\t\t\t\t} else {\n\t\t\t\t\tpprof.Index(writer, request)\n\t\t\t\t}\n\t\t\t})\n\t\t\tr.HandleFunc(\"/*\", pprof.Index)\n\t\t\tr.HandleFunc(\"/cmdline\", pprof.Cmdline)\n\t\t\tr.HandleFunc(\"/profile\", pprof.Profile)\n\t\t\tr.HandleFunc(\"/symbol\", pprof.Symbol)\n\t\t\tr.HandleFunc(\"/trace\", pprof.Trace)\n\t\t})\n\t})\n\tdebugHTTPServer = &http.Server{\n\t\tAddr:    options.Listen,\n\t\tHandler: r,\n\t}\n\tgo func() {\n\t\terr := debugHTTPServer.ListenAndServe()\n\t\tif err != nil && !E.IsClosed(err) {\n\t\t\tlog.Error(E.Cause(err, \"serve debug HTTP server\"))\n\t\t}\n\t}()\n}\n"
  },
  {
    "path": "debug_stub.go",
    "content": "//go:build !(linux || darwin)\n\npackage box\n\nfunc rusageMaxRSS() float64 {\n\treturn -1\n}\n"
  },
  {
    "path": "debug_unix.go",
    "content": "//go:build linux || darwin\n\npackage box\n\nimport (\n\t\"runtime\"\n\t\"syscall\"\n)\n\nfunc rusageMaxRSS() float64 {\n\tru := syscall.Rusage{}\n\terr := syscall.Getrusage(syscall.RUSAGE_SELF, &ru)\n\tif err != nil {\n\t\treturn 0\n\t}\n\n\trss := float64(ru.Maxrss)\n\tif runtime.GOOS == \"darwin\" || runtime.GOOS == \"ios\" {\n\t\trss /= 1 << 20 // ru_maxrss is bytes on darwin\n\t} else {\n\t\t// ru_maxrss is kilobytes elsewhere (linux, openbsd, etc)\n\t\trss /= 1 << 10\n\t}\n\treturn rss\n}\n"
  },
  {
    "path": "dns/client.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/compatible\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/task\"\n\t\"github.com/sagernet/sing/contrab/freelru\"\n\t\"github.com/sagernet/sing/contrab/maphash\"\n\n\t\"github.com/miekg/dns\"\n)\n\nvar (\n\tErrNoRawSupport           = E.New(\"no raw query support by current transport\")\n\tErrNotCached              = E.New(\"not cached\")\n\tErrResponseRejected       = E.New(\"response rejected\")\n\tErrResponseRejectedCached = E.Extend(ErrResponseRejected, \"cached\")\n)\n\nvar _ adapter.DNSClient = (*Client)(nil)\n\ntype Client struct {\n\ttimeout            time.Duration\n\tdisableCache       bool\n\tdisableExpire      bool\n\tindependentCache   bool\n\tclientSubnet       netip.Prefix\n\trdrc               adapter.RDRCStore\n\tinitRDRCFunc       func() adapter.RDRCStore\n\tlogger             logger.ContextLogger\n\tcache              freelru.Cache[dns.Question, *dns.Msg]\n\tcacheLock          compatible.Map[dns.Question, chan struct{}]\n\ttransportCache     freelru.Cache[transportCacheKey, *dns.Msg]\n\ttransportCacheLock compatible.Map[dns.Question, chan struct{}]\n}\n\ntype ClientOptions struct {\n\tTimeout          time.Duration\n\tDisableCache     bool\n\tDisableExpire    bool\n\tIndependentCache bool\n\tCacheCapacity    uint32\n\tClientSubnet     netip.Prefix\n\tRDRC             func() adapter.RDRCStore\n\tLogger           logger.ContextLogger\n}\n\nfunc NewClient(options ClientOptions) *Client {\n\tclient := &Client{\n\t\ttimeout:          options.Timeout,\n\t\tdisableCache:     options.DisableCache,\n\t\tdisableExpire:    options.DisableExpire,\n\t\tindependentCache: options.IndependentCache,\n\t\tclientSubnet:     options.ClientSubnet,\n\t\tinitRDRCFunc:     options.RDRC,\n\t\tlogger:           options.Logger,\n\t}\n\tif client.timeout == 0 {\n\t\tclient.timeout = C.DNSTimeout\n\t}\n\tcacheCapacity := options.CacheCapacity\n\tif cacheCapacity < 1024 {\n\t\tcacheCapacity = 1024\n\t}\n\tif !client.disableCache {\n\t\tif !client.independentCache {\n\t\t\tclient.cache = common.Must1(freelru.NewSharded[dns.Question, *dns.Msg](cacheCapacity, maphash.NewHasher[dns.Question]().Hash32))\n\t\t} else {\n\t\t\tclient.transportCache = common.Must1(freelru.NewSharded[transportCacheKey, *dns.Msg](cacheCapacity, maphash.NewHasher[transportCacheKey]().Hash32))\n\t\t}\n\t}\n\treturn client\n}\n\ntype transportCacheKey struct {\n\tdns.Question\n\ttransportTag string\n}\n\nfunc (c *Client) Start() {\n\tif c.initRDRCFunc != nil {\n\t\tc.rdrc = c.initRDRCFunc()\n\t}\n}\n\nfunc extractNegativeTTL(response *dns.Msg) (uint32, bool) {\n\tfor _, record := range response.Ns {\n\t\tif soa, isSOA := record.(*dns.SOA); isSOA {\n\t\t\tsoaTTL := soa.Header().Ttl\n\t\t\tsoaMinimum := soa.Minttl\n\t\t\tif soaTTL < soaMinimum {\n\t\t\t\treturn soaTTL, true\n\t\t\t}\n\t\t\treturn soaMinimum, true\n\t\t}\n\t}\n\treturn 0, false\n}\n\nfunc (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {\n\tif len(message.Question) == 0 {\n\t\tif c.logger != nil {\n\t\t\tc.logger.WarnContext(ctx, \"bad question size: \", len(message.Question))\n\t\t}\n\t\treturn FixedResponseStatus(message, dns.RcodeFormatError), nil\n\t}\n\tquestion := message.Question[0]\n\tif question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only {\n\t\tif c.logger != nil {\n\t\t\tc.logger.DebugContext(ctx, \"strategy rejected\")\n\t\t}\n\t\treturn FixedResponseStatus(message, dns.RcodeSuccess), nil\n\t}\n\tclientSubnet := options.ClientSubnet\n\tif !clientSubnet.IsValid() {\n\t\tclientSubnet = c.clientSubnet\n\t}\n\tif clientSubnet.IsValid() {\n\t\tmessage = SetClientSubnet(message, clientSubnet)\n\t}\n\n\tisSimpleRequest := len(message.Question) == 1 &&\n\t\tlen(message.Ns) == 0 &&\n\t\t(len(message.Extra) == 0 || len(message.Extra) == 1 &&\n\t\t\tmessage.Extra[0].Header().Rrtype == dns.TypeOPT &&\n\t\t\tmessage.Extra[0].Header().Class > 0 &&\n\t\t\tmessage.Extra[0].Header().Ttl == 0 &&\n\t\t\tlen(message.Extra[0].(*dns.OPT).Option) == 0) &&\n\t\t!options.ClientSubnet.IsValid()\n\tdisableCache := !isSimpleRequest || c.disableCache || options.DisableCache\n\tif !disableCache {\n\t\tif c.cache != nil {\n\t\t\tcond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))\n\t\t\tif loaded {\n\t\t\t\tselect {\n\t\t\t\tcase <-cond:\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn nil, ctx.Err()\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdefer func() {\n\t\t\t\t\tc.cacheLock.Delete(question)\n\t\t\t\t\tclose(cond)\n\t\t\t\t}()\n\t\t\t}\n\t\t} else if c.transportCache != nil {\n\t\t\tcond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{}))\n\t\t\tif loaded {\n\t\t\t\tselect {\n\t\t\t\tcase <-cond:\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn nil, ctx.Err()\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdefer func() {\n\t\t\t\t\tc.transportCacheLock.Delete(question)\n\t\t\t\t\tclose(cond)\n\t\t\t\t}()\n\t\t\t}\n\t\t}\n\t\tresponse, ttl := c.loadResponse(question, transport)\n\t\tif response != nil {\n\t\t\tlogCachedResponse(c.logger, ctx, response, ttl)\n\t\t\tresponse.Id = message.Id\n\t\t\treturn response, nil\n\t\t}\n\t}\n\n\tmessageId := message.Id\n\tcontextTransport, clientSubnetLoaded := transportTagFromContext(ctx)\n\tif clientSubnetLoaded && transport.Tag() == contextTransport {\n\t\treturn nil, E.New(\"DNS query loopback in transport[\", contextTransport, \"]\")\n\t}\n\tctx = contextWithTransportTag(ctx, transport.Tag())\n\tif !disableCache && responseChecker != nil && c.rdrc != nil {\n\t\trejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)\n\t\tif rejected {\n\t\t\treturn nil, ErrResponseRejectedCached\n\t\t}\n\t}\n\tctx, cancel := context.WithTimeout(ctx, c.timeout)\n\tresponse, err := transport.Exchange(ctx, message)\n\tcancel()\n\tif err != nil {\n\t\tvar rcodeError RcodeError\n\t\tif errors.As(err, &rcodeError) {\n\t\t\tresponse = FixedResponseStatus(message, int(rcodeError))\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\t/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {\n\t\tvalidResponse := response\n\tloop:\n\t\tfor {\n\t\t\tvar (\n\t\t\t\taddresses  int\n\t\t\t\tqueryCNAME string\n\t\t\t)\n\t\t\tfor _, rawRR := range validResponse.Answer {\n\t\t\t\tswitch rr := rawRR.(type) {\n\t\t\t\tcase *dns.A:\n\t\t\t\t\tbreak loop\n\t\t\t\tcase *dns.AAAA:\n\t\t\t\t\tbreak loop\n\t\t\t\tcase *dns.CNAME:\n\t\t\t\t\tqueryCNAME = rr.Target\n\t\t\t\t}\n\t\t\t}\n\t\t\tif queryCNAME == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\texMessage := *message\n\t\t\texMessage.Question = []dns.Question{{\n\t\t\t\tName:  queryCNAME,\n\t\t\t\tQtype: question.Qtype,\n\t\t\t}}\n\t\t\tvalidResponse, err = c.Exchange(ctx, transport, &exMessage, options, responseChecker)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif validResponse != response {\n\t\t\tresponse.Answer = append(response.Answer, validResponse.Answer...)\n\t\t}\n\t}*/\n\tdisableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)\n\tif responseChecker != nil {\n\t\tvar rejected bool\n\t\t// TODO: add accept_any rule and support to check response instead of addresses\n\t\tif response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {\n\t\t\trejected = true\n\t\t} else if len(response.Answer) == 0 {\n\t\t\trejected = !responseChecker(nil)\n\t\t} else {\n\t\t\trejected = !responseChecker(MessageToAddresses(response))\n\t\t}\n\t\tif rejected {\n\t\t\tif !disableCache && c.rdrc != nil {\n\t\t\t\tc.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)\n\t\t\t}\n\t\t\tlogRejectedResponse(c.logger, ctx, response)\n\t\t\treturn response, ErrResponseRejected\n\t\t}\n\t}\n\tif question.Qtype == dns.TypeHTTPS {\n\t\tif options.Strategy == C.DomainStrategyIPv4Only || options.Strategy == C.DomainStrategyIPv6Only {\n\t\t\tfor _, rr := range response.Answer {\n\t\t\t\thttps, isHTTPS := rr.(*dns.HTTPS)\n\t\t\t\tif !isHTTPS {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcontent := https.SVCB\n\t\t\t\tcontent.Value = common.Filter(content.Value, func(it dns.SVCBKeyValue) bool {\n\t\t\t\t\tif options.Strategy == C.DomainStrategyIPv4Only {\n\t\t\t\t\t\treturn it.Key() != dns.SVCB_IPV6HINT\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn it.Key() != dns.SVCB_IPV4HINT\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\thttps.SVCB = content\n\t\t\t}\n\t\t}\n\t}\n\tvar timeToLive uint32\n\tif len(response.Answer) == 0 {\n\t\tif soaTTL, hasSOA := extractNegativeTTL(response); hasSOA {\n\t\t\ttimeToLive = soaTTL\n\t\t}\n\t}\n\tif timeToLive == 0 {\n\t\tfor _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {\n\t\t\tfor _, record := range recordList {\n\t\t\t\tif timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {\n\t\t\t\t\ttimeToLive = record.Header().Ttl\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif options.RewriteTTL != nil {\n\t\ttimeToLive = *options.RewriteTTL\n\t}\n\tfor _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {\n\t\tfor _, record := range recordList {\n\t\t\trecord.Header().Ttl = timeToLive\n\t\t}\n\t}\n\tif !disableCache {\n\t\tc.storeCache(transport, question, response, timeToLive)\n\t}\n\tresponse.Id = messageId\n\trequestEDNSOpt := message.IsEdns0()\n\tresponseEDNSOpt := response.IsEdns0()\n\tif responseEDNSOpt != nil && (requestEDNSOpt == nil || requestEDNSOpt.Version() < responseEDNSOpt.Version()) {\n\t\tresponse.Extra = common.Filter(response.Extra, func(it dns.RR) bool {\n\t\t\treturn it.Header().Rrtype != dns.TypeOPT\n\t\t})\n\t\tif requestEDNSOpt != nil {\n\t\t\tresponse.SetEdns0(responseEDNSOpt.UDPSize(), responseEDNSOpt.Do())\n\t\t}\n\t}\n\tlogExchangedResponse(c.logger, ctx, response, timeToLive)\n\treturn response, nil\n}\n\nfunc (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {\n\tdomain = FqdnToDomain(domain)\n\tdnsName := dns.Fqdn(domain)\n\tvar strategy C.DomainStrategy\n\tif options.LookupStrategy != C.DomainStrategyAsIS {\n\t\tstrategy = options.LookupStrategy\n\t} else {\n\t\tstrategy = options.Strategy\n\t}\n\tlookupOptions := options\n\tif options.LookupStrategy != C.DomainStrategyAsIS {\n\t\tlookupOptions.Strategy = strategy\n\t}\n\tif strategy == C.DomainStrategyIPv4Only {\n\t\treturn c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, lookupOptions, responseChecker)\n\t} else if strategy == C.DomainStrategyIPv6Only {\n\t\treturn c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, lookupOptions, responseChecker)\n\t}\n\tvar response4 []netip.Addr\n\tvar response6 []netip.Addr\n\tvar group task.Group\n\tgroup.Append(\"exchange4\", func(ctx context.Context) error {\n\t\tresponse, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, lookupOptions, responseChecker)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tresponse4 = response\n\t\treturn nil\n\t})\n\tgroup.Append(\"exchange6\", func(ctx context.Context) error {\n\t\tresponse, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, lookupOptions, responseChecker)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tresponse6 = response\n\t\treturn nil\n\t})\n\terr := group.Run(ctx)\n\tif len(response4) == 0 && len(response6) == 0 {\n\t\treturn nil, err\n\t}\n\treturn sortAddresses(response4, response6, strategy), nil\n}\n\nfunc (c *Client) ClearCache() {\n\tif c.cache != nil {\n\t\tc.cache.Purge()\n\t} else if c.transportCache != nil {\n\t\tc.transportCache.Purge()\n\t}\n}\n\nfunc sortAddresses(response4 []netip.Addr, response6 []netip.Addr, strategy C.DomainStrategy) []netip.Addr {\n\tif strategy == C.DomainStrategyPreferIPv6 {\n\t\treturn append(response6, response4...)\n\t} else {\n\t\treturn append(response4, response6...)\n\t}\n}\n\nfunc (c *Client) storeCache(transport adapter.DNSTransport, question dns.Question, message *dns.Msg, timeToLive uint32) {\n\tif timeToLive == 0 {\n\t\treturn\n\t}\n\tif c.disableExpire {\n\t\tif !c.independentCache {\n\t\t\tc.cache.Add(question, message)\n\t\t} else {\n\t\t\tc.transportCache.Add(transportCacheKey{\n\t\t\t\tQuestion:     question,\n\t\t\t\ttransportTag: transport.Tag(),\n\t\t\t}, message)\n\t\t}\n\t} else {\n\t\tif !c.independentCache {\n\t\t\tc.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))\n\t\t} else {\n\t\t\tc.transportCache.AddWithLifetime(transportCacheKey{\n\t\t\t\tQuestion:     question,\n\t\t\t\ttransportTag: transport.Tag(),\n\t\t\t}, message, time.Second*time.Duration(timeToLive))\n\t\t}\n\t}\n}\n\nfunc (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {\n\tquestion := dns.Question{\n\t\tName:   name,\n\t\tQtype:  qType,\n\t\tQclass: dns.ClassINET,\n\t}\n\tdisableCache := c.disableCache || options.DisableCache\n\tif !disableCache {\n\t\tcachedAddresses, err := c.questionCache(question, transport)\n\t\tif err != ErrNotCached {\n\t\t\treturn cachedAddresses, err\n\t\t}\n\t}\n\tmessage := dns.Msg{\n\t\tMsgHdr: dns.MsgHdr{\n\t\t\tRecursionDesired: true,\n\t\t},\n\t\tQuestion: []dns.Question{question},\n\t}\n\tresponse, err := c.Exchange(ctx, transport, &message, options, responseChecker)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif response.Rcode != dns.RcodeSuccess {\n\t\treturn nil, RcodeError(response.Rcode)\n\t}\n\treturn MessageToAddresses(response), nil\n}\n\nfunc (c *Client) questionCache(question dns.Question, transport adapter.DNSTransport) ([]netip.Addr, error) {\n\tresponse, _ := c.loadResponse(question, transport)\n\tif response == nil {\n\t\treturn nil, ErrNotCached\n\t}\n\tif response.Rcode != dns.RcodeSuccess {\n\t\treturn nil, RcodeError(response.Rcode)\n\t}\n\treturn MessageToAddresses(response), nil\n}\n\nfunc (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int) {\n\tvar (\n\t\tresponse *dns.Msg\n\t\tloaded   bool\n\t)\n\tif c.disableExpire {\n\t\tif !c.independentCache {\n\t\t\tresponse, loaded = c.cache.Get(question)\n\t\t} else {\n\t\t\tresponse, loaded = c.transportCache.Get(transportCacheKey{\n\t\t\t\tQuestion:     question,\n\t\t\t\ttransportTag: transport.Tag(),\n\t\t\t})\n\t\t}\n\t\tif !loaded {\n\t\t\treturn nil, 0\n\t\t}\n\t\treturn response.Copy(), 0\n\t} else {\n\t\tvar expireAt time.Time\n\t\tif !c.independentCache {\n\t\t\tresponse, expireAt, loaded = c.cache.GetWithLifetime(question)\n\t\t} else {\n\t\t\tresponse, expireAt, loaded = c.transportCache.GetWithLifetime(transportCacheKey{\n\t\t\t\tQuestion:     question,\n\t\t\t\ttransportTag: transport.Tag(),\n\t\t\t})\n\t\t}\n\t\tif !loaded {\n\t\t\treturn nil, 0\n\t\t}\n\t\ttimeNow := time.Now()\n\t\tif timeNow.After(expireAt) {\n\t\t\tif !c.independentCache {\n\t\t\t\tc.cache.Remove(question)\n\t\t\t} else {\n\t\t\t\tc.transportCache.Remove(transportCacheKey{\n\t\t\t\t\tQuestion:     question,\n\t\t\t\t\ttransportTag: transport.Tag(),\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn nil, 0\n\t\t}\n\t\tvar originTTL int\n\t\tfor _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {\n\t\t\tfor _, record := range recordList {\n\t\t\t\tif originTTL == 0 || record.Header().Ttl > 0 && int(record.Header().Ttl) < originTTL {\n\t\t\t\t\toriginTTL = int(record.Header().Ttl)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tnowTTL := int(expireAt.Sub(timeNow).Seconds())\n\t\tif nowTTL < 0 {\n\t\t\tnowTTL = 0\n\t\t}\n\t\tresponse = response.Copy()\n\t\tif originTTL > 0 {\n\t\t\tduration := uint32(originTTL - nowTTL)\n\t\t\tfor _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {\n\t\t\t\tfor _, record := range recordList {\n\t\t\t\t\trecord.Header().Ttl = record.Header().Ttl - duration\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {\n\t\t\t\tfor _, record := range recordList {\n\t\t\t\t\trecord.Header().Ttl = uint32(nowTTL)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn response, nowTTL\n\t}\n}\n\nfunc MessageToAddresses(response *dns.Msg) []netip.Addr {\n\tif response == nil || response.Rcode != dns.RcodeSuccess {\n\t\treturn nil\n\t}\n\taddresses := make([]netip.Addr, 0, len(response.Answer))\n\tfor _, rawAnswer := range response.Answer {\n\t\tswitch answer := rawAnswer.(type) {\n\t\tcase *dns.A:\n\t\t\taddresses = append(addresses, M.AddrFromIP(answer.A))\n\t\tcase *dns.AAAA:\n\t\t\taddresses = append(addresses, M.AddrFromIP(answer.AAAA))\n\t\tcase *dns.HTTPS:\n\t\t\tfor _, value := range answer.SVCB.Value {\n\t\t\t\tif value.Key() == dns.SVCB_IPV4HINT || value.Key() == dns.SVCB_IPV6HINT {\n\t\t\t\t\taddresses = append(addresses, common.Map(strings.Split(value.String(), \",\"), M.ParseAddr)...)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn addresses\n}\n\nfunc wrapError(err error) error {\n\tswitch dnsErr := err.(type) {\n\tcase *net.DNSError:\n\t\tif dnsErr.IsNotFound {\n\t\t\treturn RcodeNameError\n\t\t}\n\tcase *net.AddrError:\n\t\treturn RcodeNameError\n\t}\n\treturn err\n}\n\ntype transportKey struct{}\n\nfunc contextWithTransportTag(ctx context.Context, transportTag string) context.Context {\n\treturn context.WithValue(ctx, transportKey{}, transportTag)\n}\n\nfunc transportTagFromContext(ctx context.Context) (string, bool) {\n\tvalue, loaded := ctx.Value(transportKey{}).(string)\n\treturn value, loaded\n}\n\nfunc FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {\n\treturn &dns.Msg{\n\t\tMsgHdr: dns.MsgHdr{\n\t\t\tId:                 message.Id,\n\t\t\tResponse:           true,\n\t\t\tAuthoritative:      true,\n\t\t\tRecursionDesired:   true,\n\t\t\tRecursionAvailable: true,\n\t\t\tRcode:              rcode,\n\t\t},\n\t\tQuestion: message.Question,\n\t}\n}\n\nfunc FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg {\n\tresponse := dns.Msg{\n\t\tMsgHdr: dns.MsgHdr{\n\t\t\tId:                 id,\n\t\t\tResponse:           true,\n\t\t\tAuthoritative:      true,\n\t\t\tRecursionDesired:   true,\n\t\t\tRecursionAvailable: true,\n\t\t\tRcode:              dns.RcodeSuccess,\n\t\t},\n\t\tQuestion: []dns.Question{question},\n\t}\n\tfor _, address := range addresses {\n\t\tif address.Is4() && question.Qtype == dns.TypeA {\n\t\t\tresponse.Answer = append(response.Answer, &dns.A{\n\t\t\t\tHdr: dns.RR_Header{\n\t\t\t\t\tName:   question.Name,\n\t\t\t\t\tRrtype: dns.TypeA,\n\t\t\t\t\tClass:  dns.ClassINET,\n\t\t\t\t\tTtl:    timeToLive,\n\t\t\t\t},\n\t\t\t\tA: address.AsSlice(),\n\t\t\t})\n\t\t} else if address.Is6() && question.Qtype == dns.TypeAAAA {\n\t\t\tresponse.Answer = append(response.Answer, &dns.AAAA{\n\t\t\t\tHdr: dns.RR_Header{\n\t\t\t\t\tName:   question.Name,\n\t\t\t\t\tRrtype: dns.TypeAAAA,\n\t\t\t\t\tClass:  dns.ClassINET,\n\t\t\t\t\tTtl:    timeToLive,\n\t\t\t\t},\n\t\t\t\tAAAA: address.AsSlice(),\n\t\t\t})\n\t\t}\n\t}\n\treturn &response\n}\n\nfunc FixedResponseCNAME(id uint16, question dns.Question, record string, timeToLive uint32) *dns.Msg {\n\tresponse := dns.Msg{\n\t\tMsgHdr: dns.MsgHdr{\n\t\t\tId:                 id,\n\t\t\tResponse:           true,\n\t\t\tAuthoritative:      true,\n\t\t\tRecursionDesired:   true,\n\t\t\tRecursionAvailable: true,\n\t\t\tRcode:              dns.RcodeSuccess,\n\t\t},\n\t\tQuestion: []dns.Question{question},\n\t\tAnswer: []dns.RR{\n\t\t\t&dns.CNAME{\n\t\t\t\tHdr: dns.RR_Header{\n\t\t\t\t\tName:   question.Name,\n\t\t\t\t\tRrtype: dns.TypeCNAME,\n\t\t\t\t\tClass:  dns.ClassINET,\n\t\t\t\t\tTtl:    timeToLive,\n\t\t\t\t},\n\t\t\t\tTarget: record,\n\t\t\t},\n\t\t},\n\t}\n\treturn &response\n}\n\nfunc FixedResponseTXT(id uint16, question dns.Question, records []string, timeToLive uint32) *dns.Msg {\n\tresponse := dns.Msg{\n\t\tMsgHdr: dns.MsgHdr{\n\t\t\tId:                 id,\n\t\t\tResponse:           true,\n\t\t\tAuthoritative:      true,\n\t\t\tRecursionDesired:   true,\n\t\t\tRecursionAvailable: true,\n\t\t\tRcode:              dns.RcodeSuccess,\n\t\t},\n\t\tQuestion: []dns.Question{question},\n\t\tAnswer: []dns.RR{\n\t\t\t&dns.TXT{\n\t\t\t\tHdr: dns.RR_Header{\n\t\t\t\t\tName:   question.Name,\n\t\t\t\t\tRrtype: dns.TypeA,\n\t\t\t\t\tClass:  dns.ClassINET,\n\t\t\t\t\tTtl:    timeToLive,\n\t\t\t\t},\n\t\t\t\tTxt: records,\n\t\t\t},\n\t\t},\n\t}\n\treturn &response\n}\n\nfunc FixedResponseMX(id uint16, question dns.Question, records []*net.MX, timeToLive uint32) *dns.Msg {\n\tresponse := dns.Msg{\n\t\tMsgHdr: dns.MsgHdr{\n\t\t\tId:                 id,\n\t\t\tResponse:           true,\n\t\t\tAuthoritative:      true,\n\t\t\tRecursionDesired:   true,\n\t\t\tRecursionAvailable: true,\n\t\t\tRcode:              dns.RcodeSuccess,\n\t\t},\n\t\tQuestion: []dns.Question{question},\n\t}\n\tfor _, record := range records {\n\t\tresponse.Answer = append(response.Answer, &dns.MX{\n\t\t\tHdr: dns.RR_Header{\n\t\t\t\tName:   question.Name,\n\t\t\t\tRrtype: dns.TypeA,\n\t\t\t\tClass:  dns.ClassINET,\n\t\t\t\tTtl:    timeToLive,\n\t\t\t},\n\t\t\tPreference: record.Pref,\n\t\t\tMx:         record.Host,\n\t\t})\n\t}\n\treturn &response\n}\n"
  },
  {
    "path": "dns/client_log.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing/common/logger\"\n\n\t\"github.com/miekg/dns\"\n)\n\nfunc logCachedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl int) {\n\tif logger == nil || len(response.Question) == 0 {\n\t\treturn\n\t}\n\tdomain := FqdnToDomain(response.Question[0].Name)\n\tlogger.DebugContext(ctx, \"cached \", domain, \" \", dns.RcodeToString[response.Rcode], \" \", ttl)\n\tfor _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {\n\t\tfor _, record := range recordList {\n\t\t\tlogger.InfoContext(ctx, \"cached \", dns.Type(record.Header().Rrtype).String(), \" \", FormatQuestion(record.String()))\n\t\t}\n\t}\n}\n\nfunc logExchangedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl uint32) {\n\tif logger == nil || len(response.Question) == 0 {\n\t\treturn\n\t}\n\tdomain := FqdnToDomain(response.Question[0].Name)\n\tlogger.DebugContext(ctx, \"exchanged \", domain, \" \", dns.RcodeToString[response.Rcode], \" \", ttl)\n\tfor _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {\n\t\tfor _, record := range recordList {\n\t\t\tlogger.InfoContext(ctx, \"exchanged \", dns.Type(record.Header().Rrtype).String(), \" \", FormatQuestion(record.String()))\n\t\t}\n\t}\n}\n\nfunc logRejectedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg) {\n\tif logger == nil || len(response.Question) == 0 {\n\t\treturn\n\t}\n\tfor _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {\n\t\tfor _, record := range recordList {\n\t\t\tlogger.InfoContext(ctx, \"rejected \", dns.Type(record.Header().Rrtype).String(), \" \", FormatQuestion(record.String()))\n\t\t}\n\t}\n}\n\nfunc FqdnToDomain(fqdn string) string {\n\tif dns.IsFqdn(fqdn) {\n\t\treturn fqdn[:len(fqdn)-1]\n\t}\n\treturn fqdn\n}\n\nfunc FormatQuestion(string string) string {\n\tfor strings.HasPrefix(string, \";\") {\n\t\tstring = string[1:]\n\t}\n\tstring = strings.ReplaceAll(string, \"\\t\", \" \")\n\tstring = strings.ReplaceAll(string, \"\\n\", \" \")\n\tstring = strings.ReplaceAll(string, \";; \", \" \")\n\tstring = strings.ReplaceAll(string, \"; \", \" \")\n\n\tfor strings.Contains(string, \"  \") {\n\t\tstring = strings.ReplaceAll(string, \"  \", \" \")\n\t}\n\treturn strings.TrimSpace(string)\n}\n"
  },
  {
    "path": "dns/client_truncate.go",
    "content": "package dns\n\nimport (\n\t\"github.com/sagernet/sing/common/buf\"\n\n\t\"github.com/miekg/dns\"\n)\n\nfunc TruncateDNSMessage(request *dns.Msg, response *dns.Msg, headroom int) (*buf.Buffer, error) {\n\tmaxLen := 512\n\tif edns0Option := request.IsEdns0(); edns0Option != nil {\n\t\tif udpSize := int(edns0Option.UDPSize()); udpSize > 512 {\n\t\t\tmaxLen = udpSize\n\t\t}\n\t}\n\tresponseLen := response.Len()\n\tif responseLen > maxLen {\n\t\tresponse = response.Copy()\n\t\tresponse.Truncate(maxLen)\n\t}\n\tbuffer := buf.NewSize(headroom*2 + 1 + responseLen)\n\tbuffer.Resize(headroom, 0)\n\trawMessage, err := response.PackBuffer(buffer.FreeBytes())\n\tif err != nil {\n\t\tbuffer.Release()\n\t\treturn nil, err\n\t}\n\tbuffer.Truncate(len(rawMessage))\n\treturn buffer, nil\n}\n"
  },
  {
    "path": "dns/extension_edns0_subnet.go",
    "content": "package dns\n\nimport (\n\t\"net/netip\"\n\n\t\"github.com/miekg/dns\"\n)\n\nfunc SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix) *dns.Msg {\n\treturn setClientSubnet(message, clientSubnet, true)\n}\n\nfunc setClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, clone bool) *dns.Msg {\n\tvar (\n\t\toptRecord    *dns.OPT\n\t\tsubnetOption *dns.EDNS0_SUBNET\n\t)\nfindExists:\n\tfor _, record := range message.Extra {\n\t\tvar isOPTRecord bool\n\t\tif optRecord, isOPTRecord = record.(*dns.OPT); isOPTRecord {\n\t\t\tfor _, option := range optRecord.Option {\n\t\t\t\tvar isEDNS0Subnet bool\n\t\t\t\tsubnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)\n\t\t\t\tif isEDNS0Subnet {\n\t\t\t\t\tbreak findExists\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif optRecord == nil {\n\t\texMessage := *message\n\t\tmessage = &exMessage\n\t\toptRecord = &dns.OPT{\n\t\t\tHdr: dns.RR_Header{\n\t\t\t\tName:   \".\",\n\t\t\t\tRrtype: dns.TypeOPT,\n\t\t\t},\n\t\t}\n\t\tmessage.Extra = append(message.Extra, optRecord)\n\t} else if clone {\n\t\treturn setClientSubnet(message.Copy(), clientSubnet, false)\n\t}\n\tif subnetOption == nil {\n\t\tsubnetOption = new(dns.EDNS0_SUBNET)\n\t\tsubnetOption.Code = dns.EDNS0SUBNET\n\t\toptRecord.Option = append(optRecord.Option, subnetOption)\n\t}\n\tif clientSubnet.Addr().Is4() {\n\t\tsubnetOption.Family = 1\n\t} else {\n\t\tsubnetOption.Family = 2\n\t}\n\tsubnetOption.SourceNetmask = uint8(clientSubnet.Bits())\n\tsubnetOption.Address = clientSubnet.Addr().AsSlice()\n\treturn message\n}\n"
  },
  {
    "path": "dns/rcode.go",
    "content": "package dns\n\nimport (\n\tmDNS \"github.com/miekg/dns\"\n)\n\nconst (\n\tRcodeSuccess     RcodeError = mDNS.RcodeSuccess\n\tRcodeFormatError RcodeError = mDNS.RcodeFormatError\n\tRcodeNameError   RcodeError = mDNS.RcodeNameError\n\tRcodeRefused     RcodeError = mDNS.RcodeRefused\n)\n\ntype RcodeError int\n\nfunc (e RcodeError) Error() string {\n\treturn mDNS.RcodeToString[int(e)]\n}\n"
  },
  {
    "path": "dns/router.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/taskmonitor\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tR \"github.com/sagernet/sing-box/route/rule\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/contrab/freelru\"\n\t\"github.com/sagernet/sing/contrab/maphash\"\n\t\"github.com/sagernet/sing/service\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nvar _ adapter.DNSRouter = (*Router)(nil)\n\ntype Router struct {\n\tctx                   context.Context\n\tlogger                logger.ContextLogger\n\ttransport             adapter.DNSTransportManager\n\toutbound              adapter.OutboundManager\n\tclient                adapter.DNSClient\n\trules                 []adapter.DNSRule\n\tdefaultDomainStrategy C.DomainStrategy\n\tdnsReverseMapping     freelru.Cache[netip.Addr, string]\n\tplatformInterface     adapter.PlatformInterface\n}\n\nfunc NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {\n\trouter := &Router{\n\t\tctx:                   ctx,\n\t\tlogger:                logFactory.NewLogger(\"dns\"),\n\t\ttransport:             service.FromContext[adapter.DNSTransportManager](ctx),\n\t\toutbound:              service.FromContext[adapter.OutboundManager](ctx),\n\t\trules:                 make([]adapter.DNSRule, 0, len(options.Rules)),\n\t\tdefaultDomainStrategy: C.DomainStrategy(options.Strategy),\n\t}\n\trouter.client = NewClient(ClientOptions{\n\t\tDisableCache:     options.DNSClientOptions.DisableCache,\n\t\tDisableExpire:    options.DNSClientOptions.DisableExpire,\n\t\tIndependentCache: options.DNSClientOptions.IndependentCache,\n\t\tCacheCapacity:    options.DNSClientOptions.CacheCapacity,\n\t\tClientSubnet:     options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),\n\t\tRDRC: func() adapter.RDRCStore {\n\t\t\tcacheFile := service.FromContext[adapter.CacheFile](ctx)\n\t\t\tif cacheFile == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif !cacheFile.StoreRDRC() {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn cacheFile\n\t\t},\n\t\tLogger: router.logger,\n\t})\n\tif options.ReverseMapping {\n\t\trouter.dnsReverseMapping = common.Must1(freelru.NewSharded[netip.Addr, string](1024, maphash.NewHasher[netip.Addr]().Hash32))\n\t}\n\treturn router\n}\n\nfunc (r *Router) Initialize(rules []option.DNSRule) error {\n\tfor i, ruleOptions := range rules {\n\t\tdnsRule, err := R.NewDNSRule(r.ctx, r.logger, ruleOptions, true)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"parse dns rule[\", i, \"]\")\n\t\t}\n\t\tr.rules = append(r.rules, dnsRule)\n\t}\n\treturn nil\n}\n\nfunc (r *Router) Start(stage adapter.StartStage) error {\n\tmonitor := taskmonitor.New(r.logger, C.StartTimeout)\n\tswitch stage {\n\tcase adapter.StartStateStart:\n\t\tmonitor.Start(\"initialize DNS client\")\n\t\tr.client.Start()\n\t\tmonitor.Finish()\n\n\t\tfor i, rule := range r.rules {\n\t\t\tmonitor.Start(\"initialize DNS rule[\", i, \"]\")\n\t\t\terr := rule.Start()\n\t\t\tmonitor.Finish()\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"initialize DNS rule[\", i, \"]\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *Router) Close() error {\n\tmonitor := taskmonitor.New(r.logger, C.StopTimeout)\n\tvar err error\n\tfor i, rule := range r.rules {\n\t\tmonitor.Start(\"close dns rule[\", i, \"]\")\n\t\terr = E.Append(err, rule.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close dns rule[\", i, \"]\")\n\t\t})\n\t\tmonitor.Finish()\n\t}\n\treturn err\n}\n\nfunc (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, isAddressQuery bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, adapter.DNSRule, int) {\n\tmetadata := adapter.ContextFrom(ctx)\n\tif metadata == nil {\n\t\tpanic(\"no context\")\n\t}\n\tvar currentRuleIndex int\n\tif ruleIndex != -1 {\n\t\tcurrentRuleIndex = ruleIndex + 1\n\t}\n\tfor ; currentRuleIndex < len(r.rules); currentRuleIndex++ {\n\t\tcurrentRule := r.rules[currentRuleIndex]\n\t\tif currentRule.WithAddressLimit() && !isAddressQuery {\n\t\t\tcontinue\n\t\t}\n\t\tmetadata.ResetRuleCache()\n\t\tif currentRule.Match(metadata) {\n\t\t\tdisplayRuleIndex := currentRuleIndex\n\t\t\tif displayRuleIndex != -1 {\n\t\t\t\tdisplayRuleIndex += displayRuleIndex + 1\n\t\t\t}\n\t\t\truleDescription := currentRule.String()\n\t\t\tif ruleDescription != \"\" {\n\t\t\t\tr.logger.DebugContext(ctx, \"match[\", displayRuleIndex, \"] \", currentRule, \" => \", currentRule.Action())\n\t\t\t} else {\n\t\t\t\tr.logger.DebugContext(ctx, \"match[\", displayRuleIndex, \"] => \", currentRule.Action())\n\t\t\t}\n\t\t\tswitch action := currentRule.Action().(type) {\n\t\t\tcase *R.RuleActionDNSRoute:\n\t\t\t\ttransport, loaded := r.transport.Transport(action.Server)\n\t\t\t\tif !loaded {\n\t\t\t\t\tr.logger.ErrorContext(ctx, \"transport not found: \", action.Server)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tisFakeIP := transport.Type() == C.DNSTypeFakeIP\n\t\t\t\tif isFakeIP && !allowFakeIP {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif action.Strategy != C.DomainStrategyAsIS {\n\t\t\t\t\toptions.Strategy = action.Strategy\n\t\t\t\t}\n\t\t\t\tif isFakeIP || action.DisableCache {\n\t\t\t\t\toptions.DisableCache = true\n\t\t\t\t}\n\t\t\t\tif action.RewriteTTL != nil {\n\t\t\t\t\toptions.RewriteTTL = action.RewriteTTL\n\t\t\t\t}\n\t\t\t\tif action.ClientSubnet.IsValid() {\n\t\t\t\t\toptions.ClientSubnet = action.ClientSubnet\n\t\t\t\t}\n\t\t\t\tif legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {\n\t\t\t\t\tif options.Strategy == C.DomainStrategyAsIS {\n\t\t\t\t\t\toptions.Strategy = legacyTransport.LegacyStrategy()\n\t\t\t\t\t}\n\t\t\t\t\tif !options.ClientSubnet.IsValid() {\n\t\t\t\t\t\toptions.ClientSubnet = legacyTransport.LegacyClientSubnet()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn transport, currentRule, currentRuleIndex\n\t\t\tcase *R.RuleActionDNSRouteOptions:\n\t\t\t\tif action.Strategy != C.DomainStrategyAsIS {\n\t\t\t\t\toptions.Strategy = action.Strategy\n\t\t\t\t}\n\t\t\t\tif action.DisableCache {\n\t\t\t\t\toptions.DisableCache = true\n\t\t\t\t}\n\t\t\t\tif action.RewriteTTL != nil {\n\t\t\t\t\toptions.RewriteTTL = action.RewriteTTL\n\t\t\t\t}\n\t\t\t\tif action.ClientSubnet.IsValid() {\n\t\t\t\t\toptions.ClientSubnet = action.ClientSubnet\n\t\t\t\t}\n\t\t\tcase *R.RuleActionReject:\n\t\t\t\treturn nil, currentRule, currentRuleIndex\n\t\t\tcase *R.RuleActionPredefined:\n\t\t\t\treturn nil, currentRule, currentRuleIndex\n\t\t\t}\n\t\t}\n\t}\n\ttransport := r.transport.Default()\n\tif legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {\n\t\tif options.Strategy == C.DomainStrategyAsIS {\n\t\t\toptions.Strategy = legacyTransport.LegacyStrategy()\n\t\t}\n\t\tif !options.ClientSubnet.IsValid() {\n\t\t\toptions.ClientSubnet = legacyTransport.LegacyClientSubnet()\n\t\t}\n\t}\n\treturn transport, nil, -1\n}\n\nfunc (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions) (*mDNS.Msg, error) {\n\tif len(message.Question) != 1 {\n\t\tr.logger.WarnContext(ctx, \"bad question size: \", len(message.Question))\n\t\tresponseMessage := mDNS.Msg{\n\t\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\t\tId:       message.Id,\n\t\t\t\tResponse: true,\n\t\t\t\tRcode:    mDNS.RcodeFormatError,\n\t\t\t},\n\t\t\tQuestion: message.Question,\n\t\t}\n\t\treturn &responseMessage, nil\n\t}\n\tr.logger.DebugContext(ctx, \"exchange \", FormatQuestion(message.Question[0].String()))\n\tvar (\n\t\tresponse  *mDNS.Msg\n\t\ttransport adapter.DNSTransport\n\t\terr       error\n\t)\n\tvar metadata *adapter.InboundContext\n\tctx, metadata = adapter.ExtendContext(ctx)\n\tmetadata.Destination = M.Socksaddr{}\n\tmetadata.QueryType = message.Question[0].Qtype\n\tswitch metadata.QueryType {\n\tcase mDNS.TypeA:\n\t\tmetadata.IPVersion = 4\n\tcase mDNS.TypeAAAA:\n\t\tmetadata.IPVersion = 6\n\t}\n\tmetadata.Domain = FqdnToDomain(message.Question[0].Name)\n\tif options.Transport != nil {\n\t\ttransport = options.Transport\n\t\tif legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {\n\t\t\tif options.Strategy == C.DomainStrategyAsIS {\n\t\t\t\toptions.Strategy = legacyTransport.LegacyStrategy()\n\t\t\t}\n\t\t\tif !options.ClientSubnet.IsValid() {\n\t\t\t\toptions.ClientSubnet = legacyTransport.LegacyClientSubnet()\n\t\t\t}\n\t\t}\n\t\tif options.Strategy == C.DomainStrategyAsIS {\n\t\t\toptions.Strategy = r.defaultDomainStrategy\n\t\t}\n\t\tresponse, err = r.client.Exchange(ctx, transport, message, options, nil)\n\t} else {\n\t\tvar (\n\t\t\trule      adapter.DNSRule\n\t\t\truleIndex int\n\t\t)\n\t\truleIndex = -1\n\t\tfor {\n\t\t\tdnsCtx := adapter.OverrideContext(ctx)\n\t\t\tdnsOptions := options\n\t\t\ttransport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions)\n\t\t\tif rule != nil {\n\t\t\t\tswitch action := rule.Action().(type) {\n\t\t\t\tcase *R.RuleActionReject:\n\t\t\t\t\tswitch action.Method {\n\t\t\t\t\tcase C.RuleActionRejectMethodDefault:\n\t\t\t\t\t\treturn &mDNS.Msg{\n\t\t\t\t\t\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\t\t\t\t\t\tId:       message.Id,\n\t\t\t\t\t\t\t\tRcode:    mDNS.RcodeRefused,\n\t\t\t\t\t\t\t\tResponse: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tQuestion: []mDNS.Question{message.Question[0]},\n\t\t\t\t\t\t}, nil\n\t\t\t\t\tcase C.RuleActionRejectMethodDrop:\n\t\t\t\t\t\treturn nil, tun.ErrDrop\n\t\t\t\t\t}\n\t\t\t\tcase *R.RuleActionPredefined:\n\t\t\t\t\treturn action.Response(message), nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tresponseCheck := addressLimitResponseCheck(rule, metadata)\n\t\t\tif dnsOptions.Strategy == C.DomainStrategyAsIS {\n\t\t\t\tdnsOptions.Strategy = r.defaultDomainStrategy\n\t\t\t}\n\t\t\tresponse, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck)\n\t\t\tvar rejected bool\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, ErrResponseRejectedCached) {\n\t\t\t\t\trejected = true\n\t\t\t\t\tr.logger.DebugContext(ctx, E.Cause(err, \"response rejected for \", FormatQuestion(message.Question[0].String())), \" (cached)\")\n\t\t\t\t} else if errors.Is(err, ErrResponseRejected) {\n\t\t\t\t\trejected = true\n\t\t\t\t\tr.logger.DebugContext(ctx, E.Cause(err, \"response rejected for \", FormatQuestion(message.Question[0].String())))\n\t\t\t\t} else if len(message.Question) > 0 {\n\t\t\t\t\tr.logger.ErrorContext(ctx, E.Cause(err, \"exchange failed for \", FormatQuestion(message.Question[0].String())))\n\t\t\t\t} else {\n\t\t\t\t\tr.logger.ErrorContext(ctx, E.Cause(err, \"exchange failed for <empty query>\"))\n\t\t\t\t}\n\t\t\t}\n\t\t\tif responseCheck != nil && rejected {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 {\n\t\tif transport == nil || transport.Type() != C.DNSTypeFakeIP {\n\t\t\tfor _, answer := range response.Answer {\n\t\t\t\tswitch record := answer.(type) {\n\t\t\t\tcase *mDNS.A:\n\t\t\t\t\tr.dnsReverseMapping.AddWithLifetime(M.AddrFromIP(record.A), FqdnToDomain(record.Hdr.Name), time.Duration(record.Hdr.Ttl)*time.Second)\n\t\t\t\tcase *mDNS.AAAA:\n\t\t\t\t\tr.dnsReverseMapping.AddWithLifetime(M.AddrFromIP(record.AAAA), FqdnToDomain(record.Hdr.Name), time.Duration(record.Hdr.Ttl)*time.Second)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn response, nil\n}\n\nfunc (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {\n\tvar (\n\t\tresponseAddrs []netip.Addr\n\t\terr           error\n\t)\n\tprintResult := func() {\n\t\tif err == nil && len(responseAddrs) == 0 {\n\t\t\terr = E.New(\"empty result\")\n\t\t}\n\t\tif err != nil {\n\t\t\tif errors.Is(err, ErrResponseRejectedCached) {\n\t\t\t\tr.logger.DebugContext(ctx, \"response rejected for \", domain, \" (cached)\")\n\t\t\t} else if errors.Is(err, ErrResponseRejected) {\n\t\t\t\tr.logger.DebugContext(ctx, \"response rejected for \", domain)\n\t\t\t} else {\n\t\t\t\tr.logger.ErrorContext(ctx, E.Cause(err, \"lookup failed for \", domain))\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\terr = E.Cause(err, \"lookup \", domain)\n\t\t}\n\t}\n\tr.logger.DebugContext(ctx, \"lookup domain \", domain)\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Destination = M.Socksaddr{}\n\tmetadata.Domain = FqdnToDomain(domain)\n\tif options.Transport != nil {\n\t\ttransport := options.Transport\n\t\tif legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {\n\t\t\tif options.Strategy == C.DomainStrategyAsIS {\n\t\t\t\toptions.Strategy = legacyTransport.LegacyStrategy()\n\t\t\t}\n\t\t\tif !options.ClientSubnet.IsValid() {\n\t\t\t\toptions.ClientSubnet = legacyTransport.LegacyClientSubnet()\n\t\t\t}\n\t\t}\n\t\tif options.Strategy == C.DomainStrategyAsIS {\n\t\t\toptions.Strategy = r.defaultDomainStrategy\n\t\t}\n\t\tresponseAddrs, err = r.client.Lookup(ctx, transport, domain, options, nil)\n\t} else {\n\t\tvar (\n\t\t\ttransport adapter.DNSTransport\n\t\t\trule      adapter.DNSRule\n\t\t\truleIndex int\n\t\t)\n\t\truleIndex = -1\n\t\tfor {\n\t\t\tdnsCtx := adapter.OverrideContext(ctx)\n\t\t\tdnsOptions := options\n\t\t\ttransport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &dnsOptions)\n\t\t\tif rule != nil {\n\t\t\t\tswitch action := rule.Action().(type) {\n\t\t\t\tcase *R.RuleActionReject:\n\t\t\t\t\treturn nil, &R.RejectedError{Cause: action.Error(ctx)}\n\t\t\t\tcase *R.RuleActionPredefined:\n\t\t\t\t\tresponseAddrs = nil\n\t\t\t\t\tif action.Rcode != mDNS.RcodeSuccess {\n\t\t\t\t\t\terr = RcodeError(action.Rcode)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = nil\n\t\t\t\t\t\tfor _, answer := range action.Answer {\n\t\t\t\t\t\t\tswitch record := answer.(type) {\n\t\t\t\t\t\t\tcase *mDNS.A:\n\t\t\t\t\t\t\t\tresponseAddrs = append(responseAddrs, M.AddrFromIP(record.A))\n\t\t\t\t\t\t\tcase *mDNS.AAAA:\n\t\t\t\t\t\t\t\tresponseAddrs = append(responseAddrs, M.AddrFromIP(record.AAAA))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tgoto response\n\t\t\t\t}\n\t\t\t}\n\t\t\tresponseCheck := addressLimitResponseCheck(rule, metadata)\n\t\t\tif dnsOptions.Strategy == C.DomainStrategyAsIS {\n\t\t\t\tdnsOptions.Strategy = r.defaultDomainStrategy\n\t\t\t}\n\t\t\tresponseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, dnsOptions, responseCheck)\n\t\t\tif responseCheck == nil || err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tprintResult()\n\t\t}\n\t}\nresponse:\n\tprintResult()\n\tif len(responseAddrs) > 0 {\n\t\tr.logger.InfoContext(ctx, \"lookup succeed for \", domain, \": \", strings.Join(F.MapToString(responseAddrs), \" \"))\n\t}\n\treturn responseAddrs, err\n}\n\nfunc isAddressQuery(message *mDNS.Msg) bool {\n\tfor _, question := range message.Question {\n\t\tif question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA || question.Qtype == mDNS.TypeHTTPS {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc addressLimitResponseCheck(rule adapter.DNSRule, metadata *adapter.InboundContext) func(responseAddrs []netip.Addr) bool {\n\tif rule == nil || !rule.WithAddressLimit() {\n\t\treturn nil\n\t}\n\tresponseMetadata := *metadata\n\treturn func(responseAddrs []netip.Addr) bool {\n\t\tcheckMetadata := responseMetadata\n\t\tcheckMetadata.DestinationAddresses = responseAddrs\n\t\treturn rule.MatchAddressLimit(&checkMetadata)\n\t}\n}\n\nfunc (r *Router) ClearCache() {\n\tr.client.ClearCache()\n\tif r.platformInterface != nil {\n\t\tr.platformInterface.ClearDNSCache()\n\t}\n}\n\nfunc (r *Router) LookupReverseMapping(ip netip.Addr) (string, bool) {\n\tif r.dnsReverseMapping == nil {\n\t\treturn \"\", false\n\t}\n\tdomain, loaded := r.dnsReverseMapping.Get(ip)\n\treturn domain, loaded\n}\n\nfunc (r *Router) ResetNetwork() {\n\tr.ClearCache()\n\tfor _, transport := range r.transport.Transports() {\n\t\ttransport.Reset()\n\t}\n}\n"
  },
  {
    "path": "dns/transport/base.go",
    "content": "package transport\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"sync\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\ntype TransportState int\n\nconst (\n\tStateNew TransportState = iota\n\tStateStarted\n\tStateClosing\n\tStateClosed\n)\n\nvar (\n\tErrTransportClosed = os.ErrClosed\n\tErrConnectionReset = E.New(\"connection reset\")\n)\n\ntype BaseTransport struct {\n\tdns.TransportAdapter\n\tLogger logger.ContextLogger\n\n\tmutex           sync.Mutex\n\tstate           TransportState\n\tinFlight        int32\n\tqueriesComplete chan struct{}\n\tcloseCtx        context.Context\n\tcloseCancel     context.CancelFunc\n}\n\nfunc NewBaseTransport(adapter dns.TransportAdapter, logger logger.ContextLogger) *BaseTransport {\n\tctx, cancel := context.WithCancel(context.Background())\n\treturn &BaseTransport{\n\t\tTransportAdapter: adapter,\n\t\tLogger:           logger,\n\t\tstate:            StateNew,\n\t\tcloseCtx:         ctx,\n\t\tcloseCancel:      cancel,\n\t}\n}\n\nfunc (t *BaseTransport) State() TransportState {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\treturn t.state\n}\n\nfunc (t *BaseTransport) SetStarted() error {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\tswitch t.state {\n\tcase StateNew:\n\t\tt.state = StateStarted\n\t\treturn nil\n\tcase StateStarted:\n\t\treturn nil\n\tdefault:\n\t\treturn ErrTransportClosed\n\t}\n}\n\nfunc (t *BaseTransport) BeginQuery() bool {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\tif t.state != StateStarted {\n\t\treturn false\n\t}\n\tt.inFlight++\n\treturn true\n}\n\nfunc (t *BaseTransport) EndQuery() {\n\tt.mutex.Lock()\n\tif t.inFlight > 0 {\n\t\tt.inFlight--\n\t}\n\tif t.inFlight == 0 && t.queriesComplete != nil {\n\t\tclose(t.queriesComplete)\n\t\tt.queriesComplete = nil\n\t}\n\tt.mutex.Unlock()\n}\n\nfunc (t *BaseTransport) CloseContext() context.Context {\n\treturn t.closeCtx\n}\n\nfunc (t *BaseTransport) Shutdown(ctx context.Context) error {\n\tt.mutex.Lock()\n\n\tif t.state >= StateClosing {\n\t\tt.mutex.Unlock()\n\t\treturn nil\n\t}\n\n\tif t.state == StateNew {\n\t\tt.state = StateClosed\n\t\tt.mutex.Unlock()\n\t\tt.closeCancel()\n\t\treturn nil\n\t}\n\n\tt.state = StateClosing\n\n\tif t.inFlight == 0 {\n\t\tt.state = StateClosed\n\t\tt.mutex.Unlock()\n\t\tt.closeCancel()\n\t\treturn nil\n\t}\n\n\tt.queriesComplete = make(chan struct{})\n\tqueriesComplete := t.queriesComplete\n\tt.mutex.Unlock()\n\n\tt.closeCancel()\n\n\tselect {\n\tcase <-queriesComplete:\n\t\tt.mutex.Lock()\n\t\tt.state = StateClosed\n\t\tt.mutex.Unlock()\n\t\treturn nil\n\tcase <-ctx.Done():\n\t\tt.mutex.Lock()\n\t\tt.state = StateClosed\n\t\tt.mutex.Unlock()\n\t\treturn ctx.Err()\n\t}\n}\n\nfunc (t *BaseTransport) Close() error {\n\tctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout)\n\tdefer cancel()\n\treturn t.Shutdown(ctx)\n}\n"
  },
  {
    "path": "dns/transport/connector.go",
    "content": "package transport\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype ConnectorCallbacks[T any] struct {\n\tIsClosed func(connection T) bool\n\tClose    func(connection T)\n\tReset    func(connection T)\n}\n\ntype Connector[T any] struct {\n\tdial      func(ctx context.Context) (T, error)\n\tcallbacks ConnectorCallbacks[T]\n\n\taccess           sync.Mutex\n\tconnection       T\n\thasConnection    bool\n\tconnectionCancel context.CancelFunc\n\tconnecting       chan struct{}\n\n\tcloseCtx context.Context\n\tclosed   bool\n}\n\nfunc NewConnector[T any](closeCtx context.Context, dial func(context.Context) (T, error), callbacks ConnectorCallbacks[T]) *Connector[T] {\n\treturn &Connector[T]{\n\t\tdial:      dial,\n\t\tcallbacks: callbacks,\n\t\tcloseCtx:  closeCtx,\n\t}\n}\n\nfunc NewSingleflightConnector(closeCtx context.Context, dial func(context.Context) (*Connection, error)) *Connector[*Connection] {\n\treturn NewConnector(closeCtx, dial, ConnectorCallbacks[*Connection]{\n\t\tIsClosed: func(connection *Connection) bool {\n\t\t\treturn connection.IsClosed()\n\t\t},\n\t\tClose: func(connection *Connection) {\n\t\t\tconnection.CloseWithError(ErrTransportClosed)\n\t\t},\n\t\tReset: func(connection *Connection) {\n\t\t\tconnection.CloseWithError(ErrConnectionReset)\n\t\t},\n\t})\n}\n\ntype contextKeyConnecting struct{}\n\nvar errRecursiveConnectorDial = E.New(\"recursive connector dial\")\n\ntype connectorDialResult[T any] struct {\n\tconnection T\n\tcancel     context.CancelFunc\n\terr        error\n}\n\nfunc (c *Connector[T]) Get(ctx context.Context) (T, error) {\n\tvar zero T\n\tfor {\n\t\tc.access.Lock()\n\n\t\tif c.closed {\n\t\t\tc.access.Unlock()\n\t\t\treturn zero, ErrTransportClosed\n\t\t}\n\n\t\tif c.hasConnection && !c.callbacks.IsClosed(c.connection) {\n\t\t\tconnection := c.connection\n\t\t\tc.access.Unlock()\n\t\t\treturn connection, nil\n\t\t}\n\n\t\tc.hasConnection = false\n\t\tif c.connectionCancel != nil {\n\t\t\tc.connectionCancel()\n\t\t\tc.connectionCancel = nil\n\t\t}\n\t\tif isRecursiveConnectorDial(ctx, c) {\n\t\t\tc.access.Unlock()\n\t\t\treturn zero, errRecursiveConnectorDial\n\t\t}\n\n\t\tif c.connecting != nil {\n\t\t\tconnecting := c.connecting\n\t\t\tc.access.Unlock()\n\n\t\t\tselect {\n\t\t\tcase <-connecting:\n\t\t\t\tcontinue\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn zero, ctx.Err()\n\t\t\tcase <-c.closeCtx.Done():\n\t\t\t\treturn zero, ErrTransportClosed\n\t\t\t}\n\t\t}\n\n\t\tif err := ctx.Err(); err != nil {\n\t\t\tc.access.Unlock()\n\t\t\treturn zero, err\n\t\t}\n\n\t\tconnecting := make(chan struct{})\n\t\tc.connecting = connecting\n\t\tdialContext := context.WithValue(ctx, contextKeyConnecting{}, c)\n\t\tdialResult := make(chan connectorDialResult[T], 1)\n\t\tc.access.Unlock()\n\n\t\tgo func() {\n\t\t\tconnection, cancel, err := c.dialWithCancellation(dialContext)\n\t\t\tdialResult <- connectorDialResult[T]{\n\t\t\t\tconnection: connection,\n\t\t\t\tcancel:     cancel,\n\t\t\t\terr:        err,\n\t\t\t}\n\t\t}()\n\n\t\tselect {\n\t\tcase result := <-dialResult:\n\t\t\treturn c.completeDial(ctx, connecting, result)\n\t\tcase <-ctx.Done():\n\t\t\tgo func() {\n\t\t\t\tresult := <-dialResult\n\t\t\t\t_, _ = c.completeDial(ctx, connecting, result)\n\t\t\t}()\n\t\t\treturn zero, ctx.Err()\n\t\tcase <-c.closeCtx.Done():\n\t\t\tgo func() {\n\t\t\t\tresult := <-dialResult\n\t\t\t\t_, _ = c.completeDial(ctx, connecting, result)\n\t\t\t}()\n\t\t\treturn zero, ErrTransportClosed\n\t\t}\n\t}\n}\n\nfunc isRecursiveConnectorDial[T any](ctx context.Context, connector *Connector[T]) bool {\n\tdialConnector, loaded := ctx.Value(contextKeyConnecting{}).(*Connector[T])\n\treturn loaded && dialConnector == connector\n}\n\nfunc (c *Connector[T]) completeDial(ctx context.Context, connecting chan struct{}, result connectorDialResult[T]) (T, error) {\n\tvar zero T\n\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tdefer func() {\n\t\tif c.connecting == connecting {\n\t\t\tc.connecting = nil\n\t\t}\n\t\tclose(connecting)\n\t}()\n\n\tif result.err != nil {\n\t\treturn zero, result.err\n\t}\n\tif c.closed || c.closeCtx.Err() != nil {\n\t\tresult.cancel()\n\t\tc.callbacks.Close(result.connection)\n\t\treturn zero, ErrTransportClosed\n\t}\n\tif err := ctx.Err(); err != nil {\n\t\tresult.cancel()\n\t\tc.callbacks.Close(result.connection)\n\t\treturn zero, err\n\t}\n\n\tc.connection = result.connection\n\tc.hasConnection = true\n\tc.connectionCancel = result.cancel\n\treturn c.connection, nil\n}\n\nfunc (c *Connector[T]) dialWithCancellation(ctx context.Context) (T, context.CancelFunc, error) {\n\tvar zero T\n\tif err := ctx.Err(); err != nil {\n\t\treturn zero, nil, err\n\t}\n\tconnCtx, cancel := context.WithCancel(c.closeCtx)\n\n\tvar (\n\t\tstateAccess  sync.Mutex\n\t\tdialComplete bool\n\t)\n\tstopCancel := context.AfterFunc(ctx, func() {\n\t\tstateAccess.Lock()\n\t\tif !dialComplete {\n\t\t\tcancel()\n\t\t}\n\t\tstateAccess.Unlock()\n\t})\n\tselect {\n\tcase <-ctx.Done():\n\t\tstateAccess.Lock()\n\t\tdialComplete = true\n\t\tstateAccess.Unlock()\n\t\tstopCancel()\n\t\tcancel()\n\t\treturn zero, nil, ctx.Err()\n\tdefault:\n\t}\n\n\tconnection, err := c.dial(valueContext{connCtx, ctx})\n\tstateAccess.Lock()\n\tdialComplete = true\n\tstateAccess.Unlock()\n\tstopCancel()\n\tif err != nil {\n\t\tcancel()\n\t\treturn zero, nil, err\n\t}\n\treturn connection, cancel, nil\n}\n\ntype valueContext struct {\n\tcontext.Context\n\tparent context.Context\n}\n\nfunc (v valueContext) Value(key any) any {\n\treturn v.parent.Value(key)\n}\n\nfunc (v valueContext) Deadline() (time.Time, bool) {\n\treturn v.parent.Deadline()\n}\n\nfunc (c *Connector[T]) Close() error {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\n\tif c.closed {\n\t\treturn nil\n\t}\n\tc.closed = true\n\n\tif c.connectionCancel != nil {\n\t\tc.connectionCancel()\n\t\tc.connectionCancel = nil\n\t}\n\tif c.hasConnection {\n\t\tc.callbacks.Close(c.connection)\n\t\tc.hasConnection = false\n\t}\n\n\treturn nil\n}\n\nfunc (c *Connector[T]) Reset() {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\n\tif c.connectionCancel != nil {\n\t\tc.connectionCancel()\n\t\tc.connectionCancel = nil\n\t}\n\tif c.hasConnection {\n\t\tc.callbacks.Reset(c.connection)\n\t\tc.hasConnection = false\n\t}\n}\n\ntype Connection struct {\n\tnet.Conn\n\n\tcloseOnce  sync.Once\n\tdone       chan struct{}\n\tcloseError error\n}\n\nfunc WrapConnection(conn net.Conn) *Connection {\n\treturn &Connection{\n\t\tConn: conn,\n\t\tdone: make(chan struct{}),\n\t}\n}\n\nfunc (c *Connection) Done() <-chan struct{} {\n\treturn c.done\n}\n\nfunc (c *Connection) IsClosed() bool {\n\tselect {\n\tcase <-c.done:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (c *Connection) CloseError() error {\n\tselect {\n\tcase <-c.done:\n\t\tif c.closeError != nil {\n\t\t\treturn c.closeError\n\t\t}\n\t\treturn ErrTransportClosed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc (c *Connection) Close() error {\n\treturn c.CloseWithError(ErrTransportClosed)\n}\n\nfunc (c *Connection) CloseWithError(err error) error {\n\tvar returnError error\n\tc.closeOnce.Do(func() {\n\t\tc.closeError = err\n\t\treturnError = c.Conn.Close()\n\t\tclose(c.done)\n\t})\n\treturn returnError\n}\n"
  },
  {
    "path": "dns/transport/connector_test.go",
    "content": "package transport\n\nimport (\n\t\"context\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype testConnectorConnection struct{}\n\nfunc TestConnectorRecursiveGetFailsFast(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tdialCount  atomic.Int32\n\t\tcloseCount atomic.Int32\n\t\tconnector  *Connector[*testConnectorConnection]\n\t)\n\n\tdial := func(ctx context.Context) (*testConnectorConnection, error) {\n\t\tdialCount.Add(1)\n\t\t_, err := connector.Get(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &testConnectorConnection{}, nil\n\t}\n\n\tconnector = NewConnector(context.Background(), dial, ConnectorCallbacks[*testConnectorConnection]{\n\t\tIsClosed: func(connection *testConnectorConnection) bool {\n\t\t\treturn false\n\t\t},\n\t\tClose: func(connection *testConnectorConnection) {\n\t\t\tcloseCount.Add(1)\n\t\t},\n\t\tReset: func(connection *testConnectorConnection) {\n\t\t\tcloseCount.Add(1)\n\t\t},\n\t})\n\n\t_, err := connector.Get(context.Background())\n\trequire.ErrorIs(t, err, errRecursiveConnectorDial)\n\trequire.EqualValues(t, 1, dialCount.Load())\n\trequire.EqualValues(t, 0, closeCount.Load())\n}\n\nfunc TestConnectorRecursiveGetAcrossConnectorsAllowed(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\touterDialCount atomic.Int32\n\t\tinnerDialCount atomic.Int32\n\t\touterConnector *Connector[*testConnectorConnection]\n\t\tinnerConnector *Connector[*testConnectorConnection]\n\t)\n\n\tinnerConnector = NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {\n\t\tinnerDialCount.Add(1)\n\t\treturn &testConnectorConnection{}, nil\n\t}, ConnectorCallbacks[*testConnectorConnection]{\n\t\tIsClosed: func(connection *testConnectorConnection) bool {\n\t\t\treturn false\n\t\t},\n\t\tClose: func(connection *testConnectorConnection) {},\n\t\tReset: func(connection *testConnectorConnection) {},\n\t})\n\n\touterConnector = NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {\n\t\touterDialCount.Add(1)\n\t\t_, err := innerConnector.Get(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &testConnectorConnection{}, nil\n\t}, ConnectorCallbacks[*testConnectorConnection]{\n\t\tIsClosed: func(connection *testConnectorConnection) bool {\n\t\t\treturn false\n\t\t},\n\t\tClose: func(connection *testConnectorConnection) {},\n\t\tReset: func(connection *testConnectorConnection) {},\n\t})\n\n\t_, err := outerConnector.Get(context.Background())\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 1, outerDialCount.Load())\n\trequire.EqualValues(t, 1, innerDialCount.Load())\n}\n\nfunc TestConnectorDialContextPreservesValueAndDeadline(t *testing.T) {\n\tt.Parallel()\n\n\ttype contextKey struct{}\n\n\tvar (\n\t\tdialValue       any\n\t\tdialDeadline    time.Time\n\t\tdialHasDeadline bool\n\t)\n\n\tconnector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {\n\t\tdialValue = ctx.Value(contextKey{})\n\t\tdialDeadline, dialHasDeadline = ctx.Deadline()\n\t\treturn &testConnectorConnection{}, nil\n\t}, ConnectorCallbacks[*testConnectorConnection]{\n\t\tIsClosed: func(connection *testConnectorConnection) bool {\n\t\t\treturn false\n\t\t},\n\t\tClose: func(connection *testConnectorConnection) {},\n\t\tReset: func(connection *testConnectorConnection) {},\n\t})\n\n\tdeadline := time.Now().Add(time.Minute)\n\trequestContext, cancel := context.WithDeadline(context.WithValue(context.Background(), contextKey{}, \"test-value\"), deadline)\n\tdefer cancel()\n\n\t_, err := connector.Get(requestContext)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"test-value\", dialValue)\n\trequire.True(t, dialHasDeadline)\n\trequire.WithinDuration(t, deadline, dialDeadline, time.Second)\n}\n\nfunc TestConnectorDialSkipsCanceledRequest(t *testing.T) {\n\tt.Parallel()\n\n\tvar dialCount atomic.Int32\n\tconnector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {\n\t\tdialCount.Add(1)\n\t\treturn &testConnectorConnection{}, nil\n\t}, ConnectorCallbacks[*testConnectorConnection]{\n\t\tIsClosed: func(connection *testConnectorConnection) bool {\n\t\t\treturn false\n\t\t},\n\t\tClose: func(connection *testConnectorConnection) {},\n\t\tReset: func(connection *testConnectorConnection) {},\n\t})\n\n\trequestContext, cancel := context.WithCancel(context.Background())\n\tcancel()\n\n\t_, err := connector.Get(requestContext)\n\trequire.ErrorIs(t, err, context.Canceled)\n\trequire.EqualValues(t, 0, dialCount.Load())\n}\n\nfunc TestConnectorCanceledRequestDoesNotCacheConnection(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tdialCount  atomic.Int32\n\t\tcloseCount atomic.Int32\n\t)\n\tdialStarted := make(chan struct{}, 1)\n\treleaseDial := make(chan struct{})\n\n\tconnector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {\n\t\tdialCount.Add(1)\n\t\tselect {\n\t\tcase dialStarted <- struct{}{}:\n\t\tdefault:\n\t\t}\n\t\t<-releaseDial\n\t\treturn &testConnectorConnection{}, nil\n\t}, ConnectorCallbacks[*testConnectorConnection]{\n\t\tIsClosed: func(connection *testConnectorConnection) bool {\n\t\t\treturn false\n\t\t},\n\t\tClose: func(connection *testConnectorConnection) {\n\t\t\tcloseCount.Add(1)\n\t\t},\n\t\tReset: func(connection *testConnectorConnection) {},\n\t})\n\n\trequestContext, cancel := context.WithCancel(context.Background())\n\tresult := make(chan error, 1)\n\tgo func() {\n\t\t_, err := connector.Get(requestContext)\n\t\tresult <- err\n\t}()\n\n\t<-dialStarted\n\tcancel()\n\tclose(releaseDial)\n\n\terr := <-result\n\trequire.ErrorIs(t, err, context.Canceled)\n\trequire.EqualValues(t, 1, dialCount.Load())\n\trequire.Eventually(t, func() bool {\n\t\treturn closeCount.Load() == 1\n\t}, time.Second, 10*time.Millisecond)\n\n\t_, err = connector.Get(context.Background())\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 2, dialCount.Load())\n}\n\nfunc TestConnectorCanceledRequestReturnsBeforeIgnoredDialCompletes(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tdialCount  atomic.Int32\n\t\tcloseCount atomic.Int32\n\t)\n\tdialStarted := make(chan struct{}, 1)\n\treleaseDial := make(chan struct{})\n\n\tconnector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {\n\t\tdialCount.Add(1)\n\t\tselect {\n\t\tcase dialStarted <- struct{}{}:\n\t\tdefault:\n\t\t}\n\t\t<-releaseDial\n\t\treturn &testConnectorConnection{}, nil\n\t}, ConnectorCallbacks[*testConnectorConnection]{\n\t\tIsClosed: func(connection *testConnectorConnection) bool {\n\t\t\treturn false\n\t\t},\n\t\tClose: func(connection *testConnectorConnection) {\n\t\t\tcloseCount.Add(1)\n\t\t},\n\t\tReset: func(connection *testConnectorConnection) {},\n\t})\n\n\trequestContext, cancel := context.WithCancel(context.Background())\n\tresult := make(chan error, 1)\n\tgo func() {\n\t\t_, err := connector.Get(requestContext)\n\t\tresult <- err\n\t}()\n\n\t<-dialStarted\n\tcancel()\n\n\tselect {\n\tcase err := <-result:\n\t\trequire.ErrorIs(t, err, context.Canceled)\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"Get did not return after request cancel\")\n\t}\n\n\trequire.EqualValues(t, 1, dialCount.Load())\n\trequire.EqualValues(t, 0, closeCount.Load())\n\n\tclose(releaseDial)\n\n\trequire.Eventually(t, func() bool {\n\t\treturn closeCount.Load() == 1\n\t}, time.Second, 10*time.Millisecond)\n\n\t_, err := connector.Get(context.Background())\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 2, dialCount.Load())\n}\n\nfunc TestConnectorWaiterDoesNotStartNewDialBeforeCanceledDialCompletes(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tdialCount  atomic.Int32\n\t\tcloseCount atomic.Int32\n\t)\n\tfirstDialStarted := make(chan struct{}, 1)\n\tsecondDialStarted := make(chan struct{}, 1)\n\treleaseFirstDial := make(chan struct{})\n\n\tconnector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {\n\t\tattempt := dialCount.Add(1)\n\t\tswitch attempt {\n\t\tcase 1:\n\t\t\tselect {\n\t\t\tcase firstDialStarted <- struct{}{}:\n\t\t\tdefault:\n\t\t\t}\n\t\t\t<-releaseFirstDial\n\t\tcase 2:\n\t\t\tselect {\n\t\t\tcase secondDialStarted <- struct{}{}:\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\treturn &testConnectorConnection{}, nil\n\t}, ConnectorCallbacks[*testConnectorConnection]{\n\t\tIsClosed: func(connection *testConnectorConnection) bool {\n\t\t\treturn false\n\t\t},\n\t\tClose: func(connection *testConnectorConnection) {\n\t\t\tcloseCount.Add(1)\n\t\t},\n\t\tReset: func(connection *testConnectorConnection) {},\n\t})\n\n\trequestContext, cancel := context.WithCancel(context.Background())\n\tfirstResult := make(chan error, 1)\n\tgo func() {\n\t\t_, err := connector.Get(requestContext)\n\t\tfirstResult <- err\n\t}()\n\n\t<-firstDialStarted\n\tcancel()\n\n\tsecondResult := make(chan error, 1)\n\tgo func() {\n\t\t_, err := connector.Get(context.Background())\n\t\tsecondResult <- err\n\t}()\n\n\tselect {\n\tcase <-secondDialStarted:\n\t\tt.Fatal(\"second dial started before first dial completed\")\n\tcase <-time.After(100 * time.Millisecond):\n\t}\n\n\tselect {\n\tcase err := <-firstResult:\n\t\trequire.ErrorIs(t, err, context.Canceled)\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"first Get did not return after request cancel\")\n\t}\n\n\tclose(releaseFirstDial)\n\n\trequire.Eventually(t, func() bool {\n\t\treturn closeCount.Load() == 1\n\t}, time.Second, 10*time.Millisecond)\n\n\tselect {\n\tcase <-secondDialStarted:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"second dial did not start after first dial completed\")\n\t}\n\n\terr := <-secondResult\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 2, dialCount.Load())\n}\n\nfunc TestConnectorDialContextNotCanceledByRequestContextAfterDial(t *testing.T) {\n\tt.Parallel()\n\n\tvar dialContext context.Context\n\tconnector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {\n\t\tdialContext = ctx\n\t\treturn &testConnectorConnection{}, nil\n\t}, ConnectorCallbacks[*testConnectorConnection]{\n\t\tIsClosed: func(connection *testConnectorConnection) bool {\n\t\t\treturn false\n\t\t},\n\t\tClose: func(connection *testConnectorConnection) {},\n\t\tReset: func(connection *testConnectorConnection) {},\n\t})\n\n\trequestContext, cancel := context.WithCancel(context.Background())\n\t_, err := connector.Get(requestContext)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, dialContext)\n\n\tcancel()\n\n\tselect {\n\tcase <-dialContext.Done():\n\t\tt.Fatal(\"dial context canceled by request context after successful dial\")\n\tcase <-time.After(100 * time.Millisecond):\n\t}\n\n\terr = connector.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestConnectorDialContextCanceledOnClose(t *testing.T) {\n\tt.Parallel()\n\n\tvar dialContext context.Context\n\tconnector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {\n\t\tdialContext = ctx\n\t\treturn &testConnectorConnection{}, nil\n\t}, ConnectorCallbacks[*testConnectorConnection]{\n\t\tIsClosed: func(connection *testConnectorConnection) bool {\n\t\t\treturn false\n\t\t},\n\t\tClose: func(connection *testConnectorConnection) {},\n\t\tReset: func(connection *testConnectorConnection) {},\n\t})\n\n\t_, err := connector.Get(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, dialContext)\n\n\tselect {\n\tcase <-dialContext.Done():\n\t\tt.Fatal(\"dial context canceled before connector close\")\n\tdefault:\n\t}\n\n\terr = connector.Close()\n\trequire.NoError(t, err)\n\n\tselect {\n\tcase <-dialContext.Done():\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"dial context not canceled after connector close\")\n\t}\n}\n"
  },
  {
    "path": "dns/transport/dhcp/dhcp.go",
    "content": "package dhcp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/task\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"github.com/insomniacslk/dhcp/dhcpv4\"\n\tmDNS \"github.com/miekg/dns\"\n\t\"golang.org/x/exp/slices\"\n)\n\nfunc RegisterTransport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.DHCPDNSServerOptions](registry, C.DNSTypeDHCP, NewTransport)\n}\n\nvar _ adapter.DNSTransport = (*Transport)(nil)\n\ntype Transport struct {\n\tdns.TransportAdapter\n\tctx               context.Context\n\tdialer            N.Dialer\n\tlogger            logger.ContextLogger\n\tnetworkManager    adapter.NetworkManager\n\tinterfaceName     string\n\tinterfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]\n\ttransportLock     sync.RWMutex\n\tupdatedAt         time.Time\n\tlastError         error\n\tservers           []M.Socksaddr\n\tsearch            []string\n\tndots             int\n\tattempts          int\n}\n\nfunc NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) {\n\ttransportDialer, err := dns.NewLocalDialer(ctx, options.LocalDNSServerOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Transport{\n\t\tTransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeDHCP, tag, options.LocalDNSServerOptions),\n\t\tctx:              ctx,\n\t\tdialer:           transportDialer,\n\t\tlogger:           logger,\n\t\tnetworkManager:   service.FromContext[adapter.NetworkManager](ctx),\n\t\tinterfaceName:    options.Interface,\n\t\tndots:            1,\n\t\tattempts:         2,\n\t}, nil\n}\n\nfunc NewRawTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) *Transport {\n\treturn &Transport{\n\t\tTransportAdapter: transportAdapter,\n\t\tctx:              ctx,\n\t\tdialer:           dialer,\n\t\tlogger:           logger,\n\t\tnetworkManager:   service.FromContext[adapter.NetworkManager](ctx),\n\t\tndots:            1,\n\t\tattempts:         2,\n\t}\n}\n\nfunc (t *Transport) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif t.interfaceName == \"\" {\n\t\tt.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)\n\t}\n\tgo func() {\n\t\t_, err := t.fetch()\n\t\tif err != nil {\n\t\t\tt.logger.Error(E.Cause(err, \"fetch DNS servers\"))\n\t\t}\n\t}()\n\treturn nil\n}\n\nfunc (t *Transport) Close() error {\n\tif t.interfaceCallback != nil {\n\t\tt.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)\n\t}\n\treturn nil\n}\n\nfunc (t *Transport) Reset() {\n\tt.transportLock.Lock()\n\tt.updatedAt = time.Time{}\n\tt.servers = nil\n\tt.transportLock.Unlock()\n}\n\nfunc (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tservers, err := t.fetch()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(servers) == 0 {\n\t\treturn nil, E.New(\"dhcp: empty DNS servers from response\")\n\t}\n\treturn t.Exchange0(ctx, message, servers)\n}\n\nfunc (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error) {\n\tquestion := message.Question[0]\n\tdomain := dns.FqdnToDomain(question.Name)\n\tif len(servers) == 1 || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {\n\t\treturn t.exchangeSingleRequest(ctx, servers, message, domain)\n\t} else {\n\t\treturn t.exchangeParallel(ctx, servers, message, domain)\n\t}\n}\n\nfunc (t *Transport) Fetch() []M.Socksaddr {\n\tservers, _ := t.fetch()\n\treturn servers\n}\n\nfunc (t *Transport) fetch() ([]M.Socksaddr, error) {\n\tt.transportLock.RLock()\n\tupdatedAt := t.updatedAt\n\tlastError := t.lastError\n\tservers := t.servers\n\tt.transportLock.RUnlock()\n\tif lastError != nil {\n\t\treturn nil, lastError\n\t}\n\tif time.Since(updatedAt) < C.DHCPTTL {\n\t\treturn servers, nil\n\t}\n\tt.transportLock.Lock()\n\tdefer t.transportLock.Unlock()\n\tif time.Since(t.updatedAt) < C.DHCPTTL {\n\t\treturn t.servers, nil\n\t}\n\terr := t.updateServers()\n\tif err != nil {\n\t\treturn servers, err\n\t}\n\treturn t.servers, nil\n}\n\nfunc (t *Transport) fetchInterface() (*control.Interface, error) {\n\tif t.interfaceName == \"\" {\n\t\tif t.networkManager.InterfaceMonitor() == nil {\n\t\t\treturn nil, E.New(\"missing monitor for auto DHCP, set route.auto_detect_interface\")\n\t\t}\n\t\tdefaultInterface := t.networkManager.InterfaceMonitor().DefaultInterface()\n\t\tif defaultInterface == nil {\n\t\t\treturn nil, E.New(\"missing default interface\")\n\t\t}\n\t\treturn defaultInterface, nil\n\t} else {\n\t\treturn t.networkManager.InterfaceFinder().ByName(t.interfaceName)\n\t}\n}\n\nfunc (t *Transport) updateServers() error {\n\tiface, err := t.fetchInterface()\n\tif err != nil {\n\t\treturn E.Cause(err, \"dhcp: prepare interface\")\n\t}\n\n\tt.logger.Info(\"dhcp: query DNS servers on \", iface.Name)\n\tfetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)\n\terr = t.fetchServers0(fetchCtx, iface)\n\tcancel()\n\tt.updatedAt = time.Now()\n\tif err != nil {\n\t\tt.lastError = err\n\t\treturn err\n\t} else if len(t.servers) == 0 {\n\t\tt.lastError = E.New(\"dhcp: empty DNS servers response\")\n\t\treturn t.lastError\n\t} else {\n\t\tt.lastError = nil\n\t\treturn nil\n\t}\n}\n\nfunc (t *Transport) interfaceUpdated(defaultInterface *control.Interface, flags int) {\n\terr := t.updateServers()\n\tif err != nil {\n\t\tt.logger.Error(\"update servers: \", err)\n\t}\n}\n\nfunc (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface) error {\n\tvar listener net.ListenConfig\n\tlistener.Control = control.Append(listener.Control, control.BindToInterface(t.networkManager.InterfaceFinder(), iface.Name, iface.Index))\n\tlistener.Control = control.Append(listener.Control, control.ReuseAddr())\n\tlistenAddr := \"0.0.0.0:68\"\n\tif runtime.GOOS == \"linux\" || runtime.GOOS == \"android\" {\n\t\tlistenAddr = \"255.255.255.255:68\"\n\t}\n\tvar (\n\t\tpacketConn net.PacketConn\n\t\terr        error\n\t)\n\tfor i := 0; i < 5; i++ {\n\t\tpacketConn, err = listener.ListenPacket(t.ctx, \"udp4\", listenAddr)\n\t\tif err == nil || !errors.Is(err, syscall.EADDRINUSE) {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Second)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer packetConn.Close()\n\n\tdiscovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(\n\t\tdhcpv4.OptionDomainName,\n\t\tdhcpv4.OptionDomainNameServer,\n\t\tdhcpv4.OptionDNSDomainSearchList,\n\t))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = packetConn.WriteTo(discovery.ToBytes(), &net.UDPAddr{IP: net.IPv4bcast, Port: 67})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar group task.Group\n\tgroup.Append0(func(ctx context.Context) error {\n\t\treturn t.fetchServersResponse(iface, packetConn, discovery.TransactionID)\n\t})\n\tgroup.Cleanup(func() {\n\t\tpacketConn.Close()\n\t})\n\treturn group.Run(ctx)\n}\n\nfunc (t *Transport) fetchServersResponse(iface *control.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error {\n\tbuffer := buf.NewSize(dhcpv4.MaxMessageSize)\n\tdefer buffer.Release()\n\n\tfor {\n\t\tbuffer.Reset()\n\t\t_, _, err := buffer.ReadPacketFrom(packetConn)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.ErrShortBuffer) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tdhcpPacket, err := dhcpv4.FromBytes(buffer.Bytes())\n\t\tif err != nil {\n\t\t\tt.logger.Trace(\"dhcp: parse DHCP response: \", err)\n\t\t\treturn err\n\t\t}\n\n\t\tif dhcpPacket.MessageType() != dhcpv4.MessageTypeOffer {\n\t\t\tt.logger.Trace(\"dhcp: expected OFFER response, but got \", dhcpPacket.MessageType())\n\t\t\tcontinue\n\t\t}\n\n\t\tif dhcpPacket.TransactionID != transactionID {\n\t\t\tt.logger.Trace(\"dhcp: expected transaction ID \", transactionID, \", but got \", dhcpPacket.TransactionID)\n\t\t\tcontinue\n\t\t}\n\n\t\treturn t.recreateServers(iface, dhcpPacket)\n\t}\n}\n\nfunc (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error {\n\tsearchList := dhcpPacket.DomainSearch()\n\tif searchList != nil && len(searchList.Labels) > 0 {\n\t\tt.search = searchList.Labels\n\t} else if dhcpPacket.DomainName() != \"\" {\n\t\tt.search = []string{dhcpPacket.DomainName()}\n\t}\n\tserverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr {\n\t\treturn M.SocksaddrFrom(M.AddrFromIP(it), 53)\n\t})\n\tif len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) {\n\t\tt.logger.Info(\"dhcp: updated DNS servers from \", iface.Name, \": [\", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), \",\"), \"], search: [\", strings.Join(t.search, \",\"), \"]\")\n\t}\n\tt.servers = serverAddrs\n\treturn nil\n}\n"
  },
  {
    "path": "dns/transport/dhcp/dhcp_shared.go",
    "content": "package dhcp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc (t *Transport) exchangeSingleRequest(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {\n\tvar lastErr error\n\tfor _, fqdn := range t.nameList(domain) {\n\t\tresponse, err := t.tryOneName(ctx, servers, fqdn, message)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\t\treturn response, nil\n\t}\n\treturn nil, lastErr\n}\n\nfunc (t *Transport) exchangeParallel(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {\n\treturned := make(chan struct{})\n\tdefer close(returned)\n\ttype queryResult struct {\n\t\tresponse *mDNS.Msg\n\t\terr      error\n\t}\n\tresults := make(chan queryResult)\n\tstartRacer := func(ctx context.Context, fqdn string) {\n\t\tresponse, err := t.tryOneName(ctx, servers, fqdn, message)\n\t\tif err == nil {\n\t\t\tif response.Rcode != mDNS.RcodeSuccess {\n\t\t\t\terr = dns.RcodeError(response.Rcode)\n\t\t\t} else if len(dns.MessageToAddresses(response)) == 0 {\n\t\t\t\terr = dns.RcodeSuccess\n\t\t\t}\n\t\t}\n\t\tselect {\n\t\tcase results <- queryResult{response, err}:\n\t\tcase <-returned:\n\t\t}\n\t}\n\tqueryCtx, queryCancel := context.WithCancel(ctx)\n\tdefer queryCancel()\n\tvar nameCount int\n\tfor _, fqdn := range t.nameList(domain) {\n\t\tnameCount++\n\t\tgo startRacer(queryCtx, fqdn)\n\t}\n\tvar errors []error\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, ctx.Err()\n\t\tcase result := <-results:\n\t\t\tif result.err == nil {\n\t\t\t\treturn result.response, nil\n\t\t\t}\n\t\t\terrors = append(errors, result.err)\n\t\t\tif len(errors) == nameCount {\n\t\t\t\treturn nil, E.Errors(errors...)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tsLen := len(servers)\n\tvar lastErr error\n\tfor i := 0; i < t.attempts; i++ {\n\t\tfor j := 0; j < sLen; j++ {\n\t\t\tserver := servers[j]\n\t\t\tquestion := message.Question[0]\n\t\t\tquestion.Name = fqdn\n\t\t\tresponse, err := t.exchangeOne(ctx, server, question)\n\t\t\tif err != nil {\n\t\t\t\tlastErr = err\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn response, nil\n\t\t}\n\t}\n\treturn nil, E.Cause(lastErr, fqdn)\n}\n\nfunc (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question) (*mDNS.Msg, error) {\n\tif server.Port == 0 {\n\t\tserver.Port = 53\n\t}\n\trequest := &mDNS.Msg{\n\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\tId:                uint16(rand.Uint32()),\n\t\t\tRecursionDesired:  true,\n\t\t\tAuthenticatedData: true,\n\t\t},\n\t\tQuestion: []mDNS.Question{question},\n\t\tCompress: true,\n\t}\n\trequest.SetEdns0(buf.UDPBufferSize, false)\n\treturn t.exchangeUDP(ctx, server, request)\n}\n\nfunc (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) {\n\tconn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer conn.Close()\n\tif deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {\n\t\tconn.SetDeadline(deadline)\n\t}\n\tbuffer := buf.Get(buf.UDPBufferSize)\n\tdefer buf.Put(buffer)\n\trawMessage, err := request.PackBuffer(buffer)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"pack request\")\n\t}\n\t_, err = conn.Write(rawMessage)\n\tif err != nil {\n\t\tif errors.Is(err, syscall.EMSGSIZE) {\n\t\t\treturn t.exchangeTCP(ctx, server, request)\n\t\t}\n\t\treturn nil, E.Cause(err, \"write request\")\n\t}\n\tn, err := conn.Read(buffer)\n\tif err != nil {\n\t\tif errors.Is(err, syscall.EMSGSIZE) {\n\t\t\treturn t.exchangeTCP(ctx, server, request)\n\t\t}\n\t\treturn nil, E.Cause(err, \"read response\")\n\t}\n\tvar response mDNS.Msg\n\terr = response.Unpack(buffer[:n])\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"unpack response\")\n\t}\n\tif response.Truncated {\n\t\treturn t.exchangeTCP(ctx, server, request)\n\t}\n\treturn &response, nil\n}\n\nfunc (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) {\n\tconn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer conn.Close()\n\tif deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {\n\t\tconn.SetDeadline(deadline)\n\t}\n\terr = transport.WriteMessage(conn, 0, request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn transport.ReadMessage(conn)\n}\n\nfunc (t *Transport) nameList(name string) []string {\n\tl := len(name)\n\trooted := l > 0 && name[l-1] == '.'\n\tif l > 254 || l == 254 && !rooted {\n\t\treturn nil\n\t}\n\n\tif rooted {\n\t\tif avoidDNS(name) {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{name}\n\t}\n\n\thasNdots := strings.Count(name, \".\") >= t.ndots\n\tname += \".\"\n\t// l++\n\n\tnames := make([]string, 0, 1+len(t.search))\n\tif hasNdots && !avoidDNS(name) {\n\t\tnames = append(names, name)\n\t}\n\tfor _, suffix := range t.search {\n\t\tfqdn := name + suffix\n\t\tif !avoidDNS(fqdn) && len(fqdn) <= 254 {\n\t\t\tnames = append(names, fqdn)\n\t\t}\n\t}\n\tif !hasNdots && !avoidDNS(name) {\n\t\tnames = append(names, name)\n\t}\n\treturn names\n}\n\nfunc avoidDNS(name string) bool {\n\tif name == \"\" {\n\t\treturn true\n\t}\n\tif name[len(name)-1] == '.' {\n\t\tname = name[:len(name)-1]\n\t}\n\treturn strings.HasSuffix(name, \".onion\")\n}\n"
  },
  {
    "path": "dns/transport/fakeip/fakeip.go",
    "content": "package fakeip\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc RegisterTransport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.FakeIPDNSServerOptions](registry, C.DNSTypeFakeIP, NewTransport)\n}\n\nvar _ adapter.FakeIPTransport = (*Transport)(nil)\n\ntype Transport struct {\n\tdns.TransportAdapter\n\tlogger logger.ContextLogger\n\tstore  adapter.FakeIPStore\n}\n\nfunc NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.FakeIPDNSServerOptions) (adapter.DNSTransport, error) {\n\tstore := NewStore(ctx, logger, options.Inet4Range.Build(netip.Prefix{}), options.Inet6Range.Build(netip.Prefix{}))\n\treturn &Transport{\n\t\tTransportAdapter: dns.NewTransportAdapter(C.DNSTypeFakeIP, tag, nil),\n\t\tlogger:           logger,\n\t\tstore:            store,\n\t}, nil\n}\n\nfunc (t *Transport) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn t.store.Start()\n}\n\nfunc (t *Transport) Close() error {\n\treturn t.store.Close()\n}\n\nfunc (t *Transport) Reset() {\n}\n\nfunc (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tquestion := message.Question[0]\n\tif question.Qtype != mDNS.TypeA && question.Qtype != mDNS.TypeAAAA {\n\t\treturn nil, E.New(\"only IP queries are supported by fakeip\")\n\t}\n\taddress, err := t.store.Create(dns.FqdnToDomain(question.Name), question.Qtype == mDNS.TypeAAAA)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dns.FixedResponse(message.Id, question, []netip.Addr{address}, C.DefaultDNSTTL), nil\n}\n\nfunc (t *Transport) Store() adapter.FakeIPStore {\n\treturn t.store\n}\n"
  },
  {
    "path": "dns/transport/fakeip/memory.go",
    "content": "package fakeip\n\nimport (\n\t\"net/netip\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\nvar _ adapter.FakeIPStorage = (*MemoryStorage)(nil)\n\ntype MemoryStorage struct {\n\taddressAccess sync.RWMutex\n\tdomainAccess  sync.RWMutex\n\taddressCache  map[netip.Addr]string\n\tdomainCache4  map[string]netip.Addr\n\tdomainCache6  map[string]netip.Addr\n}\n\nfunc NewMemoryStorage() *MemoryStorage {\n\treturn &MemoryStorage{\n\t\taddressCache: make(map[netip.Addr]string),\n\t\tdomainCache4: make(map[string]netip.Addr),\n\t\tdomainCache6: make(map[string]netip.Addr),\n\t}\n}\n\nfunc (s *MemoryStorage) FakeIPMetadata() *adapter.FakeIPMetadata {\n\treturn nil\n}\n\nfunc (s *MemoryStorage) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error {\n\treturn nil\n}\n\nfunc (s *MemoryStorage) FakeIPSaveMetadataAsync(metadata *adapter.FakeIPMetadata) {\n}\n\nfunc (s *MemoryStorage) FakeIPStore(address netip.Addr, domain string) error {\n\ts.addressAccess.Lock()\n\ts.domainAccess.Lock()\n\tif oldDomain, loaded := s.addressCache[address]; loaded {\n\t\tif address.Is4() {\n\t\t\tdelete(s.domainCache4, oldDomain)\n\t\t} else {\n\t\t\tdelete(s.domainCache6, oldDomain)\n\t\t}\n\t}\n\ts.addressCache[address] = domain\n\tif address.Is4() {\n\t\ts.domainCache4[domain] = address\n\t} else {\n\t\ts.domainCache6[domain] = address\n\t}\n\ts.domainAccess.Unlock()\n\ts.addressAccess.Unlock()\n\treturn nil\n}\n\nfunc (s *MemoryStorage) FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger) {\n\t_ = s.FakeIPStore(address, domain)\n}\n\nfunc (s *MemoryStorage) FakeIPLoad(address netip.Addr) (string, bool) {\n\ts.addressAccess.RLock()\n\tdefer s.addressAccess.RUnlock()\n\tdomain, loaded := s.addressCache[address]\n\treturn domain, loaded\n}\n\nfunc (s *MemoryStorage) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bool) {\n\ts.domainAccess.RLock()\n\tdefer s.domainAccess.RUnlock()\n\tif !isIPv6 {\n\t\taddress, loaded := s.domainCache4[domain]\n\t\treturn address, loaded\n\t} else {\n\t\taddress, loaded := s.domainCache6[domain]\n\t\treturn address, loaded\n\t}\n}\n\nfunc (s *MemoryStorage) FakeIPReset() error {\n\ts.addressAccess.Lock()\n\ts.domainAccess.Lock()\n\ts.addressCache = make(map[netip.Addr]string)\n\ts.domainCache4 = make(map[string]netip.Addr)\n\ts.domainCache6 = make(map[string]netip.Addr)\n\ts.domainAccess.Unlock()\n\ts.addressAccess.Unlock()\n\treturn nil\n}\n"
  },
  {
    "path": "dns/transport/fakeip/store.go",
    "content": "package fakeip\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nvar _ adapter.FakeIPStore = (*Store)(nil)\n\ntype Store struct {\n\tctx        context.Context\n\tlogger     logger.Logger\n\tinet4Range netip.Prefix\n\tinet6Range netip.Prefix\n\tinet4Last  netip.Addr\n\tinet6Last  netip.Addr\n\tstorage    adapter.FakeIPStorage\n\n\taddressAccess sync.Mutex\n\tinet4Current  netip.Addr\n\tinet6Current  netip.Addr\n}\n\nfunc NewStore(ctx context.Context, logger logger.Logger, inet4Range netip.Prefix, inet6Range netip.Prefix) *Store {\n\tstore := &Store{\n\t\tctx:        ctx,\n\t\tlogger:     logger,\n\t\tinet4Range: inet4Range,\n\t\tinet6Range: inet6Range,\n\t}\n\tif inet4Range.IsValid() {\n\t\tstore.inet4Last = broadcastAddress(inet4Range)\n\t}\n\tif inet6Range.IsValid() {\n\t\tstore.inet6Last = broadcastAddress(inet6Range)\n\t}\n\treturn store\n}\n\nfunc broadcastAddress(prefix netip.Prefix) netip.Addr {\n\taddr := prefix.Addr()\n\traw := addr.As16()\n\tbits := prefix.Bits()\n\tif addr.Is4() {\n\t\tbits += 96\n\t}\n\tfor i := bits; i < 128; i++ {\n\t\traw[i/8] |= 1 << (7 - i%8)\n\t}\n\tif addr.Is4() {\n\t\treturn netip.AddrFrom4([4]byte(raw[12:]))\n\t}\n\treturn netip.AddrFrom16(raw)\n}\n\nfunc (s *Store) Start() error {\n\tvar storage adapter.FakeIPStorage\n\tcacheFile := service.FromContext[adapter.CacheFile](s.ctx)\n\tif cacheFile != nil && cacheFile.StoreFakeIP() {\n\t\tstorage = cacheFile\n\t}\n\tif storage == nil {\n\t\tstorage = NewMemoryStorage()\n\t}\n\tmetadata := storage.FakeIPMetadata()\n\tif metadata != nil && metadata.Inet4Range == s.inet4Range && metadata.Inet6Range == s.inet6Range {\n\t\ts.inet4Current = metadata.Inet4Current\n\t\ts.inet6Current = metadata.Inet6Current\n\t} else {\n\t\tif s.inet4Range.IsValid() {\n\t\t\ts.inet4Current = s.inet4Range.Addr().Next()\n\t\t}\n\t\tif s.inet6Range.IsValid() {\n\t\t\ts.inet6Current = s.inet6Range.Addr().Next()\n\t\t}\n\t\t_ = storage.FakeIPReset()\n\t}\n\ts.storage = storage\n\treturn nil\n}\n\nfunc (s *Store) Contains(address netip.Addr) bool {\n\treturn s.inet4Range.Contains(address) || s.inet6Range.Contains(address)\n}\n\nfunc (s *Store) Close() error {\n\tif s.storage == nil {\n\t\treturn nil\n\t}\n\ts.addressAccess.Lock()\n\tmetadata := &adapter.FakeIPMetadata{\n\t\tInet4Range:   s.inet4Range,\n\t\tInet6Range:   s.inet6Range,\n\t\tInet4Current: s.inet4Current,\n\t\tInet6Current: s.inet6Current,\n\t}\n\ts.addressAccess.Unlock()\n\treturn s.storage.FakeIPSaveMetadata(metadata)\n}\n\nfunc (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) {\n\tif address, loaded := s.storage.FakeIPLoadDomain(domain, isIPv6); loaded {\n\t\treturn address, nil\n\t}\n\n\ts.addressAccess.Lock()\n\tdefer s.addressAccess.Unlock()\n\n\t// Double-check after acquiring lock\n\tif address, loaded := s.storage.FakeIPLoadDomain(domain, isIPv6); loaded {\n\t\treturn address, nil\n\t}\n\n\tvar address netip.Addr\n\tif !isIPv6 {\n\t\tif !s.inet4Current.IsValid() {\n\t\t\treturn netip.Addr{}, E.New(\"missing IPv4 fakeip address range\")\n\t\t}\n\t\tnextAddress := s.inet4Current.Next()\n\t\tif nextAddress == s.inet4Last || !s.inet4Range.Contains(nextAddress) {\n\t\t\tnextAddress = s.inet4Range.Addr().Next().Next()\n\t\t}\n\t\ts.inet4Current = nextAddress\n\t\taddress = nextAddress\n\t} else {\n\t\tif !s.inet6Current.IsValid() {\n\t\t\treturn netip.Addr{}, E.New(\"missing IPv6 fakeip address range\")\n\t\t}\n\t\tnextAddress := s.inet6Current.Next()\n\t\tif nextAddress == s.inet6Last || !s.inet6Range.Contains(nextAddress) {\n\t\t\tnextAddress = s.inet6Range.Addr().Next().Next()\n\t\t}\n\t\ts.inet6Current = nextAddress\n\t\taddress = nextAddress\n\t}\n\terr := s.storage.FakeIPStore(address, domain)\n\tif err != nil {\n\t\ts.logger.Warn(\"save FakeIP cache: \", err)\n\t}\n\ts.storage.FakeIPSaveMetadataAsync(&adapter.FakeIPMetadata{\n\t\tInet4Range:   s.inet4Range,\n\t\tInet6Range:   s.inet6Range,\n\t\tInet4Current: s.inet4Current,\n\t\tInet6Current: s.inet6Current,\n\t})\n\treturn address, nil\n}\n\nfunc (s *Store) Lookup(address netip.Addr) (string, bool) {\n\treturn s.storage.FakeIPLoad(address)\n}\n\nfunc (s *Store) Reset() error {\n\treturn s.storage.FakeIPReset()\n}\n"
  },
  {
    "path": "dns/transport/hosts/hosts.go",
    "content": "package hosts\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/service/filemanager\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc RegisterTransport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.HostsDNSServerOptions](registry, C.DNSTypeHosts, NewTransport)\n}\n\nvar _ adapter.DNSTransport = (*Transport)(nil)\n\ntype Transport struct {\n\tdns.TransportAdapter\n\tfiles      []*File\n\tpredefined map[string][]netip.Addr\n}\n\nfunc NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.HostsDNSServerOptions) (adapter.DNSTransport, error) {\n\tvar (\n\t\tfiles      []*File\n\t\tpredefined = make(map[string][]netip.Addr)\n\t)\n\tif len(options.Path) == 0 {\n\t\tfiles = append(files, NewFile(DefaultPath))\n\t} else {\n\t\tfor _, path := range options.Path {\n\t\t\tfiles = append(files, NewFile(filemanager.BasePath(ctx, os.ExpandEnv(path))))\n\t\t}\n\t}\n\tif options.Predefined != nil {\n\t\tfor _, entry := range options.Predefined.Entries() {\n\t\t\tpredefined[mDNS.CanonicalName(entry.Key)] = entry.Value\n\t\t}\n\t}\n\treturn &Transport{\n\t\tTransportAdapter: dns.NewTransportAdapter(C.DNSTypeHosts, tag, nil),\n\t\tfiles:            files,\n\t\tpredefined:       predefined,\n\t}, nil\n}\n\nfunc (t *Transport) Start(stage adapter.StartStage) error {\n\treturn nil\n}\n\nfunc (t *Transport) Close() error {\n\treturn nil\n}\n\nfunc (t *Transport) Reset() {\n}\n\nfunc (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tquestion := message.Question[0]\n\tdomain := mDNS.CanonicalName(question.Name)\n\tif question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {\n\t\tif addresses, ok := t.predefined[domain]; ok {\n\t\t\treturn dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil\n\t\t}\n\t\tfor _, file := range t.files {\n\t\t\taddresses := file.Lookup(domain)\n\t\t\tif len(addresses) > 0 {\n\t\t\t\treturn dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil\n\t\t\t}\n\t\t}\n\t}\n\treturn &mDNS.Msg{\n\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\tId:       message.Id,\n\t\t\tRcode:    mDNS.RcodeNameError,\n\t\t\tResponse: true,\n\t\t},\n\t\tQuestion: []mDNS.Question{question},\n\t}, nil\n}\n"
  },
  {
    "path": "dns/transport/hosts/hosts_file.go",
    "content": "package hosts\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/miekg/dns\"\n)\n\nconst cacheMaxAge = 5 * time.Second\n\ntype File struct {\n\tpath    string\n\taccess  sync.Mutex\n\tbyName  map[string][]netip.Addr\n\texpire  time.Time\n\tmodTime time.Time\n\tsize    int64\n}\n\nfunc NewFile(path string) *File {\n\treturn &File{\n\t\tpath: path,\n\t}\n}\n\nfunc (f *File) Lookup(name string) []netip.Addr {\n\tf.access.Lock()\n\tdefer f.access.Unlock()\n\tf.update()\n\treturn f.byName[dns.CanonicalName(name)]\n}\n\nfunc (f *File) update() {\n\tnow := time.Now()\n\tif now.Before(f.expire) && len(f.byName) > 0 {\n\t\treturn\n\t}\n\tstat, err := os.Stat(f.path)\n\tif err != nil {\n\t\treturn\n\t}\n\tif f.modTime.Equal(stat.ModTime()) && f.size == stat.Size() {\n\t\tf.expire = now.Add(cacheMaxAge)\n\t\treturn\n\t}\n\tbyName := make(map[string][]netip.Addr)\n\tfile, err := os.Open(f.path)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer file.Close()\n\treader := bufio.NewReader(file)\n\tvar (\n\t\tprefix   []byte\n\t\tline     []byte\n\t\tisPrefix bool\n\t)\n\tfor {\n\t\tline, isPrefix, err = reader.ReadLine()\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif isPrefix {\n\t\t\tprefix = append(prefix, line...)\n\t\t\tcontinue\n\t\t} else if len(prefix) > 0 {\n\t\t\tline = append(prefix, line...)\n\t\t\tprefix = nil\n\t\t}\n\t\tcommentIndex := strings.IndexRune(string(line), '#')\n\t\tif commentIndex != -1 {\n\t\t\tline = line[:commentIndex]\n\t\t}\n\t\tfields := strings.Fields(string(line))\n\t\tif len(fields) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tvar addr netip.Addr\n\t\taddr, err = netip.ParseAddr(fields[0])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor index := 1; index < len(fields); index++ {\n\t\t\tcanonicalName := dns.CanonicalName(fields[index])\n\t\t\tbyName[canonicalName] = append(byName[canonicalName], addr)\n\t\t}\n\t}\n\tf.expire = now.Add(cacheMaxAge)\n\tf.modTime = stat.ModTime()\n\tf.size = stat.Size()\n\tf.byName = byName\n}\n"
  },
  {
    "path": "dns/transport/hosts/hosts_test.go",
    "content": "package hosts_test\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/dns/transport/hosts\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHosts(t *testing.T) {\n\tt.Parallel()\n\trequire.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile(\"testdata/hosts\").Lookup(\"localhost\"))\n\trequire.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup(\"localhost\"))\n}\n"
  },
  {
    "path": "dns/transport/hosts/hosts_unix.go",
    "content": "//go:build !windows\n\npackage hosts\n\nvar DefaultPath = \"/etc/hosts\"\n"
  },
  {
    "path": "dns/transport/hosts/hosts_windows.go",
    "content": "package hosts\n\nimport (\n\t\"path/filepath\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\nvar DefaultPath string\n\nfunc init() {\n\tsystemDirectory, err := windows.GetSystemDirectory()\n\tif err != nil {\n\t\tsystemDirectory = \"C:\\\\Windows\\\\System32\"\n\t}\n\tDefaultPath = filepath.Join(systemDirectory, \"Drivers/etc/hosts\")\n}\n"
  },
  {
    "path": "dns/transport/hosts/testdata/hosts",
    "content": "127.0.0.1       localhost\n::1             localhost\n"
  },
  {
    "path": "dns/transport/https.go",
    "content": "package transport\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\tsHTTP \"github.com/sagernet/sing/protocol/http\"\n\n\tmDNS \"github.com/miekg/dns\"\n\t\"golang.org/x/net/http2\"\n)\n\nconst MimeType = \"application/dns-message\"\n\nvar _ adapter.DNSTransport = (*HTTPSTransport)(nil)\n\nfunc RegisterHTTPS(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.RemoteHTTPSDNSServerOptions](registry, C.DNSTypeHTTPS, NewHTTPS)\n}\n\ntype HTTPSTransport struct {\n\tdns.TransportAdapter\n\tlogger           logger.ContextLogger\n\tdialer           N.Dialer\n\tdestination      *url.URL\n\theaders          http.Header\n\ttransportAccess  sync.Mutex\n\ttransport        *HTTPSTransportWrapper\n\ttransportResetAt time.Time\n}\n\nfunc NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {\n\ttransportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttlsOptions := common.PtrValueOrDefault(options.TLS)\n\ttlsOptions.Enabled = true\n\ttlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(tlsConfig.NextProtos()) == 0 {\n\t\ttlsConfig.SetNextProtos([]string{http2.NextProtoTLS, \"http/1.1\"})\n\t}\n\theaders := options.Headers.Build()\n\thost := headers.Get(\"Host\")\n\tif host != \"\" {\n\t\theaders.Del(\"Host\")\n\t} else {\n\t\tif tlsConfig.ServerName() != \"\" {\n\t\t\thost = tlsConfig.ServerName()\n\t\t} else {\n\t\t\thost = options.Server\n\t\t}\n\t}\n\tdestinationURL := url.URL{\n\t\tScheme: \"https\",\n\t\tHost:   host,\n\t}\n\tif destinationURL.Host == \"\" {\n\t\tdestinationURL.Host = options.Server\n\t}\n\tif options.ServerPort != 0 && options.ServerPort != 443 {\n\t\tdestinationURL.Host = net.JoinHostPort(destinationURL.Host, strconv.Itoa(int(options.ServerPort)))\n\t}\n\tpath := options.Path\n\tif path == \"\" {\n\t\tpath = \"/dns-query\"\n\t}\n\terr = sHTTP.URLSetPath(&destinationURL, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tserverAddr := options.DNSServerAddressOptions.Build()\n\tif serverAddr.Port == 0 {\n\t\tserverAddr.Port = 443\n\t}\n\tif !serverAddr.IsValid() {\n\t\treturn nil, E.New(\"invalid server address: \", serverAddr)\n\t}\n\treturn NewHTTPSRaw(\n\t\tdns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),\n\t\tlogger,\n\t\ttransportDialer,\n\t\t&destinationURL,\n\t\theaders,\n\t\tserverAddr,\n\t\ttlsConfig,\n\t), nil\n}\n\nfunc NewHTTPSRaw(\n\tadapter dns.TransportAdapter,\n\tlogger log.ContextLogger,\n\tdialer N.Dialer,\n\tdestination *url.URL,\n\theaders http.Header,\n\tserverAddr M.Socksaddr,\n\ttlsConfig tls.Config,\n) *HTTPSTransport {\n\treturn &HTTPSTransport{\n\t\tTransportAdapter: adapter,\n\t\tlogger:           logger,\n\t\tdialer:           dialer,\n\t\tdestination:      destination,\n\t\theaders:          headers,\n\t\ttransport:        NewHTTPSTransportWrapper(tls.NewDialer(dialer, tlsConfig), serverAddr),\n\t}\n}\n\nfunc (t *HTTPSTransport) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn dialer.InitializeDetour(t.dialer)\n}\n\nfunc (t *HTTPSTransport) Close() error {\n\tt.transportAccess.Lock()\n\tdefer t.transportAccess.Unlock()\n\tt.transport.CloseIdleConnections()\n\tt.transport = t.transport.Clone()\n\treturn nil\n}\n\nfunc (t *HTTPSTransport) Reset() {\n\tt.transportAccess.Lock()\n\tdefer t.transportAccess.Unlock()\n\tt.transport.CloseIdleConnections()\n\tt.transport = t.transport.Clone()\n}\n\nfunc (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tstartAt := time.Now()\n\tresponse, err := t.exchange(ctx, message)\n\tif err != nil {\n\t\tif errors.Is(err, context.DeadlineExceeded) {\n\t\t\tt.transportAccess.Lock()\n\t\t\tdefer t.transportAccess.Unlock()\n\t\t\tif t.transportResetAt.After(startAt) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tt.transport.CloseIdleConnections()\n\t\t\tt.transport = t.transport.Clone()\n\t\t\tt.transportResetAt = time.Now()\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn response, nil\n}\n\nfunc (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\texMessage := *message\n\texMessage.Id = 0\n\texMessage.Compress = true\n\trequestBuffer := buf.NewSize(1 + message.Len())\n\trawMessage, err := exMessage.PackBuffer(requestBuffer.FreeBytes())\n\tif err != nil {\n\t\trequestBuffer.Release()\n\t\treturn nil, err\n\t}\n\trequest, err := http.NewRequestWithContext(ctx, http.MethodPost, t.destination.String(), bytes.NewReader(rawMessage))\n\tif err != nil {\n\t\trequestBuffer.Release()\n\t\treturn nil, err\n\t}\n\trequest.Header = t.headers.Clone()\n\trequest.Header.Set(\"Content-Type\", MimeType)\n\trequest.Header.Set(\"Accept\", MimeType)\n\tt.transportAccess.Lock()\n\tcurrentTransport := t.transport\n\tt.transportAccess.Unlock()\n\tresponse, err := currentTransport.RoundTrip(request)\n\trequestBuffer.Release()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer response.Body.Close()\n\tif response.StatusCode != http.StatusOK {\n\t\treturn nil, E.New(\"unexpected status: \", response.Status)\n\t}\n\tvar responseMessage mDNS.Msg\n\tif response.ContentLength > 0 {\n\t\tresponseBuffer := buf.NewSize(int(response.ContentLength))\n\t\tdefer responseBuffer.Release()\n\t\t_, err = responseBuffer.ReadFullFrom(response.Body, int(response.ContentLength))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = responseMessage.Unpack(responseBuffer.Bytes())\n\t} else {\n\t\trawMessage, err = io.ReadAll(response.Body)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = responseMessage.Unpack(rawMessage)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &responseMessage, nil\n}\n"
  },
  {
    "path": "dns/transport/https_transport.go",
    "content": "package transport\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync/atomic\"\n\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\n\t\"golang.org/x/net/http2\"\n)\n\nvar errFallback = E.New(\"fallback to HTTP/1.1\")\n\ntype HTTPSTransportWrapper struct {\n\thttp2Transport *http2.Transport\n\thttpTransport  *http.Transport\n\tfallback       *atomic.Bool\n}\n\nfunc NewHTTPSTransportWrapper(dialer tls.Dialer, serverAddr M.Socksaddr) *HTTPSTransportWrapper {\n\tvar fallback atomic.Bool\n\treturn &HTTPSTransportWrapper{\n\t\thttp2Transport: &http2.Transport{\n\t\t\tDialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) {\n\t\t\t\ttlsConn, err := dialer.DialTLSContext(ctx, serverAddr)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tstate := tlsConn.ConnectionState()\n\t\t\t\tif state.NegotiatedProtocol == http2.NextProtoTLS {\n\t\t\t\t\treturn tlsConn, nil\n\t\t\t\t}\n\t\t\t\ttlsConn.Close()\n\t\t\t\tfallback.Store(true)\n\t\t\t\treturn nil, errFallback\n\t\t\t},\n\t\t},\n\t\thttpTransport: &http.Transport{\n\t\t\tDialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) {\n\t\t\t\treturn dialer.DialTLSContext(ctx, serverAddr)\n\t\t\t},\n\t\t},\n\t\tfallback: &fallback,\n\t}\n}\n\nfunc (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response, error) {\n\tif h.fallback.Load() {\n\t\treturn h.httpTransport.RoundTrip(request)\n\t} else {\n\t\tresponse, err := h.http2Transport.RoundTrip(request)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, errFallback) {\n\t\t\t\treturn h.httpTransport.RoundTrip(request)\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\treturn response, nil\n\t}\n}\n\nfunc (h *HTTPSTransportWrapper) CloseIdleConnections() {\n\th.http2Transport.CloseIdleConnections()\n\th.httpTransport.CloseIdleConnections()\n}\n\nfunc (h *HTTPSTransportWrapper) Clone() *HTTPSTransportWrapper {\n\treturn &HTTPSTransportWrapper{\n\t\thttpTransport: h.httpTransport,\n\t\thttp2Transport: &http2.Transport{\n\t\t\tDialTLSContext: h.http2Transport.DialTLSContext,\n\t\t},\n\t\tfallback: h.fallback,\n\t}\n}\n"
  },
  {
    "path": "dns/transport/local/local.go",
    "content": "//go:build !darwin\n\npackage local\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport/hosts\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc RegisterTransport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport)\n}\n\nvar _ adapter.DNSTransport = (*Transport)(nil)\n\ntype Transport struct {\n\tdns.TransportAdapter\n\tctx      context.Context\n\tlogger   logger.ContextLogger\n\thosts    *hosts.File\n\tdialer   N.Dialer\n\tpreferGo bool\n\tresolved ResolvedResolver\n}\n\nfunc NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {\n\ttransportDialer, err := dns.NewLocalDialer(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Transport{\n\t\tTransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),\n\t\tctx:              ctx,\n\t\tlogger:           logger,\n\t\thosts:            hosts.NewFile(hosts.DefaultPath),\n\t\tdialer:           transportDialer,\n\t\tpreferGo:         options.PreferGo,\n\t}, nil\n}\n\nfunc (t *Transport) Start(stage adapter.StartStage) error {\n\tswitch stage {\n\tcase adapter.StartStateInitialize:\n\t\tif !t.preferGo {\n\t\t\tif isSystemdResolvedManaged() {\n\t\t\t\tresolvedResolver, err := NewResolvedResolver(t.ctx, t.logger)\n\t\t\t\tif err == nil {\n\t\t\t\t\terr = resolvedResolver.Start()\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tt.resolved = resolvedResolver\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.logger.Warn(E.Cause(err, \"initialize resolved resolver\"))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (t *Transport) Close() error {\n\tif t.resolved != nil {\n\t\treturn t.resolved.Close()\n\t}\n\treturn nil\n}\n\nfunc (t *Transport) Reset() {\n}\n\nfunc (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tif t.resolved != nil {\n\t\treturn t.resolved.Exchange(ctx, message)\n\t}\n\tquestion := message.Question[0]\n\tif question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {\n\t\taddresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))\n\t\tif len(addresses) > 0 {\n\t\t\treturn dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil\n\t\t}\n\t}\n\treturn t.exchange(ctx, message, question.Name)\n}\n"
  },
  {
    "path": "dns/transport/local/local_darwin.go",
    "content": "//go:build darwin\n\npackage local\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport/hosts\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc RegisterTransport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport)\n}\n\nvar _ adapter.DNSTransport = (*Transport)(nil)\n\ntype Transport struct {\n\tdns.TransportAdapter\n\tctx           context.Context\n\tlogger        logger.ContextLogger\n\thosts         *hosts.File\n\tdialer        N.Dialer\n\tpreferGo      bool\n\tfallback      bool\n\tdhcpTransport dhcpTransport\n\tresolver      net.Resolver\n}\n\ntype dhcpTransport interface {\n\tadapter.DNSTransport\n\tFetch() []M.Socksaddr\n\tExchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error)\n}\n\nfunc NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {\n\ttransportDialer, err := dns.NewLocalDialer(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttransportAdapter := dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options)\n\treturn &Transport{\n\t\tTransportAdapter: transportAdapter,\n\t\tctx:              ctx,\n\t\tlogger:           logger,\n\t\thosts:            hosts.NewFile(hosts.DefaultPath),\n\t\tdialer:           transportDialer,\n\t\tpreferGo:         options.PreferGo,\n\t}, nil\n}\n\nfunc (t *Transport) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tinboundManager := service.FromContext[adapter.InboundManager](t.ctx)\n\tfor _, inbound := range inboundManager.Inbounds() {\n\t\tif inbound.Type() == C.TypeTun {\n\t\t\tt.fallback = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif t.fallback {\n\t\tt.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger)\n\t\tif t.dhcpTransport != nil {\n\t\t\terr := t.dhcpTransport.Start(stage)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (t *Transport) Close() error {\n\treturn common.Close(\n\t\tt.dhcpTransport,\n\t)\n}\n\nfunc (t *Transport) Reset() {\n\tif t.dhcpTransport != nil {\n\t\tt.dhcpTransport.Reset()\n\t}\n}\n\nfunc (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tquestion := message.Question[0]\n\tif question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {\n\t\taddresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))\n\t\tif len(addresses) > 0 {\n\t\t\treturn dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil\n\t\t}\n\t}\n\tif !t.fallback {\n\t\treturn t.exchange(ctx, message, question.Name)\n\t}\n\tif t.dhcpTransport != nil {\n\t\tdhcpTransports := t.dhcpTransport.Fetch()\n\t\tif len(dhcpTransports) > 0 {\n\t\t\treturn t.dhcpTransport.Exchange0(ctx, message, dhcpTransports)\n\t\t}\n\t}\n\tif t.preferGo {\n\t\t// Assuming the user knows what they are doing, we still execute the query which will fail.\n\t\treturn t.exchange(ctx, message, question.Name)\n\t}\n\tif question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {\n\t\tvar network string\n\t\tif question.Qtype == mDNS.TypeA {\n\t\t\tnetwork = \"ip4\"\n\t\t} else {\n\t\t\tnetwork = \"ip6\"\n\t\t}\n\t\taddresses, err := t.resolver.LookupNetIP(ctx, network, question.Name)\n\t\tif err != nil {\n\t\t\tvar dnsError *net.DNSError\n\t\t\tif errors.As(err, &dnsError) && dnsError.IsNotFound {\n\t\t\t\treturn nil, dns.RcodeRefused\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\treturn dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil\n\t}\n\treturn nil, E.New(\"only A and AAAA queries are supported on Apple platforms when using TUN and DHCP unavailable.\")\n}\n"
  },
  {
    "path": "dns/transport/local/local_darwin_dhcp.go",
    "content": "//go:build darwin && with_dhcp\n\npackage local\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport/dhcp\"\n\t\"github.com/sagernet/sing-box/log\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc newDHCPTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) dhcpTransport {\n\treturn dhcp.NewRawTransport(transportAdapter, ctx, dialer, logger)\n}\n"
  },
  {
    "path": "dns/transport/local/local_darwin_nodhcp.go",
    "content": "//go:build darwin && !with_dhcp\n\npackage local\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc newDHCPTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) dhcpTransport {\n\treturn nil\n}\n"
  },
  {
    "path": "dns/transport/local/local_resolved.go",
    "content": "package local\n\nimport (\n\t\"context\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\ntype ResolvedResolver interface {\n\tStart() error\n\tClose() error\n\tExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error)\n}\n"
  },
  {
    "path": "dns/transport/local/local_resolved_linux.go",
    "content": "package local\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\tdnsTransport \"github.com/sagernet/sing-box/dns/transport\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/service/resolved\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"github.com/godbus/dbus/v5\"\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc isSystemdResolvedManaged() bool {\n\tresolvContent, err := os.Open(\"/etc/resolv.conf\")\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer resolvContent.Close()\n\tscanner := bufio.NewScanner(resolvContent)\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif line == \"\" || line[0] != '#' {\n\t\t\treturn false\n\t\t}\n\t\tif strings.Contains(line, \"systemd-resolved\") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\ntype DBusResolvedResolver struct {\n\tctx               context.Context\n\tlogger            logger.ContextLogger\n\tinterfaceMonitor  tun.DefaultInterfaceMonitor\n\tinterfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]\n\tsystemBus         *dbus.Conn\n\tsavedServerSet    atomic.Pointer[resolvedServerSet]\n\tcloseOnce         sync.Once\n}\n\ntype resolvedServerSet struct {\n\tservers []resolvedServer\n}\n\ntype resolvedServer struct {\n\tprimaryTransport  adapter.DNSTransport\n\tfallbackTransport adapter.DNSTransport\n}\n\ntype resolvedServerSpecification struct {\n\taddress    netip.Addr\n\tport       uint16\n\tserverName string\n}\n\nfunc NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {\n\tinterfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()\n\tif interfaceMonitor == nil {\n\t\treturn nil, os.ErrInvalid\n\t}\n\tsystemBus, err := dbus.SystemBus()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &DBusResolvedResolver{\n\t\tctx:              ctx,\n\t\tlogger:           logger,\n\t\tinterfaceMonitor: interfaceMonitor,\n\t\tsystemBus:        systemBus,\n\t}, nil\n}\n\nfunc (t *DBusResolvedResolver) Start() error {\n\tt.updateStatus()\n\tt.interfaceCallback = t.interfaceMonitor.RegisterCallback(t.updateDefaultInterface)\n\terr := t.systemBus.BusObject().AddMatchSignal(\n\t\t\"org.freedesktop.DBus\",\n\t\t\"NameOwnerChanged\",\n\t\tdbus.WithMatchSender(\"org.freedesktop.DBus\"),\n\t\tdbus.WithMatchArg(0, \"org.freedesktop.resolve1\"),\n\t).Err\n\tif err != nil {\n\t\treturn E.Cause(err, \"configure resolved restart listener\")\n\t}\n\terr = t.systemBus.BusObject().AddMatchSignal(\n\t\t\"org.freedesktop.DBus.Properties\",\n\t\t\"PropertiesChanged\",\n\t\tdbus.WithMatchSender(\"org.freedesktop.resolve1\"),\n\t\tdbus.WithMatchArg(0, \"org.freedesktop.resolve1.Manager\"),\n\t).Err\n\tif err != nil {\n\t\treturn E.Cause(err, \"configure resolved properties listener\")\n\t}\n\tgo t.loopUpdateStatus()\n\treturn nil\n}\n\nfunc (t *DBusResolvedResolver) Close() error {\n\tvar closeErr error\n\tt.closeOnce.Do(func() {\n\t\tserverSet := t.savedServerSet.Swap(nil)\n\t\tif serverSet != nil {\n\t\t\tcloseErr = serverSet.Close()\n\t\t}\n\t\tif t.interfaceCallback != nil {\n\t\t\tt.interfaceMonitor.UnregisterCallback(t.interfaceCallback)\n\t\t}\n\t\tif t.systemBus != nil {\n\t\t\t_ = t.systemBus.Close()\n\t\t}\n\t})\n\treturn closeErr\n}\n\nfunc (t *DBusResolvedResolver) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tserverSet := t.savedServerSet.Load()\n\tif serverSet == nil {\n\t\tvar err error\n\t\tserverSet, err = t.checkResolved(context.Background())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpreviousServerSet := t.savedServerSet.Swap(serverSet)\n\t\tif previousServerSet != nil {\n\t\t\t_ = previousServerSet.Close()\n\t\t}\n\t}\n\tresponse, err := t.exchangeServerSet(ctx, message, serverSet)\n\tif err == nil {\n\t\treturn response, nil\n\t}\n\tt.updateStatus()\n\trefreshedServerSet := t.savedServerSet.Load()\n\tif refreshedServerSet == nil || refreshedServerSet == serverSet {\n\t\treturn nil, err\n\t}\n\treturn t.exchangeServerSet(ctx, message, refreshedServerSet)\n}\n\nfunc (t *DBusResolvedResolver) loopUpdateStatus() {\n\tsignalChan := make(chan *dbus.Signal, 1)\n\tt.systemBus.Signal(signalChan)\n\tfor signal := range signalChan {\n\t\tswitch signal.Name {\n\t\tcase \"org.freedesktop.DBus.NameOwnerChanged\":\n\t\t\tif len(signal.Body) != 3 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tnewOwner, loaded := signal.Body[2].(string)\n\t\t\tif !loaded || newOwner == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.updateStatus()\n\t\tcase \"org.freedesktop.DBus.Properties.PropertiesChanged\":\n\t\t\tif !shouldUpdateResolvedServerSet(signal) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.updateStatus()\n\t\t}\n\t}\n}\n\nfunc (t *DBusResolvedResolver) updateStatus() {\n\tserverSet, err := t.checkResolved(context.Background())\n\toldServerSet := t.savedServerSet.Swap(serverSet)\n\tif oldServerSet != nil {\n\t\t_ = oldServerSet.Close()\n\t}\n\tif err != nil {\n\t\tvar dbusErr dbus.Error\n\t\tif !errors.As(err, &dbusErr) || dbusErr.Name != \"org.freedesktop.DBus.Error.NameHasNoOwner\" {\n\t\t\tt.logger.Debug(E.Cause(err, \"systemd-resolved service unavailable\"))\n\t\t}\n\t\tif oldServerSet != nil {\n\t\t\tt.logger.Debug(\"systemd-resolved service is gone\")\n\t\t}\n\t\treturn\n\t} else if oldServerSet == nil {\n\t\tt.logger.Debug(\"using systemd-resolved service as resolver\")\n\t}\n}\n\nfunc (t *DBusResolvedResolver) exchangeServerSet(ctx context.Context, message *mDNS.Msg, serverSet *resolvedServerSet) (*mDNS.Msg, error) {\n\tif serverSet == nil || len(serverSet.servers) == 0 {\n\t\treturn nil, E.New(\"link has no DNS servers configured\")\n\t}\n\tvar lastError error\n\tfor _, server := range serverSet.servers {\n\t\tresponse, err := server.primaryTransport.Exchange(ctx, message)\n\t\tif err != nil && server.fallbackTransport != nil {\n\t\t\tresponse, err = server.fallbackTransport.Exchange(ctx, message)\n\t\t}\n\t\tif err != nil {\n\t\t\tlastError = err\n\t\t\tcontinue\n\t\t}\n\t\treturn response, nil\n\t}\n\treturn nil, lastError\n}\n\nfunc (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*resolvedServerSet, error) {\n\tdbusObject := t.systemBus.Object(\"org.freedesktop.resolve1\", \"/org/freedesktop/resolve1\")\n\terr := dbusObject.Call(\"org.freedesktop.DBus.Peer.Ping\", 0).Err\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefaultInterface := t.interfaceMonitor.DefaultInterface()\n\tif defaultInterface == nil {\n\t\treturn nil, E.New(\"missing default interface\")\n\t}\n\tcall := dbusObject.(*dbus.Object).CallWithContext(\n\t\tctx,\n\t\t\"org.freedesktop.resolve1.Manager.GetLink\",\n\t\t0,\n\t\tint32(defaultInterface.Index),\n\t)\n\tif call.Err != nil {\n\t\treturn nil, call.Err\n\t}\n\tvar linkPath dbus.ObjectPath\n\terr = call.Store(&linkPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlinkObject := t.systemBus.Object(\"org.freedesktop.resolve1\", linkPath)\n\tif linkObject == nil {\n\t\treturn nil, E.New(\"missing link object for default interface\")\n\t}\n\tdnsOverTLSMode, err := loadResolvedLinkDNSOverTLS(linkObject)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlinkDNSEx, err := loadResolvedLinkDNSEx(linkObject)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlinkDNS, err := loadResolvedLinkDNS(linkObject)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(linkDNSEx) == 0 && len(linkDNS) == 0 {\n\t\tfor _, inbound := range service.FromContext[adapter.InboundManager](t.ctx).Inbounds() {\n\t\t\tif inbound.Type() == C.TypeTun {\n\t\t\t\treturn nil, E.New(\"No appropriate name servers or networks for name found\")\n\t\t\t}\n\t\t}\n\t\treturn nil, E.New(\"link has no DNS servers configured\")\n\t}\n\tserverDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{\n\t\tBindInterface:      defaultInterface.Name,\n\t\tUDPFragmentDefault: true,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar serverSpecifications []resolvedServerSpecification\n\tif len(linkDNSEx) > 0 {\n\t\tfor _, entry := range linkDNSEx {\n\t\t\tserverSpecification, loaded := buildResolvedServerSpecification(defaultInterface.Name, entry.Address, entry.Port, entry.Name)\n\t\t\tif !loaded {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tserverSpecifications = append(serverSpecifications, serverSpecification)\n\t\t}\n\t} else {\n\t\tfor _, entry := range linkDNS {\n\t\t\tserverSpecification, loaded := buildResolvedServerSpecification(defaultInterface.Name, entry.Address, 0, \"\")\n\t\t\tif !loaded {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tserverSpecifications = append(serverSpecifications, serverSpecification)\n\t\t}\n\t}\n\tif len(serverSpecifications) == 0 {\n\t\treturn nil, E.New(\"no valid DNS servers on link\")\n\t}\n\tserverSet := &resolvedServerSet{\n\t\tservers: make([]resolvedServer, 0, len(serverSpecifications)),\n\t}\n\tfor _, serverSpecification := range serverSpecifications {\n\t\tserver, createErr := t.createResolvedServer(serverDialer, dnsOverTLSMode, serverSpecification)\n\t\tif createErr != nil {\n\t\t\t_ = serverSet.Close()\n\t\t\treturn nil, createErr\n\t\t}\n\t\tserverSet.servers = append(serverSet.servers, server)\n\t}\n\treturn serverSet, nil\n}\n\nfunc (t *DBusResolvedResolver) createResolvedServer(serverDialer N.Dialer, dnsOverTLSMode string, serverSpecification resolvedServerSpecification) (resolvedServer, error) {\n\tif dnsOverTLSMode == \"yes\" {\n\t\tprimaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, true)\n\t\tif err != nil {\n\t\t\treturn resolvedServer{}, err\n\t\t}\n\t\treturn resolvedServer{\n\t\t\tprimaryTransport: primaryTransport,\n\t\t}, nil\n\t}\n\tif dnsOverTLSMode == \"opportunistic\" {\n\t\tprimaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, true)\n\t\tif err != nil {\n\t\t\treturn resolvedServer{}, err\n\t\t}\n\t\tfallbackTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, false)\n\t\tif err != nil {\n\t\t\t_ = primaryTransport.Close()\n\t\t\treturn resolvedServer{}, err\n\t\t}\n\t\treturn resolvedServer{\n\t\t\tprimaryTransport:  primaryTransport,\n\t\t\tfallbackTransport: fallbackTransport,\n\t\t}, nil\n\t}\n\tprimaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, false)\n\tif err != nil {\n\t\treturn resolvedServer{}, err\n\t}\n\treturn resolvedServer{\n\t\tprimaryTransport: primaryTransport,\n\t}, nil\n}\n\nfunc (t *DBusResolvedResolver) createResolvedTransport(serverDialer N.Dialer, serverSpecification resolvedServerSpecification, useTLS bool) (adapter.DNSTransport, error) {\n\tserverAddress := M.SocksaddrFrom(serverSpecification.address, resolvedServerPort(serverSpecification.port, useTLS))\n\tif useTLS {\n\t\ttlsAddress := serverSpecification.address\n\t\tif tlsAddress.Zone() != \"\" {\n\t\t\ttlsAddress = tlsAddress.WithZone(\"\")\n\t\t}\n\t\tserverName := serverSpecification.serverName\n\t\tif serverName == \"\" {\n\t\t\tserverName = tlsAddress.String()\n\t\t}\n\t\ttlsConfig, err := tls.NewClient(t.ctx, t.logger, tlsAddress.String(), option.OutboundTLSOptions{\n\t\t\tEnabled:    true,\n\t\t\tServerName: serverName,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tserverTransport := dnsTransport.NewTLSRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeTLS, \"\", nil), serverDialer, serverAddress, tlsConfig)\n\t\terr = serverTransport.Start(adapter.StartStateStart)\n\t\tif err != nil {\n\t\t\t_ = serverTransport.Close()\n\t\t\treturn nil, err\n\t\t}\n\t\treturn serverTransport, nil\n\t}\n\tserverTransport := dnsTransport.NewUDPRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeUDP, \"\", nil), serverDialer, serverAddress)\n\terr := serverTransport.Start(adapter.StartStateStart)\n\tif err != nil {\n\t\t_ = serverTransport.Close()\n\t\treturn nil, err\n\t}\n\treturn serverTransport, nil\n}\n\nfunc (s *resolvedServerSet) Close() error {\n\tvar errors []error\n\tfor _, server := range s.servers {\n\t\terrors = append(errors, server.primaryTransport.Close())\n\t\tif server.fallbackTransport != nil {\n\t\t\terrors = append(errors, server.fallbackTransport.Close())\n\t\t}\n\t}\n\treturn E.Errors(errors...)\n}\n\nfunc buildResolvedServerSpecification(interfaceName string, rawAddress []byte, port uint16, serverName string) (resolvedServerSpecification, bool) {\n\taddress, loaded := netip.AddrFromSlice(rawAddress)\n\tif !loaded {\n\t\treturn resolvedServerSpecification{}, false\n\t}\n\tif address.Is6() && address.IsLinkLocalUnicast() && address.Zone() == \"\" {\n\t\taddress = address.WithZone(interfaceName)\n\t}\n\treturn resolvedServerSpecification{\n\t\taddress:    address,\n\t\tport:       port,\n\t\tserverName: serverName,\n\t}, true\n}\n\nfunc resolvedServerPort(port uint16, useTLS bool) uint16 {\n\tif port > 0 {\n\t\treturn port\n\t}\n\tif useTLS {\n\t\treturn 853\n\t}\n\treturn 53\n}\n\nfunc loadResolvedLinkDNS(linkObject dbus.BusObject) ([]resolved.LinkDNS, error) {\n\tdnsProperty, err := linkObject.GetProperty(\"org.freedesktop.resolve1.Link.DNS\")\n\tif err != nil {\n\t\tif isResolvedUnknownPropertyError(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\tvar linkDNS []resolved.LinkDNS\n\terr = dnsProperty.Store(&linkDNS)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn linkDNS, nil\n}\n\nfunc loadResolvedLinkDNSEx(linkObject dbus.BusObject) ([]resolved.LinkDNSEx, error) {\n\tdnsProperty, err := linkObject.GetProperty(\"org.freedesktop.resolve1.Link.DNSEx\")\n\tif err != nil {\n\t\tif isResolvedUnknownPropertyError(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\tvar linkDNSEx []resolved.LinkDNSEx\n\terr = dnsProperty.Store(&linkDNSEx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn linkDNSEx, nil\n}\n\nfunc loadResolvedLinkDNSOverTLS(linkObject dbus.BusObject) (string, error) {\n\tdnsOverTLSProperty, err := linkObject.GetProperty(\"org.freedesktop.resolve1.Link.DNSOverTLS\")\n\tif err != nil {\n\t\tif isResolvedUnknownPropertyError(err) {\n\t\t\treturn \"\", nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\tvar dnsOverTLSMode string\n\terr = dnsOverTLSProperty.Store(&dnsOverTLSMode)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn dnsOverTLSMode, nil\n}\n\nfunc isResolvedUnknownPropertyError(err error) bool {\n\tvar dbusError dbus.Error\n\treturn errors.As(err, &dbusError) && dbusError.Name == \"org.freedesktop.DBus.Error.UnknownProperty\"\n}\n\nfunc shouldUpdateResolvedServerSet(signal *dbus.Signal) bool {\n\tif len(signal.Body) != 3 {\n\t\treturn true\n\t}\n\tchangedProperties, loaded := signal.Body[1].(map[string]dbus.Variant)\n\tif !loaded {\n\t\treturn true\n\t}\n\tfor propertyName := range changedProperties {\n\t\tswitch propertyName {\n\t\tcase \"DNS\", \"DNSEx\", \"DNSOverTLS\":\n\t\t\treturn true\n\t\t}\n\t}\n\tinvalidatedProperties, loaded := signal.Body[2].([]string)\n\tif !loaded {\n\t\treturn true\n\t}\n\tfor _, propertyName := range invalidatedProperties {\n\t\tswitch propertyName {\n\t\tcase \"DNS\", \"DNSEx\", \"DNSOverTLS\":\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) {\n\tt.updateStatus()\n}\n"
  },
  {
    "path": "dns/transport/local/local_resolved_stub.go",
    "content": "//go:build !linux\n\npackage local\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\nfunc isSystemdResolvedManaged() bool {\n\treturn false\n}\n\nfunc NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {\n\treturn nil, os.ErrInvalid\n}\n"
  },
  {
    "path": "dns/transport/local/local_shared.go",
    "content": "package local\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"math/rand\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {\n\tsystemConfig := getSystemDNSConfig(t.ctx)\n\tif systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {\n\t\treturn t.exchangeSingleRequest(ctx, systemConfig, message, domain)\n\t} else {\n\t\treturn t.exchangeParallel(ctx, systemConfig, message, domain)\n\t}\n}\n\nfunc (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {\n\tvar lastErr error\n\tfor _, fqdn := range systemConfig.nameList(domain) {\n\t\tresponse, err := t.tryOneName(ctx, systemConfig, fqdn, message)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\t\treturn response, nil\n\t}\n\treturn nil, lastErr\n}\n\nfunc (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {\n\treturned := make(chan struct{})\n\tdefer close(returned)\n\ttype queryResult struct {\n\t\tresponse *mDNS.Msg\n\t\terr      error\n\t}\n\tresults := make(chan queryResult)\n\tstartRacer := func(ctx context.Context, fqdn string) {\n\t\tresponse, err := t.tryOneName(ctx, systemConfig, fqdn, message)\n\t\tif err == nil {\n\t\t\tif response.Rcode != mDNS.RcodeSuccess {\n\t\t\t\terr = dns.RcodeError(response.Rcode)\n\t\t\t} else if len(dns.MessageToAddresses(response)) == 0 {\n\t\t\t\terr = E.New(fqdn, \": empty result\")\n\t\t\t}\n\t\t}\n\t\tselect {\n\t\tcase results <- queryResult{response, err}:\n\t\tcase <-returned:\n\t\t}\n\t}\n\tqueryCtx, queryCancel := context.WithCancel(ctx)\n\tdefer queryCancel()\n\tvar nameCount int\n\tfor _, fqdn := range systemConfig.nameList(domain) {\n\t\tnameCount++\n\t\tgo startRacer(queryCtx, fqdn)\n\t}\n\tvar errors []error\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, ctx.Err()\n\t\tcase result := <-results:\n\t\t\tif result.err == nil {\n\t\t\t\treturn result.response, nil\n\t\t\t}\n\t\t\terrors = append(errors, result.err)\n\t\t\tif len(errors) == nameCount {\n\t\t\t\treturn nil, E.Errors(errors...)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tserverOffset := config.serverOffset()\n\tsLen := uint32(len(config.servers))\n\tvar lastErr error\n\tfor i := 0; i < config.attempts; i++ {\n\t\tfor j := uint32(0); j < sLen; j++ {\n\t\t\tserver := config.servers[(serverOffset+j)%sLen]\n\t\t\tquestion := message.Question[0]\n\t\t\tquestion.Name = fqdn\n\t\t\tresponse, err := t.exchangeOne(ctx, M.ParseSocksaddr(server), question, config.timeout, config.useTCP, config.trustAD)\n\t\t\tif err != nil {\n\t\t\t\tlastErr = err\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn response, nil\n\t\t}\n\t}\n\treturn nil, E.Cause(lastErr, fqdn)\n}\n\nfunc (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {\n\tif server.Port == 0 {\n\t\tserver.Port = 53\n\t}\n\trequest := &mDNS.Msg{\n\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\tId:                uint16(rand.Uint32()),\n\t\t\tRecursionDesired:  true,\n\t\t\tAuthenticatedData: ad,\n\t\t},\n\t\tQuestion: []mDNS.Question{question},\n\t\tCompress: true,\n\t}\n\trequest.SetEdns0(buf.UDPBufferSize, false)\n\tif !useTCP {\n\t\treturn t.exchangeUDP(ctx, server, request, timeout)\n\t} else {\n\t\treturn t.exchangeTCP(ctx, server, request, timeout)\n\t}\n}\n\nfunc (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) {\n\tconn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer conn.Close()\n\tif deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {\n\t\tnewDeadline := time.Now().Add(timeout)\n\t\tif deadline.After(newDeadline) {\n\t\t\tdeadline = newDeadline\n\t\t}\n\t\tconn.SetDeadline(deadline)\n\t}\n\tbuffer := buf.Get(buf.UDPBufferSize)\n\tdefer buf.Put(buffer)\n\trawMessage, err := request.PackBuffer(buffer)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"pack request\")\n\t}\n\t_, err = conn.Write(rawMessage)\n\tif err != nil {\n\t\tif errors.Is(err, syscall.EMSGSIZE) {\n\t\t\treturn t.exchangeTCP(ctx, server, request, timeout)\n\t\t}\n\t\treturn nil, E.Cause(err, \"write request\")\n\t}\n\tn, err := conn.Read(buffer)\n\tif err != nil {\n\t\tif errors.Is(err, syscall.EMSGSIZE) {\n\t\t\treturn t.exchangeTCP(ctx, server, request, timeout)\n\t\t}\n\t\treturn nil, E.Cause(err, \"read response\")\n\t}\n\tvar response mDNS.Msg\n\terr = response.Unpack(buffer[:n])\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"unpack response\")\n\t}\n\tif response.Truncated {\n\t\treturn t.exchangeTCP(ctx, server, request, timeout)\n\t}\n\treturn &response, nil\n}\n\nfunc (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) {\n\tconn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer conn.Close()\n\tif deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {\n\t\tnewDeadline := time.Now().Add(timeout)\n\t\tif deadline.After(newDeadline) {\n\t\t\tdeadline = newDeadline\n\t\t}\n\t\tconn.SetDeadline(deadline)\n\t}\n\terr = transport.WriteMessage(conn, 0, request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn transport.ReadMessage(conn)\n}\n"
  },
  {
    "path": "dns/transport/local/resolv.go",
    "content": "package local\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\ntype resolverConfig struct {\n\tinitOnce    sync.Once\n\tch          chan struct{}\n\tlastChecked time.Time\n\tdnsConfig   atomic.Pointer[dnsConfig]\n}\n\nvar resolvConf resolverConfig\n\nfunc getSystemDNSConfig(ctx context.Context) *dnsConfig {\n\tresolvConf.tryUpdate(ctx, \"/etc/resolv.conf\")\n\treturn resolvConf.dnsConfig.Load()\n}\n\nfunc (conf *resolverConfig) init(ctx context.Context) {\n\tconf.dnsConfig.Store(dnsReadConfig(ctx, \"/etc/resolv.conf\"))\n\tconf.lastChecked = time.Now()\n\tconf.ch = make(chan struct{}, 1)\n}\n\nfunc (conf *resolverConfig) tryUpdate(ctx context.Context, name string) {\n\tconf.initOnce.Do(func() {\n\t\tconf.init(ctx)\n\t})\n\n\tif conf.dnsConfig.Load().noReload {\n\t\treturn\n\t}\n\tif !conf.tryAcquireSema() {\n\t\treturn\n\t}\n\tdefer conf.releaseSema()\n\n\tnow := time.Now()\n\tif conf.lastChecked.After(now.Add(-5 * time.Second)) {\n\t\treturn\n\t}\n\tconf.lastChecked = now\n\tif runtime.GOOS != \"windows\" {\n\t\tvar mtime time.Time\n\t\tif fi, err := os.Stat(name); err == nil {\n\t\t\tmtime = fi.ModTime()\n\t\t}\n\t\tif mtime.Equal(conf.dnsConfig.Load().mtime) {\n\t\t\treturn\n\t\t}\n\t}\n\tdnsConf := dnsReadConfig(ctx, name)\n\tconf.dnsConfig.Store(dnsConf)\n}\n\nfunc (conf *resolverConfig) tryAcquireSema() bool {\n\tselect {\n\tcase conf.ch <- struct{}{}:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (conf *resolverConfig) releaseSema() {\n\t<-conf.ch\n}\n\ntype dnsConfig struct {\n\tservers       []string\n\tsearch        []string\n\tndots         int\n\ttimeout       time.Duration\n\tattempts      int\n\trotate        bool\n\tunknownOpt    bool\n\tlookup        []string\n\terr           error\n\tmtime         time.Time\n\tsoffset       uint32\n\tsingleRequest bool\n\tuseTCP        bool\n\ttrustAD       bool\n\tnoReload      bool\n}\n\nfunc (c *dnsConfig) serverOffset() uint32 {\n\tif c.rotate {\n\t\treturn atomic.AddUint32(&c.soffset, 1) - 1 // return 0 to start\n\t}\n\treturn 0\n}\n\nfunc (c *dnsConfig) nameList(name string) []string {\n\tl := len(name)\n\trooted := l > 0 && name[l-1] == '.'\n\tif l > 254 || l == 254 && !rooted {\n\t\treturn nil\n\t}\n\n\tif rooted {\n\t\tif avoidDNS(name) {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{name}\n\t}\n\n\thasNdots := strings.Count(name, \".\") >= c.ndots\n\tname += \".\"\n\t// l++\n\n\tnames := make([]string, 0, 1+len(c.search))\n\tif hasNdots && !avoidDNS(name) {\n\t\tnames = append(names, name)\n\t}\n\tfor _, suffix := range c.search {\n\t\tfqdn := name + suffix\n\t\tif !avoidDNS(fqdn) && len(fqdn) <= 254 {\n\t\t\tnames = append(names, fqdn)\n\t\t}\n\t}\n\tif !hasNdots && !avoidDNS(name) {\n\t\tnames = append(names, name)\n\t}\n\treturn names\n}\n\nfunc avoidDNS(name string) bool {\n\tif name == \"\" {\n\t\treturn true\n\t}\n\tif name[len(name)-1] == '.' {\n\t\tname = name[:len(name)-1]\n\t}\n\treturn strings.HasSuffix(name, \".onion\")\n}\n"
  },
  {
    "path": "dns/transport/local/resolv_default.go",
    "content": "package local\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t_ \"unsafe\"\n\n\t\"github.com/miekg/dns\"\n)\n\n//go:linkname defaultNS net.defaultNS\nvar defaultNS []string\n\nfunc dnsDefaultSearch() []string {\n\thn, err := os.Hostname()\n\tif err != nil {\n\t\treturn nil\n\t}\n\tif i := strings.IndexRune(hn, '.'); i >= 0 && i < len(hn)-1 {\n\t\treturn []string{dns.Fqdn(hn[i+1:])}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "dns/transport/local/resolv_test.go",
    "content": "package local\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDNSReadConfig(t *testing.T) {\n\tt.Parallel()\n\trequire.NoError(t, dnsReadConfig(context.Background(), \"/etc/resolv.conf\").err)\n}\n"
  },
  {
    "path": "dns/transport/local/resolv_unix.go",
    "content": "//go:build !windows\n\npackage local\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/miekg/dns\"\n)\n\nfunc dnsReadConfig(_ context.Context, name string) *dnsConfig {\n\tconf := &dnsConfig{\n\t\tndots:    1,\n\t\ttimeout:  5 * time.Second,\n\t\tattempts: 2,\n\t}\n\tfile, err := os.Open(name)\n\tif err != nil {\n\t\tconf.servers = defaultNS\n\t\tconf.search = dnsDefaultSearch()\n\t\tconf.err = err\n\t\treturn conf\n\t}\n\tdefer file.Close()\n\tfi, err := file.Stat()\n\tif err == nil {\n\t\tconf.mtime = fi.ModTime()\n\t} else {\n\t\tconf.servers = defaultNS\n\t\tconf.search = dnsDefaultSearch()\n\t\tconf.err = err\n\t\treturn conf\n\t}\n\treader := bufio.NewReader(file)\n\tvar (\n\t\tprefix   []byte\n\t\tline     []byte\n\t\tisPrefix bool\n\t)\n\tfor {\n\t\tline, isPrefix, err = reader.ReadLine()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif isPrefix {\n\t\t\tprefix = append(prefix, line...)\n\t\t\tcontinue\n\t\t} else if len(prefix) > 0 {\n\t\t\tline = append(prefix, line...)\n\t\t\tprefix = nil\n\t\t}\n\t\tif len(line) > 0 && (line[0] == ';' || line[0] == '#') {\n\t\t\tcontinue\n\t\t}\n\t\tf := strings.Fields(string(line))\n\t\tif len(f) < 1 {\n\t\t\tcontinue\n\t\t}\n\t\tswitch f[0] {\n\t\tcase \"nameserver\":\n\t\t\tif len(f) > 1 && len(conf.servers) < 3 {\n\t\t\t\tif _, err := netip.ParseAddr(f[1]); err == nil {\n\t\t\t\t\tconf.servers = append(conf.servers, net.JoinHostPort(f[1], \"53\"))\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"domain\":\n\t\t\tif len(f) > 1 {\n\t\t\t\tconf.search = []string{dns.Fqdn(f[1])}\n\t\t\t}\n\n\t\tcase \"search\":\n\t\t\tconf.search = make([]string, 0, len(f)-1)\n\t\t\tfor i := 1; i < len(f); i++ {\n\t\t\t\tname := dns.Fqdn(f[i])\n\t\t\t\tif name == \".\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconf.search = append(conf.search, name)\n\t\t\t}\n\n\t\tcase \"options\":\n\t\t\tfor _, s := range f[1:] {\n\t\t\t\tswitch {\n\t\t\t\tcase strings.HasPrefix(s, \"ndots:\"):\n\t\t\t\t\tn, _, _ := dtoi(s[6:])\n\t\t\t\t\tif n < 0 {\n\t\t\t\t\t\tn = 0\n\t\t\t\t\t} else if n > 15 {\n\t\t\t\t\t\tn = 15\n\t\t\t\t\t}\n\t\t\t\t\tconf.ndots = n\n\t\t\t\tcase strings.HasPrefix(s, \"timeout:\"):\n\t\t\t\t\tn, _, _ := dtoi(s[8:])\n\t\t\t\t\tif n < 1 {\n\t\t\t\t\t\tn = 1\n\t\t\t\t\t}\n\t\t\t\t\tconf.timeout = time.Duration(n) * time.Second\n\t\t\t\tcase strings.HasPrefix(s, \"attempts:\"):\n\t\t\t\t\tn, _, _ := dtoi(s[9:])\n\t\t\t\t\tif n < 1 {\n\t\t\t\t\t\tn = 1\n\t\t\t\t\t}\n\t\t\t\t\tconf.attempts = n\n\t\t\t\tcase s == \"rotate\":\n\t\t\t\t\tconf.rotate = true\n\t\t\t\tcase s == \"single-request\" || s == \"single-request-reopen\":\n\t\t\t\t\tconf.singleRequest = true\n\t\t\t\tcase s == \"use-vc\" || s == \"usevc\" || s == \"tcp\":\n\t\t\t\t\tconf.useTCP = true\n\t\t\t\tcase s == \"trust-ad\":\n\t\t\t\t\tconf.trustAD = true\n\t\t\t\tcase s == \"edns0\":\n\t\t\t\tcase s == \"no-reload\":\n\t\t\t\t\tconf.noReload = true\n\t\t\t\tdefault:\n\t\t\t\t\tconf.unknownOpt = true\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase \"lookup\":\n\t\t\tconf.lookup = f[1:]\n\n\t\tdefault:\n\t\t\tconf.unknownOpt = true\n\t\t}\n\t}\n\tif len(conf.servers) == 0 {\n\t\tconf.servers = defaultNS\n\t}\n\tif len(conf.search) == 0 {\n\t\tconf.search = dnsDefaultSearch()\n\t}\n\treturn conf\n}\n\nconst big = 0xFFFFFF\n\nfunc dtoi(s string) (n int, i int, ok bool) {\n\tn = 0\n\tfor i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {\n\t\tn = n*10 + int(s[i]-'0')\n\t\tif n >= big {\n\t\t\treturn big, i, false\n\t\t}\n\t}\n\tif i == 0 {\n\t\treturn 0, 0, false\n\t}\n\treturn n, i, true\n}\n"
  },
  {
    "path": "dns/transport/local/resolv_windows.go",
    "content": "package local\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\nfunc dnsReadConfig(ctx context.Context, _ string) *dnsConfig {\n\tconf := &dnsConfig{\n\t\tndots:    1,\n\t\ttimeout:  5 * time.Second,\n\t\tattempts: 2,\n\t}\n\tdefer func() {\n\t\tif len(conf.servers) == 0 {\n\t\t\tconf.servers = defaultNS\n\t\t}\n\t}()\n\taddresses, err := adapterAddresses()\n\tif err != nil {\n\t\treturn nil\n\t}\n\tvar dnsAddresses []struct {\n\t\tifName string\n\t\tnetip.Addr\n\t}\n\tfor _, address := range addresses {\n\t\tif address.OperStatus != windows.IfOperStatusUp {\n\t\t\tcontinue\n\t\t}\n\t\tif address.IfType == windows.IF_TYPE_TUNNEL {\n\t\t\tcontinue\n\t\t}\n\t\tif address.FirstGatewayAddress == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor dnsServerAddress := address.FirstDnsServerAddress; dnsServerAddress != nil; dnsServerAddress = dnsServerAddress.Next {\n\t\t\trawSockaddr, err := dnsServerAddress.Address.Sockaddr.Sockaddr()\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar dnsServerAddr netip.Addr\n\t\t\tswitch sockaddr := rawSockaddr.(type) {\n\t\t\tcase *syscall.SockaddrInet4:\n\t\t\t\tdnsServerAddr = netip.AddrFrom4(sockaddr.Addr)\n\t\t\tcase *syscall.SockaddrInet6:\n\t\t\t\tif sockaddr.Addr[0] == 0xfe && sockaddr.Addr[1] == 0xc0 {\n\t\t\t\t\t// fec0/10 IPv6 addresses are site local anycast DNS\n\t\t\t\t\t// addresses Microsoft sets by default if no other\n\t\t\t\t\t// IPv6 DNS address is set. Site local anycast is\n\t\t\t\t\t// deprecated since 2004, see\n\t\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc3879\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tdnsServerAddr = netip.AddrFrom16(sockaddr.Addr)\n\t\t\t\tif sockaddr.ZoneId != 0 {\n\t\t\t\t\tdnsServerAddr = dnsServerAddr.WithZone(strconv.FormatInt(int64(sockaddr.ZoneId), 10))\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// Unexpected type.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdnsAddresses = append(dnsAddresses, struct {\n\t\t\t\tifName string\n\t\t\t\tnetip.Addr\n\t\t\t}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})\n\t\t}\n\t}\n\tvar myInterface string\n\tif networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {\n\t\tmyInterface = networkManager.InterfaceMonitor().MyInterface()\n\t}\n\tfor _, address := range dnsAddresses {\n\t\tif address.ifName == myInterface {\n\t\t\tcontinue\n\t\t}\n\t\tconf.servers = append(conf.servers, net.JoinHostPort(address.String(), \"53\"))\n\t}\n\treturn conf\n}\n\nfunc adapterAddresses() ([]*windows.IpAdapterAddresses, error) {\n\tvar b []byte\n\tl := uint32(15000) // recommended initial size\n\tfor {\n\t\tb = make([]byte, l)\n\t\tconst flags = windows.GAA_FLAG_INCLUDE_PREFIX | windows.GAA_FLAG_INCLUDE_GATEWAYS\n\t\terr := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, flags, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l)\n\t\tif err == nil {\n\t\t\tif l == 0 {\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tif err.(syscall.Errno) != syscall.ERROR_BUFFER_OVERFLOW {\n\t\t\treturn nil, os.NewSyscallError(\"getadaptersaddresses\", err)\n\t\t}\n\t\tif l <= uint32(len(b)) {\n\t\t\treturn nil, os.NewSyscallError(\"getadaptersaddresses\", err)\n\t\t}\n\t}\n\tvar aas []*windows.IpAdapterAddresses\n\tfor aa := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); aa != nil; aa = aa.Next {\n\t\taas = append(aas, aa)\n\t}\n\treturn aas, nil\n}\n"
  },
  {
    "path": "dns/transport/quic/http3.go",
    "content": "package quic\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"sync\"\n\n\t\"github.com/sagernet/quic-go\"\n\t\"github.com/sagernet/quic-go/http3\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\tsHTTP \"github.com/sagernet/sing/protocol/http\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nvar _ adapter.DNSTransport = (*HTTP3Transport)(nil)\n\nfunc RegisterHTTP3Transport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.RemoteHTTPSDNSServerOptions](registry, C.DNSTypeHTTP3, NewHTTP3)\n}\n\ntype HTTP3Transport struct {\n\tdns.TransportAdapter\n\tlogger          logger.ContextLogger\n\tdialer          N.Dialer\n\tdestination     *url.URL\n\theaders         http.Header\n\tserverAddr      M.Socksaddr\n\ttlsConfig       *tls.STDConfig\n\ttransportAccess sync.Mutex\n\ttransport       *http3.Transport\n}\n\nfunc NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {\n\ttransportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttlsOptions := common.PtrValueOrDefault(options.TLS)\n\ttlsOptions.Enabled = true\n\ttlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstdConfig, err := tlsConfig.STDConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\theaders := options.Headers.Build()\n\thost := headers.Get(\"Host\")\n\tif host != \"\" {\n\t\theaders.Del(\"Host\")\n\t} else {\n\t\tif tlsConfig.ServerName() != \"\" {\n\t\t\thost = tlsConfig.ServerName()\n\t\t} else {\n\t\t\thost = options.Server\n\t\t}\n\t}\n\tdestinationURL := url.URL{\n\t\tScheme: \"https\",\n\t\tHost:   host,\n\t}\n\tif destinationURL.Host == \"\" {\n\t\tdestinationURL.Host = options.Server\n\t}\n\tif options.ServerPort != 0 && options.ServerPort != 443 {\n\t\tdestinationURL.Host = net.JoinHostPort(destinationURL.Host, strconv.Itoa(int(options.ServerPort)))\n\t}\n\tpath := options.Path\n\tif path == \"\" {\n\t\tpath = \"/dns-query\"\n\t}\n\terr = sHTTP.URLSetPath(&destinationURL, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tserverAddr := options.DNSServerAddressOptions.Build()\n\tif serverAddr.Port == 0 {\n\t\tserverAddr.Port = 443\n\t}\n\tif !serverAddr.IsValid() {\n\t\treturn nil, E.New(\"invalid server address: \", serverAddr)\n\t}\n\tt := &HTTP3Transport{\n\t\tTransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),\n\t\tlogger:           logger,\n\t\tdialer:           transportDialer,\n\t\tdestination:      &destinationURL,\n\t\theaders:          headers,\n\t\tserverAddr:       serverAddr,\n\t\ttlsConfig:        stdConfig,\n\t}\n\tt.transport = t.newTransport()\n\treturn t, nil\n}\n\nfunc (t *HTTP3Transport) newTransport() *http3.Transport {\n\treturn &http3.Transport{\n\t\tDial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (*quic.Conn, error) {\n\t\t\tconn, dialErr := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr)\n\t\t\tif dialErr != nil {\n\t\t\t\treturn nil, dialErr\n\t\t\t}\n\t\t\tquicConn, dialErr := quic.DialEarly(ctx, bufio.NewUnbindPacketConn(conn), conn.RemoteAddr(), tlsCfg, cfg)\n\t\t\tif dialErr != nil {\n\t\t\t\tconn.Close()\n\t\t\t\treturn nil, dialErr\n\t\t\t}\n\t\t\treturn quicConn, nil\n\t\t},\n\t\tTLSClientConfig: t.tlsConfig,\n\t}\n}\n\nfunc (t *HTTP3Transport) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn dialer.InitializeDetour(t.dialer)\n}\n\nfunc (t *HTTP3Transport) Close() error {\n\tt.transportAccess.Lock()\n\tdefer t.transportAccess.Unlock()\n\treturn t.transport.Close()\n}\n\nfunc (t *HTTP3Transport) Reset() {\n\tt.transportAccess.Lock()\n\tdefer t.transportAccess.Unlock()\n\tt.transport.Close()\n\tt.transport = t.newTransport()\n}\n\nfunc (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\texMessage := *message\n\texMessage.Id = 0\n\texMessage.Compress = true\n\trequestBuffer := buf.NewSize(1 + message.Len())\n\trawMessage, err := exMessage.PackBuffer(requestBuffer.FreeBytes())\n\tif err != nil {\n\t\trequestBuffer.Release()\n\t\treturn nil, err\n\t}\n\trequest, err := http.NewRequestWithContext(ctx, http.MethodPost, t.destination.String(), bytes.NewReader(rawMessage))\n\tif err != nil {\n\t\trequestBuffer.Release()\n\t\treturn nil, err\n\t}\n\trequest.Header = t.headers.Clone()\n\trequest.Header.Set(\"Content-Type\", transport.MimeType)\n\trequest.Header.Set(\"Accept\", transport.MimeType)\n\tt.transportAccess.Lock()\n\tcurrentTransport := t.transport\n\tt.transportAccess.Unlock()\n\tresponse, err := currentTransport.RoundTrip(request)\n\trequestBuffer.Release()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer response.Body.Close()\n\tif response.StatusCode != http.StatusOK {\n\t\treturn nil, E.New(\"unexpected status: \", response.Status)\n\t}\n\tvar responseMessage mDNS.Msg\n\tif response.ContentLength > 0 {\n\t\tresponseBuffer := buf.NewSize(int(response.ContentLength))\n\t\tdefer responseBuffer.Release()\n\t\t_, err = responseBuffer.ReadFullFrom(response.Body, int(response.ContentLength))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = responseMessage.Unpack(responseBuffer.Bytes())\n\t} else {\n\t\trawMessage, err = io.ReadAll(response.Body)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = responseMessage.Unpack(rawMessage)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &responseMessage, nil\n}\n"
  },
  {
    "path": "dns/transport/quic/quic.go",
    "content": "package quic\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\n\t\"github.com/sagernet/quic-go\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tsQUIC \"github.com/sagernet/sing-quic\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nvar _ adapter.DNSTransport = (*Transport)(nil)\n\nfunc RegisterTransport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.RemoteTLSDNSServerOptions](registry, C.DNSTypeQUIC, NewQUIC)\n}\n\ntype Transport struct {\n\t*transport.BaseTransport\n\n\tctx        context.Context\n\tdialer     N.Dialer\n\tserverAddr M.Socksaddr\n\ttlsConfig  tls.Config\n\n\tconnector *transport.Connector[*quic.Conn]\n}\n\nfunc NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) {\n\ttransportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttlsOptions := common.PtrValueOrDefault(options.TLS)\n\ttlsOptions.Enabled = true\n\ttlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(tlsConfig.NextProtos()) == 0 {\n\t\ttlsConfig.SetNextProtos([]string{\"doq\"})\n\t}\n\tserverAddr := options.DNSServerAddressOptions.Build()\n\tif serverAddr.Port == 0 {\n\t\tserverAddr.Port = 853\n\t}\n\tif !serverAddr.IsValid() {\n\t\treturn nil, E.New(\"invalid server address: \", serverAddr)\n\t}\n\n\tt := &Transport{\n\t\tBaseTransport: transport.NewBaseTransport(\n\t\t\tdns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),\n\t\t\tlogger,\n\t\t),\n\t\tctx:        ctx,\n\t\tdialer:     transportDialer,\n\t\tserverAddr: serverAddr,\n\t\ttlsConfig:  tlsConfig,\n\t}\n\n\tt.connector = transport.NewConnector(t.CloseContext(), t.dial, transport.ConnectorCallbacks[*quic.Conn]{\n\t\tIsClosed: func(connection *quic.Conn) bool {\n\t\t\treturn common.Done(connection.Context())\n\t\t},\n\t\tClose: func(connection *quic.Conn) {\n\t\t\tconnection.CloseWithError(0, \"\")\n\t\t},\n\t\tReset: func(connection *quic.Conn) {\n\t\t\tconnection.CloseWithError(0, \"\")\n\t\t},\n\t})\n\n\treturn t, nil\n}\n\nfunc (t *Transport) dial(ctx context.Context) (*quic.Conn, error) {\n\tconn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"dial UDP connection\")\n\t}\n\tearlyConnection, err := sQUIC.DialEarly(\n\t\tctx,\n\t\tbufio.NewUnbindPacketConn(conn),\n\t\tt.serverAddr.UDPAddr(),\n\t\tt.tlsConfig,\n\t\tnil,\n\t)\n\tif err != nil {\n\t\tconn.Close()\n\t\treturn nil, E.Cause(err, \"establish QUIC connection\")\n\t}\n\treturn earlyConnection, nil\n}\n\nfunc (t *Transport) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\terr := t.SetStarted()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn dialer.InitializeDetour(t.dialer)\n}\n\nfunc (t *Transport) Close() error {\n\treturn E.Errors(t.BaseTransport.Close(), t.connector.Close())\n}\n\nfunc (t *Transport) Reset() {\n\tt.connector.Reset()\n}\n\nfunc (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tif !t.BeginQuery() {\n\t\treturn nil, transport.ErrTransportClosed\n\t}\n\tdefer t.EndQuery()\n\n\tvar (\n\t\tconn     *quic.Conn\n\t\terr      error\n\t\tresponse *mDNS.Msg\n\t)\n\tfor i := 0; i < 2; i++ {\n\t\tconn, err = t.connector.Get(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresponse, err = t.exchange(ctx, message, conn)\n\t\tif err == nil {\n\t\t\treturn response, nil\n\t\t} else if !isQUICRetryError(err) {\n\t\t\treturn nil, err\n\t\t} else {\n\t\t\tt.connector.Reset()\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn nil, err\n}\n\nfunc (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn *quic.Conn) (*mDNS.Msg, error) {\n\tstream, err := conn.OpenStreamSync(ctx)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"open stream\")\n\t}\n\tdefer stream.CancelRead(0)\n\terr = transport.WriteMessage(stream, 0, message)\n\tif err != nil {\n\t\tstream.Close()\n\t\treturn nil, E.Cause(err, \"write request\")\n\t}\n\tstream.Close()\n\tresponse, err := transport.ReadMessage(stream)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"read response\")\n\t}\n\treturn response, nil\n}\n\n// https://github.com/AdguardTeam/dnsproxy/blob/fd1868577652c639cce3da00e12ca548f421baf1/upstream/upstream_quic.go#L394\nfunc isQUICRetryError(err error) (ok bool) {\n\tif errors.Is(err, os.ErrClosed) {\n\t\treturn true\n\t}\n\n\tvar qAppErr *quic.ApplicationError\n\tif errors.As(err, &qAppErr) && qAppErr.ErrorCode == 0 {\n\t\treturn true\n\t}\n\n\tvar qIdleErr *quic.IdleTimeoutError\n\tif errors.As(err, &qIdleErr) {\n\t\treturn true\n\t}\n\n\tvar resetErr *quic.StatelessResetError\n\tif errors.As(err, &resetErr) {\n\t\treturn true\n\t}\n\n\tvar qTransportError *quic.TransportError\n\tif errors.As(err, &qTransportError) && qTransportError.ErrorCode == quic.NoError {\n\t\treturn true\n\t}\n\n\tif errors.Is(err, quic.Err0RTTRejected) {\n\t\treturn true\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "dns/transport/tcp.go",
    "content": "package transport\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nvar _ adapter.DNSTransport = (*TCPTransport)(nil)\n\nfunc RegisterTCP(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.RemoteDNSServerOptions](registry, C.DNSTypeTCP, NewTCP)\n}\n\ntype TCPTransport struct {\n\tdns.TransportAdapter\n\tdialer     N.Dialer\n\tserverAddr M.Socksaddr\n}\n\nfunc NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteDNSServerOptions) (adapter.DNSTransport, error) {\n\ttransportDialer, err := dns.NewRemoteDialer(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tserverAddr := options.DNSServerAddressOptions.Build()\n\tif serverAddr.Port == 0 {\n\t\tserverAddr.Port = 53\n\t}\n\tif !serverAddr.IsValid() {\n\t\treturn nil, E.New(\"invalid server address: \", serverAddr)\n\t}\n\treturn &TCPTransport{\n\t\tTransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),\n\t\tdialer:           transportDialer,\n\t\tserverAddr:       serverAddr,\n\t}, nil\n}\n\nfunc (t *TCPTransport) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn dialer.InitializeDetour(t.dialer)\n}\n\nfunc (t *TCPTransport) Close() error {\n\treturn nil\n}\n\nfunc (t *TCPTransport) Reset() {\n}\n\nfunc (t *TCPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tconn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"dial TCP connection\")\n\t}\n\tdefer conn.Close()\n\terr = WriteMessage(conn, 0, message)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"write request\")\n\t}\n\tresponse, err := ReadMessage(conn)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"read response\")\n\t}\n\treturn response, nil\n}\n\nfunc ReadMessage(reader io.Reader) (*mDNS.Msg, error) {\n\tvar responseLen uint16\n\terr := binary.Read(reader, binary.BigEndian, &responseLen)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif responseLen < 10 {\n\t\treturn nil, mDNS.ErrShortRead\n\t}\n\tbuffer := buf.NewSize(int(responseLen))\n\tdefer buffer.Release()\n\t_, err = buffer.ReadFullFrom(reader, int(responseLen))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar message mDNS.Msg\n\terr = message.Unpack(buffer.Bytes())\n\treturn &message, err\n}\n\nfunc WriteMessage(writer io.Writer, messageId uint16, message *mDNS.Msg) error {\n\trequestLen := message.Len()\n\tbuffer := buf.NewSize(3 + requestLen)\n\tdefer buffer.Release()\n\tcommon.Must(binary.Write(buffer, binary.BigEndian, uint16(requestLen)))\n\texMessage := *message\n\texMessage.Id = messageId\n\texMessage.Compress = true\n\trawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuffer.Truncate(2 + len(rawMessage))\n\treturn common.Error(writer.Write(buffer.Bytes()))\n}\n"
  },
  {
    "path": "dns/transport/tls.go",
    "content": "package transport\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nvar _ adapter.DNSTransport = (*TLSTransport)(nil)\n\nfunc RegisterTLS(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.RemoteTLSDNSServerOptions](registry, C.DNSTypeTLS, NewTLS)\n}\n\ntype TLSTransport struct {\n\t*BaseTransport\n\n\tdialer      tls.Dialer\n\tserverAddr  M.Socksaddr\n\ttlsConfig   tls.Config\n\taccess      sync.Mutex\n\tconnections list.List[*tlsDNSConn]\n}\n\ntype tlsDNSConn struct {\n\ttls.Conn\n\tqueryId uint16\n}\n\nfunc NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) {\n\ttransportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttlsOptions := common.PtrValueOrDefault(options.TLS)\n\ttlsOptions.Enabled = true\n\ttlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tserverAddr := options.DNSServerAddressOptions.Build()\n\tif serverAddr.Port == 0 {\n\t\tserverAddr.Port = 853\n\t}\n\tif !serverAddr.IsValid() {\n\t\treturn nil, E.New(\"invalid server address: \", serverAddr)\n\t}\n\treturn NewTLSRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), transportDialer, serverAddr, tlsConfig), nil\n}\n\nfunc NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer N.Dialer, serverAddr M.Socksaddr, tlsConfig tls.Config) *TLSTransport {\n\treturn &TLSTransport{\n\t\tBaseTransport: NewBaseTransport(adapter, logger),\n\t\tdialer:        tls.NewDialer(dialer, tlsConfig),\n\t\tserverAddr:    serverAddr,\n\t\ttlsConfig:     tlsConfig,\n\t}\n}\n\nfunc (t *TLSTransport) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\terr := t.SetStarted()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn dialer.InitializeDetour(t.dialer)\n}\n\nfunc (t *TLSTransport) Close() error {\n\tt.access.Lock()\n\tfor connection := t.connections.Front(); connection != nil; connection = connection.Next() {\n\t\tconnection.Value.Close()\n\t}\n\tt.connections.Init()\n\tt.access.Unlock()\n\treturn t.BaseTransport.Close()\n}\n\nfunc (t *TLSTransport) Reset() {\n\tt.access.Lock()\n\tdefer t.access.Unlock()\n\tfor connection := t.connections.Front(); connection != nil; connection = connection.Next() {\n\t\tconnection.Value.Close()\n\t}\n\tt.connections.Init()\n}\n\nfunc (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tif !t.BeginQuery() {\n\t\treturn nil, ErrTransportClosed\n\t}\n\tdefer t.EndQuery()\n\n\tt.access.Lock()\n\tconn := t.connections.PopFront()\n\tt.access.Unlock()\n\tif conn != nil {\n\t\tresponse, err := t.exchange(ctx, message, conn)\n\t\tif err == nil {\n\t\t\treturn response, nil\n\t\t}\n\t\tt.Logger.DebugContext(ctx, \"discarded pooled connection: \", err)\n\t}\n\ttlsConn, err := t.dialer.DialTLSContext(ctx, t.serverAddr)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"dial TLS connection\")\n\t}\n\treturn t.exchange(ctx, message, &tlsDNSConn{Conn: tlsConn})\n}\n\nfunc (t *TLSTransport) exchange(ctx context.Context, message *mDNS.Msg, conn *tlsDNSConn) (*mDNS.Msg, error) {\n\tif deadline, ok := ctx.Deadline(); ok {\n\t\tconn.SetDeadline(deadline)\n\t}\n\tconn.queryId++\n\terr := WriteMessage(conn, conn.queryId, message)\n\tif err != nil {\n\t\tconn.Close()\n\t\treturn nil, E.Cause(err, \"write request\")\n\t}\n\tresponse, err := ReadMessage(conn)\n\tif err != nil {\n\t\tconn.Close()\n\t\treturn nil, E.Cause(err, \"read response\")\n\t}\n\tt.access.Lock()\n\tif t.State() >= StateClosing {\n\t\tt.access.Unlock()\n\t\tconn.Close()\n\t\treturn response, nil\n\t}\n\tconn.SetDeadline(time.Time{})\n\tt.connections.PushBack(conn)\n\tt.access.Unlock()\n\treturn response, nil\n}\n"
  },
  {
    "path": "dns/transport/udp.go",
    "content": "package transport\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nvar _ adapter.DNSTransport = (*UDPTransport)(nil)\n\nfunc RegisterUDP(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.RemoteDNSServerOptions](registry, C.DNSTypeUDP, NewUDP)\n}\n\ntype UDPTransport struct {\n\t*BaseTransport\n\n\tdialer     N.Dialer\n\tserverAddr M.Socksaddr\n\tudpSize    atomic.Int32\n\n\tconnector *Connector[*Connection]\n\n\tcallbackAccess sync.RWMutex\n\tqueryId        uint16\n\tcallbacks      map[uint16]*udpCallback\n}\n\ntype udpCallback struct {\n\taccess   sync.Mutex\n\tresponse *mDNS.Msg\n\tdone     chan struct{}\n}\n\nfunc NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteDNSServerOptions) (adapter.DNSTransport, error) {\n\ttransportDialer, err := dns.NewRemoteDialer(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tserverAddr := options.DNSServerAddressOptions.Build()\n\tif serverAddr.Port == 0 {\n\t\tserverAddr.Port = 53\n\t}\n\tif !serverAddr.IsValid() {\n\t\treturn nil, E.New(\"invalid server address: \", serverAddr)\n\t}\n\treturn NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil\n}\n\nfunc NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialerInstance N.Dialer, serverAddr M.Socksaddr) *UDPTransport {\n\tt := &UDPTransport{\n\t\tBaseTransport: NewBaseTransport(adapter, logger),\n\t\tdialer:        dialerInstance,\n\t\tserverAddr:    serverAddr,\n\t\tcallbacks:     make(map[uint16]*udpCallback),\n\t}\n\tt.udpSize.Store(2048)\n\tt.connector = NewSingleflightConnector(t.CloseContext(), t.dial)\n\treturn t\n}\n\nfunc (t *UDPTransport) dial(ctx context.Context) (*Connection, error) {\n\trawConn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"dial UDP connection\")\n\t}\n\tconn := WrapConnection(rawConn)\n\tgo t.recvLoop(conn)\n\treturn conn, nil\n}\n\nfunc (t *UDPTransport) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\terr := t.SetStarted()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn dialer.InitializeDetour(t.dialer)\n}\n\nfunc (t *UDPTransport) Close() error {\n\treturn E.Errors(t.BaseTransport.Close(), t.connector.Close())\n}\n\nfunc (t *UDPTransport) Reset() {\n\tt.connector.Reset()\n}\n\nfunc (t *UDPTransport) nextAvailableQueryId() (uint16, error) {\n\tstart := t.queryId\n\tfor {\n\t\tt.queryId++\n\t\tif _, exists := t.callbacks[t.queryId]; !exists {\n\t\t\treturn t.queryId, nil\n\t\t}\n\t\tif t.queryId == start {\n\t\t\treturn 0, E.New(\"no available query ID\")\n\t\t}\n\t}\n}\n\nfunc (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tif !t.BeginQuery() {\n\t\treturn nil, ErrTransportClosed\n\t}\n\tdefer t.EndQuery()\n\n\tresponse, err := t.exchange(ctx, message)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif response.Truncated {\n\t\tt.Logger.InfoContext(ctx, \"response truncated, retrying with TCP\")\n\t\treturn t.exchangeTCP(ctx, message)\n\t}\n\treturn response, nil\n}\n\nfunc (t *UDPTransport) exchangeTCP(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tconn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"dial TCP connection\")\n\t}\n\tdefer conn.Close()\n\terr = WriteMessage(conn, message.Id, message)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"write request\")\n\t}\n\tresponse, err := ReadMessage(conn)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"read response\")\n\t}\n\treturn response, nil\n}\n\nfunc (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tif edns0Opt := message.IsEdns0(); edns0Opt != nil {\n\t\tudpSize := int32(edns0Opt.UDPSize())\n\t\tfor {\n\t\t\tcurrent := t.udpSize.Load()\n\t\t\tif udpSize <= current {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif t.udpSize.CompareAndSwap(current, udpSize) {\n\t\t\t\tt.connector.Reset()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tconn, err := t.connector.Get(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcallback := &udpCallback{\n\t\tdone: make(chan struct{}),\n\t}\n\n\tt.callbackAccess.Lock()\n\tqueryId, err := t.nextAvailableQueryId()\n\tif err != nil {\n\t\tt.callbackAccess.Unlock()\n\t\treturn nil, err\n\t}\n\tt.callbacks[queryId] = callback\n\tt.callbackAccess.Unlock()\n\n\tdefer func() {\n\t\tt.callbackAccess.Lock()\n\t\tdelete(t.callbacks, queryId)\n\t\tt.callbackAccess.Unlock()\n\t}()\n\n\tbuffer := buf.NewSize(1 + message.Len())\n\tdefer buffer.Release()\n\n\texMessage := *message\n\texMessage.Compress = true\n\toriginalId := message.Id\n\texMessage.Id = queryId\n\n\trawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = conn.Write(rawMessage)\n\tif err != nil {\n\t\tconn.CloseWithError(err)\n\t\treturn nil, E.Cause(err, \"write request\")\n\t}\n\n\tselect {\n\tcase <-callback.done:\n\t\tcallback.response.Id = originalId\n\t\treturn callback.response, nil\n\tcase <-conn.Done():\n\t\treturn nil, conn.CloseError()\n\tcase <-t.CloseContext().Done():\n\t\treturn nil, ErrTransportClosed\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\t}\n}\n\nfunc (t *UDPTransport) recvLoop(conn *Connection) {\n\tfor {\n\t\tbuffer := buf.NewSize(int(t.udpSize.Load()))\n\t\t_, err := buffer.ReadOnceFrom(conn)\n\t\tif err != nil {\n\t\t\tbuffer.Release()\n\t\t\tconn.CloseWithError(err)\n\t\t\treturn\n\t\t}\n\n\t\tvar message mDNS.Msg\n\t\terr = message.Unpack(buffer.Bytes())\n\t\tbuffer.Release()\n\t\tif err != nil {\n\t\t\tt.Logger.Debug(\"discarded malformed UDP response: \", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tt.callbackAccess.RLock()\n\t\tcallback, loaded := t.callbacks[message.Id]\n\t\tt.callbackAccess.RUnlock()\n\n\t\tif !loaded {\n\t\t\tcontinue\n\t\t}\n\n\t\tcallback.access.Lock()\n\t\tselect {\n\t\tcase <-callback.done:\n\t\tdefault:\n\t\t\tcallback.response = &message\n\t\t\tclose(callback.done)\n\t\t}\n\t\tcallback.access.Unlock()\n\t}\n}\n"
  },
  {
    "path": "dns/transport_adapter.go",
    "content": "package dns\n\nimport (\n\t\"net/netip\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n)\n\nvar _ adapter.LegacyDNSTransport = (*TransportAdapter)(nil)\n\ntype TransportAdapter struct {\n\ttransportType string\n\ttransportTag  string\n\tdependencies  []string\n\tstrategy      C.DomainStrategy\n\tclientSubnet  netip.Prefix\n}\n\nfunc NewTransportAdapter(transportType string, transportTag string, dependencies []string) TransportAdapter {\n\treturn TransportAdapter{\n\t\ttransportType: transportType,\n\t\ttransportTag:  transportTag,\n\t\tdependencies:  dependencies,\n\t}\n}\n\nfunc NewTransportAdapterWithLocalOptions(transportType string, transportTag string, localOptions option.LocalDNSServerOptions) TransportAdapter {\n\tvar dependencies []string\n\tif localOptions.DomainResolver != nil && localOptions.DomainResolver.Server != \"\" {\n\t\tdependencies = append(dependencies, localOptions.DomainResolver.Server)\n\t}\n\treturn TransportAdapter{\n\t\ttransportType: transportType,\n\t\ttransportTag:  transportTag,\n\t\tdependencies:  dependencies,\n\t\tstrategy:      C.DomainStrategy(localOptions.LegacyStrategy),\n\t\tclientSubnet:  localOptions.LegacyClientSubnet,\n\t}\n}\n\nfunc NewTransportAdapterWithRemoteOptions(transportType string, transportTag string, remoteOptions option.RemoteDNSServerOptions) TransportAdapter {\n\tvar dependencies []string\n\tif remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != \"\" {\n\t\tdependencies = append(dependencies, remoteOptions.DomainResolver.Server)\n\t}\n\tif remoteOptions.LegacyAddressResolver != \"\" {\n\t\tdependencies = append(dependencies, remoteOptions.LegacyAddressResolver)\n\t}\n\treturn TransportAdapter{\n\t\ttransportType: transportType,\n\t\ttransportTag:  transportTag,\n\t\tdependencies:  dependencies,\n\t\tstrategy:      C.DomainStrategy(remoteOptions.LegacyStrategy),\n\t\tclientSubnet:  remoteOptions.LegacyClientSubnet,\n\t}\n}\n\nfunc (a *TransportAdapter) Type() string {\n\treturn a.transportType\n}\n\nfunc (a *TransportAdapter) Tag() string {\n\treturn a.transportTag\n}\n\nfunc (a *TransportAdapter) Dependencies() []string {\n\treturn a.dependencies\n}\n\nfunc (a *TransportAdapter) LegacyStrategy() C.DomainStrategy {\n\treturn a.strategy\n}\n\nfunc (a *TransportAdapter) LegacyClientSubnet() netip.Prefix {\n\treturn a.clientSubnet\n}\n"
  },
  {
    "path": "dns/transport_dialer.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nfunc NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (N.Dialer, error) {\n\tif options.LegacyDefaultDialer {\n\t\treturn dialer.NewDefaultOutbound(ctx), nil\n\t} else {\n\t\treturn dialer.NewWithOptions(dialer.Options{\n\t\t\tContext:         ctx,\n\t\t\tOptions:         options.DialerOptions,\n\t\t\tDirectResolver:  true,\n\t\t\tLegacyDNSDialer: options.Legacy,\n\t\t})\n\t}\n}\n\nfunc NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {\n\tif options.LegacyDefaultDialer {\n\t\ttransportDialer := dialer.NewDefaultOutbound(ctx)\n\t\tif options.LegacyAddressResolver != \"\" {\n\t\t\ttransport := service.FromContext[adapter.DNSTransportManager](ctx)\n\t\t\tresolverTransport, loaded := transport.Transport(options.LegacyAddressResolver)\n\t\t\tif !loaded {\n\t\t\t\treturn nil, E.New(\"address resolver not found: \", options.LegacyAddressResolver)\n\t\t\t}\n\t\t\ttransportDialer = newTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.LegacyAddressStrategy), time.Duration(options.LegacyAddressFallbackDelay))\n\t\t} else if options.ServerIsDomain() {\n\t\t\treturn nil, E.New(\"missing address resolver for server: \", options.Server)\n\t\t}\n\t\treturn transportDialer, nil\n\t} else {\n\t\treturn dialer.NewWithOptions(dialer.Options{\n\t\t\tContext:         ctx,\n\t\t\tOptions:         options.DialerOptions,\n\t\t\tRemoteIsDomain:  options.ServerIsDomain(),\n\t\t\tDirectResolver:  true,\n\t\t\tLegacyDNSDialer: options.Legacy,\n\t\t})\n\t}\n}\n\ntype legacyTransportDialer struct {\n\tdialer        N.Dialer\n\tdnsRouter     adapter.DNSRouter\n\ttransport     adapter.DNSTransport\n\tstrategy      C.DomainStrategy\n\tfallbackDelay time.Duration\n}\n\nfunc newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *legacyTransportDialer {\n\treturn &legacyTransportDialer{\n\t\tdialer,\n\t\tdnsRouter,\n\t\ttransport,\n\t\tstrategy,\n\t\tfallbackDelay,\n\t}\n}\n\nfunc (d *legacyTransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tif destination.IsIP() {\n\t\treturn d.dialer.DialContext(ctx, network, destination)\n\t}\n\taddresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{\n\t\tTransport: d.transport,\n\t\tStrategy:  d.strategy,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)\n}\n\nfunc (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tif destination.IsIP() {\n\t\treturn d.dialer.ListenPacket(ctx, destination)\n\t}\n\taddresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{\n\t\tTransport: d.transport,\n\t\tStrategy:  d.strategy,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconn, _, err := N.ListenSerial(ctx, d.dialer, destination, addresses)\n\treturn conn, err\n}\n\nfunc (d *legacyTransportDialer) Upstream() any {\n\treturn d.dialer\n}\n"
  },
  {
    "path": "dns/transport_manager.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/taskmonitor\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\nvar _ adapter.DNSTransportManager = (*TransportManager)(nil)\n\ntype TransportManager struct {\n\tlogger                   log.ContextLogger\n\tregistry                 adapter.DNSTransportRegistry\n\toutbound                 adapter.OutboundManager\n\tdefaultTag               string\n\taccess                   sync.RWMutex\n\tstarted                  bool\n\tstage                    adapter.StartStage\n\ttransports               []adapter.DNSTransport\n\ttransportByTag           map[string]adapter.DNSTransport\n\tdependByTag              map[string][]string\n\tdefaultTransport         adapter.DNSTransport\n\tdefaultTransportFallback func() (adapter.DNSTransport, error)\n\tfakeIPTransport          adapter.FakeIPTransport\n}\n\nfunc NewTransportManager(logger logger.ContextLogger, registry adapter.DNSTransportRegistry, outbound adapter.OutboundManager, defaultTag string) *TransportManager {\n\treturn &TransportManager{\n\t\tlogger:         logger,\n\t\tregistry:       registry,\n\t\toutbound:       outbound,\n\t\tdefaultTag:     defaultTag,\n\t\ttransportByTag: make(map[string]adapter.DNSTransport),\n\t\tdependByTag:    make(map[string][]string),\n\t}\n}\n\nfunc (m *TransportManager) Initialize(defaultTransportFallback func() (adapter.DNSTransport, error)) {\n\tm.defaultTransportFallback = defaultTransportFallback\n}\n\nfunc (m *TransportManager) Start(stage adapter.StartStage) error {\n\tm.access.Lock()\n\tif m.started && m.stage >= stage {\n\t\tpanic(\"already started\")\n\t}\n\tm.started = true\n\tm.stage = stage\n\tif stage == adapter.StartStateStart {\n\t\tif m.defaultTag != \"\" && m.defaultTransport == nil {\n\t\t\tm.access.Unlock()\n\t\t\treturn E.New(\"default DNS server not found: \", m.defaultTag)\n\t\t}\n\t\tif m.defaultTransport == nil {\n\t\t\tdefaultTransport, err := m.defaultTransportFallback()\n\t\t\tif err != nil {\n\t\t\t\tm.access.Unlock()\n\t\t\t\treturn E.Cause(err, \"default DNS server fallback\")\n\t\t\t}\n\t\t\tm.transports = append(m.transports, defaultTransport)\n\t\t\tm.transportByTag[defaultTransport.Tag()] = defaultTransport\n\t\t\tm.defaultTransport = defaultTransport\n\t\t}\n\t\ttransports := m.transports\n\t\tm.access.Unlock()\n\t\treturn m.startTransports(transports)\n\t} else {\n\t\ttransports := m.transports\n\t\tm.access.Unlock()\n\t\tfor _, outbound := range transports {\n\t\t\terr := adapter.LegacyStart(outbound, stage)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, stage, \" dns/\", outbound.Type(), \"[\", outbound.Tag(), \"]\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *TransportManager) startTransports(transports []adapter.DNSTransport) error {\n\tmonitor := taskmonitor.New(m.logger, C.StartTimeout)\n\tstarted := make(map[string]bool)\n\tfor {\n\t\tcanContinue := false\n\tstartOne:\n\t\tfor _, transportToStart := range transports {\n\t\t\ttransportTag := transportToStart.Tag()\n\t\t\tif started[transportTag] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdependencies := transportToStart.Dependencies()\n\t\t\tfor _, dependency := range dependencies {\n\t\t\t\tif !started[dependency] {\n\t\t\t\t\tcontinue startOne\n\t\t\t\t}\n\t\t\t}\n\t\t\tstarted[transportTag] = true\n\t\t\tcanContinue = true\n\t\t\tif starter, isStarter := transportToStart.(adapter.Lifecycle); isStarter {\n\t\t\t\tmonitor.Start(\"start dns/\", transportToStart.Type(), \"[\", transportTag, \"]\")\n\t\t\t\terr := starter.Start(adapter.StartStateStart)\n\t\t\t\tmonitor.Finish()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn E.Cause(err, \"start dns/\", transportToStart.Type(), \"[\", transportTag, \"]\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif len(started) == len(transports) {\n\t\t\tbreak\n\t\t}\n\t\tif canContinue {\n\t\t\tcontinue\n\t\t}\n\t\tcurrentTransport := common.Find(transports, func(it adapter.DNSTransport) bool {\n\t\t\treturn !started[it.Tag()]\n\t\t})\n\t\tvar lintTransport func(oTree []string, oCurrent adapter.DNSTransport) error\n\t\tlintTransport = func(oTree []string, oCurrent adapter.DNSTransport) error {\n\t\t\tproblemTransportTag := common.Find(oCurrent.Dependencies(), func(it string) bool {\n\t\t\t\treturn !started[it]\n\t\t\t})\n\t\t\tif common.Contains(oTree, problemTransportTag) {\n\t\t\t\treturn E.New(\"circular server dependency: \", strings.Join(oTree, \" -> \"), \" -> \", problemTransportTag)\n\t\t\t}\n\t\t\tm.access.Lock()\n\t\t\tproblemTransport := m.transportByTag[problemTransportTag]\n\t\t\tm.access.Unlock()\n\t\t\tif problemTransport == nil {\n\t\t\t\treturn E.New(\"dependency[\", problemTransportTag, \"] not found for server[\", oCurrent.Tag(), \"]\")\n\t\t\t}\n\t\t\treturn lintTransport(append(oTree, problemTransportTag), problemTransport)\n\t\t}\n\t\treturn lintTransport([]string{currentTransport.Tag()}, currentTransport)\n\t}\n\treturn nil\n}\n\nfunc (m *TransportManager) Close() error {\n\tmonitor := taskmonitor.New(m.logger, C.StopTimeout)\n\tm.access.Lock()\n\tif !m.started {\n\t\tm.access.Unlock()\n\t\treturn nil\n\t}\n\tm.started = false\n\ttransports := m.transports\n\tm.transports = nil\n\tm.access.Unlock()\n\tvar err error\n\tfor _, transport := range transports {\n\t\tif closer, isCloser := transport.(io.Closer); isCloser {\n\t\t\tmonitor.Start(\"close server/\", transport.Type(), \"[\", transport.Tag(), \"]\")\n\t\t\terr = E.Append(err, closer.Close(), func(err error) error {\n\t\t\t\treturn E.Cause(err, \"close server/\", transport.Type(), \"[\", transport.Tag(), \"]\")\n\t\t\t})\n\t\t\tmonitor.Finish()\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *TransportManager) Transports() []adapter.DNSTransport {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\treturn m.transports\n}\n\nfunc (m *TransportManager) Transport(tag string) (adapter.DNSTransport, bool) {\n\tm.access.RLock()\n\toutbound, found := m.transportByTag[tag]\n\tm.access.RUnlock()\n\treturn outbound, found\n}\n\nfunc (m *TransportManager) Default() adapter.DNSTransport {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\treturn m.defaultTransport\n}\n\nfunc (m *TransportManager) FakeIP() adapter.FakeIPTransport {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\treturn m.fakeIPTransport\n}\n\nfunc (m *TransportManager) Remove(tag string) error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\ttransport, found := m.transportByTag[tag]\n\tif !found {\n\t\treturn os.ErrInvalid\n\t}\n\tdelete(m.transportByTag, tag)\n\tindex := common.Index(m.transports, func(it adapter.DNSTransport) bool {\n\t\treturn it == transport\n\t})\n\tif index == -1 {\n\t\tpanic(\"invalid inbound index\")\n\t}\n\tm.transports = append(m.transports[:index], m.transports[index+1:]...)\n\tstarted := m.started\n\tif m.defaultTransport == transport {\n\t\tif len(m.transports) > 0 {\n\t\t\tnextTransport := m.transports[0]\n\t\t\tif nextTransport.Type() != C.DNSTypeFakeIP {\n\t\t\t\treturn E.New(\"default server cannot be fakeip\")\n\t\t\t}\n\t\t\tm.defaultTransport = nextTransport\n\t\t\tm.logger.Info(\"updated default server to \", m.defaultTransport.Tag())\n\t\t} else {\n\t\t\tm.defaultTransport = nil\n\t\t}\n\t}\n\tdependBy := m.dependByTag[tag]\n\tif len(dependBy) > 0 {\n\t\treturn E.New(\"server[\", tag, \"] is depended by \", strings.Join(dependBy, \", \"))\n\t}\n\tdependencies := transport.Dependencies()\n\tfor _, dependency := range dependencies {\n\t\tif len(m.dependByTag[dependency]) == 1 {\n\t\t\tdelete(m.dependByTag, dependency)\n\t\t} else {\n\t\t\tm.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool {\n\t\t\t\treturn it != tag\n\t\t\t})\n\t\t}\n\t}\n\tif started {\n\t\ttransport.Close()\n\t}\n\treturn nil\n}\n\nfunc (m *TransportManager) Create(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) error {\n\tif tag == \"\" {\n\t\treturn os.ErrInvalid\n\t}\n\ttransport, err := m.registry.CreateDNSTransport(ctx, logger, tag, transportType, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tif m.started {\n\t\tfor _, stage := range adapter.ListStartStages {\n\t\t\terr = adapter.LegacyStart(transport, stage)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, stage, \" dns/\", transport.Type(), \"[\", transport.Tag(), \"]\")\n\t\t\t}\n\t\t}\n\t}\n\tif existsTransport, loaded := m.transportByTag[tag]; loaded {\n\t\tif m.started {\n\t\t\terr = common.Close(existsTransport)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"close dns/\", existsTransport.Type(), \"[\", existsTransport.Tag(), \"]\")\n\t\t\t}\n\t\t}\n\t\texistsIndex := common.Index(m.transports, func(it adapter.DNSTransport) bool {\n\t\t\treturn it == existsTransport\n\t\t})\n\t\tif existsIndex == -1 {\n\t\t\tpanic(\"invalid inbound index\")\n\t\t}\n\t\tm.transports = append(m.transports[:existsIndex], m.transports[existsIndex+1:]...)\n\t}\n\tm.transports = append(m.transports, transport)\n\tm.transportByTag[tag] = transport\n\tdependencies := transport.Dependencies()\n\tfor _, dependency := range dependencies {\n\t\tm.dependByTag[dependency] = append(m.dependByTag[dependency], tag)\n\t}\n\tif tag == m.defaultTag || (m.defaultTag == \"\" && m.defaultTransport == nil) {\n\t\tif transport.Type() == C.DNSTypeFakeIP {\n\t\t\treturn E.New(\"default server cannot be fakeip\")\n\t\t}\n\t\tm.defaultTransport = transport\n\t\tif m.started {\n\t\t\tm.logger.Info(\"updated default server to \", transport.Tag())\n\t\t}\n\t}\n\tif transport.Type() == C.DNSTypeFakeIP {\n\t\tif m.fakeIPTransport != nil {\n\t\t\treturn E.New(\"multiple fakeip server are not supported\")\n\t\t}\n\t\tm.fakeIPTransport = transport.(adapter.FakeIPTransport)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "dns/transport_registry.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype TransportConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.DNSTransport, error)\n\nfunc RegisterTransport[Options any](registry *TransportRegistry, transportType string, constructor TransportConstructorFunc[Options]) {\n\tregistry.register(transportType, func() any {\n\t\treturn new(Options)\n\t}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.DNSTransport, error) {\n\t\tvar options *Options\n\t\tif rawOptions != nil {\n\t\t\toptions = rawOptions.(*Options)\n\t\t}\n\t\treturn constructor(ctx, logger, tag, common.PtrValueOrDefault(options))\n\t})\n}\n\nvar _ adapter.DNSTransportRegistry = (*TransportRegistry)(nil)\n\ntype (\n\toptionsConstructorFunc func() any\n\tconstructorFunc        func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.DNSTransport, error)\n)\n\ntype TransportRegistry struct {\n\taccess       sync.Mutex\n\toptionsType  map[string]optionsConstructorFunc\n\tconstructors map[string]constructorFunc\n}\n\nfunc NewTransportRegistry() *TransportRegistry {\n\treturn &TransportRegistry{\n\t\toptionsType:  make(map[string]optionsConstructorFunc),\n\t\tconstructors: make(map[string]constructorFunc),\n\t}\n}\n\nfunc (r *TransportRegistry) CreateOptions(transportType string) (any, bool) {\n\tr.access.Lock()\n\tdefer r.access.Unlock()\n\toptionsConstructor, loaded := r.optionsType[transportType]\n\tif !loaded {\n\t\treturn nil, false\n\t}\n\treturn optionsConstructor(), true\n}\n\nfunc (r *TransportRegistry) CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (adapter.DNSTransport, error) {\n\tr.access.Lock()\n\tdefer r.access.Unlock()\n\tconstructor, loaded := r.constructors[transportType]\n\tif !loaded {\n\t\treturn nil, E.New(\"transport type not found: \" + transportType)\n\t}\n\treturn constructor(ctx, logger, tag, options)\n}\n\nfunc (r *TransportRegistry) register(transportType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {\n\tr.access.Lock()\n\tdefer r.access.Unlock()\n\tr.optionsType[transportType] = optionsConstructor\n\tr.constructors[transportType] = constructor\n}\n"
  },
  {
    "path": "docs/CNAME",
    "content": "sing-box.sagernet.org"
  },
  {
    "path": "docs/changelog.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n#### 1.14.0-alpha.3\n\n* Fixes and improvements\n\n#### 1.13.3\n\n* Add OpenWrt and Alpine APK packages to release **1**\n* Backport to macOS 10.13 High Sierra **2**\n* OCM service: Add WebSocket support for Responses API **3**\n* Fixes and improvements\n\n**1**:\n\nAlpine APK files use `linux` in the filename to distinguish from OpenWrt APKs which use the `openwrt` prefix:\n\n- OpenWrt: `sing-box_{version}_openwrt_{architecture}.apk`\n- Alpine: `sing-box_{version}_linux_{architecture}.apk`\n\n**2**:\n\nLegacy macOS binaries (with `-legacy-macos-10.13` suffix) now support\nmacOS 10.13 High Sierra, built using Go 1.25 with patches\nfrom [SagerNet/go](https://github.com/SagerNet/go).\n\n**3**:\n\nSee [OCM](/configuration/service/ocm).\n\n#### 1.12.24\n\n* Fixes and improvements\n\n#### 1.14.0-alpha.2\n\n* Add OpenWrt and Alpine APK packages to release **1**\n* Backport to macOS 10.13 High Sierra **2**\n* OCM service: Add WebSocket support for Responses API **3**\n* Fixes and improvements\n\n**1**:\n\nAlpine APK files use `linux` in the filename to distinguish from OpenWrt APKs which use the `openwrt` prefix:\n\n- OpenWrt: `sing-box_{version}_openwrt_{architecture}.apk`\n- Alpine: `sing-box_{version}_linux_{architecture}.apk`\n\n**2**:\n\nLegacy macOS binaries (with `-legacy-macos-10.13` suffix) now support\nmacOS 10.13 High Sierra, built using Go 1.25 with patches\nfrom [SagerNet/go](https://github.com/SagerNet/go).\n\n**3**:\n\nSee [OCM](/configuration/service/ocm).\n\n#### 1.14.0-alpha.1\n\n* Add `source_mac_address` and `source_hostname` rule items **1**\n* Add `include_mac_address` and `exclude_mac_address` TUN options **2**\n* Update NaiveProxy to 145.0.7632.159 **3**\n* Fixes and improvements\n\n**1**:\n\nNew rule items for matching LAN devices by MAC address and hostname via neighbor resolution.\nSupported on Linux, macOS, or in graphical clients on Android and macOS.\n\nSee [Route Rule](/configuration/route/rule/#source_mac_address), [DNS Rule](/configuration/dns/rule/#source_mac_address) and [Neighbor Resolution](/configuration/shared/neighbor/).\n\n**2**:\n\nLimit or exclude devices from TUN routing by MAC address.\nOnly supported on Linux with `auto_route` and `auto_redirect` enabled.\n\nSee [TUN](/configuration/inbound/tun/#include_mac_address).\n\n**3**:\n\nThis is not an official update from NaiveProxy. Instead, it's a Chromium codebase update maintained by Project S.\n\n#### 1.13.2\n\n* Fixes and improvements\n\n#### 1.13.1\n\n* Fixes and improvements\n\n#### 1.12.14\n\n* Backport fixes\n\n#### 1.13.0\n\nImportant changes since 1.12:\n\n* Add NaiveProxy outbound **1**\n* Add pre-match support for `auto_redirect` **2**\n* Improve `auto_redirect` **3**\n* Add Chrome Root Store certificate option **4**\n* Add new options for ACME DNS-01 challenge providers **5**\n* Add Wi-Fi state support for Linux and Windows **6**\n* Add curve preferences, pinned public key SHA256, mTLS and ECH `query_server_name` for TLS options **7**\n* Add kTLS support **8**\n* Add ICMP echo (ping) proxy support **9**\n* Add `interface_address`, `network_interface_address` and `default_interface_address` rule items **10**\n* Add `preferred_by` route rule item **11**\n* Improve `local` DNS server **12**\n* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for listen and dial fields **13**\n* Add `bind_address_no_port` option for dial fields **14**\n* Add system interface, relay server and advertise tags options for Tailscale endpoint **15**\n* Add Claude Code Multiplexer service **16**\n* Add OpenAI Codex Multiplexer service **17**\n* Apple/Android: Refactor GUI\n* Apple/Android: Add support for sharing configurations via [QRS](https://github.com/qifi-dev/qrs)\n* Android: Add support for resisting VPN detection via Xposed\n* Drop support for go1.23 **18**\n* Drop support for Android 5.0 **19**\n* Update uTLS to v1.8.2 **20**\n* Update quic-go to v0.59.0\n* Update gVisor to v20250811\n* Update Tailscale to v1.92.4\n\n**1**:\n\nNaiveProxy outbound now supports QUIC, ECH, UDP over TCP, and configurable QUIC congestion control.\n\nOnly available on Apple platforms, Android, Windows and some Linux architectures.\nEach Windows release includes `libcronet.dll` —\nensure this file is in the same directory as `sing-box.exe` or in a directory listed in `PATH`.\n\nSee [NaiveProxy outbound](/configuration/outbound/naive/).\n\n**2**:\n\n`auto_redirect` now allows you to bypass sing-box for connections based on routing rules.\n\nA new rule action `bypass` is introduced to support this feature. When matched during pre-match, the connection will bypass sing-box and connect directly.\n\nThis feature requires Linux with `auto_redirect` enabled.\n\nSee [Pre-match](/configuration/shared/pre-match/) and [Rule Action](/configuration/route/rule_action/#bypass).\n\n**3**:\n\n`auto_redirect` now rejects MPTCP connections by default to fix compatibility issues.\nYou can change it to bypass sing-box via the new `exclude_mptcp` option.\n\nAdds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default),\nensuring traffic is routed to the sing-box table when no route is found in system tables.\nThe rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768).\n\nSee [TUN](/configuration/inbound/tun/#exclude_mptcp).\n\n**4**:\n\nAdds `chrome` as a new certificate store option alongside `mozilla`.\nBoth stores filter out China-based CA certificates.\n\nSee [Certificate](/configuration/certificate/#store).\n\n**5**:\n\nSee [DNS-01 Challenge](/configuration/shared/dns01_challenge/).\n\n**6**:\n\nsing-box can now monitor Wi-Fi state on Linux and Windows to enable routing rules based on `wifi_ssid` and `wifi_bssid`.\n\nSee [Wi-Fi State](/configuration/shared/wifi-state/).\n\n**7**:\n\nSee [TLS](/configuration/shared/tls/).\n\n**8**:\n\nAdds `kernel_tx` and `kernel_rx` options for TLS inbound.\nEnables kernel-level TLS offloading via `splice(2)` on Linux 5.1+ with TLS 1.3.\n\nSee [TLS](/configuration/shared/tls/).\n\n**9**:\n\nsing-box can now proxy ICMP echo (ping) requests.\nA new `icmp` network type is available for route rules.\nSupported from TUN, WireGuard and Tailscale inbounds to Direct, WireGuard and Tailscale outbounds.\nThe `reject` action can also reply to ICMP echo requests.\n\n**10**:\n\nNew rule items for matching based on interface IP addresses, available in route rules, DNS rules and rule-sets.\n\n**11**:\n\nMatches outbounds' preferred routes.\nFor Tailscale: MagicDNS domains and peers' allowed IPs. For WireGuard: peers' allowed IPs.\n\n**12**:\n\nThe `local` DNS server now uses platform-native resolution:\n`getaddrinfo`/libresolv on Apple platforms, systemd-resolved DBus on Linux.\nA new `prefer_go` option is available to opt out.\n\nSee [Local DNS](/configuration/dns/server/local/).\n\n**13**:\n\nThe default TCP keep-alive initial period has been updated from 10 minutes to 5 minutes.\n\nSee [Dial Fields](/configuration/shared/dial/#tcp_keep_alive).\n\n**14**:\n\nAdds the Linux socket option `IP_BIND_ADDRESS_NO_PORT` support when explicitly binding to a source address.\n\nThis allows reusing the same source port for multiple connections, improving scalability for high-concurrency proxy scenarios.\n\nSee [Dial Fields](/configuration/shared/dial/#bind_address_no_port).\n\n**15**:\n\nTailscale endpoint can now create a system TUN interface to handle traffic directly.\nNew `relay_server_port` and `relay_server_static_endpoints` options for incoming relay connections.\nNew `advertise_tags` option for ACL tag advertisement.\n\nSee [Tailscale endpoint](/configuration/endpoint/tailscale/).\n\n**16**:\n\nCCM (Claude Code Multiplexer) service allows you to access your local Claude Code subscription remotely through custom tokens, eliminating the need for OAuth authentication on remote clients.\n\nSee [CCM](/configuration/service/ccm).\n\n**17**:\n\nSee [OCM](/configuration/service/ocm).\n\n**18**:\n\nDue to maintenance difficulties, sing-box 1.13.0 requires at least Go 1.24 to compile.\n\n**19**:\n\nDue to maintenance difficulties, sing-box 1.13.0 will be the last version to support Android 5.0,\nand only through a separate legacy build (with `-legacy-android-5` suffix).\n\nFor standalone binaries, the minimum Android version has been raised to Android 6.0,\nsince Termux requires Android 7.0 or later.\n\n**20**:\n\nThis update fixes missing padding extension for Chrome 120+ fingerprints.\n\nAlso, documentation has been updated with a warning about uTLS fingerprinting vulnerabilities.\nuTLS is not recommended for censorship circumvention due to fundamental architectural limitations;\nuse NaiveProxy instead for TLS fingerprint resistance.\n\n#### 1.12.23\n\n* Fixes and improvements\n\n#### 1.13.0-rc.5\n\n* Add `mipsle`, `mips64le`, `riscv64` and `loong64` support for NaiveProxy outbound\n\n#### 1.12.22\n\n* Fixes and improvements\n\n#### 1.13.0-rc.3\n\n* Fixes and improvements\n\n#### 1.12.21\n\n* Fixes and improvements\n\n#### 1.13.0-rc.2\n\n* Fixes and improvements\n\n#### 1.12.20\n\n* Fixes and improvements\n\n#### 1.13.0-rc.1\n\n* Fixes and improvements\n\n#### 1.12.19\n\n* Fixes and improvements\n\n#### 1.13.0-beta.8\n\n* Add fallback routing rule for `auto_redirect` **1**\n* Fixes and improvements\n\n**1**:\n\nAdds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default),\nensuring traffic is routed to the sing-box table when no route is found in system tables.\n\nThe rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768).\n\n#### 1.12.18\n\n* Add fallback routing rule for `auto_redirect` **1**\n* Fixes and improvements\n\n**1**:\n\nAdds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default),\nensuring traffic is routed to the sing-box table when no route is found in system tables.\n\nThe rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768).\n\n#### 1.13.0-beta.6\n\n* Update uTLS to v1.8.2 **1**\n* Fixes and improvements\n\n**1**:\n\nThis update fixes missing padding extension for Chrome 120+ fingerprints.\n\nAlso, documentation has been updated with a warning about uTLS fingerprinting vulnerabilities.\nuTLS is not recommended for censorship circumvention due to fundamental architectural limitations;\nuse NaiveProxy instead for TLS fingerprint resistance.\n\n#### 1.12.17\n\n* Update uTLS to v1.8.2 **1**\n* Fixes and improvements\n\n**1**:\n\nThis update fixes missing padding extension for Chrome 120+ fingerprints.\n\nAlso, documentation has been updated with a warning about uTLS fingerprinting vulnerabilities.\nuTLS is not recommended for censorship circumvention due to fundamental architectural limitations;\nuse NaiveProxy instead for TLS fingerprint resistance.\n\n#### 1.13.0-beta.5\n\n* Fixes and improvements\n\n#### 1.12.16\n\n* Fixes and improvements\n\n#### 1.13.0-beta.4\n\n* Apple/Android: Add support for sharing configurations via [QRS](https://github.com/qifi-dev/qrs)\n* Android: Add support for resisting VPN detection via Xposed\n* Update quic-go to v0.59.0\n* Fixes and improvements\n\n#### 1.13.0-beta.2\n\n* Add `bind_address_no_port` option for dial fields **1**\n* Fixes and improvements\n\n**1**:\n\nAdds the Linux socket option `IP_BIND_ADDRESS_NO_PORT` support when explicitly binding to a source address.\n\nThis allows reusing the same source port for multiple connections, improving scalability for high-concurrency proxy scenarios.\n\nSee [Dial Fields](/configuration/shared/dial/#bind_address_no_port).\n\n#### 1.13.0-beta.1\n\n* Add system interface support for Tailscale endpoint **1**\n* Fixes and improvements\n\n**1**:\n\nTailscale endpoint can now create a system TUN interface to handle traffic directly.\n\nSee [Tailscale endpoint](/configuration/endpoint/tailscale/#system_interface).\n\n#### 1.12.15\n\n* Fixes and improvements\n\n#### 1.13.0-alpha.36\n\n* Downgrade quic-go to v0.57.1\n* Fixes and improvements\n\n#### 1.13.0-alpha.35\n\n* Add pre-match support for `auto_redirect` **1**\n* Fixes and improvements\n\n**1**:\n\n`auto_redirect` now allows you to bypass sing-box for connections based on routing rules.\n\nA new rule action `bypass` is introduced to support this feature. When matched during pre-match, the connection will bypass sing-box and connect directly.\n\nThis feature requires Linux with `auto_redirect` enabled.\n\nSee [Pre-match](/configuration/shared/pre-match/) and [Rule Action](/configuration/route/rule_action/#bypass).\n\n#### 1.13.0-alpha.34\n\n* Add Chrome Root Store certificate option **1**\n* Add new options for ACME DNS-01 challenge providers **2**\n* Add Wi-Fi state support for Linux and Windows **3**\n* Update naiveproxy to 143.0.7499.109\n* Update quic-go to v0.58.0\n* Update tailscale to v1.92.4\n* Drop support for go1.23 **4**\n* Drop support for Android 5.0 **5**\n\n**1**:\n\nAdds `chrome` as a new certificate store option alongside `mozilla`.\nBoth stores filter out China-based CA certificates.\n\nSee [Certificate](/configuration/certificate/#store).\n\n**2**:\n\nSee [DNS-01 Challenge](/configuration/shared/dns01_challenge/).\n\n**3**:\n\nsing-box can now monitor Wi-Fi state on Linux and Windows to enable routing rules based on `wifi_ssid` and `wifi_bssid`.\n\nSee [Wi-Fi State](/configuration/shared/wifi-state/).\n\n**4**:\n\nDue to maintenance difficulties, sing-box 1.13.0 requires at least Go 1.24 to compile.\n\n**5**:\n\nDue to maintenance difficulties, sing-box 1.13.0 will be the last version to support Android 5.0,\nand only through a separate legacy build (with `-legacy-android-5` suffix).\n\nFor standalone binaries, the minimum Android version has been raised to Android 6.0,\nsince Termux requires Android 7.0 or later.\n\n#### 1.12.14\n\n* Fixes and improvements\n\n#### 1.13.0-alpha.33\n\n* Fixes and improvements\n\n#### 1.13.0-alpha.32\n\n* Remove `certificate_public_key_sha256` option for NaiveProxy outbound **1**\n* Fixes and improvements\n\n**1**:\n\nSelf-signed certificates change traffic behavior significantly, which defeats the purpose of NaiveProxy's design to resist traffic analysis.\nFor this reason, and due to maintenance costs, there is no reason to continue supporting `certificate_public_key_sha256`, which was designed to simplify the use of self-signed certificates.\n\n#### 1.13.0-alpha.31\n\n* Add QUIC support for NaiveProxy outbound **1**\n* Add QUIC congestion control option for NaiveProxy **2**\n* Fixes and improvements\n\n**1**:\n\nNaiveProxy outbound now supports QUIC.\n\nSee [NaiveProxy outbound](/configuration/outbound/naive/#quic).\n\n**2**:\n\nNaiveProxy inbound and outbound now supports configurable QUIC congestion control algorithms, including BBR and BBRv2.\n\nSee [NaiveProxy inbound](/configuration/inbound/naive/#quic_congestion_control) and [NaiveProxy outbound](/configuration/outbound/naive/#quic_congestion_control).\n\n#### 1.13.0-alpha.30\n\n* Add ECH support for NaiveProxy outbound **1**\n* Add `tls.ech.query_server_name` option **2**\n* Fix NaiveProxy outbound on Windows **3**\n* Add OpenAI Codex Multiplexer service **4**\n* Fixes and improvements\n\n**1**:\n\nSee [NaiveProxy outbound](/configuration/outbound/naive/#tls).\n\n**2**:\n\nSee [TLS](/configuration/shared/tls/#query_server_name).\n\n**3**:\n\nEach Windows release now includes `libcronet.dll`.\nEnsure this file is in the same directory as `sing-box.exe` or in a directory listed in `PATH`.\n\n**4**:\n\nSee [OCM](/configuration/service/ocm).\n\n#### 1.13.0-alpha.29\n\n* Add UDP over TCP support for naiveproxy outbound **1**\n* Fixes and improvements\n\n**1**:\n\nSee [NaiveProxy outbound](/configuration/outbound/naive/#udp_over_tcp).\n\n#### 1.13.0-alpha.28\n\n* Add naiveproxy outbound **1**\n* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for dial fields **2**\n* Update default TCP keep-alive initial period from 10 minutes to 5 minutes\n* Update quic-go to v0.57.1\n* Fixes and improvements\n\n**1**:\n\nOnly available on Apple platforms, Android, Windows and some Linux architectures.\n\nSee [NaiveProxy outbound](/configuration/outbound/naive/).\n\n**2**:\n\nSee [Dial Fields](/configuration/shared/dial/#tcp_keep_alive).\n\n* __Unfortunately, for non-technical reasons, we are currently unable to notarize the standalone version of the macOS client:\nbecause system extensions require signatures to function, we have had to temporarily halt its release.__\n\n__We plan to fix the App Store release issue and launch a new standalone desktop client, but until then,\nonly clients on TestFlight will be available (unless you have an Apple Developer Program and compile from source code).__\n\n\n#### 1.12.13\n\n* Fix naive inbound\n* Fixes and improvements\n\n__Unfortunately, for non-technical reasons, we are currently unable to notarize the standalone version of the macOS client:\nbecause system extensions require signatures to function, we have had to temporarily halt its release.__\n\n__We plan to fix the App Store release issue and launch a new standalone desktop client, but until then,\nonly clients on TestFlight will be available (unless you have an Apple Developer Program and compile from source code).__\n\n#### 1.12.12\n\n* Fixes and improvements\n\n#### 1.13.0-alpha.26\n\n* Update quic-go to v0.55.0\n* Fix memory leak in hysteria2\n* Fixes and improvements\n\n#### 1.12.11\n\n* Fixes and improvements\n\n#### 1.13.0-alpha.24\n\n* Add Claude Code Multiplexer service **1**\n* Fixes and improvements\n\n**1**:\n\nCCM (Claude Code Multiplexer) service allows you to access your local Claude Code subscription remotely through custom tokens, eliminating the need for OAuth authentication on remote clients.\n\nSee [CCM](/configuration/service/ccm).\n\n#### 1.13.0-alpha.23\n\n* Fix compatibility with MPTCP **1**\n* Fixes and improvements\n\n**1**:\n\n`auto_redirect` now rejects MPTCP connections by default to fix compatibility issues,\nbut you can change it to bypass the sing-box via the new `exclude_mptcp` option.\n\nSee [TUN](/configuration/inbound/tun/#exclude_mptcp).\n\n#### 1.13.0-alpha.22\n\n* Update uTLS to v1.8.1 **1**\n* Fixes and improvements\n\n**1**:\n\nThis update fixes an critical issue that could cause simulated Chrome fingerprints to be detected,\nsee https://github.com/refraction-networking/utls/pull/375.\n\n#### 1.12.10\n\n* Update uTLS to v1.8.1 **1**\n* Fixes and improvements\n\n**1**:\n\nThis update fixes an critical issue that could cause simulated Chrome fingerprints to be detected,\nsee https://github.com/refraction-networking/utls/pull/375.\n\n#### 1.13.0-alpha.21\n\n* Fix missing mTLS support in client options **1**\n* Fixes and improvements\n\nSee [TLS](/configuration/shared/tls/).\n\n#### 1.12.9\n\n* Fixes and improvements\n\n#### 1.13.0-alpha.16\n\n* Add curve preferences, pinned public key SHA256 and mTLS for TLS options **1**\n* Fixes and improvements\n\nSee [TLS](/configuration/shared/tls/).\n\n#### 1.13.0-alpha.15\n\n* Update quic-go to v0.54.0\n* Update gVisor to v20250811\n* Update Tailscale to v1.86.5\n* Fixes and improvements\n\n#### 1.12.8\n\n* Fixes and improvements\n\n#### 1.13.0-alpha.11\n\n* Fixes and improvements\n\n#### 1.12.5\n\n* Fixes and improvements\n\n#### 1.13.0-alpha.10\n\n* Improve kTLS support **1**\n* Fixes and improvements\n\n**1**:\n\nkTLS is now compatible with custom TLS implementations other than uTLS.\n\n#### 1.12.4\n\n* Fixes and improvements\n\n#### 1.12.3\n\n* Fixes and improvements\n\n#### 1.12.2\n\n* Fixes and improvements\n\n#### 1.12.1\n\n* Fixes and improvements\n\n#### 1.12.0\n\n* Refactor DNS servers **1**\n* Add domain resolver options**2**\n* Add TLS fragment/record fragment support to route options and outbound TLS options **3**\n* Add certificate options **4**\n* Add Tailscale endpoint and DNS server **5**\n* Drop support for go1.22 **6**\n* Add AnyTLS protocol **7**\n* Migrate to stdlib ECH implementation **8**\n* Add NTP sniffer **9**\n* Add wildcard SNI support for ShadowTLS inbound **10**\n* Improve `auto_redirect` **11**\n* Add control options for listeners **12**\n* Add DERP service **13**\n* Add Resolved service and DNS server **14**\n* Add SSM API service **15**\n* Add loopback address support for tun **16**\n* Improve tun performance on Apple platforms **17**\n* Update quic-go to v0.52.0\n* Update gVisor to 20250319.0\n* Update the status of graphical clients in stores **18**\n\n**1**:\n\nDNS servers are refactored for better performance and scalability.\n\nSee [DNS server](/configuration/dns/server/).\n\nFor migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).\n\nCompatibility for old formats will be removed in sing-box 1.14.0.\n\n**2**:\n\nLegacy `outbound` DNS rules are deprecated\nand can be replaced by the new `domain_resolver` option.\n\nSee [Dial Fields](/configuration/shared/dial/#domain_resolver) and\n[Route](/configuration/route/#default_domain_resolver).\n\nFor migration,\nsee [Migrate outbound DNS rule items to domain resolver](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).\n\n**3**:\n\nSee [Route Action](/configuration/route/rule_action/#tls_fragment) and [TLS](/configuration/shared/tls/).\n\n**4**:\n\nNew certificate options allow you to manage the default list of trusted X509 CA certificates.\n\nFor the system certificate list, fixed Go not reading Android trusted certificates correctly.\n\nYou can also use the Mozilla Included List instead, or add trusted certificates yourself.\n\nSee [Certificate](/configuration/certificate/).\n\n**5**:\n\nSee [Tailscale](/configuration/endpoint/tailscale/).\n\n**6**:\n\nDue to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.\n\nFor Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches\nfrom [MetaCubeX/go](https://github.com/MetaCubeX/go).\n\n**7**:\n\nThe new AnyTLS protocol claims to mitigate TLS proxy traffic characteristics and comes with a new multiplexing scheme.\n\nSee [AnyTLS Inbound](/configuration/inbound/anytls/) and [AnyTLS Outbound](/configuration/outbound/anytls/).\n\n**8**:\n\nSee [TLS](/configuration/shared/tls).\n\nThe build tag `with_ech` is no longer needed and has been removed.\n\n**9**:\n\nSee [Protocol Sniff](/configuration/route/sniff/).\n\n**10**:\n\nSee [ShadowTLS](/configuration/inbound/shadowtls/#wildcard_sni).\n\n**11**:\n\nNow `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,\nsee [Tun](/configuration/inbound/tun/#auto_redirect).\n\n**12**:\n\nYou can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields.\n\nSee [Listen Fields](/configuration/shared/listen/).\n\n**13**:\n\nDERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).\n\nSee [DERP Service](/configuration/service/derp/).\n\n**14**:\n\nResolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs\n(e.g. NetworkManager) and provide DNS resolution.\n\nSee [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/).\n\n**15**:\n\nSSM API service is a RESTful API server for managing Shadowsocks servers.\n\nSee [SSM API Service](/configuration/service/ssm-api/).\n\n**16**:\n\nTUN now implements SideStore's StosVPN.\n\nSee [Tun](/configuration/inbound/tun/#loopback_address).\n\n**17**:\n\nWe have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.\n\nThe following data was tested\nusing [tun_bench](https://github.com/SagerNet/sing-box/blob/dev-next/cmd/internal/tun_bench/main.go) on M4 MacBook pro.\n\n| Version     | Stack  | MTU   | Upload | Download |\n|-------------|--------|-------|--------|----------|\n| 1.11.15     | gvisor | 1500  | 852M   | 2.57G    |\n| 1.12.0-rc.4 | gvisor | 1500  | 2.90G  | 4.68G    |\n| 1.11.15     | gvisor | 4064  | 2.31G  | 6.34G    |\n| 1.12.0-rc.4 | gvisor | 4064  | 7.54G  | 12.2G    |\n| 1.11.15     | gvisor | 65535 | 27.6G  | 18.1G    |\n| 1.12.0-rc.4 | gvisor | 65535 | 39.8G  | 34.7G    |\n| 1.11.15     | system | 1500  | 664M   | 706M     |\n| 1.12.0-rc.4 | system | 1500  | 2.44G  | 2.51G    |\n| 1.11.15     | system | 4064  | 1.88G  | 1.94G    |\n| 1.12.0-rc.4 | system | 4064  | 6.45G  | 6.27G    |\n| 1.11.15     | system | 65535 | 26.2G  | 17.4G    |\n| 1.12.0-rc.4 | system | 65535 | 17.6G  | 21.0G    |\n\n**18**:\n\nWe continue to experience issues updating our sing-box apps on the App Store and Play Store.\nUntil we rewrite and resubmit the apps, they are considered irrecoverable.\nTherefore, after this release, we will not be repeating this notice unless there is new information.\n\n### 1.11.15\n\n* Fixes and improvements\n\n_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we\nviolated the rules (TestFlight users are not affected)._\n\n#### 1.12.0-beta.32\n\n* Improve tun performance on Apple platforms **1**\n* Fixes and improvements\n\n**1**:\n\nWe have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.\n\n### 1.11.14\n\n* Fixes and improvements\n\n_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we\nviolated the rules (TestFlight users are not affected)._\n\n#### 1.12.0-beta.24\n\n* Allow `tls_fragment` and `tls_record_fragment` to be enabled together **1**\n* Also add fragment options for TLS client configuration **2**\n* Fixes and improvements\n\n**1**:\n\nFor debugging only, it is recommended to disable if record fragmentation works.\n\nSee [Route Action](/configuration/route/rule_action/#tls_fragment).\n\n**2**:\n\nSee [TLS](/configuration/shared/tls/).\n\n#### 1.12.0-beta.23\n\n* Add loopback address support for tun **1**\n* Add cache support for ssm-api **2**\n* Fixes and improvements\n\n**1**:\n\nTUN now implements SideStore's StosVPN.\n\nSee [Tun](/configuration/inbound/tun/#loopback_address).\n\n**2**:\n\nSee [SSM API Service](/configuration/service/ssm-api/#cache_path).\n\n#### 1.12.0-beta.21\n\n* Fix missing `home` option for DERP service **1**\n* Fixes and improvements\n\n**1**:\n\nYou can now choose what the DERP home page shows, just like with derper's `-home` flag.\n\nSee [DERP](/configuration/service/derp/#home).\n\n### 1.11.13\n\n* Fixes and improvements\n\n_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we\nviolated the rules (TestFlight users are not affected)._\n\n#### 1.12.0-beta.17\n\n* Update quic-go to v0.52.0\n* Fixes and improvements\n\n#### 1.12.0-beta.15\n\n* Add DERP service **1**\n* Add Resolved service and DNS server **2**\n* Add SSM API service **3**\n* Fixes and improvements\n\n**1**:\n\nDERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).\n\nSee [DERP Service](/configuration/service/derp/).\n\n**2**:\n\nResolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs\n(e.g. NetworkManager) and provide DNS resolution.\n\nSee [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/).\n\n**3**:\n\nSSM API service is a RESTful API server for managing Shadowsocks servers.\n\nSee [SSM API Service](/configuration/service/ssm-api/).\n\n### 1.11.11\n\n* Fixes and improvements\n\n_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we\nviolated the rules (TestFlight users are not affected)._\n\n#### 1.12.0-beta.13\n\n* Add TLS record fragment route options **1**\n* Add missing `accept_routes` option for Tailscale **2**\n* Fixes and improvements\n\n**1**:\n\nSee [Route Action](/configuration/route/rule_action/#tls_record_fragment).\n\n**2**:\n\nSee [Tailscale](/configuration/endpoint/tailscale/#accept_routes).\n\n#### 1.12.0-beta.10\n\n* Add control options for listeners **1**\n* Fixes and improvements\n\n**1**:\n\nYou can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields.\n\nSee [Listen Fields](/configuration/shared/listen/).\n\n### 1.11.10\n\n* Undeprecate the `block` outbound **1**\n* Fixes and improvements\n\n**1**:\n\nSince we don’t have a replacement for using the `block` outbound in selectors yet,\nwe decided to temporarily undeprecate the `block` outbound until a replacement is available in the future.\n\n_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we\nviolated the rules (TestFlight users are not affected)._\n\n#### 1.12.0-beta.9\n\n* Update quic-go to v0.51.0\n* Fixes and improvements\n\n### 1.11.9\n\n* Fixes and improvements\n\n_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we\nviolated the rules (TestFlight users are not affected)._\n\n#### 1.12.0-beta.5\n\n* Fixes and improvements\n\n### 1.11.8\n\n* Improve `auto_redirect` **1**\n* Fixes and improvements\n\n**1**:\n\nNow `auto_redirect` fixes compatibility issues between TUN and Docker bridge networks,\nsee [Tun](/configuration/inbound/tun/#auto_redirect).\n\n_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we\nviolated the rules (TestFlight users are not affected)._\n\n#### 1.12.0-beta.3\n\n* Fixes and improvements\n\n### 1.11.7\n\n* Fixes and improvements\n\n_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we\nviolated the rules (TestFlight users are not affected)._\n\n#### 1.12.0-beta.1\n\n* Fixes and improvements\n\n**1**:\n\nNow `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,\nsee [Tun](/configuration/inbound/tun/#auto_redirect).\n\n### 1.11.6\n\n* Fixes and improvements\n\n_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we\nviolated the rules (TestFlight users are not affected)._\n\n#### 1.12.0-alpha.19\n\n* Update gVisor to 20250319.0\n* Fixes and improvements\n\n#### 1.12.0-alpha.18\n\n* Add wildcard SNI support for ShadowTLS inbound **1**\n* Fixes and improvements\n\n**1**:\n\nSee [ShadowTLS](/configuration/inbound/shadowtls/#wildcard_sni).\n\n#### 1.12.0-alpha.17\n\n* Add NTP sniffer **1**\n* Fixes and improvements\n\n**1**:\n\nSee [Protocol Sniff](/configuration/route/sniff/).\n\n#### 1.12.0-alpha.16\n\n* Update `domain_resolver` behavior **1**\n* Fixes and improvements\n\n**1**:\n\n`route.default_domain_resolver` or `outbound.domain_resolver` is now optional when only one DNS server is configured.\n\nSee [Dial Fields](/configuration/shared/dial/#domain_resolver).\n\n### 1.11.5\n\n* Fixes and improvements\n\n_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we\nviolated the rules (TestFlight users are not affected)._\n\n#### 1.12.0-alpha.13\n\n* Move `predefined` DNS server to DNS rule action **1**\n* Fixes and improvements\n\n**1**:\n\nSee [DNS Rule Action](/configuration/dns/rule_action/#predefined).\n\n### 1.11.4\n\n* Fixes and improvements\n\n#### 1.12.0-alpha.11\n\n* Fixes and improvements\n\n#### 1.12.0-alpha.10\n\n* Add AnyTLS protocol **1**\n* Improve `resolve` route action **2**\n* Migrate to stdlib ECH implementation **3**\n* Fixes and improvements\n\n**1**:\n\nThe new AnyTLS protocol claims to mitigate TLS proxy traffic characteristics and comes with a new multiplexing scheme.\n\nSee [AnyTLS Inbound](/configuration/inbound/anytls/) and [AnyTLS Outbound](/configuration/outbound/anytls/).\n\n**2**:\n\n`resolve` route action now accepts `disable_cache` and other options like in DNS route actions,\nsee [Route Action](/configuration/route/rule_action).\n\n**3**:\n\nSee [TLS](/configuration/shared/tls).\n\nThe build tag `with_ech` is no longer needed and has been removed.\n\n#### 1.12.0-alpha.7\n\n* Add Tailscale DNS server **1**\n* Fixes and improvements\n\n**1**:\n\nSee [Tailscale](/configuration/dns/server/tailscale/).\n\n#### 1.12.0-alpha.6\n\n* Add Tailscale endpoint **1**\n* Drop support for go1.22 **2**\n* Fixes and improvements\n\n**1**:\n\nSee [Tailscale](/configuration/endpoint/tailscale/).\n\n**2**:\n\nDue to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.\n\nFor Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches\nfrom [MetaCubeX/go](https://github.com/MetaCubeX/go).\n\n### 1.11.3\n\n* Fixes and improvements\n\n_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration\nprocess._\n\n#### 1.12.0-alpha.5\n\n* Fixes and improvements\n\n### 1.11.1\n\n* Fixes and improvements\n\n#### 1.12.0-alpha.2\n\n* Update quic-go to v0.49.0\n* Fixes and improvements\n\n#### 1.12.0-alpha.1\n\n* Refactor DNS servers **1**\n* Add domain resolver options**2**\n* Add TLS fragment route options **3**\n* Add certificate options **4**\n\n**1**:\n\nDNS servers are refactored for better performance and scalability.\n\nSee [DNS server](/configuration/dns/server/).\n\nFor migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).\n\nCompatibility for old formats will be removed in sing-box 1.14.0.\n\n**2**:\n\nLegacy `outbound` DNS rules are deprecated\nand can be replaced by the new `domain_resolver` option.\n\nSee [Dial Fields](/configuration/shared/dial/#domain_resolver) and\n[Route](/configuration/route/#default_domain_resolver).\n\nFor migration,\nsee [Migrate outbound DNS rule items to domain resolver](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).\n\n**3**:\n\nThe new TLS fragment route options allow you to fragment TLS handshakes to bypass firewalls.\n\nThis feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used\nto circumvent real censorship.\n\nSince it is not designed for performance, it should not be applied to all connections, but only to server names that are\nknown to be blocked.\n\nSee [Route Action](/configuration/route/rule_action/#tls_fragment).\n\n**4**:\n\nNew certificate options allow you to manage the default list of trusted X509 CA certificates.\n\nFor the system certificate list, fixed Go not reading Android trusted certificates correctly.\n\nYou can also use the Mozilla Included List instead, or add trusted certificates yourself.\n\nSee [Certificate](/configuration/certificate/).\n\n### 1.11.0\n\nImportant changes since 1.10:\n\n* Introducing rule actions **1**\n* Improve tun compatibility **3**\n* Merge route options to route actions **4**\n* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **5**\n* Add multi network dialing **6**\n* Add `cache_capacity` DNS option **7**\n* Add `override_address` and `override_port` route options **8**\n* Upgrade WireGuard outbound to endpoint **9**\n* Add UDP GSO support for WireGuard\n* Make GSO adaptive **10**\n* Add UDP timeout route option **11**\n* Add more masquerade options for hysteria2 **12**\n* Add `rule-set merge` command\n* Add port hopping support for Hysteria2 **13**\n* Hysteria2 `ignore_client_bandwidth` behavior update **14**\n\n**1**:\n\nNew rule actions replace legacy inbound fields and special outbound fields,\nand can be used for pre-matching **2**.\n\nSee [Rule](/configuration/route/rule/),\n[Rule Action](/configuration/route/rule_action/),\n[DNS Rule](/configuration/dns/rule/) and\n[DNS Rule Action](/configuration/dns/rule_action/).\n\nFor migration, see\n[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),\n[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)\nand [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).\n\n**2**:\n\nSimilar to Surge's pre-matching.\n\nSpecifically, new rule actions allow you to reject connections with\nTCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)\nbefore connection established to improve tun's compatibility.\n\nSee [Rule Action](/configuration/route/rule_action/).\n\n**3**:\n\nWhen `gvisor` tun stack is enabled, even if the request passes routing,\nif the outbound connection establishment fails,\nthe connection still does not need to be established and a TCP RST is replied.\n\n**4**:\n\nRoute options in DNS route actions will no longer be considered deprecated,\nsee [DNS Route Action](/configuration/dns/rule_action/).\n\nAlso, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,\nsee [Route Action](/configuration/route/rule_action/).\n\n**5**:\n\nWhen using in graphical clients, new routing rule items allow you to match on\nnetwork type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.\n\nSee [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)\nand [Headless Rule](/configuration/rule-set/headless-rule/).\n\n**6**:\n\nSimilar to Surge's strategy.\n\nNew options allow you to connect using multiple network interfaces,\nprefer or only use one type of interface,\nand configure a timeout to fallback to other interfaces.\n\nSee [Dial Fields](/configuration/shared/dial/#network_strategy),\n[Rule Action](/configuration/route/rule_action/#network_strategy)\nand [Route](/configuration/route/#default_network_strategy).\n\n**7**:\n\nSee [DNS](/configuration/dns/#cache_capacity).\n\n**8**:\n\nSee [Rule Action](/configuration/route/#override_address) and\n[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).\n\n**9**:\n\nThe new WireGuard endpoint combines inbound and outbound capabilities,\nand the old outbound will be removed in sing-box 1.13.0.\n\nSee [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)\nand [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).\n\n**10**:\n\nFor WireGuard outbound and endpoint, GSO will be automatically enabled when available,\nsee [WireGuard Outbound](/configuration/outbound/wireguard/#gso).\n\nFor TUN, GSO has been removed,\nsee [Deprecated](/deprecated/#gso-option-in-tun).\n\n**11**:\n\nSee [Rule Action](/configuration/route/rule_action/#udp_timeout).\n\n**12**:\n\nSee [Hysteria2](/configuration/inbound/hysteria2/#masquerade).\n\n**13**:\n\nSee [Hysteria2](/configuration/outbound/hysteria2/).\n\n**14**:\n\nWhen `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.\n\n### 1.10.7\n\n* Fixes and improvements\n\n#### 1.11.0-beta.20\n\n* Hysteria2 `ignore_client_bandwidth` behavior update **1**\n* Fixes and improvements\n\n**1**:\n\nWhen `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.\n\nSee [Hysteria2](/configuration/inbound/hysteria2/#ignore_client_bandwidth).\n\n#### 1.11.0-beta.17\n\n* Add port hopping support for Hysteria2 **1**\n* Fixes and improvements\n\n**1**:\n\nSee [Hysteria2](/configuration/outbound/hysteria2/).\n\n#### 1.11.0-beta.14\n\n* Allow adding route (exclude) address sets to routes **1**\n* Fixes and improvements\n\n**1**:\n\nWhen `auto_redirect` is not enabled, directly add `route[_exclude]_address_set`\nto tun routes (equivalent to `route[_exclude]_address`).\n\nNote that it **doesn't work on the Android graphical client** due to\nthe Android VpnService not being able to handle a large number of routes (DeadSystemException),\nbut otherwise it works fine on all command line clients and Apple platforms.\n\nSee [route_address_set](/configuration/inbound/tun/#route_address_set) and\n[route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set).\n\n#### 1.11.0-beta.12\n\n* Add `rule-set merge` command\n* Fixes and improvements\n\n#### 1.11.0-beta.3\n\n* Add more masquerade options for hysteria2 **1**\n* Fixes and improvements\n\n**1**:\n\nSee [Hysteria2](/configuration/inbound/hysteria2/#masquerade).\n\n#### 1.11.0-alpha.25\n\n* Update quic-go to v0.48.2\n* Fixes and improvements\n\n#### 1.11.0-alpha.22\n\n* Add UDP timeout route option **1**\n* Fixes and improvements\n\n**1**:\n\nSee [Rule Action](/configuration/route/rule_action/#udp_timeout).\n\n#### 1.11.0-alpha.20\n\n* Add UDP GSO support for WireGuard\n* Make GSO adaptive **1**\n\n**1**:\n\nFor WireGuard outbound and endpoint, GSO will be automatically enabled when available,\nsee [WireGuard Outbound](/configuration/outbound/wireguard/#gso).\n\nFor TUN, GSO has been removed,\nsee [Deprecated](/deprecated/#gso-option-in-tun).\n\n#### 1.11.0-alpha.19\n\n* Upgrade WireGuard outbound to endpoint **1**\n* Fixes and improvements\n\n**1**:\n\nThe new WireGuard endpoint combines inbound and outbound capabilities,\nand the old outbound will be removed in sing-box 1.13.0.\n\nSee [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)\nand [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).\n\n### 1.10.2\n\n* Add deprecated warnings\n* Fix proxying websocket connections in HTTP/mixed inbounds\n* Fixes and improvements\n\n#### 1.11.0-alpha.18\n\n* Fixes and improvements\n\n#### 1.11.0-alpha.16\n\n* Add `cache_capacity` DNS option **1**\n* Add `override_address` and `override_port` route options **2**\n* Fixes and improvements\n\n**1**:\n\nSee [DNS](/configuration/dns/#cache_capacity).\n\n**2**:\n\nSee [Rule Action](/configuration/route/#override_address) and\n[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).\n\n#### 1.11.0-alpha.15\n\n* Improve multi network dialing **1**\n* Fixes and improvements\n\n**1**:\n\nNew options allow you to configure the network strategy flexibly.\n\nSee [Dial Fields](/configuration/shared/dial/#network_strategy),\n[Rule Action](/configuration/route/rule_action/#network_strategy)\nand [Route](/configuration/route/#default_network_strategy).\n\n#### 1.11.0-alpha.14\n\n* Add multi network dialing **1**\n* Fixes and improvements\n\n**1**:\n\nSimilar to Surge's strategy.\n\nNew options allow you to connect using multiple network interfaces,\nprefer or only use one type of interface,\nand configure a timeout to fallback to other interfaces.\n\nSee [Dial Fields](/configuration/shared/dial/#network_strategy),\n[Rule Action](/configuration/route/rule_action/#network_strategy)\nand [Route](/configuration/route/#default_network_strategy).\n\n#### 1.11.0-alpha.13\n\n* Fixes and improvements\n\n#### 1.11.0-alpha.12\n\n* Merge route options to route actions **1**\n* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **2**\n* Fixes and improvements\n\n**1**:\n\nRoute options in DNS route actions will no longer be considered deprecated,\nsee [DNS Route Action](/configuration/dns/rule_action/).\n\nAlso, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,\nsee [Route Action](/configuration/route/rule_action/).\n\n**2**:\n\nWhen using in graphical clients, new routing rule items allow you to match on\nnetwork type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.\n\nSee [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)\nand [Headless Rule](/configuration/rule-set/headless-rule/).\n\n#### 1.11.0-alpha.9\n\n* Improve tun compatibility **1**\n* Fixes and improvements\n\n**1**:\n\nWhen `gvisor` tun stack is enabled, even if the request passes routing,\nif the outbound connection establishment fails,\nthe connection still does not need to be established and a TCP RST is replied.\n\n#### 1.11.0-alpha.7\n\n* Introducing rule actions **1**\n\n**1**:\n\nNew rule actions replace legacy inbound fields and special outbound fields,\nand can be used for pre-matching **2**.\n\nSee [Rule](/configuration/route/rule/),\n[Rule Action](/configuration/route/rule_action/),\n[DNS Rule](/configuration/dns/rule/) and\n[DNS Rule Action](/configuration/dns/rule_action/).\n\nFor migration, see\n[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),\n[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)\nand [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).\n\n**2**:\n\nSimilar to Surge's pre-matching.\n\nSpecifically, new rule actions allow you to reject connections with\nTCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)\nbefore connection established to improve tun's compatibility.\n\nSee [Rule Action](/configuration/route/rule_action/).\n\n#### 1.11.0-alpha.6\n\n* Update quic-go to v0.48.1\n* Set gateway for tun correctly\n* Fixes and improvements\n\n#### 1.11.0-alpha.2\n\n* Add warnings for usage of deprecated features\n* Fixes and improvements\n\n#### 1.11.0-alpha.1\n\n* Update quic-go to v0.48.0\n* Fixes and improvements\n\n### 1.10.1\n\n* Fixes and improvements\n\n### 1.10.0\n\nImportant changes since 1.9:\n\n* Introducing auto-redirect **1**\n* Add AdGuard DNS Filter support **2**\n* TUN address fields are merged **3**\n* Add custom options for `auto-route` and `auto-redirect` **4**\n* Drop support for go1.18 and go1.19 **5**\n* Add tailing comma support in JSON configuration\n* Improve sniffers **6**\n* Add new `inline` rule-set type **7**\n* Add access control options for Clash API **8**\n* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **9**\n* Add auto reload support for local rule-set\n* Update fsnotify usages **10**\n* Add IP address support for `rule-set match` command\n* Add `rule-set decompile` command\n* Add `process_path_regex` rule item\n* Update uTLS to v1.6.7 **11**\n* Optimize memory usages of rule-sets **12**\n\n**1**:\n\nThe new auto-redirect feature allows TUN to automatically\nconfigure connection redirection to improve proxy performance.\n\nWhen auto-redirect is enabled, new route address set options will allow you to\nautomatically configure destination IP CIDR rules from a specified rule set to the firewall.\n\nSpecified or unspecified destinations will bypass the sing-box routes to get better performance\n(for example, keep hardware offloading of direct traffics on the router).\n\nSee [TUN](/configuration/inbound/tun).\n\n**2**:\n\nThe new feature allows you to use AdGuard DNS Filter lists in a sing-box without AdGuard Home.\n\nSee [AdGuard DNS Filter](/configuration/rule-set/adguard/).\n\n**3**:\n\nSee [Migration](/migration/#tun-address-fields-are-merged).\n\n**4**:\n\nSee [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index),\n[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index),\n[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and\n[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark).\n\n**5**:\n\nDue to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.\n\n**6**:\n\nBitTorrent, DTLS, RDP, SSH sniffers are added.\n\nNow the QUIC sniffer can correctly extract the server name from Chromium requests and\ncan identify common QUIC clients, including\nChromium, Safari, Firefox, quic-go (including uquic disguised as Chrome).\n\n**7**:\n\nThe new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type)\nallows you to write headless rules directly without creating a rule-set file.\n\n**8**:\n\nWith new access control options, not only can you allow Clash dashboards\nto access the Clash API on your local network,\nyou can also manually limit the websites that can access the API instead of allowing everyone.\n\nSee [Clash API](/configuration/experimental/clash-api/).\n\n**9**:\n\nSee [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty).\n\n**10**:\n\nsing-box now uses fsnotify correctly and will not cancel watching\nif the target file is deleted or recreated via rename (e.g. `mv`).\n\nThis affects all path options that support reload, including\n`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`.\n\n**11**:\n\nSome legacy chrome fingerprints have been removed and will fallback to chrome,\nsee [utls](/configuration/shared/tls#utls).\n\n**12**:\n\nSee [Source Format](/configuration/rule-set/source-format/#version).\n\n### 1.9.7\n\n* Fixes and improvements\n\n#### 1.10.0-beta.11\n\n* Update uTLS to v1.6.7 **1**\n\n**1**:\n\nSome legacy chrome fingerprints have been removed and will fallback to chrome,\nsee [utls](/configuration/shared/tls#utls).\n\n#### 1.10.0-beta.10\n\n* Add `process_path_regex` rule item\n* Fixes and improvements\n\n_The macOS standalone versions of sing-box (>=1.9.5/<1.10.0-beta.11) now silently fail and require manual granting of\nthe **Full Disk Access** permission to system extension to start, probably due to Apple's changed security policy. We\nwill prompt users about this in feature versions._\n\n### 1.9.6\n\n* Fixes and improvements\n\n### 1.9.5\n\n* Update quic-go to v0.47.0\n* Fix direct dialer not resolving domain\n* Fix no error return when empty DNS cache retrieved\n* Fix build with go1.23\n* Fix stream sniffer\n* Fix bad redirect in clash-api\n* Fix wireguard events chan leak\n* Fix cached conn eats up read deadlines\n* Fix disconnected interface selected as default in windows\n* Update Bundle Identifiers for Apple platform clients **1**\n\n**1**:\n\nSee [Migration](/migration/#bundle-identifier-updates-in-apple-platform-clients).\n\nWe are still working on getting all sing-box apps back on the App Store, which should be completed within a week\n(SFI on the App Store and others on TestFlight are already available).\n\n#### 1.10.0-beta.8\n\n* Fixes and improvements\n\n_With the help of a netizen, we are in the process of getting sing-box apps back on the App Store, which should be\ncompleted within a month (TestFlight is already available)._\n\n#### 1.10.0-beta.7\n\n* Update quic-go to v0.47.0\n* Fixes and improvements\n\n#### 1.10.0-beta.6\n\n* Add RDP sniffer\n* Fixes and improvements\n\n#### 1.10.0-beta.5\n\n* Add PNA support for [Clash API](/configuration/experimental/clash-api/)\n* Fixes and improvements\n\n#### 1.10.0-beta.3\n\n* Add SSH sniffer\n* Fixes and improvements\n\n#### 1.10.0-beta.2\n\n* Build with go1.23\n* Fixes and improvements\n\n### 1.9.4\n\n* Update quic-go to v0.46.0\n* Update Hysteria2 BBR congestion control\n* Filter HTTPS ipv4hint/ipv6hint with domain strategy\n* Fix crash on Android when using process rules\n* Fix non-IP queries accepted by address filter rules\n* Fix UDP server for shadowsocks AEAD multi-user inbounds\n* Fix default next protos for v2ray QUIC transport\n* Fix default end value of port range configuration options\n* Fix reset v2ray transports\n* Fix panic caused by rule-set generation of duplicate keys for `domain_suffix`\n* Fix UDP connnection leak when sniffing\n* Fixes and improvements\n\n_Due to problems with our Apple developer account,\nsing-box apps on Apple platforms are temporarily unavailable for download or update.\nIf your company or organization is willing to help us return to the App Store,\nplease [contact us](mailto:contact@sagernet.org)._\n\n#### 1.10.0-alpha.29\n\n* Update quic-go to v0.46.0\n* Fixes and improvements\n\n#### 1.10.0-alpha.25\n\n* Add AdGuard DNS Filter support **1**\n\n**1**:\n\nThe new feature allows you to use AdGuard DNS Filter lists in a sing-box without AdGuard Home.\n\nSee [AdGuard DNS Filter](/configuration/rule-set/adguard/).\n\n#### 1.10.0-alpha.23\n\n* Add Chromium support for QUIC sniffer\n* Add client type detect support for QUIC sniffer **1**\n* Fixes and improvements\n\n**1**:\n\nNow the QUIC sniffer can correctly extract the server name from Chromium requests and\ncan identify common QUIC clients, including\nChromium, Safari, Firefox, quic-go (including uquic disguised as Chrome).\n\nSee [Protocol Sniff](/configuration/route/sniff/) and [Route Rule](/configuration/route/rule/#client).\n\n#### 1.10.0-alpha.22\n\n* Optimize memory usages of rule-sets **1**\n* Fixes and improvements\n\n**1**:\n\nSee [Source Format](/configuration/rule-set/source-format/#version).\n\n#### 1.10.0-alpha.20\n\n* Add DTLS sniffer\n* Fixes and improvements\n\n#### 1.10.0-alpha.19\n\n* Add `rule-set decompile` command\n* Add IP address support for `rule-set match` command\n* Fixes and improvements\n\n#### 1.10.0-alpha.18\n\n* Add new `inline` rule-set type **1**\n* Add auto reload support for local rule-set\n* Update fsnotify usages **2**\n* Fixes and improvements\n\n**1**:\n\nThe new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type)\nallows you to write headless rules directly without creating a rule-set file.\n\n**2**:\n\nsing-box now uses fsnotify correctly and will not cancel watching\nif the target file is deleted or recreated via rename (e.g. `mv`).\n\nThis affects all path options that support reload, including\n`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`.\n\n#### 1.10.0-alpha.17\n\n* Some chaotic changes **1**\n* `rule_set_ipcidr_match_source` rule items are renamed **2**\n* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **3**\n* Update quic-go to v0.45.1\n* Fixes and improvements\n\n**1**:\n\nSomething may be broken, please actively report problems with this version.\n\n**2**:\n\n`rule_set_ipcidr_match_source` route and DNS rule items are renamed to\n`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.\n\n**3**:\n\nSee [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty).\n\n#### 1.10.0-alpha.16\n\n* Add custom options for `auto-route` and `auto-redirect` **1**\n* Fixes and improvements\n\n**1**:\n\nSee [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index),\n[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index),\n[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and\n[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark).\n\n#### 1.10.0-alpha.13\n\n* TUN address fields are merged **1**\n* Add route address set support for auto-redirect **2**\n\n**1**:\n\nSee [Migration](/migration/#tun-address-fields-are-merged).\n\n**2**:\n\nThe new feature will allow you to configure the destination IP CIDR rules\nin the specified rule-sets to the firewall automatically.\n\nSpecified or unspecified destinations will bypass the sing-box routes to get better performance\n(for example, keep hardware offloading of direct traffics on the router).\n\nSee [route_address_set](/configuration/inbound/tun/#route_address_set)\nand [route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set).\n\n#### 1.10.0-alpha.12\n\n* Fix auto-redirect not configuring nftables forward chain correctly\n* Fixes and improvements\n\n### 1.9.3\n\n* Fixes and improvements\n\n#### 1.10.0-alpha.10\n\n* Fixes and improvements\n\n### 1.9.2\n\n* Fixes and improvements\n\n#### 1.10.0-alpha.8\n\n* Drop support for go1.18 and go1.19 **1**\n* Update quic-go to v0.45.0\n* Update Hysteria2 BBR congestion control\n* Fixes and improvements\n\n**1**:\n\nDue to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.\n\n### 1.9.1\n\n* Fixes and improvements\n\n#### 1.10.0-alpha.7\n\n* Fixes and improvements\n\n#### 1.10.0-alpha.5\n\n* Improve auto-redirect **1**\n\n**1**:\n\nnftables support and DNS hijacking has been added.\n\nTun inbounds with `auto_route` and `auto_redirect` now works as expected on routers **without intervention**.\n\n#### 1.10.0-alpha.4\n\n* Fix auto-redirect **1**\n* Improve auto-route on linux **2**\n\n**1**:\n\nTun inbounds with `auto_route` and `auto_redirect` now works as expected on routers.\n\n**2**:\n\nTun inbounds with `auto_route` and `strict_route` now works as expected on routers and servers,\nbut the usages of [exclude_interface](/configuration/inbound/tun/#exclude_interface) need to be updated.\n\n#### 1.10.0-alpha.2\n\n* Move auto-redirect to Tun **1**\n* Fixes and improvements\n\n**1**:\n\nLinux support are added.\n\nSee [Tun](/configuration/inbound/tun/#auto_redirect).\n\n#### 1.10.0-alpha.1\n\n* Add tailing comma support in JSON configuration\n* Add simple auto-redirect for Android **1**\n* Add BitTorrent sniffer **2**\n\n**1**:\n\nIt allows you to use redirect inbound in the sing-box Android client\nand automatically configures IPv4 TCP redirection via su.\n\nThis may alleviate the symptoms of some OCD patients who think that\nredirect can effectively save power compared to the system HTTP Proxy.\n\nSee [Redirect](/configuration/inbound/redirect/).\n\n**2**:\n\nSee [Protocol Sniff](/configuration/route/sniff/).\n\n### 1.9.0\n\n* Fixes and improvements\n\nImportant changes since 1.8:\n\n* `domain_suffix` behavior update **1**\n* `process_path` format update on Windows **2**\n* Add address filter DNS rule items **3**\n* Add support for `client-subnet` DNS options **4**\n* Add rejected DNS response cache support **5**\n* Add `bypass_domain` and `search_domain` platform HTTP proxy options **6**\n* Fix missing `rule_set_ipcidr_match_source` item in DNS rules **7**\n* Handle Windows power events\n* Always disable cache for fake-ip DNS transport if `dns.independent_cache` disabled\n* Improve DNS truncate behavior\n* Update Hysteria protocol\n* Update quic-go to v0.43.1\n* Update gVisor to 20240422.0\n* Mitigating TunnelVision attacks **8**\n\n**1**:\n\nSee [Migration](/migration/#domain_suffix-behavior-update).\n\n**2**:\n\nSee [Migration](/migration/#process_path-format-update-on-windows).\n\n**3**:\n\nThe new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS\nif using this method.\n\nSee [Address Filter Fields](/configuration/dns/rule#address-filter-fields).\n\n[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.\n\n**4**:\n\nSee [DNS](/configuration/dns), [DNS Server](/configuration/dns/server) and [DNS Rules](/configuration/dns/rule).\n\nSince this feature makes the scenario mentioned in `alpha.1` no longer leak DNS requests,\nthe [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) has been updated.\n\n**5**:\n\nThe new feature allows you to cache the check results of\n[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.\n\n**6**:\n\nSee [TUN](/configuration/inbound/tun) inbound.\n\n**7**:\n\nSee [DNS Rule](/configuration/dns/rule/).\n\n**8**:\n\nSee [TunnelVision](/manual/misc/tunnelvision).\n\n#### 1.9.0-rc.22\n\n* Fixes and improvements\n\n#### 1.9.0-rc.20\n\n* Prioritize `*_route_address` in linux auto-route\n* Fix `*_route_address` in darwin auto-route\n\n#### 1.8.14\n\n* Fix hysteria2 panic\n* Fixes and improvements\n\n#### 1.9.0-rc.18\n\n* Add custom prefix support in EDNS0 client subnet options\n* Fix hysteria2 crash\n* Fix `store_rdrc` corrupted\n* Update quic-go to v0.43.1\n* Fixes and improvements\n\n#### 1.9.0-rc.16\n\n* Mitigating TunnelVision attacks **1**\n* Fixes and improvements\n\n**1**:\n\nSee [TunnelVision](/manual/misc/tunnelvision).\n\n#### 1.9.0-rc.15\n\n* Fixes and improvements\n\n#### 1.8.13\n\n* Fix fake-ip mapping\n* Fixes and improvements\n\n#### 1.9.0-rc.14\n\n* Fixes and improvements\n\n#### 1.9.0-rc.13\n\n* Update Hysteria protocol\n* Update quic-go to v0.43.0\n* Update gVisor to 20240422.0\n* Fixes and improvements\n\n#### 1.8.12\n\n* Now we have official APT and DNF repositories **1**\n* Fix packet MTU for QUIC protocols\n* Fixes and improvements\n\n**1**:\n\nIncluding stable and beta versions, see https://sing-box.sagernet.org/installation/package-manager/\n\n#### 1.9.0-rc.11\n\n* Fixes and improvements\n\n#### 1.8.11\n\n* Fixes and improvements\n\n#### 1.8.10\n\n* Fixes and improvements\n\n#### 1.9.0-beta.17\n\n* Update `quic-go` to v0.42.0\n* Fixes and improvements\n\n#### 1.9.0-beta.16\n\n* Fixes and improvements\n\n_Our Testflight distribution has been temporarily blocked by Apple (possibly due to too many beta versions)\nand you cannot join the test, install or update the sing-box beta app right now.\nPlease wait patiently for processing._\n\n#### 1.9.0-beta.14\n\n* Update gVisor to 20240212.0-65-g71212d503\n* Fixes and improvements\n\n#### 1.8.9\n\n* Fixes and improvements\n\n#### 1.8.8\n\n* Fixes and improvements\n\n#### 1.9.0-beta.7\n\n* Fixes and improvements\n\n#### 1.9.0-beta.6\n\n* Fix address filter DNS rule items **1**\n* Fix DNS outbound responding with wrong data\n* Fixes and improvements\n\n**1**:\n\nFixed an issue where address filter DNS rule was incorrectly rejected under certain circumstances.\nIf you have enabled `store_rdrc` to save results, consider clearing the cache file.\n\n#### 1.8.7\n\n* Fixes and improvements\n\n#### 1.9.0-alpha.15\n\n* Fixes and improvements\n\n#### 1.9.0-alpha.14\n\n* Improve DNS truncate behavior\n* Fixes and improvements\n\n#### 1.9.0-alpha.13\n\n* Fixes and improvements\n\n#### 1.8.6\n\n* Fixes and improvements\n\n#### 1.9.0-alpha.12\n\n* Handle Windows power events\n* Always disable cache for fake-ip DNS transport if `dns.independent_cache` disabled\n* Fixes and improvements\n\n#### 1.9.0-alpha.11\n\n* Fix missing `rule_set_ipcidr_match_source` item in DNS rules **1**\n* Fixes and improvements\n\n**1**:\n\nSee [DNS Rule](/configuration/dns/rule/).\n\n#### 1.9.0-alpha.10\n\n* Add `bypass_domain` and `search_domain` platform HTTP proxy options **1**\n* Fixes and improvements\n\n**1**:\n\nSee [TUN](/configuration/inbound/tun) inbound.\n\n#### 1.9.0-alpha.8\n\n* Add rejected DNS response cache support **1**\n* Fixes and improvements\n\n**1**:\n\nThe new feature allows you to cache the check results of\n[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.\n\n#### 1.9.0-alpha.7\n\n* Update gVisor to 20240206.0\n* Fixes and improvements\n\n#### 1.9.0-alpha.6\n\n* Fixes and improvements\n\n#### 1.9.0-alpha.3\n\n* Update `quic-go` to v0.41.0\n* Fixes and improvements\n\n#### 1.9.0-alpha.2\n\n* Add support for `client-subnet` DNS options **1**\n* Fixes and improvements\n\n**1**:\n\nSee [DNS](/configuration/dns), [DNS Server](/configuration/dns/server) and [DNS Rules](/configuration/dns/rule).\n\nSince this feature makes the scenario mentioned in `alpha.1` no longer leak DNS requests,\nthe [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) has been updated.\n\n#### 1.9.0-alpha.1\n\n* `domain_suffix` behavior update **1**\n* `process_path` format update on Windows **2**\n* Add address filter DNS rule items **3**\n\n**1**:\n\nSee [Migration](/migration/#domain_suffix-behavior-update).\n\n**2**:\n\nSee [Migration](/migration/#process_path-format-update-on-windows).\n\n**3**:\n\nThe new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS\nif using this method.\n\nSee [Address Filter Fields](/configuration/dns/rule#address-filter-fields).\n\n[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.\n\n#### 1.8.5\n\n* Fixes and improvements\n\n#### 1.8.4\n\n* Fixes and improvements\n\n#### 1.8.2\n\n* Fixes and improvements\n\n#### 1.8.1\n\n* Fixes and improvements\n\n### 1.8.0\n\n* Fixes and improvements\n\nImportant changes since 1.7:\n\n* Migrate cache file from Clash API to independent options **1**\n* Introducing [rule-set](/configuration/rule-set/) **2**\n* Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3**\n* Allow nested logical rules **4**\n* Independent `source_ip_is_private` and `ip_is_private` rules **5**\n* Add context to JSON decode error message **6**\n* Reject internal fake-ip queries **7**\n* Add GSO support for TUN and WireGuard system interface **8**\n* Add `idle_timeout` for URLTest outbound **9**\n* Add simple loopback detect\n* Optimize memory usage of idle connections\n* Update uTLS to 1.5.4 **10**\n* Update dependencies **11**\n\n**1**:\n\nSee [Cache File](/configuration/experimental/cache-file/) and\n[Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-options).\n\n**2**:\n\nrule-set is independent collections of rules that can be compiled into binaries to improve performance.\nCompared to legacy GeoIP and Geosite resources,\nit can include more types of rules, load faster,\nuse less memory, and update automatically.\n\nSee [Route#rule_set](/configuration/route/#rule_set),\n[Route Rule](/configuration/route/rule/),\n[DNS Rule](/configuration/dns/rule/),\n[rule-set](/configuration/rule-set/),\n[Source Format](/configuration/rule-set/source-format/) and\n[Headless Rule](/configuration/rule-set/headless-rule/).\n\nFor GEO resources migration, see [Migrate GeoIP to rule-sets](/migration/#migrate-geoip-to-rule-sets) and\n[Migrate Geosite to rule-sets](/migration/#migrate-geosite-to-rule-sets).\n\n**3**:\n\nNew commands manage GeoIP, Geosite and rule-set resources, and help you migrate GEO resources to rule-sets.\n\n**4**:\n\nLogical rules in route rules, DNS rules, and the new headless rule now allow nesting of logical rules.\n\n**5**:\n\nThe `private` GeoIP country never existed and was actually implemented inside V2Ray.\nSince GeoIP was deprecated, we made this rule independent, see [Migration](/migration/#migrate-geoip-to-rule-sets).\n\n**6**:\n\nJSON parse errors will now include the current key path.\nOnly takes effect when compiled with Go 1.21+.\n\n**7**:\n\nAll internal DNS queries now skip DNS rules with `server` type `fakeip`,\nand the default DNS server can no longer be `fakeip`.\n\nThis change is intended to break incorrect usage and essentially requires no action.\n\n**8**:\n\nSee [TUN](/configuration/inbound/tun/) inbound and [WireGuard](/configuration/outbound/wireguard/) outbound.\n\n**9**:\n\nWhen URLTest is idle for a certain period of time, the scheduled delay test will be paused.\n\n**10**:\n\nAdded some new [fingerprints](/configuration/shared/tls#utls).\nAlso, starting with this release, uTLS requires at least Go 1.20.\n\n**11**:\n\nUpdated `cloudflare-tls`, `gomobile`, `smux`, `tfo-go` and `wireguard-go` to latest, `quic-go` to `0.40.1` and  `gvisor`\nto `20231204.0`\n\n#### 1.8.0-rc.11\n\n* Fixes and improvements\n\n#### 1.7.8\n\n* Fixes and improvements\n\n#### 1.8.0-rc.10\n\n* Fixes and improvements\n\n#### 1.7.7\n\n* Fix V2Ray transport `path` validation behavior **1**\n* Fixes and improvements\n\n**1**:\n\nSee [V2Ray transport](/configuration/shared/v2ray-transport/).\n\n#### 1.8.0-rc.7\n\n* Fixes and improvements\n\n#### 1.8.0-rc.3\n\n* Fix V2Ray transport `path` validation behavior **1**\n* Fixes and improvements\n\n**1**:\n\nSee [V2Ray transport](/configuration/shared/v2ray-transport/).\n\n#### 1.7.6\n\n* Fixes and improvements\n\n#### 1.8.0-rc.1\n\n* Fixes and improvements\n\n#### 1.8.0-beta.9\n\n* Add simple loopback detect\n* Fixes and improvements\n\n#### 1.7.5\n\n* Fixes and improvements\n\n#### 1.8.0-alpha.17\n\n* Add GSO support for TUN and WireGuard system interface **1**\n* Update uTLS to 1.5.4 **2**\n* Update dependencies **3**\n* Fixes and improvements\n\n**1**:\n\nSee [TUN](/configuration/inbound/tun/) inbound and [WireGuard](/configuration/outbound/wireguard/) outbound.\n\n**2**:\n\nAdded some new [fingerprints](/configuration/shared/tls#utls).\nAlso, starting with this release, uTLS requires at least Go 1.20.\n\n**3**:\n\nUpdated `cloudflare-tls`, `gomobile`, `smux`, `tfo-go` and `wireguard-go` to latest, and `gvisor` to `20231204.0`\n\nThis may break something, good luck!\n\n#### 1.7.4\n\n* Fixes and improvements\n\n_Due to the long waiting time, this version is no longer waiting for approval\nby the Apple App Store, so updates to Apple Platforms will be delayed._\n\n#### 1.8.0-alpha.16\n\n* Fixes and improvements\n\n#### 1.8.0-alpha.15\n\n* Some chaotic changes **1**\n* Fixes and improvements\n\n**1**:\n\nDesigned to optimize memory usage of idle connections, may take effect on the following protocols:\n\n| Protocol                                             | TCP              | UDP              |\n|------------------------------------------------------|------------------|------------------|\n| HTTP proxy server                                    | :material-check: | /                |\n| SOCKS5                                               | :material-close: | :material-check: |\n| Shadowsocks none/AEAD/AEAD2022                       | :material-check: | :material-check: |\n| Trojan                                               | /                | :material-check: |\n| TUIC/Hysteria/Hysteria2                              | :material-close: | :material-check: |\n| Multiplex                                            | :material-close: | :material-check: |\n| Plain TLS (Trojan/VLESS without extra sub-protocols) | :material-check: | /                |\n| Other protocols                                      | :material-close: | :material-close: |\n\nAt the same time, everything existing may be broken, please actively report problems with this version.\n\n#### 1.8.0-alpha.13\n\n* Fixes and improvements\n\n#### 1.8.0-alpha.10\n\n* Add `idle_timeout` for URLTest outbound **1**\n* Fixes and improvements\n\n**1**:\n\nWhen URLTest is idle for a certain period of time, the scheduled delay test will be paused.\n\n#### 1.7.2\n\n* Fixes and improvements\n\n#### 1.8.0-alpha.8\n\n* Add context to JSON decode error message **1**\n* Reject internal fake-ip queries **2**\n* Fixes and improvements\n\n**1**:\n\nJSON parse errors will now include the current key path.\nOnly takes effect when compiled with Go 1.21+.\n\n**2**:\n\nAll internal DNS queries now skip DNS rules with `server` type `fakeip`,\nand the default DNS server can no longer be `fakeip`.\n\nThis change is intended to break incorrect usage and essentially requires no action.\n\n#### 1.8.0-alpha.7\n\n* Fixes and improvements\n\n#### 1.7.1\n\n* Fixes and improvements\n\n#### 1.8.0-alpha.6\n\n* Fix rule-set matching logic **1**\n* Fixes and improvements\n\n**1**:\n\nNow the rules in the `rule_set` rule item can be logically considered to be merged into the rule using rule-sets,\nrather than completely following the AND logic.\n\n#### 1.8.0-alpha.5\n\n* Parallel rule-set initialization\n* Independent `source_ip_is_private` and `ip_is_private` rules **1**\n\n**1**:\n\nThe `private` GeoIP country never existed and was actually implemented inside V2Ray.\nSince GeoIP was deprecated, we made this rule independent, see [Migration](/migration/#migrate-geoip-to-rule-sets).\n\n#### 1.8.0-alpha.1\n\n* Migrate cache file from Clash API to independent options **1**\n* Introducing [rule-set](/configuration/rule-set/) **2**\n* Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3**\n* Allow nested logical rules **4**\n\n**1**:\n\nSee [Cache File](/configuration/experimental/cache-file/) and\n[Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-options).\n\n**2**:\n\nrule-set is independent collections of rules that can be compiled into binaries to improve performance.\nCompared to legacy GeoIP and Geosite resources,\nit can include more types of rules, load faster,\nuse less memory, and update automatically.\n\nSee [Route#rule_set](/configuration/route/#rule_set),\n[Route Rule](/configuration/route/rule/),\n[DNS Rule](/configuration/dns/rule/),\n[rule-set](/configuration/rule-set/),\n[Source Format](/configuration/rule-set/source-format/) and\n[Headless Rule](/configuration/rule-set/headless-rule/).\n\nFor GEO resources migration, see [Migrate GeoIP to rule-sets](/migration/#migrate-geoip-to-rule-sets) and\n[Migrate Geosite to rule-sets](/migration/#migrate-geosite-to-rule-sets).\n\n**3**:\n\nNew commands manage GeoIP, Geosite and rule-set resources, and help you migrate GEO resources to rule-sets.\n\n**4**:\n\nLogical rules in route rules, DNS rules, and the new headless rule now allow nesting of logical rules.\n\n### 1.7.0\n\n* Fixes and improvements\n\nImportant changes since 1.6:\n\n* Add [exclude route support](/configuration/inbound/tun/) for TUN inbound\n* Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen/) **1**\n* Add [HTTPUpgrade V2Ray transport](/configuration/shared/v2ray-transport#HTTPUpgrade) support **2**\n* Migrate multiplex and UoT server to inbound **3**\n* Add TCP Brutal support for multiplex **4**\n* Add `wifi_ssid` and `wifi_bssid` route and DNS rules **5**\n* Update quic-go to v0.40.0\n* Update gVisor to 20231113.0\n\n**1**:\n\nIf enabled, for UDP proxy requests addressed to a domain,\nthe original packet address will be sent in the response instead of the mapped domain.\n\nThis option is used for compatibility with clients that\ndo not support receiving UDP packets with domain addresses, such as Surge.\n\n**2**:\n\nIntroduced in V2Ray 5.10.0.\n\nThe new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse.\n\n**3**:\n\nStarting in 1.7.0, multiplexing support is no longer enabled by default\nand needs to be turned on explicitly in inbound\noptions.\n\n**4**\n\nHysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server,\nsee [TCP Brutal](/configuration/shared/tcp-brutal/) for details.\n\n**5**:\n\nOnly supported in graphical clients on Android and Apple platforms.\n\n#### 1.7.0-rc.3\n\n* Fixes and improvements\n\n#### 1.6.7\n\n* macOS: Add button for uninstall SystemExtension in the standalone graphical client\n* Fix missing UDP user context on TUIC/Hysteria2 inbounds\n* Fixes and improvements\n\n#### 1.7.0-rc.2\n\n* Fix missing UDP user context on TUIC/Hysteria2 inbounds\n* macOS: Add button for uninstall SystemExtension in the standalone graphical client\n\n#### 1.6.6\n\n* Fixes and improvements\n\n#### 1.7.0-rc.1\n\n* Fixes and improvements\n\n#### 1.7.0-beta.5\n\n* Update gVisor to 20231113.0\n* Fixes and improvements\n\n#### 1.7.0-beta.4\n\n* Add `wifi_ssid` and `wifi_bssid` route and DNS rules **1**\n* Fixes and improvements\n\n**1**:\n\nOnly supported in graphical clients on Android and Apple platforms.\n\n#### 1.7.0-beta.3\n\n* Fix zero TTL was incorrectly reset\n* Fixes and improvements\n\n#### 1.6.5\n\n* Fix crash if TUIC inbound authentication failed\n* Fixes and improvements\n\n#### 1.7.0-beta.2\n\n* Fix crash if TUIC inbound authentication failed\n* Update quic-go to v0.40.0\n* Fixes and improvements\n\n#### 1.6.4\n\n* Fixes and improvements\n\n#### 1.7.0-beta.1\n\n* Fixes and improvements\n\n#### 1.6.3\n\n* iOS/Android: Fix profile auto update\n* Fixes and improvements\n\n#### 1.7.0-alpha.11\n\n* iOS/Android: Fix profile auto update\n* Fixes and improvements\n\n#### 1.7.0-alpha.10\n\n* Fix tcp-brutal not working with TLS\n* Fix Android client not closing in some cases\n* Fixes and improvements\n\n#### 1.6.2\n\n* Fixes and improvements\n\n#### 1.6.1\n\n* Our [Android client](/installation/clients/sfa/) is now available in the Google Play Store ▶️\n* Fixes and improvements\n\n#### 1.7.0-alpha.6\n\n* Fixes and improvements\n\n#### 1.7.0-alpha.4\n\n* Migrate multiplex and UoT server to inbound **1**\n* Add TCP Brutal support for multiplex **2**\n\n**1**:\n\nStarting in 1.7.0, multiplexing support is no longer enabled by default and needs to be turned on explicitly in inbound\noptions.\n\n**2**\n\nHysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server,\nsee [TCP Brutal](/configuration/shared/tcp-brutal/) for details.\n\n#### 1.7.0-alpha.3\n\n* Add [HTTPUpgrade V2Ray transport](/configuration/shared/v2ray-transport#HTTPUpgrade) support **1**\n* Fixes and improvements\n\n**1**:\n\nIntroduced in V2Ray 5.10.0.\n\nThe new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse.\n\n### 1.6.0\n\n* Fixes and improvements\n\nImportant changes since 1.5:\n\n* Our [Apple tvOS client](/installation/clients/sft/) is now available in the App Store 🍎\n* Update BBR congestion control for TUIC and Hysteria2 **1**\n* Update brutal congestion control for Hysteria2\n* Add `brutal_debug` option for Hysteria2\n* Update legacy Hysteria protocol **2**\n* Add TLS self sign key pair generate command\n* Remove [Deprecated Features](/deprecated/) by agreement\n\n**1**:\n\nNone of the existing Golang BBR congestion control implementations have been reviewed or unit tested.\nThis update is intended to address the multi-send defects of the old implementation and may introduce new issues.\n\n**2**\n\nBased on discussions with the original author, the brutal CC and QUIC protocol parameters of\nthe old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2\n\n#### 1.7.0-alpha.2\n\n* Fix bugs introduced in 1.7.0-alpha.1\n\n#### 1.7.0-alpha.1\n\n* Add [exclude route support](/configuration/inbound/tun/) for TUN inbound\n* Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen/) **1**\n* Fixes and improvements\n\n**1**:\n\nIf enabled, for UDP proxy requests addressed to a domain,\nthe original packet address will be sent in the response instead of the mapped domain.\n\nThis option is used for compatibility with clients that\ndo not support receiving UDP packets with domain addresses, such as Surge.\n\n#### 1.5.5\n\n* Fix IPv6 `auto_route` for Linux **1**\n* Add legacy builds for old Windows and macOS systems **2**\n* Fixes and improvements\n\n**1**:\n\nWhen `auto_route` is enabled and `strict_route` is disabled, the device can now be reached from external IPv6 addresses.\n\n**2**:\n\nBuilt using Go 1.20, the last version that will run on\nWindows 7, 8, Server 2008, Server 2012 and macOS 10.13 High\nSierra, 10.14 Mojave.\n\n#### 1.6.0-rc.4\n\n* Fixes and improvements\n\n#### 1.6.0-rc.1\n\n* Add legacy builds for old Windows and macOS systems **1**\n* Fixes and improvements\n\n**1**:\n\nBuilt using Go 1.20, the last version that will run on\nWindows 7, 8, Server 2008, Server 2012 and macOS 10.13 High\nSierra, 10.14 Mojave.\n\n#### 1.6.0-beta.4\n\n* Fix IPv6 `auto_route` for Linux **1**\n* Fixes and improvements\n\n**1**:\n\nWhen `auto_route` is enabled and `strict_route` is disabled, the device can now be reached from external IPv6 addresses.\n\n#### 1.5.4\n\n* Fix Clash cache crash on arm32 devices\n* Fixes and improvements\n\n#### 1.6.0-beta.3\n\n* Update the legacy Hysteria protocol **1**\n* Fixes and improvements\n\n**1**\n\nBased on discussions with the original author, the brutal CC and QUIC protocol parameters of\nthe old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2\n\n#### 1.6.0-beta.2\n\n* Add TLS self sign key pair generate command\n* Update brutal congestion control for Hysteria2\n* Fix Clash cache crash on arm32 devices\n* Update golang.org/x/net to v0.17.0\n* Fixes and improvements\n\n#### 1.6.0-beta.3\n\n* Update the legacy Hysteria protocol **1**\n* Fixes and improvements\n\n**1**\n\nBased on discussions with the original author, the brutal CC and QUIC protocol parameters of\nthe old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2\n\n#### 1.6.0-beta.2\n\n* Add TLS self sign key pair generate command\n* Update brutal congestion control for Hysteria2\n* Fix Clash cache crash on arm32 devices\n* Update golang.org/x/net to v0.17.0\n* Fixes and improvements\n\n#### 1.5.3\n\n* Fix compatibility with Android 14\n* Fixes and improvements\n\n#### 1.6.0-beta.1\n\n* Fixes and improvements\n\n#### 1.6.0-alpha.5\n\n* Fix compatibility with Android 14\n* Update BBR congestion control for TUIC and Hysteria2 **1**\n* Fixes and improvements\n\n**1**:\n\nNone of the existing Golang BBR congestion control implementations have been reviewed or unit tested.\nThis update is intended to fix a memory leak flaw in the new implementation introduced in 1.6.0-alpha.1 and may\nintroduce new issues.\n\n#### 1.6.0-alpha.4\n\n* Add `brutal_debug` option for Hysteria2\n* Fixes and improvements\n\n#### 1.5.2\n\n* Our [Apple tvOS client](/installation/clients/sft/) is now available in the App Store 🍎\n* Fixes and improvements\n\n#### 1.6.0-alpha.3\n\n* Fixes and improvements\n\n#### 1.6.0-alpha.2\n\n* Fixes and improvements\n\n#### 1.5.1\n\n* Fixes and improvements\n\n#### 1.6.0-alpha.1\n\n* Update BBR congestion control for TUIC and Hysteria2 **1**\n* Update quic-go to v0.39.0\n* Update gVisor to 20230814.0\n* Remove [Deprecated Features](/deprecated/) by agreement\n* Fixes and improvements\n\n**1**:\n\nNone of the existing Golang BBR congestion control implementations have been reviewed or unit tested.\nThis update is intended to address the multi-send defects of the old implementation and may introduce new issues.\n\n### 1.5.0\n\n* Fixes and improvements\n\nImportant changes since 1.4:\n\n* Add TLS [ECH server](/configuration/shared/tls/) support\n* Improve TLS TCH client configuration\n* Add TLS ECH key pair generator **1**\n* Add TLS ECH support for QUIC based protocols **2**\n* Add KDE support for the `set_system_proxy` option in HTTP inbound\n* Add Hysteria2 protocol support **3**\n* Add `interrupt_exist_connections` option for `Selector` and `URLTest` outbounds **4**\n* Add DNS01 challenge support for ACME TLS certificate issuer **5**\n* Add `merge` command **6**\n* Mark [Deprecated Features](/deprecated/)\n\n**1**:\n\nCommand: `sing-box generate ech-keypair <plain_server_name> [--pq-signature-schemes-enabled]`\n\n**2**:\n\nAll inbounds and outbounds are supported, including `Naiveproxy`, `Hysteria[/2]`, `TUIC` and `V2ray QUIC transport`.\n\n**3**:\n\nSee [Hysteria2 inbound](/configuration/inbound/hysteria2/) and [Hysteria2 outbound](/configuration/outbound/hysteria2/)\n\nFor protocol description, please refer to [https://v2.hysteria.network](https://v2.hysteria.network)\n\n**4**:\n\nInterrupt existing connections when the selected outbound has changed.\n\nOnly inbound connections are affected by this setting, internal connections will always be interrupted.\n\n**5**:\n\nOnly `Alibaba Cloud DNS` and `Cloudflare` are supported, see [ACME Fields](/configuration/shared/tls#acme-fields)\nand [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/).\n\n**6**:\n\nThis command also parses path resources that appear in the configuration file and replaces them with embedded\nconfiguration, such as TLS certificates or SSH private keys.\n\n#### 1.5.0-rc.6\n\n* Fixes and improvements\n\n#### 1.4.6\n\n* Fixes and improvements\n\n#### 1.5.0-rc.5\n\n* Fixed an improper authentication vulnerability in the SOCKS5 inbound\n* Fixes and improvements\n\n**Security Advisory**\n\nThis update fixes an improper authentication vulnerability in the sing-box SOCKS inbound. This vulnerability allows an\nattacker to craft special requests to bypass user authentication. All users exposing SOCKS servers with user\nauthentication in an insecure environment are advised to update immediately.\n\n此更新修复了 sing-box SOCKS 入站中的一个不正确身份验证漏洞。 该漏洞允许攻击者制作特殊请求来绕过用户身份验证。建议所有将使用用户认证的\nSOCKS 服务器暴露在不安全环境下的用户立更新。\n\n#### 1.4.5\n\n* Fixed an improper authentication vulnerability in the SOCKS5 inbound\n* Fixes and improvements\n\n**Security Advisory**\n\nThis update fixes an improper authentication vulnerability in the sing-box SOCKS inbound. This vulnerability allows an\nattacker to craft special requests to bypass user authentication. All users exposing SOCKS servers with user\nauthentication in an insecure environment are advised to update immediately.\n\n此更新修复了 sing-box SOCKS 入站中的一个不正确身份验证漏洞。 该漏洞允许攻击者制作特殊请求来绕过用户身份验证。建议所有将使用用户认证的\nSOCKS 服务器暴露在不安全环境下的用户立更新。\n\n#### 1.5.0-rc.3\n\n* Fixes and improvements\n\n#### 1.5.0-beta.12\n\n* Add `merge` command **1**\n* Fixes and improvements\n\n**1**:\n\nThis command also parses path resources that appear in the configuration file and replaces them with embedded\nconfiguration, such as TLS certificates or SSH private keys.\n\n```\nMerge configurations\n\nUsage:\n  sing-box merge [output] [flags]\n\nFlags:\n  -h, --help   help for merge\n\nGlobal Flags:\n  -c, --config stringArray             set configuration file path\n  -C, --config-directory stringArray   set configuration directory path\n  -D, --directory string               set working directory\n      --disable-color                  disable color output\n```\n\n#### 1.5.0-beta.11\n\n* Add DNS01 challenge support for ACME TLS certificate issuer **1**\n* Fixes and improvements\n\n**1**:\n\nOnly `Alibaba Cloud DNS` and `Cloudflare` are supported,\nsee [ACME Fields](/configuration/shared/tls#acme-fields)\nand [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/).\n\n#### 1.5.0-beta.10\n\n* Add `interrupt_exist_connections` option for `Selector` and `URLTest` outbounds **1**\n* Fixes and improvements\n\n**1**:\n\nInterrupt existing connections when the selected outbound has changed.\n\nOnly inbound connections are affected by this setting, internal connections will always be interrupted.\n\n#### 1.4.3\n\n* Fixes and improvements\n\n#### 1.5.0-beta.8\n\n* Fixes and improvements\n\n#### 1.4.2\n\n* Fixes and improvements\n\n#### 1.5.0-beta.6\n\n* Fix compatibility issues with official Hysteria2 server and client\n* Fixes and improvements\n* Mark [deprecated features](/deprecated/)\n\n#### 1.5.0-beta.3\n\n* Fixes and improvements\n* Updated Hysteria2 documentation **1**\n\n**1**:\n\nAdded notes indicating compatibility issues with the official\nHysteria2 server and client when using `fastOpen=false` or UDP MTU >= 1200.\n\n#### 1.5.0-beta.2\n\n* Add hysteria2 protocol support **1**\n* Fixes and improvements\n\n**1**:\n\nSee [Hysteria2 inbound](/configuration/inbound/hysteria2/) and [Hysteria2 outbound](/configuration/outbound/hysteria2/)\n\nFor protocol description, please refer to [https://v2.hysteria.network](https://v2.hysteria.network)\n\n#### 1.5.0-beta.1\n\n* Add TLS [ECH server](/configuration/shared/tls/) support\n* Improve TLS TCH client configuration\n* Add TLS ECH key pair generator **1**\n* Add TLS ECH support for QUIC based protocols **2**\n* Add KDE support for the `set_system_proxy` option in HTTP inbound\n\n**1**:\n\nCommand: `sing-box generate ech-keypair <plain_server_name> [--pq-signature-schemes-enabled]`\n\n**2**:\n\nAll inbounds and outbounds are supported, including `Naiveproxy`, `Hysteria`, `TUIC` and `V2ray QUIC transport`.\n\n#### 1.4.1\n\n* Fixes and improvements\n\n### 1.4.0\n\n* Fix bugs and update dependencies\n\nImportant changes since 1.3:\n\n* Add TUIC support **1**\n* Add `udp_over_stream` option for TUIC client **2**\n* Add MultiPath TCP support **3**\n* Add `include_interface` and `exclude_interface` options for tun inbound\n* Pause recurring tasks when no network or device idle\n* Improve Android and Apple platform clients\n\n*1*:\n\nSee [TUIC inbound](/configuration/inbound/tuic/)\nand [TUIC outbound](/configuration/outbound/tuic/)\n\n**2**:\n\nThis is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp/), designed to provide a QUIC\nstream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or\nanother program compatible with the protocol as a server.\n\nThis mode has no positive effect in a proper UDP proxy scenario and should only be applied to relay streaming UDP\ntraffic (basically QUIC streams).\n\n*3*:\n\nRequires sing-box to be compiled with Go 1.21.\n\n#### 1.4.0-rc.3\n\n* Fixes and improvements\n\n#### 1.4.0-rc.2\n\n* Fixes and improvements\n\n#### 1.4.0-rc.1\n\n* Fix TUIC UDP\n\n#### 1.4.0-beta.6\n\n* Add `udp_over_stream` option for TUIC client **1**\n* Add `include_interface` and `exclude_interface` options for tun inbound\n* Fixes and improvements\n\n**1**:\n\nThis is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp/), designed to provide a QUIC\nstream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or\nanother program compatible with the protocol as a server.\n\nThis mode has no positive effect in a proper UDP proxy scenario and should only be applied to relay streaming UDP\ntraffic (basically QUIC streams).\n\n#### 1.4.0-beta.5\n\n* Fixes and improvements\n\n#### 1.4.0-beta.4\n\n* Graphical clients: Persistence group expansion state\n* Fixes and improvements\n\n#### 1.4.0-beta.3\n\n* Fixes and improvements\n\n#### 1.4.0-beta.2\n\n* Add MultiPath TCP support **1**\n* Drop QUIC support for Go 1.18 and 1.19 due to upstream changes\n* Fixes and improvements\n\n*1*:\n\nRequires sing-box to be compiled with Go 1.21.\n\n#### 1.4.0-beta.1\n\n* Add TUIC support **1**\n* Pause recurring tasks when no network or device idle\n* Fixes and improvements\n\n*1*:\n\nSee [TUIC inbound](/configuration/inbound/tuic/)\nand [TUIC outbound](/configuration/outbound/tuic/)\n\n#### 1.3.6\n\n* Fixes and improvements\n\n#### 1.3.5\n\n* Fixes and improvements\n* Introducing our [Apple tvOS](/installation/clients/sft/) client applications **1**\n* Add per app proxy and app installed/updated trigger support for Android client\n* Add profile sharing support for Android/iOS/macOS clients\n\n**1**:\n\nDue to the requirement of tvOS 17, the app cannot be submitted to the App Store for the time being, and can only be\ndownloaded through TestFlight.\n\n#### 1.3.4\n\n* Fixes and improvements\n* We're now on the [App Store](https://apps.apple.com/us/app/sing-box/id6451272673), always free! It should be noted\n  that due to stricter and slower review, the release of Store versions will be delayed.\n* We've made a standalone version of the macOS client (the original Application Extension relies on App Store\n  distribution), which you can download as SFM-version-universal.zip in the release artifacts.\n\n#### 1.3.3\n\n* Fixes and improvements\n\n#### 1.3.1-rc.1\n\n* Fix bugs and update dependencies\n\n#### 1.3.1-beta.3\n\n* Introducing our [new iOS](/installation/clients/sfi/) and [macOS](/installation/clients/sfm/) client applications **1\n  **\n* Fixes and improvements\n\n**1**:\n\nThe old testflight link and app are no longer valid.\n\n#### 1.3.1-beta.2\n\n* Fix bugs and update dependencies\n\n#### 1.3.1-beta.1\n\n* Fixes and improvements\n\n### 1.3.0\n\n* Fix bugs and update dependencies\n\nImportant changes since 1.2:\n\n* Add [FakeIP](/configuration/dns/fakeip/) support **1**\n* Improve multiplex **2**\n* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support\n* Add `rewrite_ttl` DNS rule action\n* Add `store_fakeip` Clash API option\n* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound\n* Add loopback detect\n* Add Clash.Meta API compatibility for Clash API\n* Download Yacd-meta by default if the specified Clash `external_ui` directory is empty\n* Add path and headers option for HTTP outbound\n* Perform URLTest recheck after network changes\n* Fix `system` tun stack for ios\n* Fix network monitor for android/ios\n* Update VLESS and XUDP protocol\n* Make splice work with traffic statistics systems like Clash API\n* Significantly reduces memory usage of idle connections\n* Improve DNS caching\n* Add `independent_cache` [option](/configuration/dns#independent_cache) for DNS\n* Reimplemented shadowsocks client\n* Add multiplex support for VLESS outbound\n* Automatically add Windows firewall rules in order for the system tun stack to work\n* Fix TLS 1.2 support for shadow-tls client\n* Add `cache_id` [option](/configuration/experimental#cache_id) for Clash cache file\n* Fix `local` DNS transport for Android\n\n*1*:\n\nSee [FAQ](/faq/fakeip/) for more information.\n\n*2*:\n\nAdded new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex/).\n\n#### 1.3-rc2\n\n* Fix `local` DNS transport for Android\n* Fix bugs and update dependencies\n\n#### 1.3-rc1\n\n* Fix bugs and update dependencies\n\n#### 1.3-beta14\n\n* Fixes and improvements\n\n#### 1.3-beta13\n\n* Fix resolving fakeip domains  **1**\n* Deprecate L3 routing\n* Fix bugs and update dependencies\n\n**1**:\n\nIf the destination address of the connection is obtained from fakeip, dns rules with server type fakeip will be skipped.\n\n#### 1.3-beta12\n\n* Automatically add Windows firewall rules in order for the system tun stack to work\n* Fix TLS 1.2 support for shadow-tls client\n* Add `cache_id` [option](/configuration/experimental#cache_id) for Clash cache file\n* Fixes and improvements\n\n#### 1.3-beta11\n\n* Fix bugs and update dependencies\n\n#### 1.3-beta10\n\n* Improve direct copy **1**\n* Improve DNS caching\n* Add `independent_cache` [option](/configuration/dns#independent_cache) for DNS\n* Reimplemented shadowsocks client **2**\n* Add multiplex support for VLESS outbound\n* Set TCP keepalive for WireGuard gVisor TCP connections\n* Fixes and improvements\n\n**1**:\n\n* Make splice work with traffic statistics systems like Clash API\n* Significantly reduces memory usage of idle connections\n\n**2**:\n\nImproved performance and reduced memory usage.\n\n#### 1.3-beta9\n\n* Improve multiplex **1**\n* Fixes and improvements\n\n*1*:\n\nAdded new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex/).\n\n#### 1.2.6\n\n* Fix bugs and update dependencies\n\n#### 1.3-beta8\n\n* Fix `system` tun stack for ios\n* Fix network monitor for android/ios\n* Update VLESS and XUDP protocol **1**\n* Fixes and improvements\n\n*1:\n\nThis is an incompatible update for XUDP in VLESS if vision flow is enabled.\n\n#### 1.3-beta7\n\n* Add `path` and `headers` options for HTTP outbound\n* Add multi-user support for Shadowsocks legacy AEAD inbound\n* Fixes and improvements\n\n#### 1.2.4\n\n* Fixes and improvements\n\n#### 1.3-beta6\n\n* Fix WireGuard reconnect\n* Perform URLTest recheck after network changes\n* Fix bugs and update dependencies\n\n#### 1.3-beta5\n\n* Add Clash.Meta API compatibility for Clash API\n* Download Yacd-meta by default if the specified Clash `external_ui` directory is empty\n* Add path and headers option for HTTP outbound\n* Fixes and improvements\n\n#### 1.3-beta4\n\n* Fix bugs\n\n#### 1.3-beta2\n\n* Download clash-dashboard if the specified Clash `external_ui` directory is empty\n* Fix bugs and update dependencies\n\n#### 1.3-beta1\n\n* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support\n* Add [L3 routing](/configuration/route/ip-rule/) support **1**\n* Add `rewrite_ttl` DNS rule action\n* Add [FakeIP](/configuration/dns/fakeip/) support **2**\n* Add `store_fakeip` Clash API option\n* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound\n* Add loopback detect\n\n*1*:\n\nIt can currently be used to [route connections directly to WireGuard](/examples/wireguard-direct/) or block connections\nat the IP layer.\n\n*2*:\n\nSee [FAQ](/faq/fakeip/) for more information.\n\n#### 1.2.3\n\n* Introducing our [new Android client application](/installation/clients/sfa/)\n* Improve UDP domain destination NAT\n* Update reality protocol\n* Fix TTL calculation for DNS response\n* Fix v2ray HTTP transport compatibility\n* Fix bugs and update dependencies\n\n#### 1.2.2\n\n* Accept `any` outbound in dns rule **1**\n* Fix bugs and update dependencies\n\n*1*:\n\nNow you can use the `any` outbound rule to match server address queries instead of filling in all server domains\nto `domain` rule.\n\n#### 1.2.1\n\n* Fix missing default host in v2ray http transport`s request\n* Flush DNS cache for macOS when tun start/close\n* Fix tun's DNS hijacking compatibility with systemd-resolved\n\n### 1.2.0\n\n* Fix bugs and update dependencies\n\nImportant changes since 1.1:\n\n* Introducing our [new iOS client application](/installation/clients/sfi/)\n* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp/)\n* Add [platform options](/configuration/inbound/tun#platform) for tun inbound\n* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)\n* Add [VLESS server](/configuration/inbound/vless/) and [vision](/configuration/outbound/vless#flow) support\n* Add [reality TLS](/configuration/shared/tls/) support\n* Add [NTP service](/configuration/ntp/)\n* Add [DHCP DNS server](/configuration/dns/server/) support\n* Add SSH [host key validation](/configuration/outbound/ssh/) support\n* Add [query_type](/configuration/dns/rule/) DNS rule item\n* Add fallback support for v2ray transport\n* Add custom TLS server support for http based v2ray transports\n* Add health check support for http-based v2ray transports\n* Add multiple configuration support\n\n#### 1.2-rc1\n\n* Fix bugs and update dependencies\n\n#### 1.2-beta10\n\n* Add multiple configuration support **1**\n* Fix bugs and update dependencies\n\n*1*:\n\nNow you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C`\nto load all configuration files in a directory.\n\nLoaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file\nname.\n\n#### 1.1.7\n\n* Improve the stability of the VMESS server\n* Fix `auto_detect_interface` incorrectly identifying the default interface on Windows\n* Fix bugs and update dependencies\n\n#### 1.2-beta9\n\n* Introducing the [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp/)\n* Add health check support for http-based v2ray transports\n* Remove length limit on short_id for reality TLS config\n* Fix bugs and update dependencies\n\n#### 1.2-beta8\n\n* Update reality and uTLS libraries\n* Fix `auto_detect_interface` incorrectly identifying the default interface on Windows\n\n#### 1.2-beta7\n\n* Fix the compatibility issue between VLESS's vision sub-protocol and the Xray-core client\n* Improve the stability of the VMESS server\n\n#### 1.2-beta6\n\n* Introducing our [new iOS client application](/installation/clients/sfi/)\n* Add [platform options](/configuration/inbound/tun#platform) for tun inbound\n* Add custom TLS server support for http based v2ray transports\n* Add generate commands\n* Enable XUDP by default in VLESS\n* Update reality server\n* Update vision protocol\n* Fixed [user flow in vless server](/configuration/inbound/vless#usersflow)\n* Bug fixes\n* Update dependencies\n\n#### 1.2-beta5\n\n* Add [VLESS server](/configuration/inbound/vless/) and [vision](/configuration/outbound/vless#flow) support\n* Add [reality TLS](/configuration/shared/tls/) support\n* Fix match private address\n\n#### 1.1.6\n\n* Improve vmess request\n* Fix ipv6 redirect on Linux\n* Fix match geoip private\n* Fix parse hysteria UDP message\n* Fix socks connect response\n* Disable vmess header protection if transport enabled\n* Update QUIC v2 version number and initial salt\n\n#### 1.2-beta4\n\n* Add [NTP service](/configuration/ntp/)\n* Add Add multiple server names and multi-user support for shadowtls\n* Add strict mode support for shadowtls v3\n* Add uTLS support for shadowtls v3\n\n#### 1.2-beta3\n\n* Update QUIC v2 version number and initial salt\n* Fix shadowtls v3 implementation\n\n#### 1.2-beta2\n\n* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)\n* Add fallback support for v2ray transport\n* Fix parse hysteria UDP message\n* Fix socks connect response\n* Disable vmess header protection if transport enabled\n\n#### 1.2-beta1\n\n* Add [DHCP DNS server](/configuration/dns/server/) support\n* Add SSH [host key validation](/configuration/outbound/ssh/) support\n* Add [query_type](/configuration/dns/rule/) DNS rule item\n* Add v2ray [user stats](/configuration/experimental#statsusers) api\n* Add new clash DNS query api\n* Improve vmess request\n* Fix ipv6 redirect on Linux\n* Fix match geoip private\n\n#### 1.1.5\n\n* Add Go 1.20 support\n* Fix inbound default DF value\n* Fix auth_user route for naive inbound\n* Fix gRPC lite header\n* Ignore domain case in route rules\n\n#### 1.1.4\n\n* Fix DNS log\n* Fix write to h2 conn after closed\n* Fix create UDP DNS transport from plain IPv6 address\n\n#### 1.1.2\n\n* Fix http proxy auth\n* Fix user from stream packet conn\n* Fix DNS response TTL\n* Fix override packet conn\n* Skip override system proxy bypass list\n* Improve DNS log\n\n#### 1.1.1\n\n* Fix acme config\n* Fix vmess packet conn\n* Suppress quic-go set DF error\n\n#### 1.1\n\n* Fix close clash cache\n\nImportant changes since 1.0:\n\n* Add support for use with android VPNService\n* Add tun support for WireGuard outbound\n* Add system tun stack\n* Add comment filter for config\n* Add option for allow optional proxy protocol header\n* Add Clash mode and persistence support\n* Add TLS ECH and uTLS support for outbound TLS options\n* Add internal simple-obfs and v2ray-plugin\n* Add ShadowsocksR outbound\n* Add VLESS outbound and XUDP\n* Skip wait for hysteria tcp handshake response\n* Add v2ray mux support for all inbound\n* Add XUDP support for VMess\n* Improve websocket writer\n* Refine tproxy write back\n* Fix DNS leak caused by\n  Windows' ordinary multihomed DNS resolution behavior\n* Add sniff_timeout listen option\n* Add custom route support for tun\n* Add option for custom wireguard reserved bytes\n* Split bind_address into ipv4 and ipv6\n* Add ShadowTLS v1 and v2 support\n\n#### 1.1-rc1\n\n* Fix TLS config for h2 server\n* Fix crash when input bad method in shadowsocks multi-user inbound\n* Fix listen UDP\n* Fix check invalid packet on macOS\n\n#### 1.1-beta18\n\n* Enhance defense against active probe for shadowtls server **1**\n\n**1**:\n\nThe `fallback_after` option has been removed.\n\n#### 1.1-beta17\n\n* Fix shadowtls server **1**\n\n*1*:\n\nAdded [fallback_after](/configuration/inbound/shadowtls#fallback_after) option.\n\n#### 1.0.7\n\n* Add support for new x/h2 deadline\n* Fix copy pipe\n* Fix decrypt xplus packet\n* Fix macOS Ventura process name match\n* Fix smux keepalive\n* Fix vmess request buffer\n* Fix h2c transport\n* Fix tor geoip\n* Fix udp connect for mux client\n* Fix default dns transport strategy\n\n#### 1.1-beta16\n\n* Improve shadowtls server\n* Fix default dns transport strategy\n* Update uTLS to v1.2.0\n\n#### 1.1-beta15\n\n* Add support for new x/h2 deadline\n* Fix udp connect for mux client\n* Fix dns buffer\n* Fix quic dns retry\n* Fix create TLS config\n* Fix websocket alpn\n* Fix tor geoip\n\n#### 1.1-beta14\n\n* Add multi-user support for hysteria inbound **1**\n* Add custom tls client support for std grpc\n* Fix smux keep alive\n* Fix vmess request buffer\n* Fix default local DNS server behavior\n* Fix h2c transport\n\n*1*:\n\nThe `auth` and `auth_str` fields have been replaced by the `users` field.\n\n#### 1.1-beta13\n\n* Add custom worker count option for WireGuard outbound\n* Split bind_address into ipv4 and ipv6\n* Move WFP manipulation to strict route\n* Fix WireGuard outbound panic when close\n* Fix macOS Ventura process name match\n* Fix QUIC connection migration by @HyNetwork\n* Fix handling QUIC client SNI by @HyNetwork\n\n#### 1.1-beta12\n\n* Fix uTLS config\n* Update quic-go to v0.30.0\n* Update cloudflare-tls to go1.18.7\n\n#### 1.1-beta11\n\n* Add option for custom wireguard reserved bytes\n* Fix shadowtls v2\n* Fix h3 dns transport\n* Fix copy pipe\n* Fix decrypt xplus packet\n* Fix v2ray api\n* Suppress no network error\n* Improve local dns transport\n\n#### 1.1-beta10\n\n* Add [sniff_timeout](/configuration/shared/listen#sniff_timeout) listen option\n* Add [custom route](/configuration/inbound/tun#inet4_route_address) support for tun **1**\n* Fix interface monitor\n* Fix websocket headroom\n* Fix uTLS handshake\n* Fix ssh outbound\n* Fix sniff fragmented quic client hello\n* Fix DF for hysteria\n* Fix naive overflow\n* Check destination before udp connect\n* Update uTLS to v1.1.5\n* Update tfo-go to v2.0.2\n* Update fsnotify to v1.6.0\n* Update grpc to v1.50.1\n\n*1*:\n\nThe `strict_route` on windows is removed.\n\n#### 1.0.6\n\n* Fix ssh outbound\n* Fix sniff fragmented quic client hello\n* Fix naive overflow\n* Check destination before udp connect\n\n#### 1.1-beta9\n\n* Fix windows route **1**\n* Add [v2ray statistics api](/configuration/experimental#v2ray-api-fields)\n* Add ShadowTLS v2 support **2**\n* Fixes and improvements\n\n**1**:\n\n* Fix DNS leak caused by\n  Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)\n* Flush Windows DNS cache when start/close\n\n**2**:\n\nSee [ShadowTLS inbound](/configuration/inbound/shadowtls#version)\nand [ShadowTLS outbound](/configuration/outbound/shadowtls#version)\n\n#### 1.1-beta8\n\n* Fix leaks on close\n* Improve websocket writer\n* Refine tproxy write back\n* Refine 4in6 processing\n* Fix shadowsocks plugins\n* Fix missing source address from transport connection\n* Fix fqdn socks5 outbound connection\n* Fix read source address from grpc-go\n\n#### 1.0.5\n\n* Fix missing source address from transport connection\n* Fix fqdn socks5 outbound connection\n* Fix read source address from grpc-go\n\n#### 1.1-beta7\n\n* Add v2ray mux and XUDP support for VMess inbound\n* Add XUDP support for VMess outbound\n* Disable DF on direct outbound by default\n* Fix bugs in 1.1-beta6\n\n#### 1.1-beta6\n\n* Add [URLTest outbound](/configuration/outbound/urltest/)\n* Fix bugs in 1.1-beta5\n\n#### 1.1-beta5\n\n* Print tags in version command\n* Redirect clash hello to external ui\n* Move shadowsocksr implementation to clash\n* Make gVisor optional **1**\n* Refactor to miekg/dns\n* Refactor bind control\n* Fix build on go1.18\n* Fix clash store-selected\n* Fix close grpc conn\n* Fix port rule match logic\n* Fix clash api proxy type\n\n*1*:\n\nThe build tag `no_gvisor` is replaced by `with_gvisor`.\n\nThe default tun stack is changed to system.\n\n#### 1.0.4\n\n* Fix close grpc conn\n* Fix port rule match logic\n* Fix clash api proxy type\n\n#### 1.1-beta4\n\n* Add internal simple-obfs and v2ray-plugin [Shadowsocks plugins](/configuration/outbound/shadowsocks#plugin)\n* Add [ShadowsocksR outbound](/configuration/outbound/shadowsocksr/)\n* Add [VLESS outbound and XUDP](/configuration/outbound/vless/)\n* Skip wait for hysteria tcp handshake response\n* Fix socks4 client\n* Fix hysteria inbound\n* Fix concurrent write\n\n#### 1.0.3\n\n* Fix socks4 client\n* Fix hysteria inbound\n* Fix concurrent write\n\n#### 1.1-beta3\n\n* Fix using custom TLS client in http2 client\n* Fix bugs in 1.1-beta2\n\n#### 1.1-beta2\n\n* Add Clash mode and persistence support **1**\n* Add TLS ECH and uTLS support for outbound TLS options **2**\n* Fix socks4 request\n* Fix processing empty dns result\n\n*1*:\n\nSwitching modes using the Clash API, and `store-selected` are now supported,\nsee [Experimental](/configuration/experimental/).\n\n*2*:\n\nECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello\nmessage, see [TLS#ECH](/configuration/shared/tls#ech).\n\nuTLS is a fork of \"crypto/tls\", which provides ClientHello fingerprinting resistance,\nsee [TLS#uTLS](/configuration/shared/tls#utls).\n\n#### 1.0.2\n\n* Fix socks4 request\n* Fix processing empty dns result\n\n#### 1.1-beta1\n\n* Add support for use with android VPNService **1**\n* Add tun support for WireGuard outbound **2**\n* Add system tun stack **3**\n* Add comment filter for config **4**\n* Add option for allow optional proxy protocol header\n* Add half close for smux\n* Set UDP DF by default **5**\n* Set default tun mtu to 9000\n* Update gVisor to 20220905.0\n\n*1*:\n\nIn previous versions, Android VPN would not work with tun enabled.\n\nThe usage of tun over VPN and VPN over tun is now supported, see [Tun Inbound](/configuration/inbound/tun#auto_route).\n\n*2*:\n\nIn previous releases, WireGuard outbound support was backed by the lower performance gVisor virtual interface.\n\nIt achieves the same performance as wireguard-go by providing automatic system interface support.\n\n*3*:\n\nIt does not depend on gVisor and has better performance in some cases.\n\nIt is less compatible and may not be available in some environments.\n\n*4*:\n\nAnnotated json configuration files are now supported.\n\n*5*:\n\nUDP fragmentation is now blocked by default.\n\nIncluding shadowsocks-libev, shadowsocks-rust and quic-go all disable segmentation by default.\n\nSee [Dial Fields](/configuration/shared/dial#udp_fragment)\nand [Listen Fields](/configuration/shared/listen#udp_fragment).\n\n#### 1.0.1\n\n* Fix match 4in6 address in ip_cidr\n* Fix clash api log level format error\n* Fix clash api unknown proxy type\n\n#### 1.0\n\n* Fix wireguard reconnect\n* Fix naive inbound\n* Fix json format error message\n* Fix processing vmess termination signal\n* Fix hysteria stream error\n* Fix listener close when proxyproto failed\n\n#### 1.0-rc1\n\n* Fix write log timestamp\n* Fix write zero\n* Fix dial parallel in direct outbound\n* Fix write trojan udp\n* Fix DNS routing\n* Add attribute support for geosite\n* Update documentation for [Dial Fields](/configuration/shared/dial/)\n\n#### 1.0-beta3\n\n* Add [chained inbound](/configuration/shared/listen#detour) support\n* Add process_path rule item\n* Add macOS redirect support\n* Add ShadowTLS [Inbound](/configuration/inbound/shadowtls/), [Outbound](/configuration/outbound/shadowtls/)\n  and [Examples](/examples/shadowtls/)\n* Fix search android package in non-owner users\n* Fix socksaddr type condition\n* Fix smux session status\n* Refactor inbound and outbound documentation\n* Minor fixes\n\n#### 1.0-beta2\n\n* Add strict_route option for [Tun inbound](/configuration/inbound/tun#strict_route)\n* Add packetaddr support for [VMess outbound](/configuration/outbound/vmess#packet_addr)\n* Add better performing alternative gRPC implementation\n* Add [docker image](https://github.com/SagerNet/sing-box/pkgs/container/sing-box)\n* Fix sniff override destination\n\n#### 1.0-beta1\n\n* Initial release\n\n##### 2022/08/26\n\n* Fix ipv6 route on linux\n* Fix read DNS message\n\n##### 2022/08/25\n\n* Let vmess use zero instead of auto if TLS enabled\n* Add trojan fallback for ALPN\n* Improve ip_cidr rule\n* Fix format bind_address\n* Fix http proxy with compressed response\n* Fix route connections\n\n##### 2022/08/24\n\n* Fix naive padding\n* Fix unix search path\n* Fix close non-duplex connections\n* Add ACME EAB support\n* Fix early close on windows and catch any\n* Initial zh-CN document translation\n\n##### 2022/08/23\n\n* Add [V2Ray Transport](/configuration/shared/v2ray-transport/) support for VMess and Trojan\n* Allow plain http request in Naive inbound (It can now be used with nginx)\n* Add proxy protocol support\n* Free memory after start\n* Parse X-Forward-For in HTTP requests\n* Handle SIGHUP signal\n\n##### 2022/08/22\n\n* Add strategy setting for each [DNS server](/configuration/dns/server/)\n* Add bind address to outbound options\n\n##### 2022/08/21\n\n* Add [Tor outbound](/configuration/outbound/tor/)\n* Add [SSH outbound](/configuration/outbound/ssh/)\n\n##### 2022/08/20\n\n* Attempt to unwrap ip-in-fqdn socksaddr\n* Fix read packages in android 12\n* Fix route on some android devices\n* Improve linux process searcher\n* Fix write socks5 username password auth request\n* Skip bind connection with private destination to interface\n* Add [Trojan connection fallback](/configuration/inbound/trojan#fallback)\n\n##### 2022/08/19\n\n* Add Hysteria [Inbound](/configuration/inbound/hysteria/) and [Outbund](/configuration/outbound/hysteria/)\n* Add [ACME TLS certificate issuer](/configuration/shared/tls/)\n* Allow read config from stdin (-c stdin)\n* Update gVisor to 20220815.0\n\n##### 2022/08/18\n\n* Fix find process with lwip stack\n* Fix crash on shadowsocks server\n* Fix crash on darwin tun\n* Fix write log to file\n\n##### 2022/08/17\n\n* Improve async dns transports\n\n##### 2022/08/16\n\n* Add ip_version (route/dns) rule item\n* Add [WireGuard](/configuration/outbound/wireguard/) outbound\n\n##### 2022/08/15\n\n* Add uid, android user and package rules support in [Tun](/configuration/inbound/tun/) routing.\n\n##### 2022/08/13\n\n* Fix dns concurrent write\n\n##### 2022/08/12\n\n* Performance improvements\n* Add UoT option for [SOCKS](/configuration/outbound/socks/) outbound\n\n##### 2022/08/11\n\n* Add UoT option for [Shadowsocks](/configuration/outbound/shadowsocks/) outbound, UoT support for all inbounds\n\n##### 2022/08/10\n\n* Add full-featured [Naive](/configuration/inbound/naive/) inbound\n* Fix default dns server option [#9] by iKirby\n\n##### 2022/08/09\n\nNo changelog before.\n\n[#9]: https://github.com/SagerNet/sing-box/pull/9\n"
  },
  {
    "path": "docs/clients/android/features.md",
    "content": "# :material-decagram: Features\n\n#### UI options\n\n* Display realtime network speed in the notification\n\n#### Service\n\nSFA allows you to run sing-box through ForegroundService or VpnService (when TUN is required).\n\n#### TUN\n\nSFA provides an unprivileged TUN implementation through Android VpnService.\n\n| TUN inbound option            | Available        | Note               |\n|-------------------------------|------------------|--------------------|\n| `interface_name`              | :material-close: | Managed by Android |\n| `inet4_address`               | :material-check: | /                  |\n| `inet6_address`               | :material-check: | /                  |\n| `mtu`                         | :material-check: | /                  |\n| `gso`                         | :material-close: | No permission      |\n| `auto_route`                  | :material-check: | /                  |\n| `strict_route`                | :material-close: | Not implemented    |\n| `inet4_route_address`         | :material-check: | /                  |\n| `inet6_route_address`         | :material-check: | /                  |\n| `inet4_route_exclude_address` | :material-check: | /                  |\n| `inet6_route_exclude_address` | :material-check: | /                  |\n| `endpoint_independent_nat`    | :material-check: | /                  |\n| `stack`                       | :material-check: | /                  |\n| `include_interface`           | :material-close: | No permission      |\n| `exclude_interface`           | :material-close: | No permission      |\n| `include_uid`                 | :material-close: | No permission      |\n| `exclude_uid`                 | :material-close: | No permission      |\n| `include_android_user`        | :material-close: | No permission      |\n| `include_package`             | :material-check: | /                  |\n| `exclude_package`             | :material-check: | /                  |\n| `platform`                    | :material-check: | /                  |\n\n| Route/DNS rule option | Available        | Note                              |\n|-----------------------|------------------|-----------------------------------|\n| `process_name`        | :material-close: | No permission                     |\n| `process_path`        | :material-close: | No permission                     |\n| `process_path_regex`  | :material-close: | No permission                     |\n| `package_name`        | :material-check: | /                                 |\n| `user`                | :material-close: | Use `package_name` instead        |\n| `user_id`             | :material-close: | Use `package_name` instead        |\n| `wifi_ssid`           | :material-check: | Fine location permission required |\n| `wifi_bssid`          | :material-check: | Fine location permission required |\n\n### Override\n\nOverrides profile configuration items with platform-specific values.\n\n#### Per-app proxy\n\nSFA allows you to select a list of Android apps that require proxying or bypassing in the graphical interface to\noverride the `include_package` and `exclude_package` configuration items.\n\nIn particular, the selector also provides the “China apps” scanning feature, providing Chinese users with an excellent\nexperience to bypass apps that do not require a proxy. Specifically, by scanning China application or SDK\ncharacteristics through dex class path and other means, there will be almost no missed reports.\n\n### Chore\n\n* The working directory is located at `/sdcard/Android/data/io.nekohasekai.sfa/files` (External files directory)\n* Crash logs is located in `$working_directory/stderr.log`\n"
  },
  {
    "path": "docs/clients/android/index.md",
    "content": "---\nicon: material/android\n---\n\n# sing-box for Android\n\nSFA allows users to manage and run local or remote sing-box configuration files, and provides\nplatform-specific function implementation, such as TUN transparent proxy implementation.\n\n## :material-graph: Requirements\n\n* Android 5.0+\n\n## :material-download: Download\n\n* [Play Store](https://play.google.com/store/apps/details?id=io.nekohasekai.sfa)\n* [Play Store (Beta)](https://play.google.com/apps/testing/io.nekohasekai.sfa)\n* [GitHub Releases](https://github.com/SagerNet/sing-box/releases)\n* [F-Droid](https://f-droid.org/packages/io.nekohasekai.sfa/) (Unified signature via reproducible builds)\n\n## :material-source-repository: Source code\n\n* [GitHub](https://github.com/SagerNet/sing-box-for-android)\n"
  },
  {
    "path": "docs/clients/apple/features.md",
    "content": "# :material-decagram: Features\n\n#### UI options\n\n* Always On\n* Include All Networks (Proxy traffic for LAN and cellular services)\n* (Apple tvOS) Import profile from iPhone/iPad\n\n#### Service\n\nSFI/SFM/SFT allows you to run sing-box through NetworkExtension with Application Extension or System Extension.\n\n#### TUN\n\nSFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension.\n\n| TUN inbound option            | Available         | Note              |\n|-------------------------------|-------------------|-------------------|\n| `interface_name`              | :material-close:️ | Managed by Darwin |\n| `inet4_address`               | :material-check:  | /                 |\n| `inet6_address`               | :material-check:  | /                 |\n| `mtu`                         | :material-check:  | /                 |\n| `gso`                         | :material-close:  | Not implemented   |\n| `auto_route`                  | :material-check:  | /                 |\n| `strict_route`                | :material-close:️ | Not implemented   |\n| `inet4_route_address`         | :material-check:  | /                 |\n| `inet6_route_address`         | :material-check:  | /                 |\n| `inet4_route_exclude_address` | :material-check:  | /                 |\n| `inet6_route_exclude_address` | :material-check:  | /                 |\n| `endpoint_independent_nat`    | :material-check:  | /                 |\n| `stack`                       | :material-check:  | /                 |\n| `include_interface`           | :material-close:️ | Not implemented   |\n| `exclude_interface`           | :material-close:️ | Not implemented   |\n| `include_uid`                 | :material-close:️ | Not implemented   |\n| `exclude_uid`                 | :material-close:️ | Not implemented   |\n| `include_android_user`        | :material-close:️ | Not implemented   |\n| `include_package`             | :material-close:️ | Not implemented   |\n| `exclude_package`             | :material-close:️ | Not implemented   |\n| `platform`                    | :material-check:  | /                 |\n\n| Route/DNS rule option | Available        | Note                  |\n|-----------------------|------------------|-----------------------|\n| `process_name`        | :material-close: | No permission         |\n| `process_path`        | :material-close: | No permission         |\n| `process_path_regex`  | :material-close: | No permission         |\n| `package_name`        | :material-close: | /                     |\n| `user`                | :material-close: | No permission         |\n| `user_id`             | :material-close: | No permission         |\n| `wifi_ssid`           | :material-alert: | Only supported on iOS |\n| `wifi_bssid`          | :material-alert: | Only supported on iOS |\n\n### Chore\n\n* Crash logs is located in `Settings` -> `View Service Log`\n"
  },
  {
    "path": "docs/clients/apple/index.md",
    "content": "---\nicon: material/apple\n---\n\n# sing-box for Apple platforms\n\nSFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides\nplatform-specific function implementation, such as TUN transparent proxy implementation.\n\n!!! failure \"\"\n\n    Due to non-technical reasons, we are temporarily unable to update the sing-box app on the App Store and release the standalone version of the macOS client (TestFlight users are not affected)\n\n## :material-graph: Requirements\n\n* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+\n* An Apple account outside of mainland China\n\n## :material-download: Download\n\n* ~~[App Store](https://apps.apple.com/app/sing-box-vt/id6673731168)~~\n* TestFlight (Beta)\n\nTestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)\n(one-time sponsorships are accepted).\nOnce you donate, you can get an invitation by join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot)\nor sending us your Apple ID [via email](mailto:contact@sagernet.org).\n\n## ~~:material-file-download: Download (macOS standalone version)~~\n\n* ~~[Homebrew Cask](https://formulae.brew.sh/cask/sfm)~~\n\n```bash\n# brew install sfm\n```\n\n* ~~[GitHub Releases](https://github.com/SagerNet/sing-box/releases)~~\n\n## :material-source-repository: Source code\n\n* [GitHub](https://github.com/SagerNet/sing-box-for-apple)\n"
  },
  {
    "path": "docs/clients/general.md",
    "content": "---\nicon: material/pencil-ruler\n---\n\n# General\n\nDescribes and explains the functions implemented uniformly by sing-box graphical clients.\n\n### Profile\n\nProfile describes a sing-box configuration file and its state.\n\n#### Local\n\n* Local Profile represents a local sing-box configuration with minimal state\n* The graphical client must provide an editor to modify configuration content\n\n#### iCloud (on iOS and macOS)\n\n* iCloud Profile represents a remote sing-box configuration with iCloud as the update source\n* The configuration file is stored in the sing-box folder under iCloud\n* The graphical client must provide an editor to modify configuration content\n\n#### Remote\n\n* Remote Profile represents a remote sing-box configuration with a URL as the update source.\n* The graphical client should provide a configuration content viewer\n* The graphical client must implement automatic profile update (default interval is 60 minutes) and HTTP Basic\n  authorization.\n\nAt the same time, the graphical client must provide support for importing remote profiles\nthrough a specific URL Scheme. The URL is defined as follows:\n\n```\nsing-box://import-remote-profile?url=urlEncodedURL#urlEncodedName\n```\n\n### Dashboard\n\nWhile the sing-box service is running, the graphical client should provide a Dashboard interface to manage the service.\n\n#### Status\n\nDashboard should display status information such as memory, connection, and traffic.\n\n#### Mode\n\nDashboard should provide a Mode selector for switching when the configuration uses at least two `clash_mode` values.\n\n#### Groups\n\nWhen the configuration includes group outbounds (specifically, Selector or URLTest),\nthe dashboard should provide a Group selector for status display or switching.\n\n### Chore\n\n#### Core\n\nGraphical clients should provide a Core region:\n\n* Display the current sing-box version\n* Provides a button to clean the working directory\n* Provides a memory limiter switch"
  },
  {
    "path": "docs/clients/index.md",
    "content": "# :material-cellphone-link: Graphical Clients\n\nMaintained by Project S to provide a unified experience and platform-specific functionality.\n\n| Platform                              | Client                                   |\n|---------------------------------------|------------------------------------------|\n| :material-android: Android            | [sing-box for Android](./android/)       |\n| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) |\n| :material-laptop: Desktop             | Working in progress                      |\n\nSome third-party projects that claim to use sing-box or use sing-box as a selling point are not listed here. The core\nmotivation of the maintainers of such projects is to acquire more users, and even though they provide friendly VPN\nclient features, the code is usually of poor quality and contains ads.\n"
  },
  {
    "path": "docs/clients/index.zh.md",
    "content": "# :material-cellphone-link: 图形界面客户端\n\n由 Project S 维护，提供统一的体验与平台特定的功能。\n\n| 平台                                    | 客户端                                      |\n|---------------------------------------|------------------------------------------|\n| :material-android: Android            | [sing-box for Android](./android/)       |\n| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) |\n| :material-laptop: Desktop             | 施工中                                      |\n\n此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户，即使它们提供友好的商业\nVPN 客户端功能， 但代码质量很差且包含广告。\n"
  },
  {
    "path": "docs/clients/privacy.md",
    "content": "---\nicon: material/security\n---\n\n# Privacy policy\n\nsing-box and official graphics clients do not collect or share personal data,\nand the data generated by the software is always on your device.\n\n## Android\n\nThe broad package (App) visibility (QUERY_ALL_PACKAGES) permission\nis used to provide per-application proxy features for VPN,\nsing-box will not collect your app list.\n\nIf your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,\nsing-box uses the location permission in the background\nto get information about the connected Wi-Fi network to make them work.\n"
  },
  {
    "path": "docs/configuration/certificate/index.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [Chrome Root Store](#store)\n\n# Certificate\n\n### Structure\n\n```json\n{\n  \"store\": \"\",\n  \"certificate\": [],\n  \"certificate_path\": [],\n  \"certificate_directory_path\": []\n}\n```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n### Fields\n\n#### store\n\nThe default X509 trusted CA certificate list.\n\n| Type               | Description                                                                                                    |\n|--------------------|----------------------------------------------------------------------------------------------------------------|\n| `system` (default) | System trusted CA certificates                                                                                 |\n| `mozilla`          | [Mozilla Included List](https://wiki.mozilla.org/CA/Included_Certificates) with China CA certificates removed |\n| `chrome`           | [Chrome Root Store](https://g.co/chrome/root-policy) with China CA certificates removed                        |\n| `none`             | Empty list                                                                                                     |\n\n#### certificate\n\nThe certificate line array to trust, in PEM format.\n\n#### certificate_path\n\n!!! note \"\"\n\n    Will be automatically reloaded if file modified.\n\nThe paths to certificates to trust, in PEM format.\n\n#### certificate_directory_path\n\n!!! note \"\"\n\n    Will be automatically reloaded if file modified.\n\nThe directory path to search for certificates to trust,in PEM format.\n"
  },
  {
    "path": "docs/configuration/certificate/index.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [Chrome Root Store](#store)\n\n# 证书\n\n### 结构\n\n```json\n{\n  \"store\": \"\",\n  \"certificate\": [],\n  \"certificate_path\": [],\n  \"certificate_directory_path\": []\n}\n```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签\n\n### 字段\n\n#### store\n\n默认的 X509 受信任 CA 证书列表。\n\n| 类型              | 描述                                                                                       |\n|-------------------|--------------------------------------------------------------------------------------------|\n| `system`（默认）   | 系统受信任的 CA 证书                                                                        |\n| `mozilla`         | [Mozilla 包含列表](https://wiki.mozilla.org/CA/Included_Certificates)（已移除中国 CA 证书） |\n| `chrome`          | [Chrome Root Store](https://g.co/chrome/root-policy)（已移除中国 CA 证书）                  |\n| `none`            | 空列表                                                                                     |\n\n#### certificate\n\n要信任的证书行数组，PEM 格式。\n\n#### certificate_path\n\n!!! note \"\"\n\n    文件修改时将自动重新加载。\n\n要信任的证书路径，PEM 格式。\n\n#### certificate_directory_path\n\n!!! note \"\"\n\n    文件修改时将自动重新加载。\n\n搜索要信任的证书的目录路径，PEM 格式。"
  },
  {
    "path": "docs/configuration/dns/fakeip.md",
    "content": "---\nicon: material/delete-clock\n---\n\n!!! failure \"Deprecated in sing-box 1.12.0\"\n\n    Legacy fake-ip configuration is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).\n\n### Structure\n\n```json\n{\n  \"enabled\": true,\n  \"inet4_range\": \"198.18.0.0/15\",\n  \"inet6_range\": \"fc00::/18\"\n}\n```\n\n### Fields\n\n#### enabled\n\nEnable FakeIP service.\n\n#### inet4_range\n\nIPv4 address range for FakeIP.\n\n#### inet6_address\n\nIPv6 address range for FakeIP.\n"
  },
  {
    "path": "docs/configuration/dns/fakeip.zh.md",
    "content": "---\nicon: material/delete-clock\n---\n\n!!! failure \"已在 sing-box 1.12.0 废弃\"\n\n    旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。\n\n### 结构\n\n```json\n{\n  \"enabled\": true,\n  \"inet4_range\": \"198.18.0.0/15\",\n  \"inet6_range\": \"fc00::/18\"\n}\n```\n\n### 字段\n\n#### enabled\n\n启用 FakeIP 服务。\n\n#### inet4_range\n\n用于 FakeIP 的 IPv4 地址范围。\n\n#### inet6_range\n\n用于 FakeIP 的 IPv6 地址范围。\n"
  },
  {
    "path": "docs/configuration/dns/index.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-decagram: [servers](#servers)\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-plus: [cache_capacity](#cache_capacity)\n\n# DNS\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [],\n    \"rules\": [],\n    \"final\": \"\",\n    \"strategy\": \"\",\n    \"disable_cache\": false,\n    \"disable_expire\": false,\n    \"independent_cache\": false,\n    \"cache_capacity\": 0,\n    \"reverse_mapping\": false,\n    \"client_subnet\": \"\",\n    \"fakeip\": {}\n  }\n}\n\n```\n\n### Fields\n\n| Key      | Format                          |\n|----------|---------------------------------|\n| `server` | List of [DNS Server](./server/) |\n| `rules`  | List of [DNS Rule](./rule/)     |\n| `fakeip` | [FakeIP](./fakeip/)             |\n\n#### final\n\nDefault dns server tag.\n\nThe first server will be used if empty.\n\n#### strategy\n\nDefault domain strategy for resolving the domain names.\n\nOne of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.\n\n#### disable_cache\n\nDisable dns cache.\n\n#### disable_expire\n\nDisable dns cache expire.\n\n#### independent_cache\n\nMake each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.\n\n#### cache_capacity\n\n!!! question \"Since sing-box 1.11.0\"\n\nLRU cache capacity.\n\nValue less than 1024 will be ignored.\n\n#### reverse_mapping\n\nStores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.\n\nSince this process relies on the act of resolving domain names by an application before making a request, it can be\nproblematic in environments such as macOS, where DNS is proxied and cached by the system.\n\n#### client_subnet\n\n!!! question \"Since sing-box 1.9.0\"\n\nAppend a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.\n\nIf value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.\n\nCan be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`.\n"
  },
  {
    "path": "docs/configuration/dns/index.zh.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-decagram: [servers](#servers)\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-plus: [cache_capacity](#cache_capacity)\n\n# DNS\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [],\n    \"rules\": [],\n    \"final\": \"\",\n    \"strategy\": \"\",\n    \"disable_cache\": false,\n    \"disable_expire\": false,\n    \"independent_cache\": false,\n    \"cache_capacity\": 0,\n    \"reverse_mapping\": false,\n    \"client_subnet\": \"\",\n    \"fakeip\": {}\n  }\n}\n\n```\n\n### 字段\n\n| 键        | 格式                      |\n|----------|-------------------------|\n| `server` | 一组 [DNS 服务器](./server/) |\n| `rules`  | 一组 [DNS 规则](./rule/)    |\n\n#### final\n\n默认 DNS 服务器的标签。\n\n默认使用第一个服务器。\n\n#### strategy\n\n默认解析域名策略。\n\n可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。\n\n#### disable_cache\n\n禁用 DNS 缓存。\n\n#### disable_expire\n\n禁用 DNS 缓存过期。\n\n#### independent_cache\n\n使每个 DNS 服务器的缓存独立，以满足特殊目的。如果启用，将轻微降低性能。\n\n#### cache_capacity\n\n!!! question \"自 sing-box 1.11.0 起\"\n\nLRU 缓存容量。\n\n小于 1024 的值将被忽略。\n\n#### reverse_mapping\n\n在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。\n\n由于此过程依赖于应用程序在发出请求之前解析域名的行为，因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。\n\n#### client_subnet\n\n!!! question \"自 sing-box 1.9.0 起\"\n\n默认情况下，将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。\n\n如果值是 IP 地址而不是前缀，则会自动附加 `/32` 或 `/128`。\n\n可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。\n\n#### fakeip\n\n[FakeIP](./fakeip/) 设置。\n"
  },
  {
    "path": "docs/configuration/dns/rule.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n!!! quote \"Changes in sing-box 1.14.0\"\n\n    :material-plus: [source_mac_address](#source_mac_address)  \n    :material-plus: [source_hostname](#source_hostname)\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [interface_address](#interface_address)  \n    :material-plus: [network_interface_address](#network_interface_address)  \n    :material-plus: [default_interface_address](#default_interface_address)\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-plus: [ip_accept_any](#ip_accept_any)  \n    :material-delete-clock: [outbound](#outbound)\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-plus: [action](#action)  \n    :material-alert: [server](#server)  \n    :material-alert: [disable_cache](#disable_cache)  \n    :material-alert: [rewrite_ttl](#rewrite_ttl)  \n    :material-alert: [client_subnet](#client_subnet)  \n    :material-plus: [network_type](#network_type)  \n    :material-plus: [network_is_expensive](#network_is_expensive)  \n    :material-plus: [network_is_constrained](#network_is_constrained)\n\n!!! quote \"Changes in sing-box 1.10.0\"\n\n    :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)  \n    :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)  \n    :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)  \n    :material-plus: [process_path_regex](#process_path_regex)\n\n!!! quote \"Changes in sing-box 1.9.0\"\n\n    :material-plus: [geoip](#geoip)  \n    :material-plus: [ip_cidr](#ip_cidr)  \n    :material-plus: [ip_is_private](#ip_is_private)  \n    :material-plus: [client_subnet](#client_subnet)  \n    :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)\n\n!!! quote \"Changes in sing-box 1.8.0\"\n\n    :material-plus: [rule_set](#rule_set)  \n    :material-plus: [source_ip_is_private](#source_ip_is_private)  \n    :material-delete-clock: [geoip](#geoip)  \n    :material-delete-clock: [geosite](#geosite)\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"rules\": [\n      {\n        \"inbound\": [\n          \"mixed-in\"\n        ],\n        \"ip_version\": 6,\n        \"query_type\": [\n          \"A\",\n          \"HTTPS\",\n          32768\n        ],\n        \"network\": \"tcp\",\n        \"auth_user\": [\n          \"usera\",\n          \"userb\"\n        ],\n        \"protocol\": [\n          \"tls\",\n          \"http\",\n          \"quic\"\n        ],\n        \"domain\": [\n          \"test.com\"\n        ],\n        \"domain_suffix\": [\n          \".cn\"\n        ],\n        \"domain_keyword\": [\n          \"test\"\n        ],\n        \"domain_regex\": [\n          \"^stun\\\\..+\"\n        ],\n        \"source_ip_cidr\": [\n          \"10.0.0.0/24\",\n          \"192.168.0.1\"\n        ],\n        \"source_ip_is_private\": false,\n        \"ip_cidr\": [\n          \"10.0.0.0/24\",\n          \"192.168.0.1\"\n        ],\n        \"ip_is_private\": false,\n        \"ip_accept_any\": false,\n        \"source_port\": [\n          12345\n        ],\n        \"source_port_range\": [\n          \"1000:2000\",\n          \":3000\",\n          \"4000:\"\n        ],\n        \"port\": [\n          80,\n          443\n        ],\n        \"port_range\": [\n          \"1000:2000\",\n          \":3000\",\n          \"4000:\"\n        ],\n        \"process_name\": [\n          \"curl\"\n        ],\n        \"process_path\": [\n          \"/usr/bin/curl\"\n        ],\n        \"process_path_regex\": [\n          \"^/usr/bin/.+\"\n        ],\n        \"package_name\": [\n          \"com.termux\"\n        ],\n        \"user\": [\n          \"sekai\"\n        ],\n        \"user_id\": [\n          1000\n        ],\n        \"clash_mode\": \"direct\",\n        \"network_type\": [\n          \"wifi\"\n        ],\n        \"network_is_expensive\": false,\n        \"network_is_constrained\": false,\n        \"interface_address\": {\n          \"en0\": [\n            \"2000::/3\"\n          ]\n        },\n        \"network_interface_address\": {\n          \"wifi\": [\n            \"2000::/3\"\n          ]\n        },\n        \"default_interface_address\": [\n          \"2000::/3\"\n        ],\n        \"source_mac_address\": [\n          \"00:11:22:33:44:55\"\n        ],\n        \"source_hostname\": [\n          \"my-device\"\n        ],\n        \"wifi_ssid\": [\n          \"My WIFI\"\n        ],\n        \"wifi_bssid\": [\n          \"00:00:00:00:00:00\"\n        ],\n        \"rule_set\": [\n          \"geoip-cn\",\n          \"geosite-cn\"\n        ],\n        \"rule_set_ip_cidr_match_source\": false,\n        \"rule_set_ip_cidr_accept_empty\": false,\n        \"invert\": false,\n        \"outbound\": [\n          \"direct\"\n        ],\n        \"action\": \"route\",\n        \"server\": \"local\",\n\n        // Deprecated\n        \n        \"rule_set_ipcidr_match_source\": false,\n        \"geosite\": [\n          \"cn\"\n        ],\n        \"source_geoip\": [\n          \"private\"\n        ],\n        \"geoip\": [\n          \"cn\"\n        ]\n      },\n      {\n        \"type\": \"logical\",\n        \"mode\": \"and\",\n        \"rules\": [],\n        \"action\": \"route\",\n        \"server\": \"local\"\n      }\n    ]\n  }\n}\n\n```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n### Default Fields\n\n!!! note \"\"\n\n    The default rule uses the following matching logic:  \n    (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&  \n    (`port` || `port_range`) &&  \n    (`source_geoip` || `source_ip_cidr` ｜｜ `source_ip_is_private`) &&  \n    (`source_port` || `source_port_range`) &&  \n    `other fields`\n\n    Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.\n\n#### inbound\n\nTags of [Inbound](/configuration/inbound/).\n\n#### ip_version\n\n4 (A DNS query) or 6 (AAAA DNS query).\n\nNot limited if empty.\n\n#### query_type\n\nDNS query type. Values can be integers or type name strings.\n\n#### network\n\n`tcp` or `udp`.\n\n#### auth_user\n\nUsername, see each inbound for details.\n\n#### protocol\n\nSniffed protocol, see [Sniff](/configuration/route/sniff/) for details.\n\n#### domain\n\nMatch full domain.\n\n#### domain_suffix\n\nMatch domain suffix.\n\n#### domain_keyword\n\nMatch domain using keyword.\n\n#### domain_regex\n\nMatch domain using regular expression.\n\n#### geosite\n\n!!! failure \"Deprecated in sing-box 1.8.0\"\n\n    Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).\n\nMatch geosite.\n\n#### source_geoip\n\n!!! failure \"Deprecated in sing-box 1.8.0\"\n\n    GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).\n\nMatch source geoip.\n\n#### source_ip_cidr\n\nMatch source IP CIDR.\n\n#### source_ip_is_private\n\n!!! question \"Since sing-box 1.8.0\"\n\nMatch non-public source IP.\n\n#### source_port\n\nMatch source port.\n\n#### source_port_range\n\nMatch source port range.\n\n#### port\n\nMatch port.\n\n#### port_range\n\nMatch port range.\n\n#### process_name\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch process name.\n\n#### process_path\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch process path.\n\n#### process_path_regex\n\n!!! question \"Since sing-box 1.10.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch process path using regular expression.\n\n#### package_name\n\nMatch android package name.\n\n#### user\n\n!!! quote \"\"\n\n    Only supported on Linux.\n\nMatch user name.\n\n#### user_id\n\n!!! quote \"\"\n\n    Only supported on Linux.\n\nMatch user id.\n\n#### clash_mode\n\nMatch Clash mode.\n\n#### network_type\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms.\n\nMatch network type.\n\nAvailable values: `wifi`, `cellular`, `ethernet` and `other`.\n\n#### network_is_expensive\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms.\n\nMatch if network is considered Metered (on Android) or considered expensive,\nsuch as Cellular or a Personal Hotspot (on Apple platforms).\n\n#### network_is_constrained\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Apple platforms.\n\nMatch if network is in Low Data Mode.\n\n#### interface_address\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch interface address.\n\n#### network_interface_address\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms.\n\nMatches network interface (same values as `network_type`) address.\n\n#### default_interface_address\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch default interface address.\n\n#### source_mac_address\n\n!!! question \"Since sing-box 1.14.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.\n\nMatch source device MAC address.\n\n#### source_hostname\n\n!!! question \"Since sing-box 1.14.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.\n\nMatch source device hostname from DHCP leases.\n\n#### wifi_ssid\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms, or on Linux.\n\nMatch WiFi SSID.\n\n#### wifi_bssid\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms, or on Linux.\n\nMatch WiFi BSSID.\n\n#### rule_set\n\n!!! question \"Since sing-box 1.8.0\"\n\nMatch [rule-set](/configuration/route/#rule_set).\n\n#### rule_set_ipcidr_match_source\n\n!!! question \"Since sing-box 1.9.0\"\n\n!!! failure \"Deprecated in sing-box 1.10.0\"\n    \n    `rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.\n\nMake `ip_cidr` rule items in rule-sets match the source IP.\n\n#### rule_set_ip_cidr_match_source\n\n!!! question \"Since sing-box 1.10.0\"\n\nMake `ip_cidr` rule items in rule-sets match the source IP.\n\n#### invert\n\nInvert match result.\n\n#### outbound\n\n!!! failure \"Deprecated in sing-box 1.12.0\"\n\n    `outbound` rule items are deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver). \n\nMatch outbound.\n\n`any` can be used as a value to match any outbound.\n\n#### action\n\n==Required==\n\nSee [DNS Rule Actions](../rule_action/) for details.\n\n#### server\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Moved to [DNS Rule Action](../rule_action#route).\n\n#### disable_cache\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Moved to [DNS Rule Action](../rule_action#route).\n\n#### rewrite_ttl\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Moved to [DNS Rule Action](../rule_action#route).\n\n#### client_subnet\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Moved to [DNS Rule Action](../rule_action#route).\n\n### Address Filter Fields\n\nOnly takes effect for address requests (A/AAAA/HTTPS). When the query results do not match the address filtering rule items, the current rule will be skipped.\n\n!!! info \"\"\n\n    `ip_cidr` items in included rule-sets also takes effect as an address filtering field.\n\n!!! note \"\"\n\n    Enable `experimental.cache_file.store_rdrc` to cache results.\n\n#### geoip\n\n!!! failure \"Removed in sing-box 1.12.0\"\n\n    GeoIP is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).\n\nMatch GeoIP with query response.\n\n#### ip_cidr\n\n!!! question \"Since sing-box 1.9.0\"\n\nMatch IP CIDR with query response.\n\n#### ip_is_private\n\n!!! question \"Since sing-box 1.9.0\"\n\nMatch private IP with query response.\n\n#### rule_set_ip_cidr_accept_empty\n\n!!! question \"Since sing-box 1.10.0\"\n\nMake `ip_cidr` rules in rule-sets accept empty query response.\n\n#### ip_accept_any\n\n!!! question \"Since sing-box 1.12.0\"\n\nMatch any IP with query response.\n\n### Logical Fields\n\n#### type\n\n`logical`\n\n#### mode\n\n`and` or `or`\n\n#### rules\n\nIncluded rules."
  },
  {
    "path": "docs/configuration/dns/rule.zh.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n!!! quote \"sing-box 1.14.0 中的更改\"\n\n    :material-plus: [source_mac_address](#source_mac_address)  \n    :material-plus: [source_hostname](#source_hostname)\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [interface_address](#interface_address)  \n    :material-plus: [network_interface_address](#network_interface_address)  \n    :material-plus: [default_interface_address](#default_interface_address)\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-plus: [ip_accept_any](#ip_accept_any)  \n    :material-delete-clock: [outbound](#outbound)\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-plus: [action](#action)  \n    :material-alert: [server](#server)  \n    :material-alert: [disable_cache](#disable_cache)  \n    :material-alert: [rewrite_ttl](#rewrite_ttl)  \n    :material-alert: [client_subnet](#client_subnet)  \n    :material-plus: [network_type](#network_type)  \n    :material-plus: [network_is_expensive](#network_is_expensive)  \n    :material-plus: [network_is_constrained](#network_is_constrained)\n\n!!! quote \"sing-box 1.10.0 中的更改\"\n\n    :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)  \n    :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)  \n    :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)  \n    :material-plus: [process_path_regex](#process_path_regex)\n\n!!! quote \"sing-box 1.9.0 中的更改\"\n\n    :material-plus: [geoip](#geoip)  \n    :material-plus: [ip_cidr](#ip_cidr)  \n    :material-plus: [ip_is_private](#ip_is_private)  \n    :material-plus: [client_subnet](#client_subnet)  \n    :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)\n\n!!! quote \"sing-box 1.8.0 中的更改\"\n\n    :material-plus: [rule_set](#rule_set)  \n    :material-plus: [source_ip_is_private](#source_ip_is_private)  \n    :material-delete-clock: [geoip](#geoip)  \n    :material-delete-clock: [geosite](#geosite)\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"rules\": [\n      {\n        \"inbound\": [\n          \"mixed-in\"\n        ],\n        \"ip_version\": 6,\n        \"query_type\": [\n          \"A\",\n          \"HTTPS\",\n          32768\n        ],\n        \"network\": \"tcp\",\n        \"auth_user\": [\n          \"usera\",\n          \"userb\"\n        ],\n        \"protocol\": [\n          \"tls\",\n          \"http\",\n          \"quic\"\n        ],\n        \"domain\": [\n          \"test.com\"\n        ],\n        \"domain_suffix\": [\n          \".cn\"\n        ],\n        \"domain_keyword\": [\n          \"test\"\n        ],\n        \"domain_regex\": [\n          \"^stun\\\\..+\"\n        ],\n        \"source_ip_cidr\": [\n          \"10.0.0.0/24\",\n          \"192.168.0.1\"\n        ],\n        \"source_ip_is_private\": false,\n        \"ip_cidr\": [\n          \"10.0.0.0/24\",\n          \"192.168.0.1\"\n        ],\n        \"ip_is_private\": false,\n        \"ip_accept_any\": false,\n        \"source_port\": [\n          12345\n        ],\n        \"source_port_range\": [\n          \"1000:2000\",\n          \":3000\",\n          \"4000:\"\n        ],\n        \"port\": [\n          80,\n          443\n        ],\n        \"port_range\": [\n          \"1000:2000\",\n          \":3000\",\n          \"4000:\"\n        ],\n        \"process_name\": [\n          \"curl\"\n        ],\n        \"process_path\": [\n          \"/usr/bin/curl\"\n        ],\n        \"process_path_regex\": [\n          \"^/usr/bin/.+\"\n        ],\n        \"package_name\": [\n          \"com.termux\"\n        ],\n        \"user\": [\n          \"sekai\"\n        ],\n        \"user_id\": [\n          1000\n        ],\n        \"clash_mode\": \"direct\",\n        \"network_type\": [\n          \"wifi\"\n        ],\n        \"network_is_expensive\": false,\n        \"network_is_constrained\": false,\n        \"interface_address\": {\n          \"en0\": [\n            \"2000::/3\"\n          ]\n        },\n        \"network_interface_address\": {\n          \"wifi\": [\n            \"2000::/3\"\n          ]\n        },\n        \"default_interface_address\": [\n          \"2000::/3\"\n        ],\n        \"source_mac_address\": [\n          \"00:11:22:33:44:55\"\n        ],\n        \"source_hostname\": [\n          \"my-device\"\n        ],\n        \"wifi_ssid\": [\n          \"My WIFI\"\n        ],\n        \"wifi_bssid\": [\n          \"00:00:00:00:00:00\"\n        ],\n        \"rule_set\": [\n          \"geoip-cn\",\n          \"geosite-cn\"\n        ],\n        \"rule_set_ip_cidr_match_source\": false,\n        \"rule_set_ip_cidr_accept_empty\": false,\n        \"invert\": false,\n        \"outbound\": [\n          \"direct\"\n        ],\n        \"action\": \"route\",\n        \"server\": \"local\",\n\n        // 已弃用\n        \"rule_set_ipcidr_match_source\": false,\n        \"geosite\": [\n          \"cn\"\n        ],\n        \"source_geoip\": [\n          \"private\"\n        ],\n        \"geoip\": [\n          \"cn\"\n        ]\n      },\n      {\n        \"type\": \"logical\",\n        \"mode\": \"and\",\n        \"rules\": [],\n        \"action\": \"route\",\n        \"server\": \"local\"\n      }\n    ]\n  }\n}\n\n```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签\n\n### 默认字段\n\n!!! note \"\"\n\n    默认规则使用以下匹配逻辑:  \n    (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&  \n    (`port` || `port_range`) &&  \n    (`source_geoip` || `source_ip_cidr` || `source_ip_is_private`) &&  \n    (`source_port` || `source_port_range`) &&  \n    `other fields`\n\n    另外，引用的规则集可视为被合并，而不是作为一个单独的规则子项。\n\n#### inbound\n\n[入站](/zh/configuration/inbound/) 标签.\n\n#### ip_version\n\n4 (A DNS 查询) 或 6 (AAAA DNS 查询)。\n\n默认不限制。\n\n#### query_type\n\nDNS 查询类型。值可以为整数或者类型名称字符串。\n\n#### network\n\n`tcp` 或 `udp`。\n\n#### auth_user\n\n认证用户名，参阅入站设置。\n\n#### protocol\n\n探测到的协议, 参阅 [协议探测](/zh/configuration/route/sniff/)。\n\n#### domain\n\n匹配完整域名。\n\n#### domain_suffix\n\n匹配域名后缀。\n\n#### domain_keyword\n\n匹配域名关键字。\n\n#### domain_regex\n\n匹配域名正则表达式。\n\n#### geosite\n\n!!! failure \"已在 sing-box 1.12.0 中被移除\"\n\n    GeoSite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移-geosite-到规则集)。\n\n匹配 Geosite。\n\n#### source_geoip\n\n!!! failure \"已在 sing-box 1.12.0 中被移除\"\n\n    GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。\n\n匹配源 GeoIP。\n\n#### source_ip_cidr\n\n匹配源 IP CIDR。\n\n#### source_ip_is_private\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n匹配非公开源 IP。\n\n#### source_port\n\n匹配源端口。\n\n#### source_port_range\n\n匹配源端口范围。\n\n#### port\n\n匹配端口。\n\n#### port_range\n\n匹配端口范围。\n\n#### process_name\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n匹配进程名称。\n\n#### process_path\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n匹配进程路径。\n\n#### process_path_regex\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n使用正则表达式匹配进程路径。\n\n#### package_name\n\n匹配 Android 应用包名。\n\n#### user\n\n!!! quote \"\"\n\n    仅支持 Linux。\n\n匹配用户名。\n\n#### user_id\n\n!!! quote \"\"\n\n    仅支持 Linux。\n\n匹配用户 ID。\n\n#### clash_mode\n\n匹配 Clash 模式。\n\n#### network_type\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端中支持。\n\n匹配网络类型。\n\nAvailable values: `wifi`, `cellular`, `ethernet` and `other`.\n\n#### network_is_expensive\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端中支持。\n\n匹配如果网络被视为计费 (在 Android) 或被视为昂贵，\n像蜂窝网络或个人热点 (在 Apple 平台)。\n\n#### network_is_constrained\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Apple 平台图形客户端中支持。\n\n匹配如果网络在低数据模式下。\n\n#### interface_address\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n匹配接口地址。\n\n#### network_interface_address\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端中支持。\n\n匹配网络接口（可用值同 `network_type`）地址。\n\n#### default_interface_address\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n匹配默认接口地址。\n\n#### source_mac_address\n\n!!! question \"自 sing-box 1.14.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、macOS，或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。\n\n匹配源设备 MAC 地址。\n\n#### source_hostname\n\n!!! question \"自 sing-box 1.14.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、macOS，或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。\n\n匹配源设备从 DHCP 租约获取的主机名。\n\n#### wifi_ssid\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。\n\n匹配 WiFi SSID。\n\n#### wifi_bssid\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。\n\n匹配 WiFi BSSID。\n\n#### rule_set\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n匹配[规则集](/zh/configuration/route/#rule_set)。\n\n#### rule_set_ipcidr_match_source\n\n!!! question \"自 sing-box 1.9.0 起\"\n\n!!! failure \"已在 sing-box 1.10.0 废弃\"\n\n    `rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。\n\n使规则集中的 `ip_cidr` 规则匹配源 IP。\n\n#### rule_set_ip_cidr_match_source\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n使规则集中的 `ip_cidr` 规则匹配源 IP。\n\n#### invert\n\n反选匹配结果。\n\n#### outbound\n\n!!! failure \"已在 sing-box 1.12.0 废弃\"\n\n    `outbound` 规则项已废弃且将在 sing-box 1.14.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移-outbound-dns-规则项到域解析选项)。\n\n匹配出站。\n\n`any` 可作为值用于匹配任意出站。\n\n#### action\n\n==必填==\n\n参阅 [规则动作](../rule_action/)。\n\n#### server\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    已移动到 [DNS 规则动作](../rule_action#route).\n\n#### disable_cache\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    已移动到 [DNS 规则动作](../rule_action#route).\n\n#### rewrite_ttl\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    已移动到 [DNS 规则动作](../rule_action#route).\n\n#### client_subnet\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    已移动到 [DNS 规则动作](../rule_action#route).\n\n### 地址筛选字段\n\n仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时，将跳过当前规则。\n\n!!! info \"\"\n\n    引用的规则集中的 `ip_cidr` 项也作为地址筛选字段生效。\n\n!!! note \"\"\n\n    启用 `experimental.cache_file.store_rdrc` 以缓存结果。\n\n#### geoip\n\n!!! failure \"已在 sing-box 1.12.0 中被移除\"\n\n    GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。\n\n\n与查询响应匹配 GeoIP。\n\n#### ip_cidr\n\n!!! question \"自 sing-box 1.9.0 起\"\n\n与查询响应匹配 IP CIDR。\n\n#### ip_is_private\n\n!!! question \"自 sing-box 1.9.0 起\"\n\n与查询响应匹配非公开 IP。\n\n#### ip_accept_any\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n匹配任意 IP。\n\n#### rule_set_ip_cidr_accept_empty\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n使规则集中的 `ip_cidr` 规则接受空查询响应。\n\n### 逻辑字段\n\n#### type\n\n`logical`\n\n#### mode\n\n==必填==\n\n`and` 或 `or`\n\n#### rules\n\n==必填==\n\n包括的规则。"
  },
  {
    "path": "docs/configuration/dns/rule_action.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-plus: [strategy](#strategy)  \n    :material-plus: [predefined](#predefined)\n\n!!! question \"Since sing-box 1.11.0\"\n\n### route\n\n```json\n{\n  \"action\": \"route\",  // default\n  \"server\": \"\",\n  \"strategy\": \"\",\n  \"disable_cache\": false,\n  \"rewrite_ttl\": null,\n  \"client_subnet\": null\n}\n```\n\n`route` inherits the classic rule behavior of routing DNS requests to the specified server.\n\n#### server\n\n==Required==\n\nTag of target server.\n\n#### strategy\n\n!!! question \"Since sing-box 1.12.0\"\n\nSet domain strategy for this query.\n\nOne of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.\n\n#### disable_cache\n\nDisable cache and save cache in this query.\n\n#### rewrite_ttl\n\nRewrite TTL in DNS responses.\n\n#### client_subnet\n\nAppend a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.\n\nIf value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.\n\nWill overrides `dns.client_subnet`.\n\n### route-options\n\n```json\n{\n  \"action\": \"route-options\",\n  \"disable_cache\": false,\n  \"rewrite_ttl\": null,\n  \"client_subnet\": null\n}\n```\n\n`route-options` set options for routing.\n\n### reject\n\n```json\n{\n  \"action\": \"reject\",\n  \"method\": \"\",\n  \"no_drop\": false\n}\n```\n\n`reject` reject DNS requests.\n\n#### method\n\n- `default`: Reply with REFUSED.\n- `drop`: Drop the request.\n\n`default` will be used by default.\n\n#### no_drop\n\nIf not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.\n\nNot available when `method` is set to drop.\n\n### predefined\n\n!!! question \"Since sing-box 1.12.0\"\n\n```json\n{\n  \"action\": \"predefined\",\n  \"rcode\": \"\",\n  \"answer\": [],\n  \"ns\": [],\n  \"extra\": []\n}\n```\n\n`predefined` responds with predefined DNS records.\n\n#### rcode\n\nThe response code.\n\n| Value      | Value in the legacy rcode server | Description     |\n|------------|----------------------------------|-----------------|\n| `NOERROR`  | `success`                        | Ok              |\n| `FORMERR`  | `format_error`                   | Bad request     |\n| `SERVFAIL` | `server_failure`                 | Server failure  |\n| `NXDOMAIN` | `name_error`                     | Not found       |\n| `NOTIMP`   | `not_implemented`                | Not implemented |\n| `REFUSED`  | `refused`                        | Refused         |\n\n`NOERROR` will be used by default.\n\n#### answer\n\nList of text DNS record to respond as answers.\n\nExamples:\n\n| Record Type | Example                       |\n|-------------|-------------------------------|\n| `A`         | `localhost. IN A 127.0.0.1`   |\n| `AAAA`      | `localhost. IN AAAA ::1`      |\n| `TXT`       | `localhost. IN TXT \\\"Hello\\\"` |\n\n#### ns\n\nList of text DNS record to respond as name servers.\n\n#### extra\n\nList of text DNS record to respond as extra records.\n"
  },
  {
    "path": "docs/configuration/dns/rule_action.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-plus: [strategy](#strategy)  \n    :material-plus: [predefined](#predefined)\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n### route\n\n```json\n{\n  \"action\": \"route\", // 默认\n  \"server\": \"\",\n  \"strategy\": \"\",\n  \"disable_cache\": false,\n  \"rewrite_ttl\": null,\n  \"client_subnet\": null\n}\n```\n\n`route` 继承了将 DNS 请求 路由到指定服务器的经典规则动作。\n\n#### server\n\n==必填==\n\n目标 DNS 服务器的标签。\n\n#### strategy\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n为此查询设置域名策略。\n\n可选项：`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。\n\n#### disable_cache\n\n在此查询中禁用缓存。\n\n#### rewrite_ttl\n\n重写 DNS 回应中的 TTL。\n\n#### client_subnet\n\n默认情况下，将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。\n\n如果值是 IP 地址而不是前缀，则会自动附加 `/32` 或 `/128`。\n\n将覆盖 `dns.client_subnet`.\n\n### route-options\n\n```json\n{\n  \"action\": \"route-options\",\n  \"disable_cache\": false,\n  \"rewrite_ttl\": null,\n  \"client_subnet\": null\n}\n```\n\n`route-options` 为路由设置选项。\n\n### reject\n\n```json\n{\n  \"action\": \"reject\",\n  \"method\": \"\",\n  \"no_drop\": false\n}\n```\n\n`reject` 拒绝 DNS 请求。\n\n#### method\n\n- `default`: 返回 REFUSED。\n- `drop`: 丢弃请求。\n\n默认使用 `defualt`。\n\n#### no_drop\n\n如果未启用，则 30 秒内触发 50 次后，`method` 将被暂时覆盖为 `drop`。\n\n当 `method` 设为 `drop` 时不可用。\n\n### predefined\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n```json\n{\n  \"action\": \"predefined\",\n  \"rcode\": \"\",\n  \"answer\": [],\n  \"ns\": [],\n  \"extra\": []\n}\n```\n\n`predefined` 以预定义的 DNS 记录响应。\n\n#### rcode\n\n响应码。\n\n| 值          | 旧 rcode DNS 服务器中的值 | 描述              |\n|------------|--------------------|-----------------|\n| `NOERROR`  | `success`          | Ok              |\n| `FORMERR`  | `format_error`     | Bad request     |\n| `SERVFAIL` | `server_failure`   | Server failure  |\n| `NXDOMAIN` | `name_error`       | Not found       |\n| `NOTIMP`   | `not_implemented`  | Not implemented |\n| `REFUSED`  | `refused`          | Refused         |\n\n默认使用 `NOERROR`。\n\n#### answer\n\n用于作为回答响应的文本 DNS 记录列表。\n\n例子:\n\n| 记录类型   | 例子                            |\n|--------|-------------------------------|\n| `A`    | `localhost. IN A 127.0.0.1`   |\n| `AAAA` | `localhost. IN AAAA ::1`      |\n| `TXT`  | `localhost. IN TXT \\\"Hello\\\"` |\n\n#### ns\n\n用于作为名称服务器响应的文本 DNS 记录列表。\n\n#### extra\n\n用于作为额外记录响应的文本 DNS 记录列表。\n"
  },
  {
    "path": "docs/configuration/dns/server/dhcp.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# DHCP\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"dhcp\",\n        \"tag\": \"\",\n\n        \"interface\": \"\",\n        \n        // Dial Fields\n      }\n    ]\n  }\n}\n```\n\n### Fields\n\n#### interface\n\nInterface name to listen on. \n\nTge default interface will be used by default.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details. \n"
  },
  {
    "path": "docs/configuration/dns/server/dhcp.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# DHCP\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"dhcp\",\n        \"tag\": \"\",\n\n        \"interface\": \"\",\n\n        // 拨号字段\n      }\n    ]\n  }\n}\n```\n\n### 字段\n\n#### interface\n\n要监听的网络接口名称。\n\n默认使用默认接口。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。"
  },
  {
    "path": "docs/configuration/dns/server/fakeip.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# Fake IP\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"fakeip\",\n        \"tag\": \"\",\n\n        \"inet4_range\": \"198.18.0.0/15\",\n        \"inet6_range\": \"fc00::/18\"\n      }\n    ]\n  }\n}\n```\n\n### Fields\n\n#### inet4_range\n\nIPv4 address range for FakeIP.\n\n#### inet6_address\n\nIPv6 address range for FakeIP.\n"
  },
  {
    "path": "docs/configuration/dns/server/fakeip.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# Fake IP\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"fakeip\",\n        \"tag\": \"\",\n\n        \"inet4_range\": \"198.18.0.0/15\",\n        \"inet6_range\": \"fc00::/18\"\n      }\n    ]\n  }\n}\n```\n\n### 字段\n\n#### inet4_range\n\nFakeIP 的 IPv4 地址范围。\n\n#### inet6_range\n\nFakeIP 的 IPv6 地址范围。"
  },
  {
    "path": "docs/configuration/dns/server/hosts.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# Hosts\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"hosts\",\n        \"tag\": \"\",\n\n        \"path\": [],\n        \"predefined\": {}\n      }\n    ]\n  }\n}\n```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n### Fields\n\n#### path\n\nList of paths to hosts files.\n\n`/etc/hosts` is used by default.\n\n`C:\\Windows\\System32\\Drivers\\etc\\hosts` is used by default on Windows.\n\nExample:\n\n```json\n{\n  // \"path\": \"/etc/hosts\"\n  \n  \"path\": [\n    \"/etc/hosts\",\n    \"$HOME/.hosts\"\n  ]\n}\n```\n\n#### predefined\n\nPredefined hosts.\n\nExample:\n\n```json\n{\n  \"predefined\": {\n    \"www.google.com\": \"127.0.0.1\",\n    \"localhost\": [\n      \"127.0.0.1\",\n      \"::1\"\n    ]\n  }\n}\n```\n\n### Examples\n\n=== \"Use hosts if available\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            ...\n          },\n          {\n            \"type\": \"hosts\",\n            \"tag\": \"hosts\"\n          }\n        ],\n        \"rules\": [\n          {\n            \"ip_accept_any\": true,\n            \"server\": \"hosts\"\n          }\n        ]\n      }\n    }\n    ```"
  },
  {
    "path": "docs/configuration/dns/server/hosts.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# Hosts\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"hosts\",\n        \"tag\": \"\",\n\n        \"path\": [],\n        \"predefined\": {}\n      }\n    ]\n  }\n}\n```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签\n\n### 字段\n\n#### path\n\nhosts 文件路径列表。\n\n默认使用 `/etc/hosts`。\n\n在 Windows 上默认使用 `C:\\Windows\\System32\\Drivers\\etc\\hosts`。\n\n示例：\n\n```json\n{\n  // \"path\": \"/etc/hosts\"\n\n  \"path\": [\n    \"/etc/hosts\",\n    \"$HOME/.hosts\"\n  ]\n}\n```\n\n#### predefined\n\n预定义的 hosts。\n\n示例：\n\n```json\n{\n  \"predefined\": {\n    \"www.google.com\": \"127.0.0.1\",\n    \"localhost\": [\n      \"127.0.0.1\",\n      \"::1\"\n    ]\n  }\n}\n```\n\n### 示例\n\n=== \"如果可用则使用 hosts\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            ...\n          },\n          {\n            \"type\": \"hosts\",\n            \"tag\": \"hosts\"\n          }\n        ],\n        \"rules\": [\n          {\n            \"ip_accept_any\": true,\n            \"server\": \"hosts\"\n          }\n        ]\n      }\n    }\n    ```"
  },
  {
    "path": "docs/configuration/dns/server/http3.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# DNS over HTTP3 (DoH3)\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"h3\",\n        \"tag\": \"\",\n        \n        \"server\": \"\",\n        \"server_port\": 443,\n        \n        \"path\": \"\",\n        \"headers\": {},\n        \n        \"tls\": {},\n        \n        // Dial Fields\n      }\n    ]\n  }\n}\n```\n\n!!! info \"Difference from legacy H3 server\"\n\n    * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.\n    * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.\n\n### Fields\n\n#### server\n\n==Required==\n\nThe address of the DNS server.\n\nIf domain name is used, `domain_resolver` must also be set to resolve IP address.\n\n#### server_port\n\nThe port of the DNS server.\n\n`443` will be used by default.\n\n#### path\n\nThe path of the DNS server.\n\n`/dns-query` will be used by default.\n\n#### headers\n\nAdditional headers to be sent to the DNS server.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/dns/server/http3.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# DNS over HTTP3 (DoH3)\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"h3\",\n        \"tag\": \"\",\n\n        \"server\": \"\",\n        \"server_port\": 443,\n\n        \"path\": \"\",\n        \"headers\": {},\n\n        \"tls\": {},\n\n        // 拨号字段\n      }\n    ]\n  }\n}\n```\n\n!!! info \"与旧版 H3 服务器的区别\"\n\n    * 旧服务器默认使用默认出站，除非指定了绕行；新服务器像出站一样使用拨号器，相当于默认使用空的直连出站。\n    * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名；新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。\n\n### 字段\n\n#### server\n\n==必填==\n\nDNS 服务器的地址。\n\n如果使用域名，还必须设置 `domain_resolver` 来解析 IP 地址。\n\n#### server_port\n\nDNS 服务器的端口。\n\n默认使用 `443`。\n\n#### path\n\nDNS 服务器的路径。\n\n默认使用 `/dns-query`。\n\n#### headers\n\n发送到 DNS 服务器的额外标头。\n\n#### tls\n\nTLS 配置，参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。"
  },
  {
    "path": "docs/configuration/dns/server/https.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# DNS over HTTPS (DoH)\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"https\",\n        \"tag\": \"\",\n        \n        \"server\": \"\",\n        \"server_port\": 443,\n        \n        \"path\": \"\",\n        \"headers\": {},\n        \n        \"tls\": {},\n        \n        // Dial Fields\n      }\n    ]\n  }\n}\n```\n\n!!! info \"Difference from legacy HTTPS server\"\n\n    * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.\n    * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.\n\n### Fields\n\n#### server\n\n==Required==\n\nThe address of the DNS server.\n\nIf domain name is used, `domain_resolver` must also be set to resolve IP address.\n\n#### server_port\n\nThe port of the DNS server.\n\n`443` will be used by default.\n\n#### path\n\nThe path of the DNS server.\n\n`/dns-query` will be used by default.\n\n#### headers\n\nAdditional headers to be sent to the DNS server.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/dns/server/https.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# DNS over HTTPS (DoH)\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"https\",\n        \"tag\": \"\",\n\n        \"server\": \"\",\n        \"server_port\": 443,\n\n        \"path\": \"\",\n        \"headers\": {},\n\n        \"tls\": {},\n\n        // 拨号字段\n      }\n    ]\n  }\n}\n```\n\n!!! info \"与旧版 HTTPS 服务器的区别\"\n\n    * 旧服务器默认使用默认出站，除非指定了绕行；新服务器像出站一样使用拨号器，相当于默认使用空的直连出站。\n    * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名；新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。\n\n### 字段\n\n#### server\n\n==必填==\n\nDNS 服务器的地址。\n\n如果使用域名，还必须设置 `domain_resolver` 来解析 IP 地址。\n\n#### server_port\n\nDNS 服务器的端口。\n\n默认使用 `443`。\n\n#### path\n\nDNS 服务器的路径。\n\n默认使用 `/dns-query`。\n\n#### headers\n\n发送到 DNS 服务器的额外标头。\n\n#### tls\n\nTLS 配置，参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。"
  },
  {
    "path": "docs/configuration/dns/server/index.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-plus: [type](#type)\n\n# DNS Server\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"\",\n        \"tag\": \"\"\n      }\n    ]\n  }\n}\n```\n\n#### type\n\nThe type of the DNS server.\n\n| Type            | Format                    |\n|-----------------|---------------------------|\n| empty (default) | [Legacy](./legacy/)       |\n| `local`         | [Local](./local/)         |\n| `hosts`         | [Hosts](./hosts/)         |\n| `tcp`           | [TCP](./tcp/)             |\n| `udp`           | [UDP](./udp/)             |\n| `tls`           | [TLS](./tls/)             |\n| `quic`          | [QUIC](./quic/)           |\n| `https`         | [HTTPS](./https/)         |\n| `h3`            | [HTTP/3](./http3/)        |\n| `dhcp`          | [DHCP](./dhcp/)           |\n| `fakeip`        | [Fake IP](./fakeip/)      |\n| `tailscale`     | [Tailscale](./tailscale/) |\n| `resolved`      | [Resolved](./resolved/)   |\n\n#### tag\n\nThe tag of the DNS server.\n"
  },
  {
    "path": "docs/configuration/dns/server/index.zh.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-plus: [type](#type)\n\n# DNS Server\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"\",\n        \"tag\": \"\"\n      }\n    ]\n  }\n}\n```\n\n#### type\n\nDNS 服务器的类型。\n\n| 类型              | 格式                        |\n|-----------------|---------------------------|\n| empty (default) | [Legacy](./legacy/)       |\n| `local`         | [Local](./local/)         |\n| `hosts`         | [Hosts](./hosts/)         |\n| `tcp`           | [TCP](./tcp/)             |\n| `udp`           | [UDP](./udp/)             |\n| `tls`           | [TLS](./tls/)             |\n| `quic`          | [QUIC](./quic/)           |\n| `https`         | [HTTPS](./https/)         |\n| `h3`            | [HTTP/3](./http3/)        |\n| `dhcp`          | [DHCP](./dhcp/)           |\n| `fakeip`        | [Fake IP](./fakeip/)      |\n| `tailscale`     | [Tailscale](./tailscale/) |\n| `resolved`      | [Resolved](./resolved/)   |\n\n#### tag\n\nDNS 服务器的标签。\n"
  },
  {
    "path": "docs/configuration/dns/server/legacy.md",
    "content": "---\nicon: material/delete-clock\n---\n\n!!! failure \"Deprecated in sing-box 1.12.0\"\n\n    Legacy DNS servers is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).\n\n!!! quote \"Changes in sing-box 1.9.0\"\n\n    :material-plus: [client_subnet](#client_subnet)\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"tag\": \"\",\n        \"address\": \"\",\n        \"address_resolver\": \"\",\n        \"address_strategy\": \"\",\n        \"strategy\": \"\",\n        \"detour\": \"\",\n        \"client_subnet\": \"\"\n      }\n    ]\n  }\n}\n```\n\n### Fields\n\n#### tag\n\nThe tag of the dns server.\n\n#### address\n\n==Required==\n\nThe address of the dns server.\n\n| Protocol                             | Format                        |\n|--------------------------------------|-------------------------------|\n| `System`                             | `local`                       |\n| `TCP`                                | `tcp://1.0.0.1`               |\n| `UDP`                                | `8.8.8.8` `udp://8.8.4.4`     |\n| `TLS`                                | `tls://dns.google`            |\n| `HTTPS`                              | `https://1.1.1.1/dns-query`   |\n| `QUIC`                               | `quic://dns.adguard.com`      |\n| `HTTP3`                              | `h3://8.8.8.8/dns-query`      |\n| `RCode`                              | `rcode://refused`             |\n| `DHCP`                               | `dhcp://auto` or `dhcp://en0` |\n| [FakeIP](/configuration/dns/fakeip/) | `fakeip`                      |\n\n!!! warning \"\"\n\n    To ensure that Android system DNS is in effect, rather than Go's built-in default resolver, enable CGO at compile time.\n\n!!! info \"\"\n\n    the RCode transport is often used to block queries. Use with rules and the `disable_cache` rule option.\n\n| RCode             | Description           | \n|-------------------|-----------------------|\n| `success`         | `No error`            |\n| `format_error`    | `Format error`        |\n| `server_failure`  | `Server failure`      |\n| `name_error`      | `Non-existent domain` |\n| `not_implemented` | `Not implemented`     |\n| `refused`         | `Query refused`       |\n\n#### address_resolver\n\n==Required if address contains domain==\n\nTag of a another server to resolve the domain name in the address.\n\n#### address_strategy\n\nThe domain strategy for resolving the domain name in the address.\n\nOne of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.\n\n`dns.strategy` will be used if empty.\n\n#### strategy\n\nDefault domain strategy for resolving the domain names.\n\nOne of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.\n\nTake no effect if overridden by other settings.\n\n#### detour\n\nTag of an outbound for connecting to the dns server.\n\nDefault outbound will be used if empty.\n\n#### client_subnet\n\n!!! question \"Since sing-box 1.9.0\"\n\nAppend a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.\n\nIf value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.\n\nCan be overrides by `rules.[].client_subnet`.\n\nWill overrides `dns.client_subnet`.\n"
  },
  {
    "path": "docs/configuration/dns/server/legacy.zh.md",
    "content": "---\nicon: material/delete-clock\n---\n\n!!! failure \"Deprecated in sing-box 1.12.0\"\n\n    旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。\n\n!!! quote \"sing-box 1.9.0 中的更改\"\n\n    :material-plus: [client_subnet](#client_subnet)\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"tag\": \"\",\n        \"address\": \"\",\n        \"address_resolver\": \"\",\n        \"address_strategy\": \"\",\n        \"strategy\": \"\",\n        \"detour\": \"\",\n        \"client_subnet\": \"\"\n      }\n    ]\n  }\n}\n```\n\n### 字段\n\n#### tag\n\nDNS 服务器的标签。\n\n#### address\n\n==必填==\n\nDNS 服务器的地址。\n\n| 协议                                   | 格式                           |\n|--------------------------------------|------------------------------|\n| `System`                             | `local`                      |\n| `TCP`                                | `tcp://1.0.0.1`              |\n| `UDP`                                | `8.8.8.8` `udp://8.8.4.4`    |\n| `TLS`                                | `tls://dns.google`           |\n| `HTTPS`                              | `https://1.1.1.1/dns-query`  |\n| `QUIC`                               | `quic://dns.adguard.com`     |\n| `HTTP3`                              | `h3://8.8.8.8/dns-query`     |\n| `RCode`                              | `rcode://refused`            |\n| `DHCP`                               | `dhcp://auto` 或 `dhcp://en0` |\n| [FakeIP](/zh/configuration/dns/fakeip/) | `fakeip`                     |\n\n!!! warning \"\"\n\n    为了确保 Android 系统 DNS 生效，而不是 Go 的内置默认解析器，请在编译时启用 CGO。\n\n!!! info \"\"\n\n    RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。\n\n| RCode             | 描述       | \n|-------------------|----------|\n| `success`         | `无错误`    |\n| `format_error`    | `请求格式错误` |\n| `server_failure`  | `服务器出错`  |\n| `name_error`      | `域名不存在`  |\n| `not_implemented` | `功能未实现`  |\n| `refused`         | `请求被拒绝`  |\n\n#### address_resolver\n\n==如果服务器地址包括域名则必须==\n\n用于解析本 DNS 服务器的域名的另一个 DNS 服务器的标签。\n\n#### address_strategy\n\n用于解析本 DNS 服务器的域名的策略。\n\n可选项：`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。\n\n默认使用 `dns.strategy`。\n\n#### strategy\n\n默认解析策略。\n\n可选项：`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。\n\n如果被其他设置覆盖则不生效。\n\n#### detour\n\n用于连接到 DNS 服务器的出站的标签。\n\n如果为空，将使用默认出站。\n\n#### client_subnet\n\n!!! question \"自 sing-box 1.9.0 起\"\n\n默认情况下，将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。\n\n如果值是 IP 地址而不是前缀，则会自动附加 `/32` 或 `/128`。\n\n可以被 `rules.[].client_subnet` 覆盖。\n\n将覆盖 `dns.client_subnet`。\n"
  },
  {
    "path": "docs/configuration/dns/server/local.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [prefer_go](#prefer_go)\n\n!!! question \"Since sing-box 1.12.0\"\n\n# Local\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"local\",\n        \"tag\": \"\",\n        \"prefer_go\": false\n\n        // Dial Fields\n      }\n    ]\n  }\n}\n```\n\n!!! info \"Difference from legacy local server\"\n\n    * The old legacy local server only handles IP requests; the new one handles all types of requests and supports concurrent for IP requests.\n    * The old local server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.\n\n### Fields\n\n#### prefer_go\n\n!!! question \"Since sing-box 1.13.0\"\n\nWhen enabled, `local` DNS server will resolve DNS by dialing itself whenever possible.\n\nSpecifically, it disables following behaviors which was added as features in sing-box 1.13.0:\n\n1. On Apple platforms: Attempt to resolve A/AAAA requests using `getaddrinfo` in NetworkExtension.\n2. On Linux: Resolve through `systemd-resolvd`'s DBus interface when available.\n\nAs a sole exception, it cannot disable the following behavior:\n\n1. In the Android graphical client,\n`local` will always resolve DNS through the platform interface,\nas there is no other way to obtain upstream DNS servers;\nOn devices running Android versions lower than 10, this interface can only resolve A/AAAA requests.\n\n2. On macOS, `local` will try DHCP first in Network Extension, since DHCP respects DIal Fields,\nit will not be disabled by `prefer_go`.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/dns/server/local.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [prefer_go](#prefer_go)\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# Local\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"local\",\n        \"tag\": \"\",\n        \"prefer_go\": false,\n\n        // 拨号字段\n      }\n    ]\n  }\n}\n```\n\n!!! info \"与旧版本地服务器的区别\"\n\n    * 旧的传统本地服务器只处理 IP 请求；新的服务器处理所有类型的请求，并支持 IP 请求的并发处理。\n    * 旧的本地服务器默认使用默认出站，除非指定了绕行；新服务器像出站一样使用拨号器，相当于默认使用空的直连出站。\n\n### 字段\n\n#### prefer_go\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n启用后，`local` DNS 服务器将尽可能通过拨号自身来解析 DNS。\n\n具体来说，它禁用了在 sing-box 1.13.0 中作为功能添加的以下行为：\n\n1. 在 Apple 平台上：尝试在 NetworkExtension 中使用 `getaddrinfo` 解析 A/AAAA 请求。\n2. 在 Linux 上：当可用时通过 `systemd-resolvd` 的 DBus 接口进行解析。\n\n作为唯一的例外，它无法禁用以下行为：\n\n1. 在 Android 图形客户端中，\n`local` 将始终通过平台接口解析 DNS，\n因为没有其他方法来获取上游 DNS 服务器；\n在运行 Android 10 以下版本的设备上，此接口只能解析 A/AAAA 请求。\n\n2. 在 macOS 上，`local` 会在 Network Extension 中首先尝试 DHCP，由于 DHCP 遵循拨号字段，\n它不会被 `prefer_go` 禁用。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。"
  },
  {
    "path": "docs/configuration/dns/server/quic.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# DNS over QUIC (DoQ)\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"quic\",\n        \"tag\": \"\",\n        \n        \"server\": \"\",\n        \"server_port\": 853,\n        \n        \"tls\": {},\n        \n        // Dial Fields\n      }\n    ]\n  }\n}\n```\n\n!!! info \"Difference from legacy QUIC server\"\n\n    * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.\n    * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.\n\n### Fields\n\n#### server\n\n==Required==\n\nThe address of the DNS server.\n\nIf domain name is used, `domain_resolver` must also be set to resolve IP address.\n\n#### server_port\n\nThe port of the DNS server.\n\n`853` will be used by default.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/dns/server/quic.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# DNS over QUIC (DoQ)\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"quic\",\n        \"tag\": \"\",\n\n        \"server\": \"\",\n        \"server_port\": 853,\n\n        \"tls\": {},\n\n        // 拨号字段\n      }\n    ]\n  }\n}\n```\n\n!!! info \"与旧版 QUIC 服务器的区别\"\n\n    * 旧服务器默认使用默认出站，除非指定了绕行；新服务器像出站一样使用拨号器，相当于默认使用空的直连出站。\n    * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名；新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。\n\n### 字段\n\n#### server\n\n==必填==\n\nDNS 服务器的地址。\n\n如果使用域名，还必须设置 `domain_resolver` 来解析 IP 地址。\n\n#### server_port\n\nDNS 服务器的端口。\n\n默认使用 `853`。\n\n#### tls\n\nTLS 配置，参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。"
  },
  {
    "path": "docs/configuration/dns/server/resolved.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# Resolved\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"resolved\",\n        \"tag\": \"\",\n\n        \"service\": \"resolved\",\n        \"accept_default_resolvers\": false\n      }\n    ]\n  }\n}\n```\n\n\n### Fields\n\n#### service\n\n==Required==\n\nThe tag of the [Resolved Service](/configuration/service/resolved).\n\n#### accept_default_resolvers\n\nIndicates whether the default DNS resolvers should be accepted for fallback queries in addition to matching domains.\n\nSpecifically, default DNS resolvers are DNS servers that have `SetLinkDefaultRoute` or `SetLinkDomains ~.` set.\n\nIf not enabled, `NXDOMAIN` will be returned for requests that do not match search or match domains.\n\n### Examples\n\n=== \"Split DNS only\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"local\",\n            \"tag\": \"local\"\n          },\n          {\n            \"type\": \"resolved\",\n            \"tag\": \"resolved\",\n            \"service\": \"resolved\"\n          }\n        ],\n        \"rules\": [\n          {\n            \"ip_accept_any\": true,\n            \"server\": \"resolved\"\n          }\n        ]\n      }\n    }\n    ```\n\n=== \"Use as global DNS\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"resolved\",\n            \"service\": \"resolved\",\n            \"accept_default_resolvers\": true\n          }\n        ]\n      }\n    }\n    ```"
  },
  {
    "path": "docs/configuration/dns/server/resolved.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# Resolved\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"resolved\",\n        \"tag\": \"\",\n\n        \"service\": \"resolved\",\n        \"accept_default_resolvers\": false\n      }\n    ]\n  }\n}\n```\n\n### 字段\n\n#### service\n\n==必填==\n\n[Resolved 服务](/zh/configuration/service/resolved) 的标签。\n\n#### accept_default_resolvers\n\n指示是否除了匹配域名外，还应接受默认 DNS 解析器以进行回退查询。\n\n具体来说，默认 DNS 解析器是设置了 `SetLinkDefaultRoute` 或 `SetLinkDomains ~.` 的 DNS 服务器。\n\n如果未启用，对于不匹配搜索域或匹配域的请求，将返回 `NXDOMAIN`。\n\n### 示例\n\n=== \"仅分割 DNS\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"local\",\n            \"tag\": \"local\"\n          },\n          {\n            \"type\": \"resolved\",\n            \"tag\": \"resolved\",\n            \"service\": \"resolved\"\n          }\n        ],\n        \"rules\": [\n          {\n            \"ip_accept_any\": true,\n            \"server\": \"resolved\"\n          }\n        ]\n      }\n    }\n    ```\n\n=== \"用作全局 DNS\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"resolved\",\n            \"service\": \"resolved\",\n            \"accept_default_resolvers\": true\n          }\n        ]\n      }\n    }\n    ```"
  },
  {
    "path": "docs/configuration/dns/server/tailscale.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# Tailscale\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"tailscale\",\n        \"tag\": \"\",\n\n        \"endpoint\": \"ts-ep\",\n        \"accept_default_resolvers\": false\n      }\n    ]\n  }\n}\n```\n\n### Fields\n\n#### endpoint\n\n==Required==\n\nThe tag of the [Tailscale Endpoint](/configuration/endpoint/tailscale).\n\n#### accept_default_resolvers\n\nIndicates whether default DNS resolvers should be accepted for fallback queries in addition to MagicDNS。\n\nif not enabled, `NXDOMAIN` will be returned for non-Tailscale domain queries.\n\n### Examples\n\n=== \"MagicDNS only\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"local\",\n            \"tag\": \"local\"\n          },\n          {\n            \"type\": \"tailscale\",\n            \"tag\": \"ts\",\n            \"endpoint\": \"ts-ep\"\n          }\n        ],\n        \"rules\": [\n          {\n            \"ip_accept_any\": true,\n            \"server\": \"ts\"\n          }\n        ]\n      }\n    }\n    ```\n\n=== \"Use as global DNS\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"tailscale\",\n            \"endpoint\": \"ts-ep\",\n            \"accept_default_resolvers\": true\n          }\n        ]\n      }\n    }\n    ```\n"
  },
  {
    "path": "docs/configuration/dns/server/tailscale.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# Tailscale\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"tailscale\",\n        \"tag\": \"\",\n\n        \"endpoint\": \"ts-ep\",\n        \"accept_default_resolvers\": false\n      }\n    ]\n  }\n}\n```\n\n### 字段\n\n#### endpoint\n\n==必填==\n\n[Tailscale 端点](/zh/configuration/endpoint/tailscale) 的标签。\n\n#### accept_default_resolvers\n\n指示是否除了 MagicDNS 外，还应接受默认 DNS 解析器以进行回退查询。\n\n如果未启用，对于非 Tailscale 域名查询将返回 `NXDOMAIN`。\n\n### 示例\n\n=== \"仅 MagicDNS\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"local\",\n            \"tag\": \"local\"\n          },\n          {\n            \"type\": \"tailscale\",\n            \"tag\": \"ts\",\n            \"endpoint\": \"ts-ep\"\n          }\n        ],\n        \"rules\": [\n          {\n            \"ip_accept_any\": true,\n            \"server\": \"ts\"\n          }\n        ]\n      }\n    }\n    ```\n\n=== \"用作全局 DNS\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"tailscale\",\n            \"endpoint\": \"ts-ep\",\n            \"accept_default_resolvers\": true\n          }\n        ]\n      }\n    }\n    ```"
  },
  {
    "path": "docs/configuration/dns/server/tcp.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# TCP\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"tcp\",\n        \"tag\": \"\",\n        \n        \"server\": \"\",\n        \"server_port\": 53,\n        \n        // Dial Fields\n      }\n    ]\n  }\n}\n```\n\n!!! info \"Difference from legacy TCP server\"\n\n    * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.\n    * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.\n\n### Fields\n\n#### server\n\n==Required==\n\nThe address of the DNS server.\n\nIf domain name is used, `domain_resolver` must also be set to resolve IP address.\n\n#### server_port\n\nThe port of the DNS server.\n\n`53` will be used by default.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/dns/server/tcp.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# TCP\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"tcp\",\n        \"tag\": \"\",\n\n        \"server\": \"\",\n        \"server_port\": 53,\n\n        // 拨号字段\n      }\n    ]\n  }\n}\n```\n\n!!! info \"与旧版 TCP 服务器的区别\"\n\n    * 旧服务器默认使用默认出站，除非指定了绕行；新服务器像出站一样使用拨号器，相当于默认使用空的直连出站。\n    * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名；新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。\n\n### 字段\n\n#### server\n\n==必填==\n\nDNS 服务器的地址。\n\n如果使用域名，还必须设置 `domain_resolver` 来解析 IP 地址。\n\n#### server_port\n\nDNS 服务器的端口。\n\n默认使用 `53`。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。"
  },
  {
    "path": "docs/configuration/dns/server/tls.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# DNS over TLS (DoT)\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"tls\",\n        \"tag\": \"\",\n        \n        \"server\": \"\",\n        \"server_port\": 853,\n        \n        \"tls\": {},\n        \n        // Dial Fields\n      }\n    ]\n  }\n}\n```\n\n!!! info \"Difference from legacy TLS server\"\n\n    * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.\n    * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.\n\n### Fields\n\n#### server\n\n==Required==\n\nThe address of the DNS server.\n\nIf domain name is used, `domain_resolver` must also be set to resolve IP address.\n\n#### server_port\n\nThe port of the DNS server.\n\n`853` will be used by default.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/dns/server/tls.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# DNS over TLS (DoT)\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"tls\",\n        \"tag\": \"\",\n\n        \"server\": \"\",\n        \"server_port\": 853,\n\n        \"tls\": {},\n\n        // 拨号字段\n      }\n    ]\n  }\n}\n```\n\n!!! info \"与旧版 TLS 服务器的区别\"\n\n    * 旧服务器默认使用默认出站，除非指定了绕行；新服务器像出站一样使用拨号器，相当于默认使用空的直连出站。\n    * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名；新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。\n\n### 字段\n\n#### server\n\n==必填==\n\nDNS 服务器的地址。\n\n如果使用域名，还必须设置 `domain_resolver` 来解析 IP 地址。\n\n#### server_port\n\nDNS 服务器的端口。\n\n默认使用 `853`。\n\n#### tls\n\nTLS 配置，参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。"
  },
  {
    "path": "docs/configuration/dns/server/udp.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# UDP\n\n### Structure\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"udp\",\n        \"tag\": \"\",\n        \n        \"server\": \"\",\n        \"server_port\": 53,\n        \n        // Dial Fields\n      }\n    ]\n  }\n}\n```\n\n!!! info \"Difference from legacy UDP server\"\n\n    * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.\n    * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.\n\n### Fields\n\n#### server\n\n==Required==\n\nThe address of the DNS server.\n\nIf domain name is used, `domain_resolver` must also be set to resolve IP address.\n\n#### server_port\n\nThe port of the DNS server.\n\n`53` will be used by default.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/dns/server/udp.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# UDP\n\n### 结构\n\n```json\n{\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"udp\",\n        \"tag\": \"\",\n\n        \"server\": \"\",\n        \"server_port\": 53,\n\n        // 拨号字段\n      }\n    ]\n  }\n}\n```\n\n!!! info \"与旧版 UDP 服务器的区别\"\n\n    * 旧服务器默认使用默认出站，除非指定了绕行；新服务器像出站一样使用拨号器，相当于默认使用空的直连出站。\n    * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名；新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。\n\n### 字段\n\n#### server\n\n==必填==\n\nDNS 服务器的地址。\n\n如果使用域名，还必须设置 `domain_resolver` 来解析 IP 地址。\n\n#### server_port\n\nDNS 服务器的端口。\n\n默认使用 `53`。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。"
  },
  {
    "path": "docs/configuration/endpoint/index.md",
    "content": "!!! question \"Since sing-box 1.11.0\"\n\n# Endpoint\n\nAn endpoint is a protocol with inbound and outbound behavior.\n\n### Structure\n\n```json\n{\n  \"endpoints\": [\n    {\n      \"type\": \"\",\n      \"tag\": \"\"\n    }\n  ]\n}\n```\n\n### Fields\n\n| Type        | Format                    |\n|-------------|---------------------------|\n| `wireguard` | [WireGuard](./wireguard/) |\n| `tailscale` | [Tailscale](./tailscale/) |\n\n#### tag\n\nThe tag of the endpoint.\n"
  },
  {
    "path": "docs/configuration/endpoint/index.zh.md",
    "content": "!!! question \"自 sing-box 1.11.0 起\"\n\n# 端点\n\n端点是具有入站和出站行为的协议。\n\n### 结构\n\n```json\n{\n  \"endpoints\": [\n    {\n      \"type\": \"\",\n      \"tag\": \"\"\n    }\n  ]\n}\n```\n\n### 字段\n\n| 类型          | 格式                        |\n|-------------|---------------------------|\n| `wireguard` | [WireGuard](./wireguard/) |\n| `tailscale` | [Tailscale](./tailscale/) |\n\n#### tag\n\n端点的标签。\n"
  },
  {
    "path": "docs/configuration/endpoint/tailscale.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [relay_server_port](#relay_server_port)  \n    :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints)  \n    :material-plus: [system_interface](#system_interface)  \n    :material-plus: [system_interface_name](#system_interface_name)  \n    :material-plus: [system_interface_mtu](#system_interface_mtu)  \n    :material-plus: [advertise_tags](#advertise_tags)\n\n!!! question \"Since sing-box 1.12.0\"\n\n### Structure\n\n```json\n{\n  \"type\": \"tailscale\",\n  \"tag\": \"ts-ep\",\n  \"state_directory\": \"\",\n  \"auth_key\": \"\",\n  \"control_url\": \"\",\n  \"ephemeral\": false,\n  \"hostname\": \"\",\n  \"accept_routes\": false,\n  \"exit_node\": \"\",\n  \"exit_node_allow_lan_access\": false,\n  \"advertise_routes\": [],\n  \"advertise_exit_node\": false,\n  \"advertise_tags\": [],\n  \"relay_server_port\": 0,\n  \"relay_server_static_endpoints\": [],\n  \"system_interface\": false,\n  \"system_interface_name\": \"\",\n  \"system_interface_mtu\": 0,\n  \"udp_timeout\": \"5m\",\n\n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### state_directory\n\nThe directory where the Tailscale state is stored.\n\n`tailscale` is used by default.\n\nExample: `$HOME/.tailscale`\n\n#### auth_key\n\n!!! note\n    \n    Auth key is not required. By default, sing-box will log the login URL (or popup a notification on graphical clients).\n\nThe auth key to create the node. If the node is already created (from state previously stored), then this field is not\nused.\n\n#### control_url\n\nThe coordination server URL.\n\n`https://controlplane.tailscale.com` is used by default.\n\n#### ephemeral\n\nIndicates whether the instance should register as an Ephemeral node (https://tailscale.com/s/ephemeral-nodes).\n\n#### hostname\n\nThe hostname of the node.\n\nSystem hostname is used by default.\n\nExample: `localhost`\n\n#### accept_routes\n\nIndicates whether the node should accept routes advertised by other nodes.\n\n#### exit_node\n\nThe exit node name or IP address to use.\n\n#### exit_node_allow_lan_access\n\n!!! note\n\n    When the exit node does not have a corresponding advertised route, private traffics cannot be routed to the exit node even if `exit_node_allow_lan_access is` set.\n\nIndicates whether locally accessible subnets should be routed directly or via the exit node.\n\n#### advertise_routes\n\nCIDR prefixes to advertise into the Tailscale network as reachable through the current node.\n\nExample: `[\"192.168.1.1/24\"]`\n\n#### advertise_exit_node\n\nIndicates whether the node should advertise itself as an exit node.\n\n#### advertise_tags\n\n!!! question \"Since sing-box 1.13.0\"\n\nTags to advertise for this node, for ACL enforcement purposes.\n\nExample: `[\"tag:server\"]`\n\n#### relay_server_port\n\n!!! question \"Since sing-box 1.13.0\"\n\nThe port to listen on for incoming relay connections from other Tailscale nodes.\n\n#### relay_server_static_endpoints\n\n!!! question \"Since sing-box 1.13.0\"\n\nStatic endpoints to advertise for the relay server.\n\n#### system_interface\n\n!!! question \"Since sing-box 1.13.0\"\n\nCreate a system TUN interface for Tailscale.\n\n#### system_interface_name\n\n!!! question \"Since sing-box 1.13.0\"\n\nCustom TUN interface name. By default, `tailscale` (or `utun` on macOS) will be used.\n\n#### system_interface_mtu\n\n!!! question \"Since sing-box 1.13.0\"\n\nOverride the TUN MTU. By default, Tailscale's own MTU is used.\n\n#### udp_timeout\n\nUDP NAT expiration time.\n\n`5m` will be used by default.\n\n### Dial Fields\n\n!!! note\n\n    Dial Fields in Tailscale endpoints only control how it connects to the control plane and have nothing to do with actual connections.\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/endpoint/tailscale.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [relay_server_port](#relay_server_port)  \n    :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints)  \n    :material-plus: [system_interface](#system_interface)  \n    :material-plus: [system_interface_name](#system_interface_name)  \n    :material-plus: [system_interface_mtu](#system_interface_mtu)  \n    :material-plus: [advertise_tags](#advertise_tags)\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n### 结构\n\n```json\n{\n  \"type\": \"tailscale\",\n  \"tag\": \"ts-ep\",\n  \"state_directory\": \"\",\n  \"auth_key\": \"\",\n  \"control_url\": \"\",\n  \"ephemeral\": false,\n  \"hostname\": \"\",\n  \"accept_routes\": false,\n  \"exit_node\": \"\",\n  \"exit_node_allow_lan_access\": false,\n  \"advertise_routes\": [],\n  \"advertise_exit_node\": false,\n  \"advertise_tags\": [],\n  \"relay_server_port\": 0,\n  \"relay_server_static_endpoints\": [],\n  \"system_interface\": false,\n  \"system_interface_name\": \"\",\n  \"system_interface_mtu\": 0,\n  \"udp_timeout\": \"5m\",\n\n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### state_directory\n\n存储 Tailscale 状态的目录。\n\n默认使用 `tailscale`。\n\n示例：`$HOME/.tailscale`\n\n#### auth_key\n\n!!! note\n\n    认证密钥不是必需的。默认情况下，sing-box 将记录登录 URL（或在图形客户端上弹出通知）。\n\n用于创建节点的认证密钥。如果节点已经创建（从之前存储的状态），则不使用此字段。\n\n#### control_url\n\n协调服务器 URL。\n\n默认使用 `https://controlplane.tailscale.com`。\n\n#### ephemeral\n\n指示实例是否应注册为临时节点 (https://tailscale.com/s/ephemeral-nodes)。\n\n#### hostname\n\n节点的主机名。\n\n默认使用系统主机名。\n\n示例：`localhost`\n\n#### accept_routes\n\n指示节点是否应接受其他节点通告的路由。\n\n#### exit_node\n\n要使用的出口节点名称或 IP 地址。\n\n#### exit_node_allow_lan_access\n\n!!! note\n\n    当出口节点没有相应的通告路由时，即使设置了 `exit_node_allow_lan_access`，私有流量也无法路由到出口节点。\n\n指示本地可访问的子网应该直接路由还是通过出口节点路由。\n\n#### advertise_routes\n\n通告到 Tailscale 网络的 CIDR 前缀，作为可通过当前节点访问的路由。\n\n示例：`[\"192.168.1.1/24\"]`\n\n#### advertise_exit_node\n\n指示节点是否应将自己通告为出口节点。\n\n#### advertise_tags\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n为此节点通告的标签，用于 ACL 执行。\n\n示例：`[\"tag:server\"]`\n\n#### relay_server_port\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n监听来自其他 Tailscale 节点的中继连接的端口。\n\n#### relay_server_static_endpoints\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n为中继服务器通告的静态端点。\n\n#### system_interface\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n为 Tailscale 创建系统 TUN 接口。\n\n#### system_interface_name\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n自定义 TUN 接口名。默认使用 `tailscale`（macOS 上为 `utun`）。\n\n#### system_interface_mtu\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n覆盖 TUN 的 MTU。默认使用 Tailscale 自己的 MTU。\n\n#### udp_timeout\n\nUDP NAT 过期时间。\n\n默认使用 `5m`。\n\n### 拨号字段\n\n!!! note\n\n    Tailscale 端点中的拨号字段仅控制它如何连接到控制平面，与实际连接无关。\n\n参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。\n"
  },
  {
    "path": "docs/configuration/endpoint/wireguard.md",
    "content": "!!! question \"Since sing-box 1.11.0\"\n\n### Structure\n\n```json\n{\n  \"type\": \"wireguard\",\n  \"tag\": \"wg-ep\",\n  \n  \"system\": false,\n  \"name\": \"\",\n  \"mtu\": 1408,\n  \"address\": [],\n  \"private_key\": \"\",\n  \"listen_port\": 10000,\n  \"peers\": [\n    {\n      \"address\": \"127.0.0.1\",\n      \"port\": 10001,\n      \"public_key\": \"\",\n      \"pre_shared_key\": \"\",\n      \"allowed_ips\": [],\n      \"persistent_keepalive_interval\": 0,\n      \"reserved\": [0, 0, 0]\n    }\n  ],\n  \"udp_timeout\": \"\",\n  \"workers\": 0,\n \n  ... // Dial Fields\n}\n```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n### Fields\n\n#### system\n\nUse system interface.\n\nRequires privilege and cannot conflict with exists system interfaces.\n\n#### name\n\nCustom interface name for system interface.\n\n#### mtu\n\nWireGuard MTU.\n\n`1408` will be used by default.\n\n#### address\n\n==Required==\n\nList of IP (v4 or v6) address prefixes to be assigned to the interface.\n\n#### private_key\n\n==Required==\n\nWireGuard requires base64-encoded public and private keys. These can be generated using the wg(8) utility:\n\n```shell\nwg genkey\necho \"private key\" || wg pubkey\n```\n\nor `sing-box generate wg-keypair`.\n\n#### peers\n\n==Required==\n\nList of WireGuard peers.\n\n#### peers.address\n\nWireGuard peer address.\n\n#### peers.port\n\nWireGuard peer port.\n\n#### peers.public_key\n\n==Required==\n\nWireGuard peer public key.\n\n#### peers.pre_shared_key\n\nWireGuard peer pre-shared key.\n\n#### peers.allowed_ips\n\n==Required==\n\nWireGuard allowed IPs.\n\n#### peers.persistent_keepalive_interval\n\nWireGuard persistent keepalive interval, in seconds.\n\nDisabled by default.\n\n#### peers.reserved\n\nWireGuard reserved field bytes.\n\n#### udp_timeout\n\nUDP NAT expiration time.\n\n`5m` will be used by default.\n\n#### workers\n\nWireGuard worker count.\n\nCPU count is used by default.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/endpoint/wireguard.zh.md",
    "content": "!!! question \"自 sing-box 1.11.0 起\"\n\n### 结构\n\n```json\n{\n  \"type\": \"wireguard\",\n  \"tag\": \"wg-ep\",\n\n  \"system\": false,\n  \"name\": \"\",\n  \"mtu\": 1408,\n  \"address\": [],\n  \"private_key\": \"\",\n  \"listen_port\": 10000,\n  \"peers\": [\n    {\n      \"address\": \"127.0.0.1\",\n      \"port\": 10001,\n      \"public_key\": \"\",\n      \"pre_shared_key\": \"\",\n      \"allowed_ips\": [],\n      \"persistent_keepalive_interval\": 0,\n      \"reserved\": [0, 0, 0]\n    }\n  ],\n  \"udp_timeout\": \"\",\n  \"workers\": 0,\n\n  ... // 拨号字段\n}\n```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签\n\n### 字段\n\n#### system\n\n使用系统设备。\n\n需要特权且不能与已有系统接口冲突。\n\n#### name\n\n为系统接口自定义设备名称。\n\n#### mtu\n\nWireGuard MTU。\n\n默认使用 1408。\n\n#### address\n\n==必填==\n\n接口的 IPv4/IPv6 地址或地址段的列表。\n\n要分配给接口的 IP（v4 或 v6）地址段列表。\n\n#### private_key\n\n==必填==\n\nWireGuard 需要 base64 编码的公钥和私钥。 这些可以使用 wg(8) 实用程序生成：\n\n```shell\nwg genkey\necho \"private key\" || wg pubkey\n```\n\n或 `sing-box generate wg-keypair`.\n\n#### peers\n\n==必填==\n\nWireGuard 对等方的列表。\n\n#### peers.address\n\n对等方的 IP 地址。\n\n#### peers.port\n\n对等方的 WireGuard 端口。\n\n#### peers.public_key\n\n==必填==\n\n对等方的 WireGuard 公钥。\n\n#### peers.pre_shared_key\n\n对等方的预共享密钥。\n\n#### peers.allowed_ips\n\n==必填==\n\n对等方的允许 IP 地址。\n\n#### peers.persistent_keepalive_interval\n\n对等方的持久性保持活动间隔，以秒为单位。\n\n默认禁用。\n\n#### peers.reserved\n\n对等方的保留字段字节。\n\n#### udp_timeout\n\nUDP NAT 过期时间。\n\n默认使用 `5m`。\n\n#### workers\n\nWireGuard worker 数量。\n\n默认使用 CPU 数量。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/experimental/cache-file.md",
    "content": "!!! question \"Since sing-box 1.8.0\"\n\n!!! quote \"Changes in sing-box 1.9.0\"\n\n    :material-plus: [store_rdrc](#store_rdrc)  \n    :material-plus: [rdrc_timeout](#rdrc_timeout)\n\n### Structure\n\n```json\n{\n  \"enabled\": true,\n  \"path\": \"\",\n  \"cache_id\": \"\",\n  \"store_fakeip\": false,\n  \"store_rdrc\": false,\n  \"rdrc_timeout\": \"\"\n}\n```\n\n### Fields\n\n#### enabled\n\nEnable cache file.\n\n#### path\n\nPath to the cache file.\n\n`cache.db` will be used if empty.\n\n#### cache_id\n\nIdentifier in the cache file\n\nIf not empty, configuration specified data will use a separate store keyed by it.\n\n#### store_fakeip\n\nStore fakeip in the cache file\n\n#### store_rdrc\n\nStore rejected DNS response cache in the cache file\n\nThe check results of [Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields)\nwill be cached until expiration.\n\n#### rdrc_timeout\n\nTimeout of rejected DNS response cache.\n\n`7d` is used by default.\n"
  },
  {
    "path": "docs/configuration/experimental/cache-file.zh.md",
    "content": "!!! question \"自 sing-box 1.8.0 起\"\n\n!!! quote \"sing-box 1.9.0 中的更改\"\n\n    :material-plus: [store_rdrc](#store_rdrc)  \n    :material-plus: [rdrc_timeout](#rdrc_timeout)\n\n### 结构\n\n```json\n{\n  \"enabled\": true,\n  \"path\": \"\",\n  \"cache_id\": \"\",\n  \"store_fakeip\": false,\n  \"store_rdrc\": false,\n  \"rdrc_timeout\": \"\"\n}\n```\n\n### 字段\n\n#### enabled\n\n启用缓存文件。\n\n#### path\n\n缓存文件路径，默认使用`cache.db`。\n\n#### cache_id\n\n缓存文件中的标识符。\n\n如果不为空，配置特定的数据将使用由其键控的单独存储。\n\n#### store_fakeip\n\n将 fakeip 存储在缓存文件中。\n\n#### store_rdrc\n\n将拒绝的 DNS 响应缓存存储在缓存文件中。\n\n[地址筛选 DNS 规则项](/zh/configuration/dns/rule/#地址筛选字段) 的检查结果将被缓存至过期。\n\n#### rdrc_timeout\n\n拒绝的 DNS 响应缓存超时。\n\n默认使用 `7d`。\n"
  },
  {
    "path": "docs/configuration/experimental/clash-api.md",
    "content": "!!! quote \"Changes in sing-box 1.10.0\"\n\n    :material-plus: [access_control_allow_origin](#access_control_allow_origin)  \n    :material-plus: [access_control_allow_private_network](#access_control_allow_private_network)\n\n!!! quote \"Changes in sing-box 1.8.0\"\n\n    :material-delete-alert: [store_mode](#store_mode)  \n    :material-delete-alert: [store_selected](#store_selected)  \n    :material-delete-alert: [store_fakeip](#store_fakeip)  \n    :material-delete-alert: [cache_file](#cache_file)  \n    :material-delete-alert: [cache_id](#cache_id)\n\n### Structure\n\n=== \"Structure\"\n\n    ```json\n    {\n      \"external_controller\": \"127.0.0.1:9090\",\n      \"external_ui\": \"\",\n      \"external_ui_download_url\": \"\",\n      \"external_ui_download_detour\": \"\",\n      \"secret\": \"\",\n      \"default_mode\": \"\",\n      \"access_control_allow_origin\": [],\n      \"access_control_allow_private_network\": false,\n      \n      // Deprecated\n      \n      \"store_mode\": false,\n      \"store_selected\": false,\n      \"store_fakeip\": false,\n      \"cache_file\": \"\",\n      \"cache_id\": \"\"\n    }\n    ```\n\n=== \"Example (online)\"\n\n    !!! question \"Since sing-box 1.10.0\"\n\n    ```json\n    {\n      \"external_controller\": \"127.0.0.1:9090\",\n      \"access_control_allow_origin\": [\n        \"http://127.0.0.1\",\n        \"http://yacd.haishan.me\"\n      ],\n      \"access_control_allow_private_network\": true\n    }\n    ```\n\n=== \"Example (download)\"\n\n    !!! question \"Since sing-box 1.10.0\"\n\n    ```json\n    {\n      \"external_controller\": \"0.0.0.0:9090\",\n      \"external_ui\": \"dashboard\"\n      // \"external_ui_download_detour\": \"direct\"\n    }\n    ```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n### Fields\n\n#### external_controller\n\nRESTful web API listening address. Clash API will be disabled if empty.\n\n#### external_ui\n\nA relative path to the configuration directory or an absolute path to a\ndirectory in which you put some static web resource. sing-box will then\nserve it at `http://{{external-controller}}/ui`.\n\n#### external_ui_download_url\n\nZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.\n\n`https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.\n\n#### external_ui_download_detour\n\nThe tag of the outbound to download the external UI.\n\nDefault outbound will be used if empty.\n\n#### secret\n\nSecret for the RESTful API (optional)\nAuthenticate by spedifying HTTP header `Authorization: Bearer ${secret}`\nALWAYS set a secret if RESTful API is listening on 0.0.0.0\n\n#### default_mode\n\nDefault mode in clash, `Rule` will be used if empty.\n\nThis setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item.\n\n#### access_control_allow_origin\n\n!!! question \"Since sing-box 1.10.0\"\n\nCORS allowed origins, `*` will be used if empty.\n\nTo access the Clash API on a private network from a public website, you must explicitly specify it in `access_control_allow_origin` instead of using `*`.\n\n#### access_control_allow_private_network\n\n!!! question \"Since sing-box 1.10.0\"\n\nAllow access from private network.\n\nTo access the Clash API on a private network from a public website, `access_control_allow_private_network` must be enabled.\n\n#### store_mode\n\n!!! failure \"Deprecated in sing-box 1.8.0\"\n\n    `store_mode` is deprecated in Clash API and enabled by default if `cache_file.enabled`.\n\nStore Clash mode in cache file.\n\n#### store_selected\n\n!!! failure \"Deprecated in sing-box 1.8.0\"\n\n    `store_selected` is deprecated in Clash API and enabled by default if `cache_file.enabled`.\n\n!!! note \"\"\n\n    The tag must be set for target outbounds.\n\nStore selected outbound for the `Selector` outbound in cache file.\n\n#### store_fakeip\n\n!!! failure \"Deprecated in sing-box 1.8.0\"\n\n    `store_selected` is deprecated in Clash API and migrated to `cache_file.store_fakeip`.\n\nStore fakeip in cache file.\n\n#### cache_file\n\n!!! failure \"Deprecated in sing-box 1.8.0\"\n\n    `cache_file` is deprecated in Clash API and migrated to `cache_file.enabled` and `cache_file.path`.\n\nCache file path, `cache.db` will be used if empty.\n\n#### cache_id\n\n!!! failure \"Deprecated in sing-box 1.8.0\"\n\n    `cache_id` is deprecated in Clash API and migrated to `cache_file.cache_id`.\n\nIdentifier in cache file.\n\nIf not empty, configuration specified data will use a separate store keyed by it.\n"
  },
  {
    "path": "docs/configuration/experimental/clash-api.zh.md",
    "content": "!!! quote \"sing-box 1.10.0 中的更改\"\n\n    :material-plus: [access_control_allow_origin](#access_control_allow_origin)  \n    :material-plus: [access_control_allow_private_network](#access_control_allow_private_network)\n\n!!! quote \"sing-box 1.8.0 中的更改\"\n\n    :material-delete-alert: [store_mode](#store_mode)  \n    :material-delete-alert: [store_selected](#store_selected)  \n    :material-delete-alert: [store_fakeip](#store_fakeip)  \n    :material-delete-alert: [cache_file](#cache_file)  \n    :material-delete-alert: [cache_id](#cache_id)\n\n### 结构\n\n=== \"结构\"\n\n    ```json\n    {\n      \"external_controller\": \"127.0.0.1:9090\",\n      \"external_ui\": \"\",\n      \"external_ui_download_url\": \"\",\n      \"external_ui_download_detour\": \"\",\n      \"secret\": \"\",\n      \"default_mode\": \"\",\n      \"access_control_allow_origin\": [],\n      \"access_control_allow_private_network\": false,\n      \n      // Deprecated\n      \n      \"store_mode\": false,\n      \"store_selected\": false,\n      \"store_fakeip\": false,\n      \"cache_file\": \"\",\n      \"cache_id\": \"\"\n    }\n    ```\n\n=== \"示例 (在线)\"\n\n    !!! question \"自 sing-box 1.10.0 起\"\n\n    ```json\n    {\n      \"external_controller\": \"127.0.0.1:9090\",\n      \"access_control_allow_origin\": [\n        \"http://127.0.0.1\",\n        \"http://yacd.haishan.me\"\n      ],\n      \"access_control_allow_private_network\": true\n    }\n    ```\n\n=== \"示例 (下载)\"\n\n    !!! question \"自 sing-box 1.10.0 起\"\n\n    ```json\n    {\n      \"external_controller\": \"0.0.0.0:9090\",\n      \"external_ui\": \"dashboard\"\n      // \"external_ui_download_detour\": \"direct\"\n    }\n    ```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签\n\n### Fields\n\n#### external_controller\n\nRESTful web API 监听地址。如果为空，则禁用 Clash API。\n\n#### external_ui\n\n到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。\n\n#### external_ui_download_url\n\n静态网页资源的 ZIP 下载 URL，如果指定的 `external_ui` 目录为空，将使用。\n\n默认使用 `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip`。\n\n#### external_ui_download_detour\n\n用于下载静态网页资源的出站的标签。\n\n如果为空，将使用默认出站。\n\n#### secret\n\nRESTful API 的密钥（可选）\n通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证\n如果 RESTful API 正在监听 0.0.0.0，请始终设置一个密钥。\n\n#### default_mode\n\nClash 中的默认模式，默认使用 `Rule`。\n\n此设置没有直接影响，但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。\n\n#### access_control_allow_origin\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n允许的 CORS 来源，默认使用 `*`。\n\n要从公共网站访问私有网络上的 Clash API，必须在 `access_control_allow_origin` 中明确指定它而不是使用 `*`。\n\n#### access_control_allow_private_network\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n允许从私有网络访问。\n\n要从公共网站访问私有网络上的 Clash API，必须启用 `access_control_allow_private_network`。\n\n#### store_mode\n\n!!! failure \"已在 sing-box 1.8.0 废弃\"\n\n    `store_mode` 已在 Clash API 中废弃，且默认启用当 `cache_file.enabled`。\n\n将 Clash 模式存储在缓存文件中。\n\n#### store_selected\n\n!!! failure \"已在 sing-box 1.8.0 废弃\"\n\n    `store_selected` 已在 Clash API 中废弃，且默认启用当 `cache_file.enabled`。\n\n!!! note \"\"\n\n    必须为目标出站设置标签。\n\n将 `Selector` 中出站的选定的目标出站存储在缓存文件中。\n\n#### store_fakeip\n\n!!! failure \"已在 sing-box 1.8.0 废弃\"\n\n    `store_selected` 已在 Clash API 中废弃，且已迁移到 `cache_file.store_fakeip`。\n\n将 fakeip 存储在缓存文件中。\n\n#### cache_file\n\n!!! failure \"已在 sing-box 1.8.0 废弃\"\n \n    `cache_file` 已在 Clash API 中废弃，且已迁移到 `cache_file.enabled` 和 `cache_file.path`。\n\n缓存文件路径，默认使用`cache.db`。\n\n#### cache_id\n\n!!! failure \"已在 sing-box 1.8.0 废弃\"\n \n    `cache_id` 已在 Clash API 中废弃，且已迁移到 `cache_file.cache_id`。\n\n缓存 ID。\n\n如果不为空，配置特定的数据将使用由其键控的单独存储。\n"
  },
  {
    "path": "docs/configuration/experimental/index.md",
    "content": "# Experimental\n\n!!! quote \"Changes in sing-box 1.8.0\"\n\n    :material-plus: [cache_file](#cache_file)  \n    :material-alert-decagram: [clash_api](#clash_api)\n\n### Structure\n\n```json\n{\n  \"experimental\": {\n    \"cache_file\": {},\n    \"clash_api\": {},\n    \"v2ray_api\": {}\n  }\n}\n```\n\n### Fields\n\n| Key          | Format                     |\n|--------------|----------------------------|\n| `cache_file` | [Cache File](./cache-file/) |\n| `clash_api`  | [Clash API](./clash-api/)   |\n| `v2ray_api`  | [V2Ray API](./v2ray-api/)   |"
  },
  {
    "path": "docs/configuration/experimental/index.zh.md",
    "content": "# 实验性\n\n!!! quote \"sing-box 1.8.0 中的更改\"\n\n    :material-plus: [cache_file](#cache_file)  \n    :material-alert-decagram: [clash_api](#clash_api)\n\n### 结构\n\n```json\n{\n  \"experimental\": {\n    \"cache_file\": {},\n    \"clash_api\": {},\n    \"v2ray_api\": {}\n  }\n}\n```\n\n### 字段\n\n| 键            | 格式                       |\n|--------------|--------------------------|\n| `cache_file` | [缓存文件](./cache-file/)     |\n| `clash_api`  | [Clash API](./clash-api/) |\n| `v2ray_api`  | [V2Ray API](./v2ray-api/) |"
  },
  {
    "path": "docs/configuration/experimental/v2ray-api.md",
    "content": "!!! quote \"\"\n\n    V2Ray API is not included by default, see [Installation](/installation/build-from-source/#build-tags).\n\n### Structure\n\n```json\n{\n  \"listen\": \"127.0.0.1:8080\",\n  \"stats\": {\n    \"enabled\": true,\n    \"inbounds\": [\n      \"socks-in\"\n    ],\n    \"outbounds\": [\n      \"proxy\",\n      \"direct\"\n    ],\n    \"users\": [\n      \"sekai\"\n    ]\n  }\n}\n```\n\n### Fields\n\n#### listen\n\ngRPC API listening address. V2Ray API will be disabled if empty.\n\n#### stats\n\nTraffic statistics service settings.\n\n#### stats.enabled\n\nEnable statistics service.\n\n#### stats.inbounds\n\nInbound list to count traffic.\n\n#### stats.outbounds\n\nOutbound list to count traffic.\n\n#### stats.users\n\nUser list to count traffic."
  },
  {
    "path": "docs/configuration/experimental/v2ray-api.zh.md",
    "content": "!!! quote \"\"\n\n    默认安装不包含 V2Ray API，参阅 [安装](/zh/installation/build-from-source/#构建标记)。\n\n### 结构\n\n```json\n{\n  \"listen\": \"127.0.0.1:8080\",\n  \"stats\": {\n    \"enabled\": true,\n    \"inbounds\": [\n      \"socks-in\"\n    ],\n    \"outbounds\": [\n      \"proxy\",\n      \"direct\"\n    ],\n    \"users\": [\n      \"sekai\"\n    ]\n  }\n}\n```\n\n### 字段\n\n#### listen\n\ngRPC API 监听地址。如果为空，则禁用 V2Ray API。\n\n#### stats\n\n流量统计服务设置。\n\n#### stats.enabled\n\n启用统计服务。\n\n#### stats.inbounds\n\n统计流量的入站列表。\n\n#### stats.outbounds\n\n统计流量的出站列表。\n\n#### stats.users\n\n统计流量的用户列表。"
  },
  {
    "path": "docs/configuration/inbound/anytls.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n### Structure\n\n```json\n{\n  \"type\": \"anytls\",\n  \"tag\": \"anytls-in\",\n\n  ... // Listen Fields\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"password\": \"8JCsPssfgS8tiRwiMlhARg==\"\n    }\n  ],\n  \"padding_scheme\": [],\n  \"tls\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### users\n\n==Required==\n\nAnyTLS users.\n\n#### padding_scheme\n\nAnyTLS padding scheme line array.\n\nDefault padding scheme:\n\n```json\n[\n  \"stop=8\",\n  \"0=30-30\",\n  \"1=100-400\",\n  \"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000\",\n  \"3=9-9,500-1000\",\n  \"4=500-1000\",\n  \"5=500-1000\",\n  \"6=500-1000\",\n  \"7=500-1000\"\n]\n```\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound).\n"
  },
  {
    "path": "docs/configuration/inbound/anytls.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n### 结构\n\n```json\n{\n  \"type\": \"anytls\",\n  \"tag\": \"anytls-in\",\n\n  ... // 监听字段\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"password\": \"8JCsPssfgS8tiRwiMlhARg==\"\n    }\n  ],\n  \"padding_scheme\": [],\n  \"tls\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### users\n\n==必填==\n\nAnyTLS 用户。\n\n#### padding_scheme\n\nAnyTLS 填充方案行数组。\n\n默认填充方案:\n\n```json\n[\n  \"stop=8\",\n  \"0=30-30\",\n  \"1=100-400\",\n  \"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000\",\n  \"3=9-9,500-1000\",\n  \"4=500-1000\",\n  \"5=500-1000\",\n  \"6=500-1000\",\n  \"7=500-1000\"\n]\n```\n\n#### tls\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。\n"
  },
  {
    "path": "docs/configuration/inbound/direct.md",
    "content": "`direct` inbound is a tunnel server.\n\n### Structure\n\n```json\n{\n  \"type\": \"direct\",\n  \"tag\": \"direct-in\",\n  \n  ... // Listen Fields\n\n  \"network\": \"udp\",\n  \"override_address\": \"1.0.0.1\",\n  \"override_port\": 53\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### network\n\nListen network, one of `tcp` `udp`.\n\nBoth if empty.\n\n#### override_address\n\nOverride the connection destination address.\n\n#### override_port\n\nOverride the connection destination port."
  },
  {
    "path": "docs/configuration/inbound/direct.zh.md",
    "content": "`direct` 入站是一个隧道服务器。\n\n### 结构\n\n```json\n{\n  \"type\": \"direct\",\n  \"tag\": \"direct-in\",\n\n  ... // 监听字段\n\n  \"network\": \"udp\",\n  \"override_address\": \"1.0.0.1\",\n  \"override_port\": 53\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### network\n\n监听的网络协议，`tcp` `udp` 之一。\n\n默认所有。\n\n#### override_address\n\n覆盖连接目标地址。\n\n#### override_port\n\n覆盖连接目标端口。\n\n"
  },
  {
    "path": "docs/configuration/inbound/http.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"http\",\n  \"tag\": \"http-in\",\n  \n  ... // Listen Fields\n  \n  \"users\": [\n    {\n      \"username\": \"admin\",\n      \"password\": \"admin\"\n    }\n  ],\n  \"tls\": {},\n  \"set_system_proxy\": false\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound).\n\n#### users\n\nHTTP users.\n\nNo authentication required if empty.\n\n#### set_system_proxy\n\n!!! quote \"\"\n\n    Only supported on Linux, Android, Windows, and macOS.\n\n!!! warning \"\"\n\n    To work on Android and Apple platforms without privileges, use tun.platform.http_proxy instead.\n\nAutomatically set system proxy configuration when start and clean up when stop.\n"
  },
  {
    "path": "docs/configuration/inbound/http.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"http\",\n  \"tag\": \"http-in\",\n\n  ... // 监听字段\n\n  \"users\": [\n    {\n      \"username\": \"admin\",\n      \"password\": \"admin\"\n    }\n  ],\n  \"tls\": {},\n  \"set_system_proxy\": false\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### tls\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。\n\n#### users\n\nHTTP 用户\n\n如果为空则不需要验证。\n\n#### set_system_proxy\n\n!!! quote \"\"\n\n    仅支持 Linux、Android、Windows 和 macOS。\n\n!!! warning \"\"\n\n    要在无特权的 Android 和 iOS 上工作，请改用 tun.platform.http_proxy。\n\n启动时自动设置系统代理，停止时自动清理。"
  },
  {
    "path": "docs/configuration/inbound/hysteria.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"hysteria\",\n  \"tag\": \"hysteria-in\",\n  \n  ... // Listen Fields\n\n  \"up\": \"100 Mbps\",\n  \"up_mbps\": 100,\n  \"down\": \"100 Mbps\",\n  \"down_mbps\": 100,\n  \"obfs\": \"fuck me till the daylight\",\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"auth\": \"\",\n      \"auth_str\": \"password\"\n    }\n  ],\n  \n  \"recv_window_conn\": 0,\n  \"recv_window_client\": 0,\n  \"max_conn_client\": 0,\n  \"disable_mtu_discovery\": false,\n  \"tls\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### up, down\n\n==Required==\n\nFormat: `[Integer] [Unit]` e.g. `100 Mbps, 640 KBps, 2 Gbps`\n\nSupported units (case sensitive, b = bits, B = bytes, 8b=1B):\n\n    bps (bits per second)\n    Bps (bytes per second)\n    Kbps (kilobits per second)\n    KBps (kilobytes per second)\n    Mbps (megabits per second)\n    MBps (megabytes per second)\n    Gbps (gigabits per second)\n    GBps (gigabytes per second)\n    Tbps (terabits per second)\n    TBps (terabytes per second)\n\n#### up_mbps, down_mbps\n\n==Required==\n\n`up, down` in Mbps.\n\n#### obfs\n\nObfuscated password.\n\n#### users\n\nHysteria users\n\n#### users.auth\n\nAuthentication password, in base64.\n\n#### users.auth_str\n\nAuthentication password.\n\n#### recv_window_conn\n\nThe QUIC stream-level flow control window for receiving data.\n\n`15728640 (15 MB/s)` will be used if empty.\n\n#### recv_window_client\n\nThe QUIC connection-level flow control window for receiving data.\n\n`67108864 (64 MB/s)` will be used if empty.\n\n#### max_conn_client\n\nThe maximum number of QUIC concurrent bidirectional streams that a peer is allowed to open.\n\n`1024` will be used if empty.\n\n#### disable_mtu_discovery\n\nDisables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 (IPv4) / 1232 (IPv6) bytes in size.\n\nForce enabled on for systems other than Linux and Windows (according to upstream).\n\n#### tls\n\n==Required==\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound)."
  },
  {
    "path": "docs/configuration/inbound/hysteria.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"hysteria\",\n  \"tag\": \"hysteria-in\",\n  \n  ... // 监听字段\n\n  \"up\": \"100 Mbps\",\n  \"up_mbps\": 100,\n  \"down\": \"100 Mbps\",\n  \"down_mbps\": 100,\n  \"obfs\": \"fuck me till the daylight\",\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"auth\": \"\",\n      \"auth_str\": \"password\"\n    }\n  ],\n\n  \"recv_window_conn\": 0,\n  \"recv_window_client\": 0,\n  \"max_conn_client\": 0,\n  \"disable_mtu_discovery\": false,\n  \"tls\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### up, down\n\n==必填==\n\n格式: `[Integer] [Unit]` 例如： `100 Mbps, 640 KBps, 2 Gbps`\n\n支持的单位 (大小写敏感, b = bits, B = bytes, 8b=1B)：\n\n    bps (bits per second)\n    Bps (bytes per second)\n    Kbps (kilobits per second)\n    KBps (kilobytes per second)\n    Mbps (megabits per second)\n    MBps (megabytes per second)\n    Gbps (gigabits per second)\n    GBps (gigabytes per second)\n    Tbps (terabits per second)\n    TBps (terabytes per second)\n\n#### up_mbps, down_mbps\n\n==必填==\n\n以 Mbps 为单位的 `up, down`。\n\n#### obfs\n\n混淆密码。\n\n#### users\n\nHysteria 用户\n\n#### users.auth\n\nbase64 编码的认证密码。\n\n#### users.auth_str\n\n认证密码。\n\n#### recv_window_conn\n\n用于接收数据的 QUIC 流级流控制窗口。\n\n默认 `15728640 (15 MB/s)`。\n\n#### recv_window_client\n\n用于接收数据的 QUIC 连接级流控制窗口。\n\n默认 `67108864 (64 MB/s)`。\n\n#### max_conn_client\n\n允许对等点打开的 QUIC 并发双向流的最大数量。\n\n默认 `1024`。\n\n#### disable_mtu_discovery\n\n禁用路径 MTU 发现 (RFC 8899)。 数据包的大小最多为 1252 (IPv4) / 1232 (IPv6) 字节。\n\n强制为 Linux 和 Windows 以外的系统启用（根据上游）。\n\n#### tls\n\n==必填==\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。"
  },
  {
    "path": "docs/configuration/inbound/hysteria2.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-alert: [masquerade](#masquerade)  \n    :material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth)\n\n### Structure\n\n```json\n{\n  \"type\": \"hysteria2\",\n  \"tag\": \"hy2-in\",\n  \n  ... // Listen Fields\n\n  \"up_mbps\": 100,\n  \"down_mbps\": 100,\n  \"obfs\": {\n    \"type\": \"salamander\",\n    \"password\": \"cry_me_a_r1ver\"\n  },\n  \"users\": [\n    {\n      \"name\": \"tobyxdd\",\n      \"password\": \"goofy_ahh_password\"\n    }\n  ],\n  \"ignore_client_bandwidth\": false,\n  \"tls\": {},\n  \"masquerade\": \"\", // or {}\n  \"brutal_debug\": false\n}\n```\n\n!!! warning \"Difference from official Hysteria2\"\n\n    The official program supports an authentication method called **userpass**,\n    which essentially uses a combination of `<username>:<password>` as the actual password,\n    while sing-box does not provide this alias.\n    To use sing-box with the official program, you need to fill in that combination as the actual password.\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### up_mbps, down_mbps\n\nMax bandwidth, in Mbps.\n\nNot limited if empty.\n\nConflict with `ignore_client_bandwidth`.\n\n#### obfs.type\n\nQUIC traffic obfuscator type, only available with `salamander`.\n\nDisabled if empty.\n\n#### obfs.password\n\nQUIC traffic obfuscator password.\n\n#### users\n\nHysteria2 users\n\n#### users.password\n\nAuthentication password\n\n#### ignore_client_bandwidth\n\n*When `up_mbps` and `down_mbps` are not set*:\n\nCommands clients to use the BBR CC instead of Hysteria CC.\n\n*When `up_mbps` and `down_mbps` are set*:\n\nDeny clients to use the BBR CC.\n\n#### tls\n\n==Required==\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound).\n\n#### masquerade\n\nHTTP3 server behavior (URL string configuration) when authentication fails.\n\n| Scheme       | Example                 | Description        |\n|--------------|-------------------------|--------------------|\n| `file`       | `file:///var/www`       | As a file server   |\n| `http/https` | `http://127.0.0.1:8080` | As a reverse proxy |\n\nConflict with `masquerade.type`.\n\nA 404 page will be returned if masquerade is not configured.\n\n#### masquerade.type\n\nHTTP3 server behavior (Object configuration) when authentication fails.\n\n| Type     | Description                 | Fields                              |\n|----------|-----------------------------|-------------------------------------|\n| `file`   | As a file server            | `directory`                         |\n| `proxy`  | As a reverse proxy          | `url`, `rewrite_host`               |\n| `string` | Reply with a fixed response | `status_code`, `headers`, `content` |\n\nConflict with `masquerade`.\n\nA 404 page will be returned if masquerade is not configured.\n\n#### masquerade.directory\n\nFile server root directory.\n\n#### masquerade.url\n\nReverse proxy target URL.\n\n#### masquerade.rewrite_host\n\nRewrite the `Host` header to the target URL.\n\n#### masquerade.status_code\n\nFixed response status code.\n\n#### masquerade.headers\n\nFixed response headers.\n\n#### masquerade.content\n\nFixed response content.\n\n#### brutal_debug\n\nEnable debug information logging for Hysteria Brutal CC.\n"
  },
  {
    "path": "docs/configuration/inbound/hysteria2.zh.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-alert: [masquerade](#masquerade)  \n    :material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth)\n\n### 结构\n\n```json\n{\n  \"type\": \"hysteria2\",\n  \"tag\": \"hy2-in\",\n  \n  ... // 监听字段\n\n  \"up_mbps\": 100,\n  \"down_mbps\": 100,\n  \"obfs\": {\n    \"type\": \"salamander\",\n    \"password\": \"cry_me_a_r1ver\"\n  },\n  \"users\": [\n    {\n      \"name\": \"tobyxdd\",\n      \"password\": \"goofy_ahh_password\"\n    }\n  ],\n  \"ignore_client_bandwidth\": false,\n  \"tls\": {},\n  \"masquerade\": \"\", // 或 {}\n  \"brutal_debug\": false\n}\n```\n\n!!! warning \"与官方 Hysteria2 的区别\"\n\n    官方程序支持一种名为 **userpass** 的验证方式，\n    本质上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码，而 sing-box 不提供此别名。\n    要将 sing-box 与官方程序一起使用， 您需要填写该组合作为实际密码。\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### up_mbps, down_mbps\n\n支持的速率，默认不限制。\n\n与 `ignore_client_bandwidth` 冲突。\n\n#### obfs.type\n\nQUIC 流量混淆器类型，仅可设为 `salamander`。\n\n如果为空则禁用。\n\n#### obfs.password\n\nQUIC 流量混淆器密码.\n\n#### users\n\nHysteria 用户\n\n#### users.password\n\n认证密码。\n\n#### ignore_client_bandwidth\n\n*当 `up_mbps` 和 `down_mbps` 未设定时*:\n\n命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。\n\n*当 `up_mbps` 和 `down_mbps` 已设定时*:\n\n禁止客户端使用 BBR 拥塞控制算法。\n\n#### tls\n\n==必填==\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。\n\n#### masquerade\n\nHTTP3 服务器认证失败时的行为 （URL 字符串配置）。\n\n| Scheme       | 示例                      | 描述      |\n|--------------|-------------------------|---------|\n| `file`       | `file:///var/www`       | 作为文件服务器 |\n| `http/https` | `http://127.0.0.1:8080` | 作为反向代理  |\n\n如果 masquerade 未配置，则返回 404 页。\n\n与 `masquerade.type` 冲突。\n\n#### masquerade.type\n\nHTTP3 服务器认证失败时的行为 （对象配置）。\n\n| Type     | 描述      | 字段                                  |\n|----------|---------|-------------------------------------|\n| `file`   | 作为文件服务器 | `directory`                         |\n| `proxy`  | 作为反向代理  | `url`, `rewrite_host`               |\n| `string` | 返回固定响应  | `status_code`, `headers`, `content` |\n\n如果 masquerade 未配置，则返回 404 页。\n\n与 `masquerade` 冲突。\n\n#### masquerade.directory\n\n文件服务器根目录。\n\n#### masquerade.url\n\n反向代理目标 URL。\n\n#### masquerade.rewrite_host\n\n重写请求头中的 Host 字段到目标 URL。\n\n#### masquerade.status_code\n\n固定响应状态码。\n\n#### masquerade.headers\n\n固定响应头。\n\n#### masquerade.content\n\n固定响应内容。\n\n#### brutal_debug\n\n启用 Hysteria Brutal CC 的调试信息日志记录。\n"
  },
  {
    "path": "docs/configuration/inbound/index.md",
    "content": "# Inbound\n\n### Structure\n\n```json\n{\n  \"inbounds\": [\n    {\n      \"type\": \"\",\n      \"tag\": \"\"\n    }\n  ]\n}\n```\n\n### Fields\n\n| Type          | Format                        | Injectable       |\n|---------------|-------------------------------|------------------|\n| `direct`      | [Direct](./direct/)           | :material-close: |\n| `mixed`       | [Mixed](./mixed/)             | TCP              |\n| `socks`       | [SOCKS](./socks/)             | TCP              |\n| `http`        | [HTTP](./http/)               | TCP              |\n| `shadowsocks` | [Shadowsocks](./shadowsocks/) | TCP              |\n| `vmess`       | [VMess](./vmess/)             | TCP              |\n| `trojan`      | [Trojan](./trojan/)           | TCP              |\n| `naive`       | [Naive](./naive/)             | :material-close: |\n| `hysteria`    | [Hysteria](./hysteria/)       | :material-close: |\n| `shadowtls`   | [ShadowTLS](./shadowtls/)     | TCP              |\n| `tuic`        | [TUIC](./tuic/)               | :material-close: |\n| `hysteria2`   | [Hysteria2](./hysteria2/)     | :material-close: |\n| `vless`       | [VLESS](./vless/)             | TCP              |\n| `anytls`      | [AnyTLS](./anytls/)           | TCP              |\n| `tun`         | [Tun](./tun/)                 | :material-close: |\n| `redirect`    | [Redirect](./redirect/)       | :material-close: |\n| `tproxy`      | [TProxy](./tproxy/)           | :material-close: |\n\n#### tag\n\nThe tag of the inbound."
  },
  {
    "path": "docs/configuration/inbound/index.zh.md",
    "content": "# 入站\n\n### 结构\n\n```json\n{\n  \"inbounds\": [\n    {\n      \"type\": \"\",\n      \"tag\": \"\"\n    }\n  ]\n}\n```\n\n### 字段\n\n| 类型            | 格式                            | 注入支持             |\n|---------------|-------------------------------|------------------|\n| `direct`      | [Direct](./direct/)           | :material-close: |\n| `mixed`       | [Mixed](./mixed/)             | TCP              |\n| `socks`       | [SOCKS](./socks/)             | TCP              |\n| `http`        | [HTTP](./http/)               | TCP              |\n| `shadowsocks` | [Shadowsocks](./shadowsocks/) | TCP              |\n| `vmess`       | [VMess](./vmess/)             | TCP              |\n| `trojan`      | [Trojan](./trojan/)           | TCP              |\n| `naive`       | [Naive](./naive/)             | :material-close: |\n| `hysteria`    | [Hysteria](./hysteria/)       | :material-close: |\n| `shadowtls`   | [ShadowTLS](./shadowtls/)     | TCP              |\n| `tuic`        | [TUIC](./tuic/)               | :material-close: |\n| `hysteria2`   | [Hysteria2](./hysteria2/)     | :material-close: |\n| `vless`       | [VLESS](./vless/)             | TCP              |\n| `anytls`      | [AnyTLS](./anytls/)           | TCP              |\n| `tun`         | [Tun](./tun/)                 | :material-close: |\n| `redirect`    | [Redirect](./redirect/)       | :material-close: |\n| `tproxy`      | [TProxy](./tproxy/)           | :material-close: |\n\n#### tag\n\n入站的标签。"
  },
  {
    "path": "docs/configuration/inbound/mixed.md",
    "content": "`mixed` inbound is a socks4, socks4a, socks5 and http server.\n\n### Structure\n\n```json\n{\n  \"type\": \"mixed\",\n  \"tag\": \"mixed-in\",\n\n  ... // Listen Fields\n\n  \"users\": [\n    {\n      \"username\": \"admin\",\n      \"password\": \"admin\"\n    }\n  ],\n  \"set_system_proxy\": false\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### users\n\nSOCKS and HTTP users.\n\nNo authentication required if empty.\n\n#### set_system_proxy\n\n!!! quote \"\"\n\n    Only supported on Linux, Android, Windows, and macOS.\n\n!!! warning \"\"\n\n    To work on Android and Apple platforms without privileges, use tun.platform.http_proxy instead.\n\nAutomatically set system proxy configuration when start and clean up when stop.\n"
  },
  {
    "path": "docs/configuration/inbound/mixed.zh.md",
    "content": "`mixed` 入站是一个 socks4, socks4a, socks5 和 http 服务器.\n\n### 结构\n\n```json\n{\n  \"type\": \"mixed\",\n  \"tag\": \"mixed-in\",\n\n  ... // 监听字段\n\n  \"users\": [\n    {\n      \"username\": \"admin\",\n      \"password\": \"admin\"\n    }\n  ],\n  \"set_system_proxy\": false\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### users\n\nSOCKS 和 HTTP 用户\n\n如果为空则不需要验证。\n\n#### set_system_proxy\n\n!!! quote \"\"\n\n    仅支持 Linux、Android、Windows 和 macOS。\n\n!!! warning \"\"\n\n    要在无特权的 Android 和 iOS 上工作，请改用 tun.platform.http_proxy。\n\n启动时自动设置系统代理，停止时自动清理。"
  },
  {
    "path": "docs/configuration/inbound/naive.md",
    "content": "!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [quic_congestion_control](#quic_congestion_control)\n\n### Structure\n\n```json\n{\n\"type\": \"naive\",\n\"tag\": \"naive-in\",\n\"network\": \"udp\",\n...\n// Listen Fields\n\n\"users\": [\n{\n\"username\": \"sekai\",\n\"password\": \"password\"\n}\n],\n\"quic_congestion_control\": \"\",\n\"tls\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### network\n\nListen network, one of `tcp` `udp`.\n\nBoth if empty.\n\n#### users\n\n==Required==\n\nNaive users.\n\n#### quic_congestion_control\n\n!!! question \"Since sing-box 1.13.0\"\n\nQUIC congestion control algorithm.\n\n| Algorithm      | Description                     |\n|----------------|---------------------------------|\n| `bbr`          | BBR                             |\n| `bbr_standard` | BBR (Standard version)         |\n| `bbr2`         | BBRv2                           |\n| `bbr2_variant` | BBRv2 (An experimental variant) |\n| `cubic`        | CUBIC                           |\n| `reno`         | New Reno                        |\n\n`bbr` is used by default (the default of QUICHE, used by Chromium which NaiveProxy is based on).\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound)."
  },
  {
    "path": "docs/configuration/inbound/naive.zh.md",
    "content": "!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [quic_congestion_control](#quic_congestion_control)\n\n### 结构\n\n```json\n{\n\"type\": \"naive\",\n\"tag\": \"naive-in\",\n\"network\": \"udp\",\n\n... // 监听字段\n\n\"users\": [\n{\n\"username\": \"sekai\",\n\"password\": \"password\"\n}\n],\n\"quic_congestion_control\": \"\",\n\"tls\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### network\n\n监听的网络协议，`tcp` `udp` 之一。\n\n默认所有。\n\n#### users\n\n==必填==\n\nNaive 用户。\n\n#### quic_congestion_control\n\n!!! question \"Since sing-box 1.13.0\"\n\nQUIC 拥塞控制算法。\n\n| 算法             | 描述                 |\n|----------------|--------------------|\n| `bbr`          | BBR                |\n| `bbr_standard` | BBR (标准版) |\n| `bbr2`         | BBRv2              |\n| `bbr2_variant` | BBRv2 (一种试验变体)     |\n| `cubic`        | CUBIC              |\n| `reno`         | New Reno           |\n\n默认使用 `bbr`（NaiveProxy 基于的 Chromium 使用的 QUICHE 的默认值）。\n\n#### tls\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。"
  },
  {
    "path": "docs/configuration/inbound/redirect.md",
    "content": "!!! quote \"\"\n\n    Only supported on Linux and macOS.\n\n### Structure\n\n```json\n{\n  \"type\": \"redirect\",\n  \"tag\": \"redirect-in\",\n\n  ... // Listen Fields\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n"
  },
  {
    "path": "docs/configuration/inbound/redirect.zh.md",
    "content": "!!! quote \"\"\n\n    仅支持 Linux 和 macOS。\n\n### 结构\n\n```json\n{\n  \"type\": \"redirect\",\n  \"tag\": \"redirect-in\",\n\n  ... // 监听字段\n}\n```\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n"
  },
  {
    "path": "docs/configuration/inbound/shadowsocks.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"shadowsocks\",\n  \"tag\": \"ss-in\",\n\n  ... // Listen Fields\n\n  \"method\": \"2022-blake3-aes-128-gcm\",\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"managed\": false,\n  \"multiplex\": {}\n}\n```\n\n### Multi-User Structure\n\n```json\n{\n  \"method\": \"2022-blake3-aes-128-gcm\",\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"password\": \"PCD2Z4o12bKUoFa3cC97Hw==\"\n    }\n  ],\n  \"multiplex\": {}\n}\n```\n\n### Relay Structure\n\n```json\n{\n  \"type\": \"shadowsocks\",\n  \"method\": \"2022-blake3-aes-128-gcm\",\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"destinations\": [\n    {\n      \"name\": \"test\",\n      \"server\": \"example.com\",\n      \"server_port\": 8080,\n      \"password\": \"PCD2Z4o12bKUoFa3cC97Hw==\"\n    }\n  ],\n  \"multiplex\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### network\n\nListen network, one of `tcp` `udp`.\n\nBoth if empty.\n\n#### method\n\n==Required==\n\n| Method                        | Key Length |\n|-------------------------------|------------|\n| 2022-blake3-aes-128-gcm       | 16         |\n| 2022-blake3-aes-256-gcm       | 32         |\n| 2022-blake3-chacha20-poly1305 | 32         |\n| none                          | /          |\n| aes-128-gcm                   | /          |\n| aes-192-gcm                   | /          |\n| aes-256-gcm                   | /          |\n| chacha20-ietf-poly1305        | /          |\n| xchacha20-ietf-poly1305       | /          |\n\n#### password\n\n==Required==\n\n| Method        | Password Format                                |\n|---------------|------------------------------------------------|\n| none          | /                                              |\n| 2022 methods  | `sing-box generate rand --base64 <Key Length>` |\n| other methods | any string                                     |\n\n#### managed\n\nDefaults to `false`. Enable this when the inbound is managed by the [SSM API](/configuration/service/ssm-api) for dynamic user.\n\n#### multiplex\n\nSee [Multiplex](/configuration/shared/multiplex#inbound) for details.\n"
  },
  {
    "path": "docs/configuration/inbound/shadowsocks.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"shadowsocks\",\n  \"tag\": \"ss-in\",\n\n  ... // 监听字段\n\n  \"method\": \"2022-blake3-aes-128-gcm\",\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"managed\": false,\n  \"multiplex\": {}\n}\n```\n\n### 多用户结构\n\n```json\n{\n  \"method\": \"2022-blake3-aes-128-gcm\",\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"password\": \"PCD2Z4o12bKUoFa3cC97Hw==\"\n    }\n  ],\n  \"multiplex\": {}\n}\n```\n\n### 中转结构\n\n```json\n{\n  \"type\": \"shadowsocks\",\n  \"method\": \"2022-blake3-aes-128-gcm\",\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"destinations\": [\n    {\n      \"name\": \"test\",\n      \"server\": \"example.com\",\n      \"server_port\": 8080,\n      \"password\": \"PCD2Z4o12bKUoFa3cC97Hw==\"\n    }\n  ],\n  \"multiplex\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### network\n\n监听的网络协议，`tcp` `udp` 之一。\n\n默认所有。\n\n#### method\n\n==必填==\n\n| 方法                            | 密钥长度 |\n|-------------------------------|------|\n| 2022-blake3-aes-128-gcm       | 16   |\n| 2022-blake3-aes-256-gcm       | 32   |\n| 2022-blake3-chacha20-poly1305 | 32   |\n| none                          | /    |\n| aes-128-gcm                   | /    |\n| aes-192-gcm                   | /    |\n| aes-256-gcm                   | /    |\n| chacha20-ietf-poly1305        | /    |\n| xchacha20-ietf-poly1305       | /    |\n\n#### password\n\n==必填==\n\n| 方法            | 密码格式                                     |\n|---------------|------------------------------------------|\n| none          | /                                        |\n| 2022 methods  | `sing-box generate rand --base64 <密钥长度>` |\n| other methods | 任意字符串                                    |\n\n#### managed\n\n默认为 `false`。当该入站需要由 [SSM API](/zh/configuration/service/ssm-api) 管理用户时必须启用此字段。\n\n#### multiplex\n\n参阅 [多路复用](/zh/configuration/shared/multiplex#入站)。\n"
  },
  {
    "path": "docs/configuration/inbound/shadowtls.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-plus: [wildcard_sni](#wildcard_sni)\n\n### Structure\n\n```json\n{\n  \"type\": \"shadowtls\",\n  \"tag\": \"st-in\",\n\n  ... // Listen Fields\n\n  \"version\": 3,\n  \"password\": \"fuck me till the daylight\",\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"password\": \"8JCsPssfgS8tiRwiMlhARg==\"\n    }\n  ],\n  \"handshake\": {\n    \"server\": \"google.com\",\n    \"server_port\": 443,\n    \n    ... // Dial Fields\n  },\n  \"handshake_for_server_name\": {\n    \"example.com\": {\n      \"server\": \"example.com\",\n      \"server_port\": 443,\n\n      ... // Dial Fields\n    }\n  },\n  \"strict_mode\": false,\n  \"wildcard_sni\": \"\"\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### version\n\nShadowTLS protocol version.\n\n| Value         | Protocol Version                                                                        |\n|---------------|-----------------------------------------------------------------------------------------|\n| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |\n| `2`           | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |\n| `3`           | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |\n\n#### password\n\nShadowTLS password.\n\nOnly available in the ShadowTLS protocol 2.\n\n#### users\n\nShadowTLS users.\n\nOnly available in the ShadowTLS protocol 3.\n\n#### handshake\n\n==Required==\n\nWhen `wildcard_sni` is configured to `all`, the server address is optional.\n\nHandshake server address and [Dial Fields](/configuration/shared/dial/).\n\n#### handshake_for_server_name\n\nHandshake server address and [Dial Fields](/configuration/shared/dial/) for specific server name.\n\nOnly available in the ShadowTLS protocol 2/3.\n\n#### strict_mode\n\nShadowTLS strict mode.\n\nOnly available in the ShadowTLS protocol 3.\n\n#### wildcard_sni\n\n!!! question \"Since sing-box 1.12.0\"\n\nShadowTLS wildcard SNI mode.\n\nAvailable values are:\n\n* `off`: (default) Disabled.\n* `authed`: Authenticated connections will have their destination overwritten to `(servername):443`\n* `all`: All connections will have their destination overwritten to `(servername):443`\n\nAdditionally, connections matching `handshake_for_server_name` are not affected.\n\nOnly available in the ShadowTLS protocol 3.\n"
  },
  {
    "path": "docs/configuration/inbound/shadowtls.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-plus: [wildcard_sni](#wildcard_sni)\n\n### 结构\n\n```json\n{\n  \"type\": \"shadowtls\",\n  \"tag\": \"st-in\",\n\n  ... // 监听字段\n\n  \"version\": 3,\n  \"password\": \"fuck me till the daylight\",\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"password\": \"8JCsPssfgS8tiRwiMlhARg==\"\n    }\n  ],\n  \"handshake\": {\n    \"server\": \"google.com\",\n    \"server_port\": 443,\n\n    ... // 拨号字段\n  },\n  \"handshake_for_server_name\": {\n    \"example.com\": {\n      \"server\": \"example.com\",\n      \"server_port\": 443,\n      \n      ... // 拨号字段\n    }\n  },\n  \"strict_mode\": false,\n  \"wildcard_sni\": \"\"\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### version\n\nShadowTLS 协议版本。\n\n| 值             | 协议版本                                                                                    |\n|---------------|-----------------------------------------------------------------------------------------|\n| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |\n| `2`           | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |\n| `3`           | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |\n\n#### password\n\nShadowTLS 密码。\n\n仅在 ShadowTLS 协议版本 2 中可用。\n\n#### users\n\nShadowTLS 用户。\n\n仅在 ShadowTLS 协议版本 3 中可用。\n\n#### handshake\n\n==必填==\n\n握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。\n\n#### handshake_for_server_name\n\n==必填==\n\n对于特定服务器名称的握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。\n\n仅在 ShadowTLS 协议版本 2/3 中可用。\n\n#### strict_mode\n\nShadowTLS 严格模式。\n\n仅在 ShadowTLS 协议版本 3 中可用。\n\n#### wildcard_sni\n\n!!! question \"自 sing-box 1.12.0 起\"\n\nShadowTLS 通配符 SNI 模式。\n\n可用值：\n\n* `off`：（默认）禁用。\n* `authed`：已认证的连接的目标将被重写为 `(servername):443`。\n* `all`：所有连接的目标将被重写为 `(servername):443`。\n\n此外，匹配 `handshake_for_server_name` 的连接不受影响。\n\n仅在 ShadowTLS 协议 3 中可用。\n"
  },
  {
    "path": "docs/configuration/inbound/socks.md",
    "content": "`socks` inbound is a socks4, socks4a, socks5 server.\n\n### Structure\n\n```json\n{\n  \"type\": \"socks\",\n  \"tag\": \"socks-in\",\n\n  ... // Listen Fields\n\n  \"users\": [\n    {\n      \"username\": \"admin\",\n      \"password\": \"admin\"\n    }\n  ]\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### users\n\nSOCKS users.\n\nNo authentication required if empty.\n"
  },
  {
    "path": "docs/configuration/inbound/socks.zh.md",
    "content": "`socks` 入站是一个 socks4, socks4a 和 socks5 服务器.\n\n### 结构\n\n```json\n{\n  \"type\": \"socks\",\n  \"tag\": \"socks-in\",\n\n  ... // 监听字段\n\n  \"users\": [\n    {\n      \"username\": \"admin\",\n      \"password\": \"admin\"\n    }\n  ]\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### users\n\nSOCKS 用户\n\n如果为空则不需要验证。\n"
  },
  {
    "path": "docs/configuration/inbound/tproxy.md",
    "content": "!!! quote \"\"\n\n    Only supported on Linux.\n\n### Structure\n\n```json\n{\n  \"type\": \"tproxy\",\n  \"tag\": \"tproxy-in\",\n\n  ... // Listen Fields\n\n  \"network\": \"udp\"\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### network\n\nListen network, one of `tcp` `udp`.\n\nBoth if empty.\n"
  },
  {
    "path": "docs/configuration/inbound/tproxy.zh.md",
    "content": "!!! quote \"\"\n\n    仅支持 Linux。\n\n### 结构\n\n```json\n{\n  \"type\": \"tproxy\",\n  \"tag\": \"tproxy-in\",\n\n  ... // 监听字段\n\n  \"network\": \"udp\"\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### network\n\n监听的网络协议，`tcp` `udp` 之一。\n\n默认所有。\n"
  },
  {
    "path": "docs/configuration/inbound/trojan.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"trojan\",\n  \"tag\": \"trojan-in\",\n\n  ... // Listen Fields\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"password\": \"8JCsPssfgS8tiRwiMlhARg==\"\n    }\n  ],\n  \"tls\": {},\n  \"fallback\": {\n    \"server\": \"127.0.0.1\",\n    \"server_port\": 8080\n  },\n  \"fallback_for_alpn\": {\n    \"http/1.1\": {\n      \"server\": \"127.0.0.1\",\n      \"server_port\": 8081\n    }\n  },\n  \"multiplex\": {},\n  \"transport\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### users\n\n==Required==\n\nTrojan users.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound).\n\n#### fallback\n\n!!! failure \"\"\n\n    There is no evidence that GFW detects and blocks Trojan servers based on HTTP responses, and opening the standard http/s port on the server is a much bigger signature.\n\nFallback server configuration. Disabled if `fallback` and `fallback_for_alpn` are empty.\n\n#### fallback_for_alpn\n\nFallback server configuration for specified ALPN.\n\nIf not empty, TLS fallback requests with ALPN not in this table will be rejected.\n\n#### multiplex\n\nSee [Multiplex](/configuration/shared/multiplex#inbound) for details.\n\n#### transport\n\nV2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport/).\n"
  },
  {
    "path": "docs/configuration/inbound/trojan.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"trojan\",\n  \"tag\": \"trojan-in\",\n\n  ... // 监听字段\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"password\": \"8JCsPssfgS8tiRwiMlhARg==\"\n    }\n  ],\n  \"tls\": {},\n  \"fallback\": {\n    \"server\": \"127.0.0.1\",\n    \"server_port\": 8080\n  },\n  \"fallback_for_alpn\": {\n    \"http/1.1\": {\n      \"server\": \"127.0.0.1\",\n      \"server_port\": 8081\n    }\n  },\n  \"multiplex\": {},\n  \"transport\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### users\n\n==必填==\n\nTrojan 用户。\n\n#### tls\n\nTLS 配置，参阅 [TLS](/zh/configuration/shared/tls/#入站)。\n\n#### fallback\n\n!!! failure \"\"\n\n    没有证据表明 GFW 基于 HTTP 响应检测并阻止 Trojan 服务器，并且在服务器上打开标准 http/s 端口是一个更大的特征。\n\n回退服务器配置。如果 `fallback` 和 `fallback_for_alpn` 为空，则禁用回退。\n\n#### fallback_for_alpn\n\n为 ALPN 指定回退服务器配置。\n\n如果不为空，ALPN 不在此列表中的 TLS 回退请求将被拒绝。\n\n#### multiplex\n\n参阅 [多路复用](/zh/configuration/shared/multiplex#入站)。\n\n#### transport\n\nV2Ray 传输配置，参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport/)。"
  },
  {
    "path": "docs/configuration/inbound/tuic.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"tuic\",\n  \"tag\": \"tuic-in\",\n  \n  ... // Listen Fields\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"uuid\": \"059032A9-7D40-4A96-9BB1-36823D848068\",\n      \"password\": \"hello\"\n    }\n  ],\n  \"congestion_control\": \"cubic\",\n  \"auth_timeout\": \"3s\",\n  \"zero_rtt_handshake\": false,\n  \"heartbeat\": \"10s\",\n  \"tls\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### users\n\nTUIC users\n\n#### users.uuid\n\n==Required==\n\nTUIC user uuid\n\n#### users.password\n\nTUIC user password\n\n#### congestion_control\n\nQUIC congestion control algorithm\n\nOne of: `cubic`, `new_reno`, `bbr`\n\n`cubic` is used by default.\n\n#### auth_timeout\n\nHow long the server should wait for the client to send the authentication command\n\n`3s` is used by default.\n\n#### zero_rtt_handshake\n\nEnable 0-RTT QUIC connection handshake on the client side  \nThis is not impacting much on the performance, as the protocol is fully multiplexed  \n\n!!! warning \"\"\n    Disabling this is highly recommended, as it is vulnerable to replay attacks.\n    See [Attack of the clones](https://blog.cloudflare.com/even-faster-connection-establishment-with-quic-0-rtt-resumption/#attack-of-the-clones)\n\n#### heartbeat\n\nInterval for sending heartbeat packets for keeping the connection alive\n\n`10s` is used by default.\n\n#### tls\n\n==Required==\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound)."
  },
  {
    "path": "docs/configuration/inbound/tuic.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"tuic\",\n  \"tag\": \"tuic-in\",\n\n  ... // 监听字段\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"uuid\": \"059032A9-7D40-4A96-9BB1-36823D848068\",\n      \"password\": \"hello\"\n    }\n  ],\n  \"congestion_control\": \"cubic\",\n  \"auth_timeout\": \"3s\",\n  \"zero_rtt_handshake\": false,\n  \"heartbeat\": \"10s\",\n  \"tls\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### users\n\nTUIC 用户\n\n#### users.uuid\n\n==必填==\n\nTUIC 用户 UUID\n\n#### users.password\n\nTUIC 用户密码\n\n#### congestion_control\n\nQUIC 拥塞控制算法\n\n可选值: `cubic`, `new_reno`, `bbr`\n\n默认使用 `cubic`。\n\n#### auth_timeout\n\n服务器等待客户端发送认证命令的时间\n\n默认使用 `3s`。\n\n#### zero_rtt_handshake\n\n在客户端启用 0-RTT QUIC 连接握手\n这对性能影响不大，因为协议是完全复用的\n\n!!! warning \"\"\n强烈建议禁用此功能，因为它容易受到重放攻击。\n请参阅 [Attack of the clones](https://blog.cloudflare.com/even-faster-connection-establishment-with-quic-0-rtt-resumption/#attack-of-the-clones)\n\n#### heartbeat\n\n发送心跳包以保持连接存活的时间间隔\n\n默认使用 `10s`。\n\n#### tls\n\n==必填==\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。"
  },
  {
    "path": "docs/configuration/inbound/tun.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.14.0\"\n\n    :material-plus: [include_mac_address](#include_mac_address)\n    :material-plus: [exclude_mac_address](#exclude_mac_address)\n\n!!! quote \"Changes in sing-box 1.13.3\"\n\n    :material-alert: [strict_route](#strict_route)\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark)  \n    :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue)  \n    :material-plus: [exclude_mptcp](#exclude_mptcp)  \n    :material-plus: [auto_redirect_iproute2_fallback_rule_index](#auto_redirect_iproute2_fallback_rule_index)\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-plus: [loopback_address](#loopback_address)\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-delete-alert: [gso](#gso)  \n    :material-alert-decagram: [route_address_set](#stack)  \n    :material-alert-decagram: [route_exclude_address_set](#stack)\n\n!!! quote \"Changes in sing-box 1.10.0\"\n\n    :material-plus: [address](#address)  \n    :material-delete-clock: [inet4_address](#inet4_address)  \n    :material-delete-clock: [inet6_address](#inet6_address)  \n    :material-plus: [route_address](#route_address)  \n    :material-delete-clock: [inet4_route_address](#inet4_route_address)  \n    :material-delete-clock: [inet6_route_address](#inet6_route_address)  \n    :material-plus: [route_exclude_address](#route_address)  \n    :material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address)  \n    :material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address)  \n    :material-plus: [iproute2_table_index](#iproute2_table_index)  \n    :material-plus: [iproute2_rule_index](#iproute2_table_index)  \n    :material-plus: [auto_redirect](#auto_redirect)  \n    :material-plus: [auto_redirect_input_mark](#auto_redirect_input_mark)  \n    :material-plus: [auto_redirect_output_mark](#auto_redirect_output_mark)  \n    :material-plus: [route_address_set](#route_address_set)  \n    :material-plus: [route_exclude_address_set](#route_address_set)\n\n!!! quote \"Changes in sing-box 1.9.0\"\n\n    :material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)  \n    :material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain)\n\n!!! quote \"Changes in sing-box 1.8.0\"\n\n    :material-plus: [gso](#gso)  \n    :material-alert-decagram: [stack](#stack)\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows and macOS.\n\n### Structure\n\n```json\n{\n  \"type\": \"tun\",\n  \"tag\": \"tun-in\",\n  \"interface_name\": \"tun0\",\n  \"address\": [\n    \"172.18.0.1/30\",\n    \"fdfe:dcba:9876::1/126\"\n  ],\n  \"mtu\": 9000,\n  \"auto_route\": true,\n  \"iproute2_table_index\": 2022,\n  \"iproute2_rule_index\": 9000,\n  \"auto_redirect\": true,\n  \"auto_redirect_input_mark\": \"0x2023\",\n  \"auto_redirect_output_mark\": \"0x2024\",\n  \"auto_redirect_reset_mark\": \"0x2025\",\n  \"auto_redirect_nfqueue\": 100,\n  \"auto_redirect_iproute2_fallback_rule_index\": 32768,\n  \"exclude_mptcp\": false,\n  \"loopback_address\": [\n    \"10.7.0.1\"\n  ],\n  \"strict_route\": true,\n  \"route_address\": [\n    \"0.0.0.0/1\",\n    \"128.0.0.0/1\",\n    \"::/1\",\n    \"8000::/1\"\n  ],\n  \"route_exclude_address\": [\n    \"192.168.0.0/16\",\n    \"fc00::/7\"\n  ],\n  \"route_address_set\": [\n    \"geoip-cloudflare\"\n  ],\n  \"route_exclude_address_set\": [\n    \"geoip-cn\"\n  ],\n  \"endpoint_independent_nat\": false,\n  \"udp_timeout\": \"5m\",\n  \"stack\": \"system\",\n  \"include_interface\": [\n    \"lan0\"\n  ],\n  \"exclude_interface\": [\n    \"lan1\"\n  ],\n  \"include_uid\": [\n    0\n  ],\n  \"include_uid_range\": [\n    \"1000:99999\"\n  ],\n  \"exclude_uid\": [\n    1000\n  ],\n  \"exclude_uid_range\": [\n    \"1000:99999\"\n  ],\n  \"include_android_user\": [\n    0,\n    10\n  ],\n  \"include_package\": [\n    \"com.android.chrome\"\n  ],\n  \"exclude_package\": [\n    \"com.android.captiveportallogin\"\n  ],\n  \"include_mac_address\": [\n    \"00:11:22:33:44:55\"\n  ],\n  \"exclude_mac_address\": [\n    \"66:77:88:99:aa:bb\"\n  ],\n  \"platform\": {\n    \"http_proxy\": {\n      \"enabled\": false,\n      \"server\": \"127.0.0.1\",\n      \"server_port\": 8080,\n      \"bypass_domain\": [],\n      \"match_domain\": []\n    }\n  },\n  // Deprecated\n  \"gso\": false,\n  \"inet4_address\": [\n    \"172.19.0.1/30\"\n  ],\n  \"inet6_address\": [\n    \"fdfe:dcba:9876::1/126\"\n  ],\n  \"inet4_route_address\": [\n    \"0.0.0.0/1\",\n    \"128.0.0.0/1\"\n  ],\n  \"inet6_route_address\": [\n    \"::/1\",\n    \"8000::/1\"\n  ],\n  \"inet4_route_exclude_address\": [\n    \"192.168.0.0/16\"\n  ],\n  \"inet6_route_exclude_address\": [\n    \"fc00::/7\"\n  ],\n  ...\n  // Listen Fields\n}\n```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n!!! warning \"\"\n\n    If tun is running in non-privileged mode, addresses and MTU will not be configured automatically, please make sure the settings are accurate.\n\n### Fields\n\n#### interface_name\n\nVirtual device name, automatically selected if empty.\n\n#### address\n\n!!! question \"Since sing-box 1.10.0\"\n\nIPv4 and IPv6 prefix for the tun interface.\n\n#### inet4_address\n\n!!! failure \"Deprecated in sing-box 1.10.0\"\n\n    `inet4_address` is merged to `address` and will be removed in sing-box 1.12.0.\n\nIPv4 prefix for the tun interface.\n\n#### inet6_address\n\n!!! failure \"Deprecated in sing-box 1.10.0\"\n\n    `inet6_address` is merged to `address` and will be removed in sing-box 1.12.0.\n\nIPv6 prefix for the tun interface.\n\n#### mtu\n\nThe maximum transmission unit.\n\n#### gso\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works, and will be removed in sing-box 1.12.0.\n\n!!! question \"Since sing-box 1.8.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux with `auto_route` enabled.\n\nEnable generic segmentation offload.\n\n#### auto_route\n\nSet the default route to the Tun.\n\n!!! quote \"\"\n\n    To avoid traffic loopback, set `route.auto_detect_interface` or `route.default_interface` or `outbound.bind_interface`\n\n!!! note \"Use with Android VPN\"\n\n    By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.\n\n!!! note \"Also enable `auto_redirect`\"\n\n    `auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts between TUN and Docker bridge networks.\n\n#### iproute2_table_index\n\n!!! question \"Since sing-box 1.10.0\"\n\nLinux iproute2 table index generated by `auto_route`.\n\n`2022` is used by default.\n\n#### iproute2_rule_index\n\n!!! question \"Since sing-box 1.10.0\"\n\nLinux iproute2 rule start index generated by `auto_route`.\n\n`9000` is used by default.\n\n#### auto_redirect\n\n!!! question \"Since sing-box 1.10.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux with `auto_route` enabled.\n\nImprove TUN routing and performance using nftables.\n\n`auto_redirect` is always recommended on Linux, it provides better routing,\nhigher performance (better than tproxy),\nand avoids conflicts between TUN and Docker bridge networks.\n\nNote that `auto_redirect` also works on Android, \nbut due to the lack of `nftables` and `ip6tables`,\nonly simple IPv4 TCP forwarding is performed.\nTo share your VPN connection over hotspot or repeater on Android,\nuse [VPNHotspot](https://github.com/Mygod/VPNHotspot).\n\n`auto_redirect` also automatically inserts compatibility rules\ninto the OpenWrt fw4 table, i.e. \nit will work on routers without any extra configuration.\n\nConflict with `route.default_mark` and `[dialOptions].routing_mark`.\n\n#### auto_redirect_input_mark\n\n!!! question \"Since sing-box 1.10.0\"\n\nConnection input mark used by `auto_redirect`.\n\n`0x2023` is used by default.\n\n#### auto_redirect_output_mark\n\n!!! question \"Since sing-box 1.10.0\"\n\nConnection output mark used by `auto_redirect`.\n\n`0x2024` is used by default.\n\n#### auto_redirect_reset_mark\n\n!!! question \"Since sing-box 1.13.0\"\n\nConnection reset mark used by `auto_redirect` pre-matching.\n\n`0x2025` is used by default.\n\n#### auto_redirect_nfqueue\n\n!!! question \"Since sing-box 1.13.0\"\n\nNFQueue number used by `auto_redirect` pre-matching.\n\n`100` is used by default.\n\n#### auto_redirect_iproute2_fallback_rule_index\n\n!!! question \"Since sing-box 1.12.18\"\n\nLinux iproute2 fallback rule index generated by `auto_redirect`.\n\nThis rule is checked after system default rules (32766: main, 32767: default),\nrouting traffic to the sing-box table only when no route is found in system tables.\n\n`32768` is used by default.\n\n#### exclude_mptcp\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.\n\nMPTCP cannot be transparently proxied due to protocol limitations.\n\nSuch traffic is usually created by Apple systems.\n\nWhen enabled, MPTCP connections will bypass sing-box and connect directly, otherwise, will be rejected to avoid errors by default.\n\n#### loopback_address\n\n!!! question \"Since sing-box 1.12.0\"\n\nLoopback addresses make TCP connections to the specified address connect to the source address.\n\nSetting option value to `10.7.0.1` achieves the same behavior as SideStore/StosVPN.\n\nWhen `auto_redirect` is enabled, the same behavior can be achieved for LAN devices (not just local) as a gateway.\n\n#### strict_route\n\nEnforce strict routing rules when `auto_route` is enabled:\n\n*In Linux*:\n\n* Let unsupported network unreachable\n* For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN.\n* When `auto_redirect` is enabled, `strict_route` also affects `SO_BINDTODEVICE` traffic:\n    * Enabled: `SO_BINDTODEVICE` traffic is redirected through sing-box.\n    * Disabled: `SO_BINDTODEVICE` traffic bypasses sing-box.\n\n*In Windows*:\n\n* Let unsupported network unreachable\n* prevent DNS leak caused by\n  Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)\n\nIt may prevent some Windows applications (such as VirtualBox) from working properly in certain situations.\n\n#### route_address\n\n!!! question \"Since sing-box 1.10.0\"\n\nUse custom routes instead of default when `auto_route` is enabled.\n\n#### inet4_route_address\n\n!!! failure \"Deprecated in sing-box 1.10.0\"\n\n`inet4_route_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_address](#route_address)\ninstead.\n\nUse custom routes instead of default when `auto_route` is enabled.\n\n#### inet6_route_address\n\n!!! failure \"Deprecated in sing-box 1.10.0\"\n\n`inet6_route_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_address](#route_address)\ninstead.\n\nUse custom routes instead of default when `auto_route` is enabled.\n\n#### route_exclude_address\n\n!!! question \"Since sing-box 1.10.0\"\n\nExclude custom routes when `auto_route` is enabled.\n\n#### inet4_route_exclude_address\n\n!!! failure \"Deprecated in sing-box 1.10.0\"\n\n`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.12.0, please\nuse [route_exclude_address](#route_exclude_address) instead.\n\nExclude custom routes when `auto_route` is enabled.\n\n#### inet6_route_exclude_address\n\n!!! failure \"Deprecated in sing-box 1.10.0\"\n\n`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.12.0, please\nuse [route_exclude_address](#route_exclude_address) instead.\n\nExclude custom routes when `auto_route` is enabled.\n\n#### route_address_set\n\n=== \"With `auto_redirect` enabled\"\n\n    !!! question \"Since sing-box 1.10.0\"\n\n    !!! quote \"\"\n    \n        Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.\n    \n    Add the destination IP CIDR rules in the specified rule-sets to the firewall.\n    Unmatched traffic will bypass the sing-box routes.\n    \n    Conflict with `route.default_mark` and `[dialOptions].routing_mark`.\n\n=== \"Without `auto_redirect` enabled\"\n\n    !!! question \"Since sing-box 1.11.0\"\n    \n    Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_address`.\n    Unmatched traffic will bypass the sing-box routes.\n\n    Note that it **doesn't work on the Android graphical client** due to\n    the Android VpnService not being able to handle a large number of routes (DeadSystemException),\n    but otherwise it works fine on all command line clients and Apple platforms.\n\n#### route_exclude_address_set\n\n=== \"With `auto_redirect` enabled\"\n\n    !!! question \"Since sing-box 1.10.0\"\n\n    !!! quote \"\"\n\n    Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.\n\n    Add the destination IP CIDR rules in the specified rule-sets to the firewall.\n    Matched traffic will bypass the sing-box routes.\n\n=== \"Without `auto_redirect` enabled\"\n\n    !!! question \"Since sing-box 1.11.0\"\n    \n    Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_exclude_address`.\n    Matched traffic will bypass the sing-box routes.\n\n    Note that it **doesn't work on the Android graphical client** due to\n    the Android VpnService not being able to handle a large number of routes (DeadSystemException),\n    but otherwise it works fine on all command line clients and Apple platforms.\n\n#### endpoint_independent_nat\n\n!!! info \"\"\n\n    This item is only available on the gvisor stack, other stacks are endpoint-independent NAT by default.\n\nEnable endpoint-independent NAT.\n\nPerformance may degrade slightly, so it is not recommended to enable on when it is not needed.\n\n#### udp_timeout\n\nUDP NAT expiration time.\n\n`5m` will be used by default.\n\n#### stack\n\n!!! quote \"Changes in sing-box 1.8.0\"\n\n    :material-delete-alert: The legacy LWIP stack has been deprecated and removed.\n\nTCP/IP stack.\n\n| Stack    | Description                                                                                           | \n|----------|-------------------------------------------------------------------------------------------------------|\n| `system` | Perform L3 to L4 translation using the system network stack                                           |\n| `gvisor` | Perform L3 to L4 translation using [gVisor](https://github.com/google/gvisor)'s virtual network stack |\n| `mixed`  | Mixed `system` TCP stack and `gvisor` UDP stack                                                       |\n\nDefaults to the `mixed` stack if the gVisor build tag is enabled, otherwise defaults to the `system` stack.\n\n#### include_interface\n\n!!! quote \"\"\n\n    Interface rules are only supported on Linux and require auto_route.\n\nLimit interfaces in route. Not limited by default.\n\nConflict with `exclude_interface`.\n\n#### exclude_interface\n\n!!! warning \"\"\n\n    When `strict_route` enabled, return traffic to excluded interfaces will not be automatically excluded, so add them as well (example: `br-lan` and `pppoe-wan`).\n\nExclude interfaces in route.\n\nConflict with `include_interface`.\n\n#### include_uid\n\n!!! quote \"\"\n\n    UID rules are only supported on Linux and require auto_route.\n\nLimit users in route. Not limited by default.\n\n#### include_uid_range\n\nLimit users in route, but in range.\n\n#### exclude_uid\n\nExclude users in route.\n\n#### exclude_uid_range\n\nExclude users in route, but in range.\n\n#### include_android_user\n\n!!! quote \"\"\n\n    Android user and package rules are only supported on Android and require auto_route.\n\nLimit android users in route.\n\n| Common user  | ID |\n|--------------|----|\n| Main         | 0  |\n| Work Profile | 10 |\n\n#### include_package\n\nLimit android packages in route.\n\n#### exclude_package\n\nExclude android packages in route.\n\n#### include_mac_address\n\n!!! question \"Since sing-box 1.14.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux with `auto_route` and `auto_redirect` enabled.\n\nLimit MAC addresses in route. Not limited by default.\n\nConflict with `exclude_mac_address`.\n\n#### exclude_mac_address\n\n!!! question \"Since sing-box 1.14.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux with `auto_route` and `auto_redirect` enabled.\n\nExclude MAC addresses in route.\n\nConflict with `include_mac_address`.\n\n#### platform\n\nPlatform-specific settings, provided by client applications.\n\n#### platform.http_proxy\n\nSystem HTTP proxy settings.\n\n#### platform.http_proxy.enabled\n\nEnable system HTTP proxy.\n\n#### platform.http_proxy.server\n\n==Required==\n\nHTTP proxy server address.\n\n#### platform.http_proxy.server_port\n\n==Required==\n\nHTTP proxy server port.\n\n#### platform.http_proxy.bypass_domain\n\n!!! note \"\"\n\n    On Apple platforms, `bypass_domain` items matches hostname **suffixes**.\n\nHostnames that bypass the HTTP proxy.\n\n#### platform.http_proxy.match_domain\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Apple platforms.\n\nHostnames that use the HTTP proxy.\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n"
  },
  {
    "path": "docs/configuration/inbound/tun.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.14.0 中的更改\"\n\n    :material-plus: [include_mac_address](#include_mac_address)  \n    :material-plus: [exclude_mac_address](#exclude_mac_address)\n\n!!! quote \"sing-box 1.13.3 中的更改\"\n\n    :material-alert: [strict_route](#strict_route)\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark)  \n    :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue)  \n    :material-plus: [exclude_mptcp](#exclude_mptcp)  \n    :material-plus: [auto_redirect_iproute2_fallback_rule_index](#auto_redirect_iproute2_fallback_rule_index)\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-plus: [loopback_address](#loopback_address)\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-delete-alert: [gso](#gso)  \n    :material-alert-decagram: [route_address_set](#stack)  \n    :material-alert-decagram: [route_exclude_address_set](#stack)\n\n!!! quote \"sing-box 1.10.0 中的更改\"\n\n    :material-plus: [address](#address)  \n    :material-delete-clock: [inet4_address](#inet4_address)  \n    :material-delete-clock: [inet6_address](#inet6_address)  \n    :material-plus: [route_address](#route_address)  \n    :material-delete-clock: [inet4_route_address](#inet4_route_address)  \n    :material-delete-clock: [inet6_route_address](#inet6_route_address)  \n    :material-plus: [route_exclude_address](#route_address)  \n    :material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address)  \n    :material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address)  \n    :material-plus: [iproute2_table_index](#iproute2_table_index)  \n    :material-plus: [iproute2_rule_index](#iproute2_table_index)  \n    :material-plus: [auto_redirect](#auto_redirect)  \n    :material-plus: [auto_redirect_input_mark](#auto_redirect_input_mark)  \n    :material-plus: [auto_redirect_output_mark](#auto_redirect_output_mark)  \n    :material-plus: [route_address_set](#route_address_set)  \n    :material-plus: [route_exclude_address_set](#route_address_set)\n\n!!! quote \"sing-box 1.9.0 中的更改\"\n\n    :material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)  \n    :material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain)\n\n!!! quote \"sing-box 1.8.0 中的更改\"\n\n    :material-plus: [gso](#gso)  \n    :material-alert-decagram: [stack](#stack)\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS。\n\n### 结构\n\n```json\n{\n  \"type\": \"tun\",\n  \"tag\": \"tun-in\",\n  \"interface_name\": \"tun0\",\n  \"address\": [\n    \"172.18.0.1/30\",\n    \"fdfe:dcba:9876::1/126\"\n  ],\n  \"mtu\": 9000,\n  \"auto_route\": true,\n  \"iproute2_table_index\": 2022,\n  \"iproute2_rule_index\": 9000,\n  \"auto_redirect\": true,\n  \"auto_redirect_input_mark\": \"0x2023\",\n  \"auto_redirect_output_mark\": \"0x2024\",\n  \"auto_redirect_reset_mark\": \"0x2025\",\n  \"auto_redirect_nfqueue\": 100,\n  \"auto_redirect_iproute2_fallback_rule_index\": 32768,\n  \"exclude_mptcp\": false,\n  \"loopback_address\": [\n    \"10.7.0.1\"\n  ],\n  \"strict_route\": true,\n  \"route_address\": [\n    \"0.0.0.0/1\",\n    \"128.0.0.0/1\",\n    \"::/1\",\n    \"8000::/1\"\n  ],\n\n  \"route_exclude_address\": [\n    \"192.168.0.0/16\",\n    \"fc00::/7\"\n  ],\n  \"route_address_set\": [\n    \"geoip-cloudflare\"\n  ],\n  \"route_exclude_address_set\": [\n    \"geoip-cn\"\n  ],\n  \"endpoint_independent_nat\": false,\n  \"udp_timeout\": \"5m\",\n  \"stack\": \"system\",\n  \"include_interface\": [\n    \"lan0\"\n  ],\n  \"exclude_interface\": [\n    \"lan1\"\n  ],\n  \"include_uid\": [\n    0\n  ],\n  \"include_uid_range\": [\n    \"1000:99999\"\n  ],\n  \"exclude_uid\": [\n    1000\n  ],\n  \"exclude_uid_range\": [\n    \"1000:99999\"\n  ],\n  \"include_android_user\": [\n    0,\n    10\n  ],\n  \"include_package\": [\n    \"com.android.chrome\"\n  ],\n  \"exclude_package\": [\n    \"com.android.captiveportallogin\"\n  ],\n  \"include_mac_address\": [\n    \"00:11:22:33:44:55\"\n  ],\n  \"exclude_mac_address\": [\n    \"66:77:88:99:aa:bb\"\n  ],\n  \"platform\": {\n    \"http_proxy\": {\n      \"enabled\": false,\n      \"server\": \"127.0.0.1\",\n      \"server_port\": 8080,\n      \"bypass_domain\": [],\n      \"match_domain\": []\n    }\n  },\n\n  // 已弃用\n  \"gso\": false,\n  \"inet4_address\": [\n    \"172.19.0.1/30\"\n  ],\n  \"inet6_address\": [\n    \"fdfe:dcba:9876::1/126\"\n  ],\n  \"inet4_route_address\": [\n    \"0.0.0.0/1\",\n    \"128.0.0.0/1\"\n  ],\n  \"inet6_route_address\": [\n    \"::/1\",\n    \"8000::/1\"\n  ],\n  \"inet4_route_exclude_address\": [\n    \"192.168.0.0/16\"\n  ],\n  \"inet6_route_exclude_address\": [\n    \"fc00::/7\"\n  ],\n  \n  ... // 监听字段\n}\n```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签。\n\n!!! warning \"\"\n\n    如果 tun 在非特权模式下运行，地址和 MTU 将不会自动配置，请确保设置正确。\n\n### Tun 字段\n\n#### interface_name\n\n虚拟设备名称，默认自动选择。\n\n#### address\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n==必填==\n\ntun 接口的 IPv4 和 IPv6 前缀。\n\n#### inet4_address\n\n!!! failure \"已在 sing-box 1.10.0 废弃\"\n\n    `inet4_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。\n\n==必填==\n\ntun 接口的 IPv4 前缀。\n\n#### inet6_address\n\n!!! failure \"已在 sing-box 1.10.0 废弃\"\n\n    `inet6_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。\n\ntun 接口的 IPv6 前缀。\n\n#### mtu\n\n最大传输单元。\n\n#### gso\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    GSO 对于透明代理场景没有优势，已废弃和不再生效，且将在 sing-box 1.12.0 中被移除。\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux。\n\n启用通用分段卸载。\n\n#### auto_route\n\n设置到 Tun 的默认路由。\n\n!!! quote \"\"\n\n    为避免流量环回，请设置 `route.auto_detect_interface` 或 `route.default_interface` 或 `outbound.bind_interface`。\n\n!!! note \"与 Android VPN 一起使用\"\n\n    VPN 默认优先于 tun。要使 tun 经过 VPN，启用 `route.override_android_vpn`。\n\n!!! note \"也启用 `auto_redirect`\"\n\n  在 Linux 上始终推荐使用 `auto_redirect`，它提供更好的路由， 更高的性能（优于 tproxy）， 并避免 TUN 与 Docker 桥接网络冲突。\n\n#### iproute2_table_index\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n`auto_route` 生成的 iproute2 路由表索引。\n\n默认使用 `2022`。\n\n#### iproute2_rule_index\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n`auto_route` 生成的 iproute2 规则起始索引。\n\n默认使用 `9000`。\n\n#### auto_redirect\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux，且需要 `auto_route` 已启用。\n\n通过使用 nftables 改善 TUN 路由和性能。\n\n在 Linux 上始终推荐使用 `auto_redirect`，它提供更好的路由、更高的性能（优于 tproxy），并避免了 TUN 和 Docker 桥接网络之间的冲突。\n\n请注意，`auto_redirect` 也适用于 Android，但由于缺少 `nftables` 和 `ip6tables`，仅执行简单的 IPv4 TCP 转发。  \n若要在 Android 上通过热点或中继器共享 VPN 连接，请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。\n\n`auto_redirect` 还会自动将兼容性规则插入 OpenWrt 的 fw4 表中，即无需额外配置即可在路由器上工作。\n\n与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。\n\n#### auto_redirect_input_mark\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n`auto_redirect` 使用的连接输入标记。\n\n默认使用 `0x2023`。\n\n#### auto_redirect_output_mark\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n`auto_redirect` 使用的连接输出标记。\n\n默认使用 `0x2024`。\n\n#### auto_redirect_reset_mark\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n`auto_redirect` 预匹配使用的连接重置标记。\n\n默认使用 `0x2025`。\n\n#### auto_redirect_nfqueue\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n`auto_redirect` 预匹配使用的 NFQueue 编号。\n\n默认使用 `100`。\n\n#### auto_redirect_iproute2_fallback_rule_index\n\n!!! question \"自 sing-box 1.12.18 起\"\n\n`auto_redirect` 生成的 iproute2 回退规则索引。\n\n此规则在系统默认规则（32766: main，32767: default）之后检查，\n仅当系统路由表中未找到路由时才将流量路由到 sing-box 路由表。\n\n默认使用 `32768`。\n\n#### exclude_mptcp\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux，且需要 nftables，`auto_route` 和 `auto_redirect` 已启用。\n\n由于协议限制，MPTCP 无法被透明代理。\n\n此类流量通常由 Apple 系统创建。\n\n启用时，MPTCP 连接将绕过 sing-box 直接连接，否则，将被拒绝以避免错误。\n\n#### loopback_address\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n环回地址是用于使指向指定地址的 TCP 连接连接到来源地址的。\n\n将选项值设置为 `10.7.0.1` 可实现与 SideStore/StosVPN 相同的行为。\n\n当启用 `auto_redirect` 时，可以作为网关为局域网设备（而不仅仅是本地）实现相同的行为。\n\n#### strict_route\n\n当启用 `auto_route` 时，强制执行严格的路由规则：\n\n*在 Linux 中*：\n\n* 使不支持的网络不可达。\n* 出于历史遗留原因，当未启用 `strict_route` 或 `auto_redirect` 时，所有 ICMP 流量将不会通过 TUN。\n* 当启用 `auto_redirect` 时，`strict_route` 也影响 `SO_BINDTODEVICE` 流量：\n    * 启用：`SO_BINDTODEVICE` 流量被重定向通过 sing-box。\n    * 禁用：`SO_BINDTODEVICE` 流量绕过 sing-box。\n\n*在 Windows 中*：\n\n* 使不支持的网络不可达。\n* 阻止 Windows 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) 造成的 DNS 泄露\n\n它可能会使某些 Windows 应用程序（如 VirtualBox）在某些情况下无法正常工作。\n\n#### route_address\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n设置到 Tun 的自定义路由。\n\n#### inet4_route_address\n\n!!! failure \"已在 sing-box 1.10.0 废弃\"\n\n    `inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。\n\n启用 `auto_route` 时使用自定义路由而不是默认路由。\n\n#### inet6_route_address\n\n!!! failure \"已在 sing-box 1.10.0 废弃\"\n\n    `inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。\n\n启用 `auto_route` 时使用自定义路由而不是默认路由。\n\n#### route_exclude_address\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n设置到 Tun 的排除自定义路由。\n\n#### inet4_route_exclude_address\n\n!!! failure \"已在 sing-box 1.10.0 废弃\"\n\n    `inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。\n\n启用 `auto_route` 时排除自定义路由。\n\n#### inet6_route_exclude_address\n\n!!! failure \"已在 sing-box 1.10.0 废弃\"\n\n    `inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。\n\n启用 `auto_route` 时排除自定义路由。\n\n#### route_address_set\n\n=== \"`auto_redirect` 已启用\"\n\n    !!! question \"自 sing-box 1.10.0 起\"\n    \n    !!! quote \"\"\n    \n        仅支持 Linux，且需要 nftables，`auto_route` 和 `auto_redirect` 已启用。 \n    \n    将指定规则集中的目标 IP CIDR 规则添加到防火墙。\n    不匹配的流量将绕过 sing-box 路由。\n\n=== \"`auto_redirect` 未启用\"\n\n    !!! question \"自 sing-box 1.11.0 起\"\n\n    将指定规则集中的目标 IP CIDR 规则添加到路由，相当于添加到 `route_address`。\n    不匹配的流量将绕过 sing-box 路由。\n\n    请注意，由于 Android VpnService 无法处理大量路由（DeadSystemException），\n    因此它**在 Android 图形客户端上不起作用**，但除此之外，它在所有命令行客户端和 Apple 平台上都可以正常工作。\n\n#### route_exclude_address_set\n\n=== \"`auto_redirect` 已启用\"\n\n    !!! question \"自 sing-box 1.10.0 起\"\n    \n    !!! quote \"\"\n    \n        仅支持 Linux，且需要 nftables，`auto_route` 和 `auto_redirect` 已启用。 \n\n    将指定规则集中的目标 IP CIDR 规则添加到防火墙。\n    匹配的流量将绕过 sing-box 路由。\n\n    与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。\n\n=== \"`auto_redirect` 未启用\"\n\n    !!! question \"自 sing-box 1.11.0 起\"\n\n    将指定规则集中的目标 IP CIDR 规则添加到路由，相当于添加到 `route_exclude_address`。\n    匹配的流量将绕过 sing-box 路由。\n\n    请注意，由于 Android VpnService 无法处理大量路由（DeadSystemException），\n    因此它**在 Android 图形客户端上不起作用**，但除此之外，它在所有命令行客户端和 Apple 平台上都可以正常工作。\n\n#### endpoint_independent_nat\n\n启用独立于端点的 NAT。\n\n性能可能会略有下降，所以不建议在不需要的时候开启。\n\n#### udp_timeout\n\nUDP NAT 过期时间。\n\n默认使用 `5m`。\n\n#### stack\n\n!!! quote \"sing-box 1.8.0 中的更改\"\n\n    :material-delete-alert: 旧的 LWIP 栈已被弃用并移除。\n\nTCP/IP 栈。\n\n| 栈       | 描述                                                                                                  | \n|----------|-------------------------------------------------------------------------------------------------------|\n| `system` | 基于系统网络栈执行 L3 到 L4 转换                                                                        |\n| `gvisor` | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换                            |\n| `mixed`  | 混合 `system` TCP 栈与 `gvisor` UDP 栈                                                                 |\n\n默认使用 `mixed` 栈如果 gVisor 构建标记已启用，否则默认使用 `system` 栈。\n\n#### include_interface\n\n!!! quote \"\"\n\n    接口规则仅在 Linux 下被支持，并且需要 `auto_route`。\n\n限制被路由的接口。默认不限制。\n\n与 `exclude_interface` 冲突。\n\n#### exclude_interface\n\n!!! warning \"\"\n\n    当 `strict_route` 启用，到被排除接口的回程流量将不会被自动排除，因此也要添加它们（例：`br-lan` 与 `pppoe-wan`）。\n\n排除路由的接口。\n\n与 `include_interface` 冲突。\n\n#### include_uid\n\n!!! quote \"\"\n\n    UID 规则仅在 Linux 下被支持，并且需要 `auto_route`。\n\n限制被路由的用户。默认不限制。\n\n#### include_uid_range\n\n限制被路由的用户范围。\n\n#### exclude_uid\n\n排除路由的用户。\n\n#### exclude_uid_range\n\n排除路由的用户范围。\n\n#### include_android_user\n\n!!! quote \"\"\n\n    Android 用户和应用规则仅在 Android 下被支持，并且需要 `auto_route`。\n\n限制被路由的 Android 用户。\n\n| 常用用户 | ID |\n|------|----|\n| 您    | 0  |\n| 工作资料 | 10 |\n\n#### include_package\n\n限制被路由的 Android 应用包名。\n\n#### exclude_package\n\n排除路由的 Android 应用包名。\n\n#### include_mac_address\n\n!!! question \"自 sing-box 1.14.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux，且需要 `auto_route` 和 `auto_redirect` 已启用。\n\n限制被路由的 MAC 地址。默认不限制。\n\n与 `exclude_mac_address` 冲突。\n\n#### exclude_mac_address\n\n!!! question \"自 sing-box 1.14.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux，且需要 `auto_route` 和 `auto_redirect` 已启用。\n\n排除路由的 MAC 地址。\n\n与 `include_mac_address` 冲突。\n\n#### platform\n\n平台特定的设置，由客户端应用提供。\n\n#### platform.http_proxy\n\n系统 HTTP 代理设置。\n\n##### platform.http_proxy.enabled\n\n启用系统 HTTP 代理。\n\n##### platform.http_proxy.server\n\n==必填==\n\n系统 HTTP 代理服务器地址。\n\n##### platform.http_proxy.server_port\n\n==必填==\n\n系统 HTTP 代理服务器端口。\n\n##### platform.http_proxy.bypass_domain\n\n!!! note \"\"\n\n    在 Apple 平台，`bypass_domain` 项匹配主机名 **后缀**.\n\n绕过代理的主机名列表。\n\n##### platform.http_proxy.match_domain\n\n!!! quote \"\"\n\n    仅在 Apple 平台图形客户端中支持。\n\n代理的主机名列表。\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n"
  },
  {
    "path": "docs/configuration/inbound/vless.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"vless\",\n  \"tag\": \"vless-in\",\n\n  ... // Listen Fields\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"uuid\": \"bf000d23-0752-40b4-affe-68f7707a9661\",\n      \"flow\": \"\"\n    }\n  ],\n  \"tls\": {},\n  \"multiplex\": {},\n  \"transport\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### users\n\n==Required==\n\nVLESS users.\n\n#### users.uuid\n\n==Required==\n\nVLESS user id.\n\n#### users.flow\n\nVLESS Sub-protocol.\n\nAvailable values:\n\n* `xtls-rprx-vision`\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound).\n\n#### multiplex\n\nSee [Multiplex](/configuration/shared/multiplex#inbound) for details.\n\n#### transport\n\nV2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport/).\n"
  },
  {
    "path": "docs/configuration/inbound/vless.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"vless\",\n  \"tag\": \"vless-in\",\n\n  ... // 监听字段\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"uuid\": \"bf000d23-0752-40b4-affe-68f7707a9661\",\n      \"flow\": \"\"\n    }\n  ],\n  \"tls\": {},\n  \"multiplex\": {},\n  \"transport\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### users\n\n==必填==\n\nVLESS 用户。\n\n#### users.uuid\n\n==必填==\n\nVLESS 用户 ID。\n\n#### users.flow\n\nVLESS 子协议。\n\n可用值：\n\n* `xtls-rprx-vision`\n\n#### tls\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。\n\n#### multiplex\n\n参阅 [多路复用](/zh/configuration/shared/multiplex#入站)。\n\n#### transport\n\nV2Ray 传输配置，参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport/)。\n"
  },
  {
    "path": "docs/configuration/inbound/vmess.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"vmess\",\n  \"tag\": \"vmess-in\",\n\n  ... // Listen Fields\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"uuid\": \"bf000d23-0752-40b4-affe-68f7707a9661\",\n      \"alterId\": 0\n    }\n  ],\n  \"tls\": {},\n  \"multiplex\": {},\n  \"transport\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### users\n\n==Required==\n\nVMess users.\n\n| Alter ID | Description             |\n|----------|-------------------------|\n| 0        | Disable legacy protocol |\n| > 0      | Enable legacy protocol  |\n\n!!! warning \"\"\n\n    Legacy protocol support (VMess MD5 Authentication) is provided for compatibility purposes only, use of alterId > 1 is not recommended.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound).\n\n#### multiplex\n\nSee [Multiplex](/configuration/shared/multiplex#inbound) for details.\n\n#### transport\n\nV2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport/).\n"
  },
  {
    "path": "docs/configuration/inbound/vmess.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"vmess\",\n  \"tag\": \"vmess-in\",\n\n  ... // 监听字段\n\n  \"users\": [\n    {\n      \"name\": \"sekai\",\n      \"uuid\": \"bf000d23-0752-40b4-affe-68f7707a9661\",\n      \"alterId\": 0\n    }\n  ],\n  \"tls\": {},\n  \"multiplex\": {},\n  \"transport\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/)。\n\n### 字段\n\n#### users\n\n==必填==\n\nVMess 用户。\n\n| Alter ID | 描述    |\n|----------|-------|\n| 0        | 禁用旧协议 |\n| > 0      | 启用旧协议 |\n\n!!! warning \"\"\n\n    提供旧协议支持（VMess MD5 身份验证）仅出于兼容性目的，不建议使用 alterId > 1。\n\n#### tls\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。\n\n#### multiplex\n\n参阅 [多路复用](/zh/configuration/shared/multiplex#入站)。\n\n#### transport\n\nV2Ray 传输配置，参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport/)。\n"
  },
  {
    "path": "docs/configuration/index.md",
    "content": "# Introduction\n\nsing-box uses JSON for configuration files.\n\n### Structure\n\n```json\n{\n  \"log\": {},\n  \"dns\": {},\n  \"ntp\": {},\n  \"certificate\": {},\n  \"endpoints\": [],\n  \"inbounds\": [],\n  \"outbounds\": [],\n  \"route\": {},\n  \"services\": [],\n  \"experimental\": {}\n}\n```\n\n### Fields\n\n| Key            | Format                          |\n|----------------|---------------------------------|\n| `log`          | [Log](./log/)                   |\n| `dns`          | [DNS](./dns/)                   |\n| `ntp`          | [NTP](./ntp/)                   |\n| `certificate`  | [Certificate](./certificate/)   |\n| `endpoints`    | [Endpoint](./endpoint/)         |\n| `inbounds`     | [Inbound](./inbound/)           |\n| `outbounds`    | [Outbound](./outbound/)         |\n| `route`        | [Route](./route/)               |\n| `services`     | [Service](./service/)           |\n| `experimental` | [Experimental](./experimental/) |\n\n### Check\n\n```bash\nsing-box check\n```\n\n### Format\n\n```bash\nsing-box format -w -c config.json -D config_directory\n```\n\n### Merge\n\n```bash\nsing-box merge output.json -c config.json -D config_directory\n```"
  },
  {
    "path": "docs/configuration/index.zh.md",
    "content": "# 引言\n\nsing-box 使用 JSON 作为配置文件格式。\n\n### 结构\n\n```json\n{\n  \"log\": {},\n  \"dns\": {},\n  \"ntp\": {},\n  \"certificate\": {},\n  \"endpoints\": [],\n  \"inbounds\": [],\n  \"outbounds\": [],\n  \"route\": {},\n  \"services\": [],\n  \"experimental\": {}\n}\n```\n\n### 字段\n\n| Key            | Format                 |\n|----------------|------------------------|\n| `log`          | [日志](./log/)           |\n| `dns`          | [DNS](./dns/)          |\n| `ntp`          | [NTP](./ntp/)          |\n| `certificate`  | [证书](./certificate/)   |\n| `endpoints`    | [端点](./endpoint/)      |\n| `inbounds`     | [入站](./inbound/)       |\n| `outbounds`    | [出站](./outbound/)      |\n| `route`        | [路由](./route/)         |\n| `services`     | [服务](./service/)       |\n| `experimental` | [实验性](./experimental/) |\n\n### 检查\n\n```bash\nsing-box check\n```\n\n### 格式化\n\n```bash\nsing-box format -w -c config.json -D config_directory\n```\n\n### 合并\n\n```bash\nsing-box merge output.json -c config.json -D config_directory\n```"
  },
  {
    "path": "docs/configuration/log/index.md",
    "content": "# Log\n\n### Structure\n\n```json\n{\n  \"log\": {\n    \"disabled\": false,\n    \"level\": \"info\",\n    \"output\": \"box.log\",\n    \"timestamp\": true\n  }\n}\n\n```\n\n### Fields\n\n#### disabled\n\nDisable logging, no output after start.\n\n#### level\n\nLog level. One of: `trace` `debug` `info` `warn` `error` `fatal` `panic`.\n\n#### output\n\nOutput file path. Will not write log to console after enable.\n\n#### timestamp\n\nAdd time to each line."
  },
  {
    "path": "docs/configuration/log/index.zh.md",
    "content": "# 日志\n\n### 结构\n\n```json\n{\n  \"log\": {\n    \"disabled\": false,\n    \"level\": \"info\",\n    \"output\": \"box.log\",\n    \"timestamp\": true\n  }\n}\n\n```\n\n### 字段\n\n#### disabled\n\n禁用日志，启动后不输出日志。\n\n#### level\n\n日志等级，可选值：`trace` `debug` `info` `warn` `error` `fatal` `panic`。\n\n#### output\n\n输出文件路径，启动后将不输出到控制台。\n\n#### timestamp\n\n添加时间到每行。"
  },
  {
    "path": "docs/configuration/ntp/index.md",
    "content": "# NTP\n\nBuilt-in NTP client service.\n\nIf enabled, it will provide time for protocols like TLS/Shadowsocks/VMess, which is useful for environments where time\nsynchronization is not possible.\n\n### Structure\n\n```json\n{\n  \"ntp\": {\n    \"enabled\": false,\n    \"server\": \"time.apple.com\",\n    \"server_port\": 123,\n    \"interval\": \"30m\",\n    \n    ... // Dial Fields\n  }\n}\n\n```\n\n### Fields\n\n#### enabled\n\nEnable NTP service.\n\n#### server\n\n==Required==\n\nNTP server address.\n\n#### server_port\n\nNTP server port.\n\n123 is used by default.\n\n#### interval\n\nTime synchronization interval.\n\n30 minutes is used by default.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details."
  },
  {
    "path": "docs/configuration/ntp/index.zh.md",
    "content": "# NTP\n\n内建的 NTP 客户端服务。\n\n如果启用，它将为像 TLS/Shadowsocks/VMess 这样的协议提供时间，这对于无法进行时间同步的环境很有用。\n\n### 结构\n\n```json\n{\n  \"ntp\": {\n    \"enabled\": false,\n    \"server\": \"time.apple.com\",\n    \"server_port\": 123,\n    \"interval\": \"30m\",\n    \n    ... // 拨号字段\n  }\n}\n\n```\n\n### 字段\n\n#### enabled\n\n启用 NTP 服务。\n\n#### server\n\n==必填==\n\nNTP 服务器地址。\n\n#### server_port\n\nNTP 服务器端口。\n\n默认使用 123。\n\n#### interval\n\n时间同步间隔。\n\n默认使用 30 分钟。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/anytls.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n### Structure\n\n```json\n{\n  \"type\": \"anytls\",\n  \"tag\": \"anytls-out\",\n\n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"idle_session_check_interval\": \"30s\",\n  \"idle_session_timeout\": \"30s\",\n  \"min_idle_session\": 5,\n  \"tls\": {},\n\n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\n#### password\n\n==Required==\n\nThe AnyTLS password.\n\n#### idle_session_check_interval\n\nInterval checking for idle sessions. Default: 30s.\n\n#### idle_session_timeout\n\nIn the check, close sessions that have been idle for longer than this. Default: 30s.\n\n#### min_idle_session\n\nIn the check, at least the first `n` idle sessions are kept open. Default value: `n`=0\n\n#### tls\n\n==Required==\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/anytls.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n### 结构\n\n```json\n{\n  \"type\": \"anytls\",\n  \"tag\": \"anytls-out\",\n\n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"idle_session_check_interval\": \"30s\",\n  \"idle_session_timeout\": \"30s\",\n  \"min_idle_session\": 5,\n  \"tls\": {},\n\n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### password\n\n==必填==\n\nAnyTLS 密码。\n\n#### idle_session_check_interval\n\n检查空闲会话的时间间隔。默认值：30秒。\n\n#### idle_session_timeout\n\n在检查中，关闭闲置时间超过此值的会话。默认值：30秒。\n\n#### min_idle_session\n\n在检查中，至少前 `n` 个空闲会话保持打开状态。默认值：`n`=0\n\n#### tls\n\n==必填==\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/block.md",
    "content": "---\nicon: material/delete-clock\n---\n\n### Structure\n\n```json\n{\n  \"type\": \"block\",\n  \"tag\": \"block\"\n}\n```\n\n### Fields\n\nNo fields.\n"
  },
  {
    "path": "docs/configuration/outbound/block.zh.md",
    "content": "---\nicon: material/delete-clock\n---\n\n`block` 出站关闭所有传入请求。\n\n### 结构\n\n```json\n{\n  \"type\": \"block\",\n  \"tag\": \"block\"\n}\n```\n\n### 字段\n\n无字段。\n"
  },
  {
    "path": "docs/configuration/outbound/direct.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-delete-clock: [override_address](#override_address)  \n    :material-delete-clock: [override_port](#override_port)\n\n`direct` outbound send requests directly.\n\n### Structure\n\n```json\n{\n  \"type\": \"direct\",\n  \"tag\": \"direct-out\",\n  \n  \"override_address\": \"1.0.0.1\",\n  \"override_port\": 53,\n  \n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### override_address\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options).\n\nOverride the connection destination address.\n\n#### override_port\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options).\n\nOverride the connection destination port.\n\nProtocol value can be `1` or `2`.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/direct.zh.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-delete-clock: [override_address](#override_address)  \n    :material-delete-clock: [override_port](#override_port)\n\n`direct` 出站直接发送请求。\n\n### 结构\n\n```json\n{\n  \"type\": \"direct\",\n  \"tag\": \"direct-out\",\n  \n  \"override_address\": \"1.0.0.1\",\n  \"override_port\": 53,\n\n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### override_address\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    目标覆盖字段在 sing-box 1.11.0 中已废弃，并将在 sing-box 1.13.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移-direct-出站中的目标地址覆盖字段到路由字段)。\n\n覆盖连接目标地址。\n\n#### override_port\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    目标覆盖字段在 sing-box 1.11.0 中已废弃，并将在 sing-box 1.13.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移-direct-出站中的目标地址覆盖字段到路由字段)。\n\n覆盖连接目标端口。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/dns.md",
    "content": "---\nicon: material/delete-clock\n---\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions).\n\n`dns` outbound is a internal DNS server.\n\n### Structure\n\n```json\n{\n  \"type\": \"dns\",\n  \"tag\": \"dns-out\"\n}\n```\n\n!!! note \"\"\n\n    There are no outbound connections by the DNS outbound, all requests are handled internally.\n\n### Fields\n\nNo fields."
  },
  {
    "path": "docs/configuration/outbound/dns.zh.md",
    "content": "---\nicon: material/delete-clock\n---\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    旧的特殊出站已被弃用，且将在 sing-box 1.13.0 中被移除, 参阅 [迁移指南](/zh/migration/#迁移旧的特殊出站到规则动作). \n\n`dns` 出站是一个内部 DNS 服务器。\n\n### 结构\n\n```json\n{\n  \"type\": \"dns\",\n  \"tag\": \"dns-out\"\n}\n```\n\n!!! note \"\"\n\n    DNS 出站没有出站连接，所有请求均在内部处理。\n\n### 字段\n\n无字段。"
  },
  {
    "path": "docs/configuration/outbound/http.md",
    "content": "`http` outbound is a HTTP CONNECT proxy client.\n\n### Structure\n\n```json\n{\n  \"type\": \"http\",\n  \"tag\": \"http-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"username\": \"sekai\",\n  \"password\": \"admin\",\n  \"path\": \"\",\n  \"headers\": {},\n  \"tls\": {},\n  \n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\n#### username\n\nBasic authorization username.\n\n#### password\n\nBasic authorization password.\n\n#### path\n\nPath of HTTP request.\n\n#### headers\n\nExtra headers of HTTP request.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/http.zh.md",
    "content": "`http` 出站是一个 HTTP CONNECT 代理客户端\n\n### 结构\n\n```json\n{\n  \"type\": \"http\",\n  \"tag\": \"http-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"username\": \"sekai\",\n  \"password\": \"admin\",\n  \"path\": \"\",\n  \"headers\": {},\n  \"tls\": {},\n\n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### username\n\nBasic 认证用户名。\n\n#### password\n\nBasic 认证密码。\n\n#### path\n\nHTTP 请求路径。\n\n#### headers\n\nHTTP 请求的额外标头。\n\n#### tls\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/hysteria.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-plus: [server_ports](#server_ports)  \n    :material-plus: [hop_interval](#hop_interval)\n\n### Structure\n\n```json\n{\n  \"type\": \"hysteria\",\n  \"tag\": \"hysteria-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"server_ports\": [\n    \"2080:3000\"\n  ],\n  \"hop_interval\": \"\",\n  \"up\": \"100 Mbps\",\n  \"up_mbps\": 100,\n  \"down\": \"100 Mbps\",\n  \"down_mbps\": 100,\n  \"obfs\": \"fuck me till the daylight\",\n  \"auth\": \"\",\n  \"auth_str\": \"password\",\n  \"recv_window_conn\": 0,\n  \"recv_window\": 0,\n  \"disable_mtu_discovery\": false,\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\n#### server_ports\n\n!!! question \"Since sing-box 1.12.0\"\n\nServer port range list.\n\nConflicts with `server_port`.\n\n#### hop_interval\n\n!!! question \"Since sing-box 1.12.0\"\n\nPort hopping interval.\n\n`30s` is used by default.\n\n#### up, down\n\n==Required==\n\nFormat: `[Integer] [Unit]` e.g. `100 Mbps, 640 KBps, 2 Gbps`\n\nSupported units (case sensitive, b = bits, B = bytes, 8b=1B):\n\n    bps (bits per second)\n    Bps (bytes per second)\n    Kbps (kilobits per second)\n    KBps (kilobytes per second)\n    Mbps (megabits per second)\n    MBps (megabytes per second)\n    Gbps (gigabits per second)\n    GBps (gigabytes per second)\n    Tbps (terabits per second)\n    TBps (terabytes per second)\n\n#### up_mbps, down_mbps\n\n==Required==\n\n`up, down` in Mbps.\n\n#### obfs\n\nObfuscated password.\n\n#### auth\n\nAuthentication password, in base64.\n\n#### auth_str\n\nAuthentication password.\n\n#### recv_window_conn\n\nThe QUIC stream-level flow control window for receiving data.\n\n`15728640 (15 MB/s)` will be used if empty.\n\n#### recv_window\n\nThe QUIC connection-level flow control window for receiving data.\n\n`67108864 (64 MB/s)` will be used if empty.\n\n#### disable_mtu_discovery\n\nDisables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 (IPv4) / 1232 (IPv6) bytes in size.\n\nForce enabled on for systems other than Linux and Windows (according to upstream).\n\n#### network\n\nEnabled network\n\nOne of `tcp` `udp`.\n\nBoth is enabled by default.\n\n#### tls\n\n==Required==\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/hysteria.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-plus: [server_ports](#server_ports)  \n    :material-plus: [hop_interval](#hop_interval)\n\n### 结构\n\n```json\n{\n  \"type\": \"hysteria\",\n  \"tag\": \"hysteria-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"server_ports\": [\n    \"2080:3000\"\n  ],\n  \"hop_interval\": \"\",\n  \"up\": \"100 Mbps\",\n  \"up_mbps\": 100,\n  \"down\": \"100 Mbps\",\n  \"down_mbps\": 100,\n  \"obfs\": \"fuck me till the daylight\",\n  \"auth\": \"\",\n  \"auth_str\": \"password\",\n  \"recv_window_conn\": 0,\n  \"recv_window\": 0,\n  \"disable_mtu_discovery\": false,\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### server_ports\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n服务器端口范围列表。\n\n与 `server_port` 冲突。\n\n#### hop_interval\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n端口跳跃间隔。\n\n默认使用 `30s`。\n\n#### up, down\n\n==必填==\n\n格式： `[Integer] [Unit]` 例如： `100 Mbps, 640 KBps, 2 Gbps`\n\n支持的单位 (大小写敏感, b = bits, B = bytes, 8b=1B)：\n\n    bps (bits per second)\n    Bps (bytes per second)\n    Kbps (kilobits per second)\n    KBps (kilobytes per second)\n    Mbps (megabits per second)\n    MBps (megabytes per second)\n    Gbps (gigabits per second)\n    GBps (gigabytes per second)\n    Tbps (terabits per second)\n    TBps (terabytes per second)\n\n#### up_mbps, down_mbps\n\n==必填==\n\n以 Mbps 为单位的 `up, down`。\n\n#### obfs\n\n混淆密码。\n\n#### auth\n\nbase64 编码的认证密码。\n\n#### auth_str\n\n认证密码。\n\n#### recv_window_conn\n\n用于接收数据的 QUIC 流级流控制窗口。\n\n默认 `15728640 (15 MB/s)`。\n\n#### recv_window\n\n用于接收数据的 QUIC 连接级流控制窗口。\n\n默认 `67108864 (64 MB/s)`。\n\n#### disable_mtu_discovery\n\n禁用路径 MTU 发现 (RFC 8899)。 数据包的大小最多为 1252 (IPv4) / 1232 (IPv6) 字节。\n\n强制为 Linux 和 Windows 以外的系统启用（根据上游）。\n\n#### network\n\n启用的网络协议。\n\n`tcp` 或 `udp`。\n\n默认所有。\n\n#### tls\n\n==必填==\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/hysteria2.md",
    "content": "!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-plus: [server_ports](#server_ports)  \n    :material-plus: [hop_interval](#hop_interval)\n\n### Structure\n\n```json\n{\n  \"type\": \"hysteria2\",\n  \"tag\": \"hy2-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"server_ports\": [\n    \"2080:3000\"\n  ],\n  \"hop_interval\": \"\",\n  \"up_mbps\": 100,\n  \"down_mbps\": 100,\n  \"obfs\": {\n    \"type\": \"salamander\",\n    \"password\": \"cry_me_a_r1ver\"\n  },\n  \"password\": \"goofy_ahh_password\",\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \"brutal_debug\": false,\n  \n  ... // Dial Fields\n}\n```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n!!! warning \"Difference from official Hysteria2\"\n\n    The official Hysteria2 supports an authentication method called **userpass**,\n    which essentially uses a combination of `<username>:<password>` as the actual password,\n    while sing-box does not provide this alias.\n    If you are planning to use sing-box with the official program,\n    please note that you will need to fill the combination as the password.\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\nIgnored if `server_ports` is set.\n\n#### server_ports\n\n!!! question \"Since sing-box 1.11.0\"\n\nServer port range list.\n\nConflicts with `server_port`.\n\n#### hop_interval\n\n!!! question \"Since sing-box 1.11.0\"\n\nPort hopping interval.\n\n`30s` is used by default.\n\n#### up_mbps, down_mbps\n\nMax bandwidth, in Mbps.\n\nIf empty, the BBR congestion control algorithm will be used instead of Hysteria CC.\n\n#### obfs.type\n\nQUIC traffic obfuscator type, only available with `salamander`.\n\nDisabled if empty.\n\n#### obfs.password\n\nQUIC traffic obfuscator password.\n\n#### password\n\nAuthentication password.\n\n#### network\n\nEnabled network\n\nOne of `tcp` `udp`.\n\nBoth is enabled by default.\n\n#### tls\n\n==Required==\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n#### brutal_debug\n\nEnable debug information logging for Hysteria Brutal CC.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/hysteria2.zh.md",
    "content": "!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-plus: [server_ports](#server_ports)  \n    :material-plus: [hop_interval](#hop_interval)\n\n### 结构\n\n```json\n{\n  \"type\": \"hysteria2\",\n  \"tag\": \"hy2-out\",\n\n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"server_ports\": [\n    \"2080:3000\"\n  ],\n  \"hop_interval\": \"\",\n  \"up_mbps\": 100,\n  \"down_mbps\": 100,\n  \"obfs\": {\n    \"type\": \"salamander\",\n    \"password\": \"cry_me_a_r1ver\"\n  },\n  \"password\": \"goofy_ahh_password\",\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \"brutal_debug\": false,\n  \n  ... // 拨号字段\n}\n```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签\n\n!!! warning \"与官方 Hysteria2 的区别\"\n\n    官方程序支持一种名为 **userpass** 的验证方式，\n    本质上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码，而 sing-box 不提供此别名。\n    要将 sing-box 与官方程序一起使用， 您需要填写该组合作为实际密码。\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n如果设置了 `server_ports`，则忽略此项。\n\n#### server_ports\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n服务器端口范围列表。\n\n与 `server_port` 冲突。\n\n#### hop_interval\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n端口跳跃间隔。\n\n默认使用 `30s`。\n\n#### up_mbps, down_mbps\n\n最大带宽。\n\n如果为空，将使用 BBR 拥塞控制算法而不是 Hysteria CC。\n\n#### obfs.type\n\nQUIC 流量混淆器类型，仅可设为 `salamander`。\n\n如果为空则禁用。\n\n#### obfs.password\n\nQUIC 流量混淆器密码.\n\n#### password\n\n认证密码。\n\n#### network\n\n启用的网络协议。\n\n`tcp` 或 `udp`。\n\n默认所有。\n\n#### tls\n\n==必填==\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n#### brutal_debug\n\n启用 Hysteria Brutal CC 的调试信息日志记录。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/index.md",
    "content": "# Outbound\n\n### Structure\n\n```json\n{\n  \"outbounds\": [\n    {\n      \"type\": \"\",\n      \"tag\": \"\"\n    }\n  ]\n}\n```\n\n### Fields\n\n| Type           | Format                         |\n|----------------|--------------------------------|\n| `direct`       | [Direct](./direct/)             |\n| `block`        | [Block](./block/)               |\n| `socks`        | [SOCKS](./socks/)               |\n| `http`         | [HTTP](./http/)                 |\n| `shadowsocks`  | [Shadowsocks](./shadowsocks/)   |\n| `vmess`        | [VMess](./vmess/)               |\n| `trojan`       | [Trojan](./trojan/)             |\n| `wireguard`    | [Wireguard](./wireguard/)       |\n| `hysteria`     | [Hysteria](./hysteria/)         |\n| `vless`        | [VLESS](./vless/)               |\n| `shadowtls`    | [ShadowTLS](./shadowtls/)       |\n| `tuic`         | [TUIC](./tuic/)                 |\n| `hysteria2`    | [Hysteria2](./hysteria2/)       |\n| `anytls`       | [AnyTLS](./anytls/)             |\n| `tor`          | [Tor](./tor/)                   |\n| `ssh`          | [SSH](./ssh/)                   |\n| `dns`          | [DNS](./dns/)                   |\n| `selector`     | [Selector](./selector/)         |\n| `urltest`      | [URLTest](./urltest/)           |\n| `naive`        | [NaiveProxy](./naive/)          |\n\n#### tag\n\nThe tag of the outbound.\n\n### Features\n\n#### Outbounds that support IP connection\n\n* `WireGuard`\n"
  },
  {
    "path": "docs/configuration/outbound/index.zh.md",
    "content": "# 出站\n\n### 结构\n\n```json\n{\n  \"outbounds\": [\n    {\n      \"type\": \"\",\n      \"tag\": \"\"\n    }\n  ]\n}\n```\n\n### 字段\n\n| 类型             | 格式                             |\n|----------------|--------------------------------|\n| `direct`       | [Direct](./direct/)             |\n| `block`        | [Block](./block/)               |\n| `socks`        | [SOCKS](./socks/)               |\n| `http`         | [HTTP](./http/)                 |\n| `shadowsocks`  | [Shadowsocks](./shadowsocks/)   |\n| `vmess`        | [VMess](./vmess/)               |\n| `trojan`       | [Trojan](./trojan/)             |\n| `wireguard`    | [Wireguard](./wireguard/)       |\n| `hysteria`     | [Hysteria](./hysteria/)         |\n| `vless`        | [VLESS](./vless/)               |\n| `shadowtls`    | [ShadowTLS](./shadowtls/)       |\n| `tuic`         | [TUIC](./tuic/)                 |\n| `hysteria2`    | [Hysteria2](./hysteria2/)       |\n| `anytls`       | [AnyTLS](./anytls/)             |\n| `tor`          | [Tor](./tor/)                   |\n| `ssh`          | [SSH](./ssh/)                   |\n| `dns`          | [DNS](./dns/)                   |\n| `selector`     | [Selector](./selector/)         |\n| `urltest`      | [URLTest](./urltest/)           |\n| `naive`        | [NaiveProxy](./naive/)          |\n\n#### tag\n\n出站的标签。\n\n### 特性\n\n#### 支持 IP 连接的出站\n\n* `WireGuard`\n"
  },
  {
    "path": "docs/configuration/outbound/naive.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.13.0\"\n\n### Structure\n\n```json\n{\n  \"type\": \"naive\",\n  \"tag\": \"naive-out\",\n\n  \"server\": \"127.0.0.1\",\n  \"server_port\": 443,\n  \"username\": \"sekai\",\n  \"password\": \"password\",\n  \"insecure_concurrency\": 0,\n  \"extra_headers\": {},\n  \"udp_over_tcp\": false | {},\n  \"quic\": false,\n  \"quic_congestion_control\": \"\",\n  \"tls\": {},\n\n  ... // Dial Fields\n}\n```\n\n!!! warning \"Platform Support\"\n\n    NaiveProxy outbound is only available on Apple platforms, Android, Windows and certain Linux builds.\n\n    **Official Release Build Variants:**\n\n    | Build Variant | Platforms | Description |\n    |---------------|-----------|-------------|\n    | (no suffix) | Linux amd64/arm64 | purego build, `libcronet.so` included |\n    | `-glibc` | Linux 386/amd64/arm/arm64/mipsle/mips64le/riscv64/loong64 | CGO build, dynamically linked with glibc, requires glibc >= 2.31 (loong64: >= 2.36) |\n    | `-musl` | Linux 386/amd64/arm/arm64/mipsle/riscv64/loong64 | CGO build, statically linked with musl |\n    | (no suffix) | Windows amd64/arm64 | purego build, `libcronet.dll` included |\n\n    For Linux, choose the glibc or musl variant based on your distribution's libc type.\n\n    **Runtime Requirements:**\n\n    - **Linux purego**: `libcronet.so` must be in the same directory as the sing-box binary or in system library path\n    - **Windows**: `libcronet.dll` must be in the same directory as `sing-box.exe` or in a directory listed in `PATH`\n\n    For self-built binaries, see [Build from source](/installation/build-from-source/#with_naive_outbound).\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\n#### username\n\nAuthentication username.\n\n#### password\n\nAuthentication password.\n\n#### insecure_concurrency\n\nNumber of concurrent tunnel connections. Multiple connections make the tunneling easier to detect through traffic analysis, which defeats the purpose of NaiveProxy's design to resist traffic analysis.\n\n#### extra_headers\n\nExtra headers to send in HTTP requests.\n\n#### udp_over_tcp\n\nUDP over TCP protocol settings.\n\nSee [UDP Over TCP](/configuration/shared/udp-over-tcp/) for details.\n\n#### quic\n\nUse QUIC instead of HTTP/2.\n\n#### quic_congestion_control\n\nQUIC congestion control algorithm.\n\n| Algorithm | Description |\n|-----------|-------------|\n| `bbr` | BBR |\n| `bbr2` | BBRv2 |\n| `cubic` | CUBIC |\n| `reno` | New Reno |\n\n`bbr` is used by default (the default of QUICHE, used by Chromium which NaiveProxy is based on).\n\n#### tls\n\n==Required==\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\nOnly `server_name`, `certificate`, `certificate_path` and `ech` are supported.\n\nSelf-signed certificates change traffic behavior significantly, which defeats the purpose of NaiveProxy's design to resist traffic analysis, and should not be used in production.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/naive.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n### 结构\n\n```json\n{\n  \"type\": \"naive\",\n  \"tag\": \"naive-out\",\n\n  \"server\": \"127.0.0.1\",\n  \"server_port\": 443,\n  \"username\": \"sekai\",\n  \"password\": \"password\",\n  \"insecure_concurrency\": 0,\n  \"extra_headers\": {},\n  \"udp_over_tcp\": false | {},\n  \"quic\": false,\n  \"quic_congestion_control\": \"\",\n  \"tls\": {},\n\n  ... // 拨号字段\n}\n```\n\n!!! warning \"平台支持\"\n\n    NaiveProxy 出站仅在 Apple 平台、Android、Windows 和特定 Linux 构建上可用。\n\n    **官方发布版本区别：**\n\n    | 构建变体 | 平台 | 说明 |\n    |---|---|---|\n    | (无后缀) | Linux amd64/arm64 | purego 构建，包含 `libcronet.so` |\n    | `-glibc` | Linux 386/amd64/arm/arm64/mipsle/mips64le/riscv64/loong64 | CGO 构建，动态链接 glibc，要求 glibc >= 2.31（loong64: >= 2.36） |\n    | `-musl` | Linux 386/amd64/arm/arm64/mipsle/riscv64/loong64 | CGO 构建，静态链接 musl |\n    | (无后缀) | Windows amd64/arm64 | purego 构建，包含 `libcronet.dll` |\n\n    对于 Linux，请根据发行版的 libc 类型选择 glibc 或 musl 变体。\n\n    **运行时要求：**\n\n    - **Linux purego**：`libcronet.so` 必须位于 sing-box 二进制文件相同目录或系统库路径中\n    - **Windows**：`libcronet.dll` 必须位于 `sing-box.exe` 相同目录或 `PATH` 中的任意目录\n\n    自行构建请参阅 [从源代码构建](/zh/installation/build-from-source/#with_naive_outbound)。\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### username\n\n认证用户名。\n\n#### password\n\n认证密码。\n\n#### insecure_concurrency\n\n并发隧道连接数。多连接使隧道更容易被流量分析检测，违背 NaiveProxy 抵抗流量分析的设计目的。\n\n#### extra_headers\n\nHTTP 请求中发送的额外头部。\n\n#### udp_over_tcp\n\nUDP over TCP 配置。\n\n参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp/)。\n\n#### quic\n\n使用 QUIC 代替 HTTP/2。\n\n#### quic_congestion_control\n\nQUIC 拥塞控制算法。\n\n| 算法 | 描述 |\n|------|------|\n| `bbr` | BBR |\n| `bbr2` | BBRv2 |\n| `cubic` | CUBIC |\n| `reno` | New Reno |\n\n默认使用 `bbr`（NaiveProxy 基于的 Chromium 使用的 QUICHE 的默认值）。\n\n#### tls\n\n==必填==\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n只有 `server_name`、`certificate`、`certificate_path` 和 `ech` 是被支持的。\n\n自签名证书会显著改变流量行为，违背了 NaiveProxy 旨在抵抗流量分析的设计初衷，不应该在生产环境中使用。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/selector.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"selector\",\n  \"tag\": \"select\",\n  \n  \"outbounds\": [\n    \"proxy-a\",\n    \"proxy-b\",\n    \"proxy-c\"\n  ],\n  \"default\": \"proxy-c\",\n  \"interrupt_exist_connections\": false\n}\n```\n\n!!! quote \"\"\n\n    The selector can only be controlled through the [Clash API](/configuration/experimental#clash-api-fields) currently.\n\n### Fields\n\n#### outbounds\n\n==Required==\n\nList of outbound tags to select.\n\n#### default\n\nThe default outbound tag. The first outbound will be used if empty.\n\n#### interrupt_exist_connections\n\nInterrupt existing connections when the selected outbound has changed.\n\nOnly inbound connections are affected by this setting, internal connections will always be interrupted.\n"
  },
  {
    "path": "docs/configuration/outbound/selector.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"selector\",\n  \"tag\": \"select\",\n\n  \"outbounds\": [\n    \"proxy-a\",\n    \"proxy-b\",\n    \"proxy-c\"\n  ],\n  \"default\": \"proxy-c\",\n  \"interrupt_exist_connections\": false\n}\n```\n\n!!! quote \"\"\n\n    选择器目前只能通过 [Clash API](/zh/configuration/experimental/clash-api/) 来控制。\n\n### 字段\n\n#### outbounds\n\n==必填==\n\n用于选择的出站标签列表。\n\n#### default\n\n默认的出站标签。默认使用第一个出站。\n\n#### interrupt_exist_connections\n\n当选定的出站发生更改时，中断现有连接。\n\n仅入站连接受此设置影响，内部连接将始终被中断。"
  },
  {
    "path": "docs/configuration/outbound/shadowsocks.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"shadowsocks\",\n  \"tag\": \"ss-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"method\": \"2022-blake3-aes-128-gcm\",\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"plugin\": \"\",\n  \"plugin_opts\": \"\",\n  \"network\": \"udp\",\n  \"udp_over_tcp\": false | {},\n  \"multiplex\": {},\n\n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\n#### method\n\n==Required==\n\nEncryption methods:\n\n* `2022-blake3-aes-128-gcm`\n* `2022-blake3-aes-256-gcm`\n* `2022-blake3-chacha20-poly1305`\n* `none`\n* `aes-128-gcm`\n* `aes-192-gcm`\n* `aes-256-gcm`\n* `chacha20-ietf-poly1305`\n* `xchacha20-ietf-poly1305`\n\nLegacy encryption methods:\n\n* `aes-128-ctr`\n* `aes-192-ctr`\n* `aes-256-ctr`\n* `aes-128-cfb`\n* `aes-192-cfb`\n* `aes-256-cfb`\n* `rc4-md5`\n* `chacha20-ietf`\n* `xchacha20`\n\n#### password\n\n==Required==\n\nThe shadowsocks password.\n\n#### plugin\n\nShadowsocks SIP003 plugin, implemented in internal.\n\nOnly `obfs-local` and `v2ray-plugin` are supported.\n\n#### plugin_opts\n\nShadowsocks SIP003 plugin options.\n\n#### network\n\nEnabled network\n\nOne of `tcp` `udp`.\n\nBoth is enabled by default.\n\n#### udp_over_tcp\n\nUDP over TCP configuration.\n\nSee [UDP Over TCP](/configuration/shared/udp-over-tcp/) for details.\n\nConflict with `multiplex`.\n\n#### multiplex\n\nSee [Multiplex](/configuration/shared/multiplex#outbound) for details.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/shadowsocks.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"shadowsocks\",\n  \"tag\": \"ss-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"method\": \"2022-blake3-aes-128-gcm\",\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"plugin\": \"\",\n  \"plugin_opts\": \"\",\n  \"network\": \"udp\",\n  \"udp_over_tcp\": false | {},\n  \"multiplex\": {},\n\n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### method\n\n==必填==\n\n加密方法：\n\n* `2022-blake3-aes-128-gcm`\n* `2022-blake3-aes-256-gcm`\n* `2022-blake3-chacha20-poly1305`\n* `none`\n* `aes-128-gcm`\n* `aes-192-gcm`\n* `aes-256-gcm`\n* `chacha20-ietf-poly1305`\n* `xchacha20-ietf-poly1305`\n\n旧加密方法：\n\n* `aes-128-ctr`\n* `aes-192-ctr`\n* `aes-256-ctr`\n* `aes-128-cfb`\n* `aes-192-cfb`\n* `aes-256-cfb`\n* `rc4-md5`\n* `chacha20-ietf`\n* `xchacha20`\n\n#### password\n\n==必填==\n\nShadowsocks 密码。\n\n#### plugin\n\nShadowsocks SIP003 插件，由内部实现。\n\n仅支持 `obfs-local` 和 `v2ray-plugin`。\n\n#### plugin_opts\n\nShadowsocks SIP003 插件参数。\n\n#### network\n\n启用的网络协议\n\n`tcp` 或 `udp`。\n\n默认所有。\n\n#### udp_over_tcp\n\nUDP over TCP 配置。\n\n参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp/)。\n\n与 `multiplex` 冲突。\n\n#### multiplex\n\n参阅 [多路复用](/zh/configuration/shared/multiplex#出站)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/shadowtls.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"shadowtls\",\n  \"tag\": \"st-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"version\": 3,\n  \"password\": \"fuck me till the daylight\",\n  \"tls\": {},\n\n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\n#### version\n\nShadowTLS protocol version.\n\n| Value         | Protocol Version                                                                        |\n|---------------|-----------------------------------------------------------------------------------------|\n| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |\n| `2`           | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |\n| `3`           | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |\n\n#### password\n\nSet password.\n\nOnly available in the ShadowTLS v2/v3 protocol.\n\n#### tls\n\n==Required==\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/shadowtls.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"shadowtls\",\n  \"tag\": \"st-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"version\": 3,\n  \"password\": \"fuck me till the daylight\",\n  \"tls\": {},\n\n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### version\n\nShadowTLS 协议版本。\n\n| 值             | 协议版本                                                                                    |\n|---------------|-----------------------------------------------------------------------------------------|\n| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |\n| `2`           | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |\n| `3`           | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |\n\n#### password\n\n设置密码。\n\n仅在 ShadowTLS v2/v3 协议中可用。\n\n#### tls\n\n==必填==\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/socks.md",
    "content": "`socks` outbound is a socks4/socks4a/socks5 client.\n\n### Structure\n\n```json\n{\n  \"type\": \"socks\",\n  \"tag\": \"socks-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"version\": \"5\",\n  \"username\": \"sekai\",\n  \"password\": \"admin\",\n  \"network\": \"udp\",\n  \"udp_over_tcp\": false | {},\n\n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\n#### version\n\nThe SOCKS version, one of `4` `4a` `5`.\n\nSOCKS5 used by default.\n\n#### username\n\nSOCKS username.\n\n#### password\n\nSOCKS5 password.\n\n#### network\n\nEnabled network\n\nOne of `tcp` `udp`.\n\nBoth is enabled by default.\n\n#### udp_over_tcp\n\nUDP over TCP protocol settings.\n\nSee [UDP Over TCP](/configuration/shared/udp-over-tcp/) for details.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/socks.zh.md",
    "content": "`socks` 出站是 socks4/socks4a/socks5 客户端\n\n### 结构\n\n```json\n{\n  \"type\": \"socks\",\n  \"tag\": \"socks-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"version\": \"5\",\n  \"username\": \"sekai\",\n  \"password\": \"admin\",\n  \"network\": \"udp\",\n  \"udp_over_tcp\": false | {},\n\n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### version\n\nSOCKS 版本, 可为 `4` `4a` `5`.\n\n默认使用 SOCKS5。\n\n#### username\n\nSOCKS 用户名。\n\n#### password\n\nSOCKS5 密码。\n\n#### network\n\n启用的网络协议\n\n`tcp` 或 `udp`。\n\n默认所有。\n\n#### udp_over_tcp\n\nUDP over TCP 配置。\n\n参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp/)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/ssh.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"ssh\",\n  \"tag\": \"ssh-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 22,\n  \"user\": \"root\",\n  \"password\": \"admin\",\n  \"private_key\": \"\",\n  \"private_key_path\": \"$HOME/.ssh/id_rsa\",\n  \"private_key_passphrase\": \"\",\n  \"host_key\": [\n    \"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH...\"\n  ],\n  \"host_key_algorithms\": [],\n  \"client_version\": \"SSH-2.0-OpenSSH_7.4p1\",\n\n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required==\n\nServer address.\n\n#### server_port\n\nServer port. 22 will be used if empty.\n\n#### user\n\nSSH user, root will be used if empty.\n\n#### password\n\nPassword.\n\n#### private_key\n\nPrivate key.\n\n#### private_key_path\n\nPrivate key path.\n\n#### private_key_passphrase\n\nPrivate key passphrase.\n\n#### host_key\n\nHost key. Accept any if empty.\n\n#### host_key_algorithms\n\nHost key algorithms.\n\n#### client_version\n\nClient version. Random version will be used if empty.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/ssh.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"ssh\",\n  \"tag\": \"ssh-out\",\n\n  \"server\": \"127.0.0.1\",\n  \"server_port\": 22,\n  \"user\": \"root\",\n  \"password\": \"admin\",\n  \"private_key\": \"\",\n  \"private_key_path\": \"$HOME/.ssh/id_rsa\",\n  \"private_key_passphrase\": \"\",\n  \"host_key\": [\n    \"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH...\"\n  ],\n  \"host_key_algorithms\": [],\n  \"client_version\": \"SSH-2.0-OpenSSH_7.4p1\",\n\n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n服务器端口，默认使用 22。\n\n#### user\n\nSSH 用户, 默认使用 root。\n\n#### password\n\n密码。\n\n#### private_key\n\n密钥。\n\n#### private_key_path\n\n密钥路径。\n\n#### private_key_passphrase\n\n密钥密码。\n\n#### host_key\n\n主机密钥，留空接受所有。\n\n#### host_key_algorithms\n\n主机密钥算法。\n\n#### client_version\n\n客户端版本，默认使用随机值。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/tor.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"tor\",\n  \"tag\": \"tor-out\",\n  \n  \"executable_path\": \"/usr/bin/tor\",\n  \"extra_args\": [],\n  \"data_directory\": \"$HOME/.cache/tor\",\n  \"torrc\": {\n    \"ClientOnly\": 1\n  },\n\n  ... // Dial Fields\n}\n```\n\n!!! info \"\"\n\n    Embedded Tor is not included by default, see [Installation](/installation/build-from-source/#build-tags).\n\n### Fields\n\n#### executable_path\n\nThe path to the Tor executable.\n\nEmbedded Tor will be ignored if set.\n\n#### extra_args\n\nList of extra arguments passed to the Tor instance when started.\n\n#### data_directory\n\n==Recommended==\n\nThe data directory of Tor.\n\nEach start will be very slow if not specified.\n\n#### torrc\n\nMap of torrc options.\n\nSee [tor(1)](https://linux.die.net/man/1/tor) for details.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/tor.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"tor\",\n  \"tag\": \"tor-out\",\n\n  \"executable_path\": \"/usr/bin/tor\",\n  \"extra_args\": [],\n  \"data_directory\": \"$HOME/.cache/tor\",\n  \"torrc\": {\n    \"ClientOnly\": 1\n  },\n\n  ... // 拨号字段\n}\n```\n\n!!! info \"\"\n\n    默认安装不包含嵌入式 Tor, 参阅 [安装](/zh/installation/build-from-source/#构建标记)。\n\n### 字段\n\n#### executable_path\n\nTor 可执行文件路径\n\n如果设置，将覆盖嵌入式 Tor。\n\n#### extra_args\n\n启动 Tor 时传递的附加参数列表。\n\n#### data_directory\n\n==推荐==\n\nTor 的数据目录。\n\n如未设置，每次启动都需要长时间。\n\n#### torrc\n\ntorrc 参数表。\n\n参阅 [tor(1)](https://linux.die.net/man/1/tor)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/trojan.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"trojan\",\n  \"tag\": \"trojan-out\",\n\n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \"multiplex\": {},\n  \"transport\": {},\n\n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\n#### password\n\n==Required==\n\nThe Trojan password.\n\n#### network\n\nEnabled network\n\nOne of `tcp` `udp`.\n\nBoth is enabled by default.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n#### multiplex\n\nSee [Multiplex](/configuration/shared/multiplex#outbound) for details.\n\n#### transport\n\nV2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport/).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/trojan.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"trojan\",\n  \"tag\": \"trojan-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"password\": \"8JCsPssfgS8tiRwiMlhARg==\",\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \"multiplex\": {},\n  \"transport\": {},\n\n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### password\n\n==必填==\n\nTrojan 密码。\n\n#### network\n\n启用的网络协议。\n\n`tcp` 或 `udp`。\n\n默认所有。\n\n#### tls\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n#### multiplex\n\n参阅 [多路复用](/zh/configuration/shared/multiplex#出站)。\n\n#### transport\n\nV2Ray 传输配置，参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport/)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/tuic.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"tuic\",\n  \"tag\": \"tuic-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"uuid\": \"2DD61D93-75D8-4DA4-AC0E-6AECE7EAC365\",\n  \"password\": \"hello\",\n  \"congestion_control\": \"cubic\",\n  \"udp_relay_mode\": \"native\",\n  \"udp_over_stream\": false,\n  \"zero_rtt_handshake\": false,\n  \"heartbeat\": \"10s\",\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\n#### uuid\n\n==Required==\n\nTUIC user uuid\n\n#### password\n\nTUIC user password\n\n#### congestion_control\n\nQUIC congestion control algorithm\n\nOne of: `cubic`, `new_reno`, `bbr`\n\n`cubic` is used by default.\n\n#### udp_relay_mode\n\nUDP packet relay mode\n\n| Mode   | Description                                                              |\n|:-------|:-------------------------------------------------------------------------|\n| native | native UDP characteristics                                               |\n| quic   | lossless UDP relay using QUIC streams, additional overhead is introduced |\n\n`native` is used by default.\n\nConflict with `udp_over_stream`.\n\n#### udp_over_stream\n\nThis is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp/), designed to provide a QUIC\nstream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or\nanother program compatible with the protocol as a server.\n\nThis mode has no positive effect in a proper UDP proxy scenario and should only be applied to relay streaming UDP\ntraffic (basically QUIC streams).\n\nConflict with `udp_relay_mode`.\n\n#### network\n\nEnabled network\n\nOne of `tcp` `udp`.\n\nBoth is enabled by default.\n\n#### tls\n\n==Required==\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/tuic.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"tuic\",\n  \"tag\": \"tuic-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"uuid\": \"2DD61D93-75D8-4DA4-AC0E-6AECE7EAC365\",\n  \"password\": \"hello\",\n  \"congestion_control\": \"cubic\",\n  \"udp_relay_mode\": \"native\",\n  \"udp_over_stream\": false,\n  \"zero_rtt_handshake\": false,\n  \"heartbeat\": \"10s\",\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### uuid\n\n==必填==\n\nTUIC 用户 UUID\n\n#### password\n\nTUIC 用户密码\n\n#### congestion_control\n\nQUIC 拥塞控制算法\n\n可选值: `cubic`, `new_reno`, `bbr`\n\n默认使用 `cubic`。\n\n#### udp_relay_mode\n\nUDP 包中继模式\n\n| 模式     | 描述                           |\n|--------|------------------------------|\n| native | 原生 UDP                       |\n| quic   | 使用 QUIC 流的无损 UDP 中继，引入了额外的开销 |\n\n与 `udp_over_stream` 冲突。\n\n#### udp_over_stream\n\n这是 TUIC 的 [UDP over TCP 协议](/zh/configuration/shared/udp-over-tcp/) 移植， 旨在提供 TUIC 不提供的 基于 QUIC 流的 UDP 中继模式。 由于它是一个附加协议，因此您需要使用 sing-box 或其他兼容的程序作为服务器。\n\n此模式在正确的 UDP 代理场景中没有任何积极作用，仅适用于中继流式 UDP 流量（基本上是 QUIC 流）。\n\n与 `udp_relay_mode` 冲突。\n\n#### zero_rtt_handshake\n\n在客户端启用 0-RTT QUIC 连接握手\n这对性能影响不大，因为协议是完全复用的\n\n!!! warning \"\"\n强烈建议禁用此功能，因为它容易受到重放攻击。\n请参阅 [Attack of the clones](https://blog.cloudflare.com/even-faster-connection-establishment-with-quic-0-rtt-resumption/#attack-of-the-clones)\n\n#### heartbeat\n\n发送心跳包以保持连接存活的时间间隔\n\n#### network\n\n启用的网络协议。\n\n`tcp` 或 `udp`。\n\n默认所有。\n\n#### tls\n\n==必填==\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/urltest.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"urltest\",\n  \"tag\": \"auto\",\n  \n  \"outbounds\": [\n    \"proxy-a\",\n    \"proxy-b\",\n    \"proxy-c\"\n  ],\n  \"url\": \"\",\n  \"interval\": \"\",\n  \"tolerance\": 0,\n  \"idle_timeout\": \"\",\n  \"interrupt_exist_connections\": false\n}\n```\n\n### Fields\n\n#### outbounds\n\n==Required==\n\nList of outbound tags to test.\n\n#### url\n\nThe URL to test. `https://www.gstatic.com/generate_204` will be used if empty.\n\n#### interval\n\nThe test interval. `3m` will be used if empty.\n\n#### tolerance\n\nThe test tolerance in milliseconds. `50` will be used if empty.\n\n#### idle_timeout\n\nThe idle timeout. `30m` will be used if empty.\n\n#### interrupt_exist_connections\n\nInterrupt existing connections when the selected outbound has changed.\n\nOnly inbound connections are affected by this setting, internal connections will always be interrupted.\n"
  },
  {
    "path": "docs/configuration/outbound/urltest.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"urltest\",\n  \"tag\": \"auto\",\n  \n  \"outbounds\": [\n    \"proxy-a\",\n    \"proxy-b\",\n    \"proxy-c\"\n  ],\n  \"url\": \"\",\n  \"interval\": \"\",\n  \"tolerance\": 50,\n  \"idle_timeout\": \"\",\n  \"interrupt_exist_connections\": false\n}\n```\n\n### 字段\n\n#### outbounds\n\n==必填==\n\n用于测试的出站标签列表。\n\n#### url\n\n用于测试的链接。默认使用 `https://www.gstatic.com/generate_204`。\n\n#### interval\n\n测试间隔。 默认使用 `3m`。\n\n#### tolerance\n\n以毫秒为单位的测试容差。 默认使用 `50`。\n\n#### idle_timeout\n\n空闲超时。默认使用 `30m`。\n\n#### interrupt_exist_connections\n\n当选定的出站发生更改时，中断现有连接。\n\n仅入站连接受此设置影响，内部连接将始终被中断。"
  },
  {
    "path": "docs/configuration/outbound/vless.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"vless\",\n  \"tag\": \"vless-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"uuid\": \"bf000d23-0752-40b4-affe-68f7707a9661\",\n  \"flow\": \"xtls-rprx-vision\",\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \"packet_encoding\": \"\",\n  \"multiplex\": {},\n  \"transport\": {},\n\n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\n#### uuid\n\n==Required==\n\nVLESS user id.\n\n#### flow\n\nVLESS Sub-protocol.\n\nAvailable values:\n\n* `xtls-rprx-vision`\n\n#### network\n\nEnabled network\n\nOne of `tcp` `udp`.\n\nBoth is enabled by default.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n#### packet_encoding\n\nUDP packet encoding, xudp is used by default.\n\n| Encoding   | Description           |\n|------------|-----------------------|\n| (none)     | Disabled              |\n| packetaddr | Supported by v2ray 5+ |\n| xudp       | Supported by xray     |\n\n#### multiplex\n\nSee [Multiplex](/configuration/shared/multiplex#outbound) for details.\n\n#### transport\n\nV2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport/).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/vless.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"vless\",\n  \"tag\": \"vless-out\",\n\n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"uuid\": \"bf000d23-0752-40b4-affe-68f7707a9661\",\n  \"flow\": \"xtls-rprx-vision\",\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \"packet_encoding\": \"\",\n  \"multiplex\": {},\n  \"transport\": {},\n  \n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### uuid\n\n==必填==\n\nVLESS 用户 ID。\n\n#### flow\n\nVLESS 子协议。\n\n可用值：\n\n* `xtls-rprx-vision`\n\n#### network\n\n启用的网络协议。\n\n`tcp` 或 `udp`。\n\n默认所有。\n\n#### tls\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n#### packet_encoding\n\nUDP 包编码，默认使用 xudp。\n\n| 编码         | 描述            |\n|------------|---------------|\n| (空)        | 禁用            |\n| packetaddr | 由 v2ray 5+ 支持 |\n| xudp       | 由 xray 支持     |\n\n#### multiplex\n\n参阅 [多路复用](/zh/configuration/shared/multiplex#出站)。\n\n#### transport\n\nV2Ray 传输配置，参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport/)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/vmess.md",
    "content": "### Structure\n\n```json\n{\n  \"type\": \"vmess\",\n  \"tag\": \"vmess-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"uuid\": \"bf000d23-0752-40b4-affe-68f7707a9661\",\n  \"security\": \"auto\",\n  \"alter_id\": 0,\n  \"global_padding\": false,\n  \"authenticated_length\": true,\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \"packet_encoding\": \"\",\n  \"transport\": {},\n  \"multiplex\": {},\n\n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required==\n\nThe server address.\n\n#### server_port\n\n==Required==\n\nThe server port.\n\n#### uuid\n\n==Required==\n\nThe VMess user id.\n\n#### security\n\nEncryption methods:\n\n* `auto`\n* `none`\n* `zero`\n* `aes-128-gcm`\n* `chacha20-poly1305`\n\nLegacy encryption methods:\n\n* `aes-128-ctr`\n\n#### alter_id\n\n| Alter ID | Description         |\n|----------|---------------------|\n| 0        | Use AEAD protocol   |\n| 1        | Use legacy protocol |\n| > 1      | Unused, same as 1   |\n\n#### global_padding\n\nProtocol parameter. Will waste traffic randomly if enabled (enabled by default in v2ray and cannot be disabled).\n\n#### authenticated_length\n\nProtocol parameter. Enable length block encryption.\n\n#### network\n\nEnabled network\n\nOne of `tcp` `udp`.\n\nBoth is enabled by default.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#outbound).\n\n#### packet_encoding\n\nUDP packet encoding.\n\n| Encoding   | Description           |\n|------------|-----------------------|\n| (none)     | Disabled              |\n| packetaddr | Supported by v2ray 5+ |\n| xudp       | Supported by xray     |\n\n#### multiplex\n\nSee [Multiplex](/configuration/shared/multiplex#outbound) for details.\n\n#### transport\n\nV2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport/).\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/vmess.zh.md",
    "content": "### 结构\n\n```json\n{\n  \"type\": \"vmess\",\n  \"tag\": \"vmess-out\",\n\n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"uuid\": \"bf000d23-0752-40b4-affe-68f7707a9661\",\n  \"security\": \"auto\",\n  \"alter_id\": 0,\n  \"global_padding\": false,\n  \"authenticated_length\": true,\n  \"network\": \"tcp\",\n  \"tls\": {},\n  \"packet_encoding\": \"\",\n  \"multiplex\": {},\n  \"transport\": {},\n\n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### uuid\n\n==必填==\n\nVMess 用户 ID。\n\n#### security\n\n加密方法：\n\n* `auto`\n* `none`\n* `zero`\n* `aes-128-gcm`\n* `chacha20-poly1305`\n\n旧加密方法：\n\n* `aes-128-ctr`\n\n#### alter_id\n\n| Alter ID | 描述         |\n|----------|------------|\n| 0        | 禁用旧协议      |\n| 1        | 启用旧协议      |\n| > 1      | 未使用, 行为同 1 |\n\n#### global_padding\n\n协议参数。 如果启用会随机浪费流量（在 v2ray 中默认启用并且无法禁用）。\n\n#### authenticated_length\n\n协议参数。启用长度块加密。\n\n#### network\n\n启用的网络协议。\n\n`tcp` 或 `udp`。\n\n默认所有。\n\n#### tls\n\nTLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。\n\n#### packet_encoding\n\nUDP 包编码。\n\n| 编码         | 描述            |\n|------------|---------------|\n| (空)        | 禁用            |\n| packetaddr | 由 v2ray 5+ 支持 |\n| xudp       | 由 xray 支持     |\n\n#### multiplex\n\n参阅 [多路复用](/zh/configuration/shared/multiplex#出站)。\n\n#### transport\n\nV2Ray 传输配置，参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport/)。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/outbound/wireguard.md",
    "content": "---\nicon: material/delete-clock\n---\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    WireGuard outbound is deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-wireguard-outbound-to-endpoint).\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-delete-alert: [gso](#gso)\n\n!!! quote \"Changes in sing-box 1.8.0\"\n    \n    :material-plus: [gso](#gso)\n\n### Structure\n\n```json\n{\n  \"type\": \"wireguard\",\n  \"tag\": \"wireguard-out\",\n  \n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"system_interface\": false,\n  \"interface_name\": \"wg0\",\n  \"local_address\": [\n    \"10.0.0.1/32\"\n  ],\n  \"private_key\": \"YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=\",\n  \"peers\": [\n    {\n      \"server\": \"127.0.0.1\",\n      \"server_port\": 1080,\n      \"public_key\": \"Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=\",\n      \"pre_shared_key\": \"31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=\",\n      \"allowed_ips\": [\n        \"0.0.0.0/0\"\n      ],\n      \"reserved\": [0, 0, 0]\n    }\n  ],\n  \"peer_public_key\": \"Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=\",\n  \"pre_shared_key\": \"31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=\",\n  \"reserved\": [0, 0, 0],\n  \"workers\": 4,\n  \"mtu\": 1408,\n  \"network\": \"tcp\",\n\n  // Deprecated\n  \n  \"gso\": false,\n\n  ... // Dial Fields\n}\n```\n\n### Fields\n\n#### server\n\n==Required if multi-peer disabled==\n\nThe server address.\n\n#### server_port\n\n==Required if multi-peer disabled==\n\nThe server port.\n\n#### system_interface\n\nUse system interface.\n\nRequires privilege and cannot conflict with exists system interfaces.\n\nForced if gVisor not included in the build.\n\n#### interface_name\n\nCustom interface name for system interface.\n\n#### gso\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    GSO will be automatically enabled when available since sing-box 1.11.0.\n\n!!! question \"Since sing-box 1.8.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux.\n\nTry to enable generic segmentation offload.\n\n#### local_address\n\n==Required==\n\nList of IP (v4 or v6) address prefixes to be assigned to the interface.\n\n#### private_key\n\n==Required==\n\nWireGuard requires base64-encoded public and private keys. These can be generated using the wg(8) utility:\n\n```shell\nwg genkey\necho \"private key\" || wg pubkey\n```\n\n#### peers\n\nMulti-peer support. \n\nIf enabled, `server, server_port, peer_public_key, pre_shared_key` will be ignored.\n\n#### peers.allowed_ips\n\nWireGuard allowed IPs.\n\n#### peers.reserved\n\nWireGuard reserved field bytes.\n\n`$outbound.reserved` will be used if empty.\n\n#### peer_public_key\n\n==Required if multi-peer disabled==\n\nWireGuard peer public key.\n\n#### pre_shared_key\n\nWireGuard pre-shared key.\n\n#### reserved\n\nWireGuard reserved field bytes.\n\n#### workers\n\nWireGuard worker count.\n\nCPU count is used by default.\n\n#### mtu\n\nWireGuard MTU.\n\n1408 will be used if empty.\n\n#### network\n\nEnabled network\n\nOne of `tcp` `udp`.\n\nBoth is enabled by default.\n\n### Dial Fields\n\nSee [Dial Fields](/configuration/shared/dial/) for details.\n"
  },
  {
    "path": "docs/configuration/outbound/wireguard.zh.md",
    "content": "---\nicon: material/delete-clock\n---\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    WireGuard 出站已被弃用，且将在 sing-box 1.13.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移-wireguard-出站到端点)。\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-delete-alert: [gso](#gso)\n\n!!! quote \"sing-box 1.8.0 中的更改\"\n\n    :material-plus: [gso](#gso)\n\n### 结构\n\n```json\n{\n  \"type\": \"wireguard\",\n  \"tag\": \"wireguard-out\",\n\n  \"server\": \"127.0.0.1\",\n  \"server_port\": 1080,\n  \"system_interface\": false,\n  \"interface_name\": \"wg0\",\n  \"local_address\": [\n    \"10.0.0.1/32\"\n  ],\n  \"private_key\": \"YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=\",\n  \"peer_public_key\": \"Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=\",\n  \"pre_shared_key\": \"31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=\",\n  \"reserved\": [0, 0, 0],\n  \"workers\": 4,\n  \"mtu\": 1408,\n  \"network\": \"tcp\",\n  \n  // 废弃的\n  \n  \"gso\": false,\n\n  ... // 拨号字段\n}\n```\n\n### 字段\n\n#### server\n\n==必填==\n\n服务器地址。\n\n#### server_port\n\n==必填==\n\n服务器端口。\n\n#### system_interface\n\n使用系统设备。\n\n需要特权且不能与已有系统接口冲突。\n\n如果 gVisor 未包含在构建中，则强制执行。\n\n#### interface_name\n\n为系统接口自定义设备名称。\n\n#### gso\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    自 sing-box 1.11.0 起，GSO 将可用时自动启用。\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux。\n\n尝试启用通用分段卸载。\n\n#### local_address\n\n==必填==\n\n接口的 IPv4/IPv6 地址或地址段的列表。\n\n要分配给接口的 IP（v4 或 v6）地址段列表。\n\n#### private_key\n\n==必填==\n\nWireGuard 需要 base64 编码的公钥和私钥。 这些可以使用 wg(8) 实用程序生成：\n\n```shell\nwg genkey\necho \"private key\" || wg pubkey\n```\n\n#### peer_public_key\n\n==必填==\n\nWireGuard 对等公钥。\n\n#### pre_shared_key\n\nWireGuard 预共享密钥。\n\n#### reserved\n\nWireGuard 保留字段字节。\n\n#### workers\n\nWireGuard worker 数量。\n\n默认使用 CPU 数量。\n\n#### mtu\n\nWireGuard MTU。\n\n默认使用 1408。\n\n#### network\n\n启用的网络协议\n\n`tcp` 或 `udp`。\n\n默认所有。\n\n### 拨号字段\n\n参阅 [拨号字段](/zh/configuration/shared/dial/)。\n"
  },
  {
    "path": "docs/configuration/route/geoip.md",
    "content": "---\nicon: material/note-remove\n---\n\n!!! failure \"Removed in sing-box 1.12.0\"\n\n    GeoIP is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).\n\n### Structure\n\n```json\n{\n  \"route\": {\n    \"geoip\": {\n      \"path\": \"\",\n      \"download_url\": \"\",\n      \"download_detour\": \"\"\n    }\n  }\n}\n```\n\n### Fields\n\n#### path\n\nThe path to the sing-geoip database.\n\n`geoip.db` will be used if empty.\n\n#### download_url\n\nThe download URL of the sing-geoip database.\n\nDefault is `https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db`.\n\n#### download_detour\n\nThe tag of the outbound to download the database.\n\nDefault outbound will be used if empty."
  },
  {
    "path": "docs/configuration/route/geoip.zh.md",
    "content": "---\nicon: material/note-remove\n---\n\n!!! failure \"已在 sing-box 1.12.0 中被移除\"\n\n    GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。\n\n### 结构\n\n```json\n{\n  \"route\": {\n    \"geoip\": {\n      \"path\": \"\",\n      \"download_url\": \"\",\n      \"download_detour\": \"\"\n    }\n  }\n}\n```\n\n### 字段\n\n#### path\n\n指定 GeoIP 资源的路径。\n\n默认 `geoip.db`。\n\n#### download_url\n\n指定 GeoIP 资源的下载链接。\n\n默认为 `https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db`。\n\n#### download_detour\n\n用于下载 GeoIP 资源的出站的标签。\n\n如果为空，将使用默认出站。"
  },
  {
    "path": "docs/configuration/route/geosite.md",
    "content": "---\nicon: material/note-remove\n---\n\n!!! failure \"Removed in sing-box 1.12.0\"\n\n    Geosite is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).\n\n### Structure\n\n```json\n{\n  \"route\": {\n    \"geosite\": {\n      \"path\": \"\",\n      \"download_url\": \"\",\n      \"download_detour\": \"\"\n    }\n  }\n}\n```\n\n### Fields\n\n#### path\n\nThe path to the sing-geosite database.\n\n`geosite.db` will be used if empty.\n\n#### download_url\n\nThe download URL of the sing-geoip database.\n\nDefault is `https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db`.\n\n#### download_detour\n\nThe tag of the outbound to download the database.\n\nDefault outbound will be used if empty."
  },
  {
    "path": "docs/configuration/route/geosite.zh.md",
    "content": "---\nicon: material/note-remove\n---\n\n!!! failure \"已在 sing-box 1.12.0 中被移除\"\n\n    Geosite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移-geosite-到规则集)。\n\n### 结构\n\n```json\n{\n  \"route\": {\n    \"geosite\": {\n      \"path\": \"\",\n      \"download_url\": \"\",\n      \"download_detour\": \"\"\n    }\n  }\n}\n```\n\n### 字段\n\n#### path\n\n指定 GeoSite 资源的路径。\n\n默认 `geosite.db`。\n\n#### download_url\n\n指定 GeoSite 资源的下载链接。\n\n默认为 `https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db`。\n\n#### download_detour\n\n用于下载 GeoSite 资源的出站的标签。\n\n如果为空，将使用默认出站。"
  },
  {
    "path": "docs/configuration/route/index.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n# Route\n\n!!! quote \"Changes in sing-box 1.14.0\"\n\n    :material-plus: [find_neighbor](#find_neighbor)  \n    :material-plus: [dhcp_lease_files](#dhcp_lease_files)\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-plus: [default_domain_resolver](#default_domain_resolver)  \n    :material-note-remove: [geoip](#geoip)  \n    :material-note-remove: [geosite](#geosite)\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-plus: [default_network_strategy](#default_network_strategy)  \n    :material-plus: [default_network_type](#default_network_type)  \n    :material-plus: [default_fallback_network_type](#default_fallback_network_type)  \n    :material-plus: [default_fallback_delay](#default_fallback_delay)\n\n!!! quote \"Changes in sing-box 1.8.0\"\n\n    :material-plus: [rule_set](#rule_set)  \n    :material-delete-clock: [geoip](#geoip)  \n    :material-delete-clock: [geosite](#geosite)\n\n### Structure\n\n```json\n{\n  \"route\": {\n    \"rules\": [],\n    \"rule_set\": [],\n    \"final\": \"\",\n    \"auto_detect_interface\": false,\n    \"override_android_vpn\": false,\n    \"default_interface\": \"\",\n    \"default_mark\": 0,\n    \"find_process\": false,\n    \"find_neighbor\": false,\n    \"dhcp_lease_files\": [],\n    \"default_domain_resolver\": \"\", // or {}\n    \"default_network_strategy\": \"\",\n    \"default_network_type\": [],\n    \"default_fallback_network_type\": [],\n    \"default_fallback_delay\": \"\",\n    \n    // Removed\n\n    \"geoip\": {},\n    \"geosite\": {}\n  }\n}\n```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n### Fields\n\n#### rules\n\nList of [Route Rule](./rule/)\n\n#### rule_set\n\n!!! question \"Since sing-box 1.8.0\"\n\nList of [rule-set](/configuration/rule-set/)\n\n#### final\n\nDefault outbound tag. the first outbound will be used if empty.\n\n#### auto_detect_interface\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows and macOS.\n\nBind outbound connections to the default NIC by default to prevent routing loops under tun.\n\nTakes no effect if `outbound.bind_interface` is set.\n\n#### override_android_vpn\n\n!!! quote \"\"\n\n    Only supported on Android.\n\nAccept Android VPN as upstream NIC when `auto_detect_interface` enabled.\n\n#### default_interface\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows and macOS.\n\nBind outbound connections to the specified NIC by default to prevent routing loops under tun.\n\nTakes no effect if `auto_detect_interface` is set.\n\n#### default_mark\n\n!!! quote \"\"\n\n    Only supported on Linux.\n\nSet routing mark by default.\n\nTakes no effect if `outbound.routing_mark` is set.\n\n#### find_process\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nEnable process search for logging when no `process_name`, `process_path`, `package_name`, `user` or `user_id` rules exist.\n\n#### find_neighbor\n\n!!! question \"Since sing-box 1.14.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux and macOS.\n\nEnable neighbor resolution for logging when no `source_mac_address` or `source_hostname` rules exist.\n\nSee [Neighbor Resolution](/configuration/shared/neighbor/) for setup.\n\n#### dhcp_lease_files\n\n!!! question \"Since sing-box 1.14.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux and macOS.\n\nCustom DHCP lease file paths for hostname and MAC address resolution.\n\nAutomatically detected from common DHCP servers (dnsmasq, odhcpd, ISC dhcpd, Kea) if empty.\n\n#### default_domain_resolver\n\n!!! question \"Since sing-box 1.12.0\"\n\nSee [Dial Fields](/configuration/shared/dial/#domain_resolver) for details.\n\nCan be overrides by `outbound.domain_resolver`.\n\n#### default_network_strategy\n\n!!! question \"Since sing-box 1.11.0\"\n\nSee [Dial Fields](/configuration/shared/dial/#network_strategy) for details.\n\nTakes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set.\n\nCan be overrides by `outbound.network_strategy`.\n\nConflicts with `default_interface`.\n\n#### default_network_type\n\n!!! question \"Since sing-box 1.11.0\"\n\nSee [Dial Fields](/configuration/shared/dial/#network_type) for details.\n\n#### default_fallback_network_type\n\n!!! question \"Since sing-box 1.11.0\"\n\nSee [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details.\n\n#### default_fallback_delay\n\n!!! question \"Since sing-box 1.11.0\"\n\nSee [Dial Fields](/configuration/shared/dial/#fallback_delay) for details.\n"
  },
  {
    "path": "docs/configuration/route/index.zh.md",
    "content": "---\nicon: material/alert-decagram\n---\n\n# 路由\n\n!!! quote \"sing-box 1.14.0 中的更改\"\n\n    :material-plus: [find_neighbor](#find_neighbor)  \n    :material-plus: [dhcp_lease_files](#dhcp_lease_files)\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-plus: [default_domain_resolver](#default_domain_resolver)  \n    :material-note-remove: [geoip](#geoip)  \n    :material-note-remove: [geosite](#geosite)\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-plus: [default_network_strategy](#default_network_strategy)  \n    :material-plus: [default_network_type](#default_network_type)  \n    :material-plus: [default_fallback_network_type](#default_fallback_network_type)  \n    :material-plus: [default_fallback_delay](#default_fallback_delay)\n\n!!! quote \"sing-box 1.8.0 中的更改\"\n\n    :material-plus: [rule_set](#rule_set)  \n    :material-delete-clock: [geoip](#geoip)  \n    :material-delete-clock: [geosite](#geosite)\n\n### 结构\n\n```json\n{\n  \"route\": {\n    \"geoip\": {},\n    \"geosite\": {},\n    \"rules\": [],\n    \"rule_set\": [],\n    \"final\": \"\",\n    \"auto_detect_interface\": false,\n    \"override_android_vpn\": false,\n    \"default_interface\": \"\",\n    \"default_mark\": 0,\n    \"find_process\": false,\n    \"find_neighbor\": false,\n    \"dhcp_lease_files\": [],\n    \"default_network_strategy\": \"\",\n    \"default_fallback_delay\": \"\"\n  }\n}\n```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签\n\n### 字段\n\n| 键         | 格式                    |\n|-----------|-----------------------|\n| `geoip`   | [GeoIP](./geoip/)     |\n| `geosite` | [Geosite](./geosite/) |\n\n#### rule\n\n一组 [路由规则](./rule/)    。\n\n#### rule_set\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n一组 [规则集](/zh/configuration/rule-set/)。\n\n#### final\n\n默认出站标签。如果为空，将使用第一个可用于对应协议的出站。\n\n#### auto_detect_interface\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS。\n\n默认将出站连接绑定到默认网卡，以防止在 tun 下出现路由环路。\n\n如果设置了 `outbound.bind_interface` 设置，则不生效。\n\n#### override_android_vpn\n\n!!! quote \"\"\n\n    仅支持 Android。\n\n启用 `auto_detect_interface` 时接受 Android VPN 作为上游网卡。\n\n#### default_interface\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS。\n\n默认将出站连接绑定到指定网卡，以防止在 tun 下出现路由环路。\n\n如果设置了 `auto_detect_interface` 设置，则不生效。\n\n#### default_mark\n\n!!! quote \"\"\n\n    仅支持 Linux。\n\n默认为出站连接设置路由标记。\n\n如果设置了 `outbound.routing_mark` 设置，则不生效。\n\n#### find_process\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS。\n\n在没有 `process_name`、`process_path`、`package_name`、`user` 或 `user_id` 规则时启用进程搜索以输出日志。\n\n#### find_neighbor\n\n!!! question \"自 sing-box 1.14.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux 和 macOS。\n\n在没有 `source_mac_address` 或 `source_hostname` 规则时启用邻居解析以输出日志。\n\n参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。\n\n#### dhcp_lease_files\n\n!!! question \"自 sing-box 1.14.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux 和 macOS。\n\n用于主机名和 MAC 地址解析的自定义 DHCP 租约文件路径。\n\n为空时自动从常见 DHCP 服务器（dnsmasq、odhcpd、ISC dhcpd、Kea）检测。\n\n#### default_domain_resolver\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n详情参阅 [拨号字段](/zh/configuration/shared/dial/#domain_resolver)。\n\n可以被 `outbound.domain_resolver` 覆盖。\n\n#### network_strategy\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n详情参阅 [拨号字段](/zh/configuration/shared/dial/#network_strategy)。\n\n当 `outbound.bind_interface`, `outbound.inet4_bind_address` 或 `outbound.inet6_bind_address` 已设置时不生效。\n\n可以被 `outbound.network_strategy` 覆盖。\n\n与 `default_interface` 冲突。\n\n#### default_network_type\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n详情参阅 [拨号字段](/zh/configuration/shared/dial/#default_network_type)。\n\n#### default_fallback_network_type\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n详情参阅 [拨号字段](/zh/configuration/shared/dial/#default_fallback_network_type)。\n\n#### default_fallback_delay\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n详情参阅 [拨号字段](/zh/configuration/shared/dial/#fallback_delay)。\n"
  },
  {
    "path": "docs/configuration/route/rule.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.14.0\"\n\n    :material-plus: [source_mac_address](#source_mac_address)  \n    :material-plus: [source_hostname](#source_hostname)\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [interface_address](#interface_address)  \n    :material-plus: [network_interface_address](#network_interface_address)  \n    :material-plus: [default_interface_address](#default_interface_address)  \n    :material-plus: [preferred_by](#preferred_by)  \n    :material-alert: [network](#network)\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-plus: [action](#action)  \n    :material-alert: [outbound](#outbound)  \n    :material-plus: [network_type](#network_type)  \n    :material-plus: [network_is_expensive](#network_is_expensive)  \n    :material-plus: [network_is_constrained](#network_is_constrained)\n\n!!! quote \"Changes in sing-box 1.10.0\"\n\n    :material-plus: [client](#client)  \n    :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)  \n    :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)  \n    :material-plus: [process_path_regex](#process_path_regex)\n\n!!! quote \"Changes in sing-box 1.8.0\"\n\n    :material-plus: [rule_set](#rule_set)  \n    :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)  \n    :material-plus: [source_ip_is_private](#source_ip_is_private)  \n    :material-plus: [ip_is_private](#ip_is_private)  \n    :material-delete-clock: [source_geoip](#source_geoip)  \n    :material-delete-clock: [geoip](#geoip)  \n    :material-delete-clock: [geosite](#geosite)\n\n### Structure\n\n```json\n{\n  \"route\": {\n    \"rules\": [\n      {\n        \"inbound\": [\n          \"mixed-in\"\n        ],\n        \"ip_version\": 6,\n        \"network\": [\n          \"tcp\"\n        ],\n        \"auth_user\": [\n          \"usera\",\n          \"userb\"\n        ],\n        \"protocol\": [\n          \"tls\",\n          \"http\",\n          \"quic\"\n        ],\n        \"client\": [\n          \"chromium\",\n          \"safari\",\n          \"firefox\",\n          \"quic-go\"\n        ],\n        \"domain\": [\n          \"test.com\"\n        ],\n        \"domain_suffix\": [\n          \".cn\"\n        ],\n        \"domain_keyword\": [\n          \"test\"\n        ],\n        \"domain_regex\": [\n          \"^stun\\\\..+\"\n        ],\n        \"geosite\": [\n          \"cn\"\n        ],\n        \"source_geoip\": [\n          \"private\"\n        ],\n        \"geoip\": [\n          \"cn\"\n        ],\n        \"source_ip_cidr\": [\n          \"10.0.0.0/24\",\n          \"192.168.0.1\"\n        ],\n        \"source_ip_is_private\": false,\n        \"ip_cidr\": [\n          \"10.0.0.0/24\",\n          \"192.168.0.1\"\n        ],\n        \"ip_is_private\": false,\n        \"source_port\": [\n          12345\n        ],\n        \"source_port_range\": [\n          \"1000:2000\",\n          \":3000\",\n          \"4000:\"\n        ],\n        \"port\": [\n          80,\n          443\n        ],\n        \"port_range\": [\n          \"1000:2000\",\n          \":3000\",\n          \"4000:\"\n        ],\n        \"process_name\": [\n          \"curl\"\n        ],\n        \"process_path\": [\n          \"/usr/bin/curl\"\n        ],\n        \"process_path_regex\": [\n          \"^/usr/bin/.+\"\n        ],\n        \"package_name\": [\n          \"com.termux\"\n        ],\n        \"user\": [\n          \"sekai\"\n        ],\n        \"user_id\": [\n          1000\n        ],\n        \"clash_mode\": \"direct\",\n        \"network_type\": [\n          \"wifi\"\n        ],\n        \"network_is_expensive\": false,\n        \"network_is_constrained\": false,\n        \"interface_address\": {\n          \"en0\": [\n            \"2000::/3\"\n          ]\n        },\n        \"network_interface_address\": {\n          \"wifi\": [\n            \"2000::/3\"\n          ]\n        },\n        \"default_interface_address\": [\n          \"2000::/3\"\n        ],\n        \"wifi_ssid\": [\n          \"My WIFI\"\n        ],\n        \"wifi_bssid\": [\n          \"00:00:00:00:00:00\"\n        ],\n        \"preferred_by\": [\n          \"tailscale\",\n          \"wireguard\"\n        ],\n        \"source_mac_address\": [\n          \"00:11:22:33:44:55\"\n        ],\n        \"source_hostname\": [\n          \"my-device\"\n        ],\n        \"rule_set\": [\n          \"geoip-cn\",\n          \"geosite-cn\"\n        ],\n        // deprecated\n        \"rule_set_ipcidr_match_source\": false,\n        \"rule_set_ip_cidr_match_source\": false,\n        \"invert\": false,\n        \"action\": \"route\",\n        \"outbound\": \"direct\"\n      },\n      {\n        \"type\": \"logical\",\n        \"mode\": \"and\",\n        \"rules\": [],\n        \"invert\": false,\n        \"action\": \"route\",\n        \"outbound\": \"direct\"\n      }\n    ]\n  }\n}\n\n```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n### Default Fields\n\n!!! note \"\"\n\n    The default rule uses the following matching logic:  \n    (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr` || `ip_is_private`) &&  \n    (`port` || `port_range`) &&  \n    (`source_geoip` || `source_ip_cidr` || `source_ip_is_private`) &&  \n    (`source_port` || `source_port_range`) &&  \n    `other fields`\n\n    Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.\n\n#### inbound\n\nTags of [Inbound](/configuration/inbound/).\n\n#### ip_version\n\n4 or 6.\n\nNot limited if empty.\n\n#### auth_user\n\nUsername, see each inbound for details.\n\n#### protocol\n\nSniffed protocol, see [Protocol Sniff](/configuration/route/sniff/) for details.\n\n#### client\n\n!!! question \"Since sing-box 1.10.0\"\n\nSniffed client type, see [Protocol Sniff](/configuration/route/sniff/) for details.\n\n#### network\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    Since sing-box 1.13.0, you can match ICMP echo (ping) requests via the new `icmp` network.\n    \n    Such traffic originates from `TUN`, `WireGuard`, and `Tailscale` inbounds and can be routed to `Direct`, `WireGuard`, and `Tailscale` outbounds.\n\nMatch network type.\n\n`tcp`, `udp` or `icmp`.\n\n#### domain\n\nMatch full domain.\n\n#### domain_suffix\n\nMatch domain suffix.\n\n#### domain_keyword\n\nMatch domain using keyword.\n\n#### domain_regex\n\nMatch domain using regular expression.\n\n#### geosite\n\n!!! failure \"Deprecated in sing-box 1.8.0\"\n\n    Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).\n\nMatch geosite.\n\n#### source_geoip\n\n!!! failure \"Deprecated in sing-box 1.8.0\"\n\n    GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).\n\nMatch source geoip.\n\n#### geoip\n\n!!! failure \"Deprecated in sing-box 1.8.0\"\n\n    GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).\n\nMatch geoip.\n\n#### source_ip_cidr\n\nMatch source IP CIDR.\n\n#### ip_is_private\n\n!!! question \"Since sing-box 1.8.0\"\n\nMatch non-public IP.\n\n#### ip_cidr\n\nMatch IP CIDR.\n\n#### source_ip_is_private\n\n!!! question \"Since sing-box 1.8.0\"\n\nMatch non-public source IP.\n\n#### source_port\n\nMatch source port.\n\n#### source_port_range\n\nMatch source port range.\n\n#### port\n\nMatch port.\n\n#### port_range\n\nMatch port range.\n\n#### process_name\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch process name.\n\n#### process_path\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch process path.\n\n#### process_path_regex\n\n!!! question \"Since sing-box 1.10.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch process path using regular expression.\n\n#### package_name\n\nMatch android package name.\n\n#### user\n\n!!! quote \"\"\n\n    Only supported on Linux.\n\nMatch user name.\n\n#### user_id\n\n!!! quote \"\"\n\n    Only supported on Linux.\n\nMatch user id.\n\n#### clash_mode\n\nMatch Clash mode.\n\n#### network_type\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms.\n\nMatch network type.\n\nAvailable values: `wifi`, `cellular`, `ethernet` and `other`.\n\n#### network_is_expensive\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms.\n\nMatch if network is considered Metered (on Android) or considered expensive,\nsuch as Cellular or a Personal Hotspot (on Apple platforms).\n\n#### network_is_constrained\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Apple platforms.\n\nMatch if network is in Low Data Mode.\n\n#### interface_address\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch interface address.\n\n#### network_interface_address\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms.\n\nMatches network interface (same values as `network_type`) address.\n\n#### default_interface_address\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch default interface address.\n\n#### wifi_ssid\n\nMatch WiFi SSID.\n\nSee [Wi-Fi State](/configuration/shared/wifi-state/) for details.\n\n#### wifi_bssid\n\nMatch WiFi BSSID.\n\nSee [Wi-Fi State](/configuration/shared/wifi-state/) for details.\n\n#### preferred_by\n\n!!! question \"Since sing-box 1.13.0\"\n\nMatch specified outbounds' preferred routes.\n\n| Type        | Match                                         |\n|-------------|-----------------------------------------------|\n| `tailscale` | Match MagicDNS domains and peers' allowed IPs |\n| `wireguard` | Match peers's allowed IPs                     |\n\n#### source_mac_address\n\n!!! question \"Since sing-box 1.14.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.\n\nMatch source device MAC address.\n\n#### source_hostname\n\n!!! question \"Since sing-box 1.14.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.\n\nMatch source device hostname from DHCP leases.\n\n#### rule_set\n\n!!! question \"Since sing-box 1.8.0\"\n\nMatch [rule-set](/configuration/route/#rule_set).\n\n#### rule_set_ipcidr_match_source\n\n!!! question \"Since sing-box 1.8.0\"\n\n!!! failure \"Deprecated in sing-box 1.10.0\"\n\n    `rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.\n\nMake `ip_cidr` in rule-sets match the source IP.\n\n#### rule_set_ip_cidr_match_source\n\n!!! question \"Since sing-box 1.10.0\"\n\nMake `ip_cidr` in rule-sets match the source IP.\n\n#### invert\n\nInvert match result.\n\n#### action\n\n==Required==\n\nSee [Rule Actions](../rule_action/) for details.\n\n#### outbound\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Moved to [Rule Action](../rule_action#route).\n\n### Logical Fields\n\n#### type\n\n`logical`\n\n#### mode\n\n==Required==\n\n`and` or `or`\n\n#### rules\n\n==Required==\n\nIncluded rules.\n"
  },
  {
    "path": "docs/configuration/route/rule.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.14.0 中的更改\"\n\n    :material-plus: [source_mac_address](#source_mac_address)  \n    :material-plus: [source_hostname](#source_hostname)\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [interface_address](#interface_address)  \n    :material-plus: [network_interface_address](#network_interface_address)  \n    :material-plus: [default_interface_address](#default_interface_address)  \n    :material-plus: [preferred_by](#preferred_by)  \n    :material-alert: [network](#network)\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-plus: [action](#action)  \n    :material-alert: [outbound](#outbound)  \n    :material-plus: [network_type](#network_type)  \n    :material-plus: [network_is_expensive](#network_is_expensive)  \n    :material-plus: [network_is_constrained](#network_is_constrained)\n\n!!! quote \"sing-box 1.10.0 中的更改\"\n\n    :material-plus: [client](#client)  \n    :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)  \n    :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)  \n    :material-plus: [process_path_regex](#process_path_regex)\n\n!!! quote \"sing-box 1.8.0 中的更改\"\n\n    :material-plus: [rule_set](#rule_set)  \n    :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)  \n    :material-plus: [source_ip_is_private](#source_ip_is_private)  \n    :material-plus: [ip_is_private](#ip_is_private)  \n    :material-delete-clock: [source_geoip](#source_geoip)  \n    :material-delete-clock: [geoip](#geoip)  \n    :material-delete-clock: [geosite](#geosite)\n\n### 结构\n\n```json\n{\n  \"route\": {\n    \"rules\": [\n      {\n        \"inbound\": [\n          \"mixed-in\"\n        ],\n        \"ip_version\": 6,\n        \"network\": [\n          \"tcp\"\n        ],\n        \"auth_user\": [\n          \"usera\",\n          \"userb\"\n        ],\n        \"protocol\": [\n          \"tls\",\n          \"http\",\n          \"quic\"\n        ],\n        \"client\": [\n          \"chromium\",\n          \"safari\",\n          \"firefox\",\n          \"quic-go\"\n        ],\n        \"domain\": [\n          \"test.com\"\n        ],\n        \"domain_suffix\": [\n          \".cn\"\n        ],\n        \"domain_keyword\": [\n          \"test\"\n        ],\n        \"domain_regex\": [\n          \"^stun\\\\..+\"\n        ],\n        \"geosite\": [\n          \"cn\"\n        ],\n        \"source_geoip\": [\n          \"private\"\n        ],\n        \"geoip\": [\n          \"cn\"\n        ],\n        \"source_ip_cidr\": [\n          \"10.0.0.0/24\"\n        ],\n        \"source_ip_is_private\": false,\n        \"ip_cidr\": [\n          \"10.0.0.0/24\"\n        ],\n        \"ip_is_private\": false,\n        \"source_port\": [\n          12345\n        ],\n        \"source_port_range\": [\n          \"1000:2000\",\n          \":3000\",\n          \"4000:\"\n        ],\n        \"port\": [\n          80,\n          443\n        ],\n        \"port_range\": [\n          \"1000:2000\",\n          \":3000\",\n          \"4000:\"\n        ],\n        \"process_name\": [\n          \"curl\"\n        ],\n        \"process_path\": [\n          \"/usr/bin/curl\"\n        ],\n        \"process_path_regex\": [\n          \"^/usr/bin/.+\"\n        ],\n        \"package_name\": [\n          \"com.termux\"\n        ],\n        \"user\": [\n          \"sekai\"\n        ],\n        \"user_id\": [\n          1000\n        ],\n        \"clash_mode\": \"direct\",\n        \"network_type\": [\n          \"wifi\"\n        ],\n        \"network_is_expensive\": false,\n        \"network_is_constrained\": false,\n        \"interface_address\": {\n          \"en0\": [\n            \"2000::/3\"\n          ]\n        },\n        \"network_interface_address\": {\n          \"wifi\": [\n            \"2000::/3\"\n          ]\n        },\n        \"default_interface_address\": [\n          \"2000::/3\"\n        ],\n        \"wifi_ssid\": [\n          \"My WIFI\"\n        ],\n        \"wifi_bssid\": [\n          \"00:00:00:00:00:00\"\n        ],\n        \"preferred_by\": [\n          \"tailscale\",\n          \"wireguard\"\n        ],\n        \"source_mac_address\": [\n          \"00:11:22:33:44:55\"\n        ],\n        \"source_hostname\": [\n          \"my-device\"\n        ],\n        \"rule_set\": [\n          \"geoip-cn\",\n          \"geosite-cn\"\n        ],\n        // 已弃用\n        \"rule_set_ipcidr_match_source\": false,\n        \"rule_set_ip_cidr_match_source\": false,\n        \"invert\": false,\n        \"action\": \"route\",\n        \"outbound\": \"direct\"\n      },\n      {\n        \"type\": \"logical\",\n        \"mode\": \"and\",\n        \"rules\": [],\n        \"invert\": false,\n        \"action\": \"route\",\n        \"outbound\": \"direct\"\n      }\n    ]\n  }\n}\n\n```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签。\n\n### 默认字段\n\n!!! note \"\"\n\n    默认规则使用以下匹配逻辑:  \n    (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr` || `ip_is_private`) &&  \n    (`port` || `port_range`) &&  \n    (`source_geoip` || `source_ip_cidr` || `source_ip_is_private`) &&  \n    (`source_port` || `source_port_range`) &&  \n    `other fields`\n\n    另外，引用的规则集可视为被合并，而不是作为一个单独的规则子项。\n\n#### inbound\n\n[入站](/zh/configuration/inbound/) 标签。\n\n#### ip_version\n\n4 或 6。\n\n默认不限制。\n\n#### auth_user\n\n认证用户名，参阅入站设置。\n\n#### protocol\n\n探测到的协议, 参阅 [协议探测](/zh/configuration/route/sniff/)。\n\n#### client\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n探测到的客户端类型, 参阅 [协议探测](/zh/configuration/route/sniff/)。\n\n#### network\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    自 sing-box 1.13.0 起，您可以通过新的 `icmp` 网络匹配 ICMP 回显（ping）请求。\n\n    此类流量源自 `TUN`、`WireGuard` 和 `Tailscale` 入站，并可路由至 `Direct`、`WireGuard` 和 `Tailscale` 出站。\n\n匹配网络类型。\n\n`tcp`、`udp` 或 `icmp`。\n\n#### domain\n\n匹配完整域名。\n\n#### domain_suffix\n\n匹配域名后缀。\n\n#### domain_keyword\n\n匹配域名关键字。\n\n#### domain_regex\n\n匹配域名正则表达式。\n\n#### geosite\n\n!!! failure \"已在 sing-box 1.8.0 废弃\"\n\n    Geosite 已废弃且可能在不久的将来移除，参阅 [迁移指南](/zh/migration/#迁移-geosite-到规则集)。\n\n匹配 Geosite。\n\n#### source_geoip\n\n!!! failure \"已在 sing-box 1.8.0 废弃\"\n\n    GeoIP 已废弃且可能在不久的将来移除，参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。\n\n匹配源 GeoIP。\n\n#### geoip\n\n!!! failure \"已在 sing-box 1.8.0 废弃\"\n\n    GeoIP 已废弃且可能在不久的将来移除，参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。\n\n匹配 GeoIP。\n\n#### source_ip_cidr\n\n匹配源 IP CIDR。\n\n#### source_ip_is_private\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n匹配非公开源 IP。\n\n#### ip_cidr\n\n匹配 IP CIDR。\n\n#### ip_is_private\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n匹配非公开 IP。\n\n#### source_port\n\n匹配源端口。\n\n#### source_port_range\n\n匹配源端口范围。\n\n#### port\n\n匹配端口。\n\n#### port_range\n\n匹配端口范围。\n\n#### process_name\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS。\n\n匹配进程名称。\n\n#### process_path\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n匹配进程路径。\n\n#### process_path_regex\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n使用正则表达式匹配进程路径。\n\n#### package_name\n\n匹配 Android 应用包名。\n\n#### user\n\n!!! quote \"\"\n\n    仅支持 Linux.\n\n匹配用户名。\n\n#### user_id\n\n!!! quote \"\"\n\n    仅支持 Linux.\n\n匹配用户 ID。\n\n#### clash_mode\n\n匹配 Clash 模式。\n\n#### network_type\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端中支持。\n\n匹配网络类型。\n\n可用值: `wifi`, `cellular`, `ethernet` and `other`.\n\n#### network_is_expensive\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端中支持。\n\n匹配如果网络被视为计费 (在 Android) 或被视为昂贵，\n像蜂窝网络或个人热点 (在 Apple 平台)。\n\n#### network_is_constrained\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Apple 平台图形客户端中支持。\n\n匹配如果网络在低数据模式下。\n\n#### interface_address\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n匹配接口地址。\n\n#### network_interface_address\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端中支持。\n\n匹配网络接口（可用值同 `network_type`）地址。\n\n#### default_interface_address\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n匹配默认接口地址。\n\n#### wifi_ssid\n\n匹配 WiFi SSID。\n\n参阅 [Wi-Fi 状态](/zh/configuration/shared/wifi-state/)。\n\n#### wifi_bssid\n\n匹配 WiFi BSSID。\n\n参阅 [Wi-Fi 状态](/zh/configuration/shared/wifi-state/)。\n\n#### preferred_by\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n匹配制定出站的首选路由。\n\n| 类型          | 匹配                             |\n|-------------|--------------------------------|\n| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |\n| `wireguard` | 匹配对端的 allowed IPs              |\n\n#### source_mac_address\n\n!!! question \"自 sing-box 1.14.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、macOS，或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。\n\n匹配源设备 MAC 地址。\n\n#### source_hostname\n\n!!! question \"自 sing-box 1.14.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、macOS，或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。\n\n匹配源设备从 DHCP 租约获取的主机名。\n\n#### rule_set\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n匹配[规则集](/zh/configuration/route/#rule_set)。\n\n#### rule_set_ipcidr_match_source\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n!!! failure \"已在 sing-box 1.10.0 废弃\"\n\n    `rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。\n\n使规则集中的 `ip_cidr` 规则匹配源 IP。\n\n#### rule_set_ip_cidr_match_source\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n使规则集中的 `ip_cidr` 规则匹配源 IP。\n\n#### invert\n\n反选匹配结果。\n\n#### action\n\n==必填==\n\n参阅 [规则动作](../rule_action/)。\n\n#### outbound\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    已移动到 [规则动作](../rule_action#route).\n\n### 逻辑字段\n\n#### type\n\n`logical`\n\n#### mode\n\n==必填==\n\n`and` 或 `or`\n\n#### rules\n\n==必填==\n\n包括的规则。"
  },
  {
    "path": "docs/configuration/route/rule_action.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [bypass](#bypass)  \n    :material-alert: [reject](#reject)\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-plus: [tls_fragment](#tls_fragment)  \n    :material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)  \n    :material-plus: [tls_record_fragment](#tls_record_fragment)  \n    :material-plus: [resolve.disable_cache](#disable_cache)  \n    :material-plus: [resolve.rewrite_ttl](#rewrite_ttl)  \n    :material-plus: [resolve.client_subnet](#client_subnet)\n\n## Final actions\n\n### route\n\n```json\n{\n  \"action\": \"route\", // default\n  \"outbound\": \"\",\n \n  ... // route-options Fields\n}\n```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n`route` inherits the classic rule behavior of routing connection to the specified outbound.\n\n#### outbound\n\n==Required==\n\nTag of target outbound.\n\n#### route-options Fields\n\nSee `route-options` fields below.\n\n### bypass\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux with `auto_redirect` enabled.\n\n```json\n{\n  \"action\": \"bypass\",\n  \"outbound\": \"\",\n\n  ... // route-options Fields\n}\n```\n\n`bypass` bypasses sing-box at the kernel level for auto redirect connections in pre-match.\n\nFor non-auto-redirect connections and already established connections,\nif `outbound` is specified, the behavior is the same as `route`;\notherwise, the rule will be skipped.\n\n#### outbound\n\nTag of target outbound.\n\nIf not specified, the rule only matches in [pre-match](/configuration/shared/pre-match/)\nfrom auto redirect, and will be skipped in other contexts.\n\n#### route-options Fields\n\nSee `route-options` fields below.\n\n### reject\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    Since sing-box 1.13.0, you can reject (or directly reply to) ICMP echo (ping) requests using `reject` action.\n\n```json\n{\n  \"action\": \"reject\",\n  \"method\": \"default\", // default\n  \"no_drop\": false\n}\n```\n\n`reject` reject connections\n\nThe specified method is used for reject tun connections if `sniff` action has not been performed yet.\n\nFor non-tun connections and already established connections, will just be closed.\n\n#### method\n\nFor TCP and UDP connections:\n\n- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.\n- `drop`: Drop packets.\n\nFor ICMP echo requests:\n\n- `default`: Reply with ICMP host unreachable.\n- `drop`: Drop packets.\n- `reply`: Reply with ICMP echo reply.\n\n#### no_drop\n\nIf not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.\n\nNot available when `method` is set to drop.\n\n### hijack-dns\n\n```json\n{\n  \"action\": \"hijack-dns\"\n}\n```\n\n`hijack-dns` hijack DNS requests to the sing-box DNS module.\n\n## Non-final actions\n\n### route-options\n\n```json\n{\n  \"action\": \"route-options\",\n  \"override_address\": \"\",\n  \"override_port\": 0,\n  \"network_strategy\": \"\",\n  \"fallback_delay\": \"\",\n  \"udp_disable_domain_unmapping\": false,\n  \"udp_connect\": false,\n  \"udp_timeout\": \"\",\n  \"tls_fragment\": false,\n  \"tls_fragment_fallback_delay\": \"\",\n  \"tls_record_fragment\": \"\"\n}\n```\n\n`route-options` set options for routing.\n\n#### override_address\n\nOverride the connection destination address.\n\n#### override_port\n\nOverride the connection destination port.\n\n#### network_strategy\n\nSee [Dial Fields](/configuration/shared/dial/#network_strategy) for details.\n\nOnly take effect if outbound is direct without `outbound.bind_interface`,\n`outbound.inet4_bind_address` and `outbound.inet6_bind_address` set.\n\n#### network_type\n\nSee [Dial Fields](/configuration/shared/dial/#network_type) for details.\n\n#### fallback_network_type\n\nSee [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details.\n\n#### fallback_delay\n\nSee [Dial Fields](/configuration/shared/dial/#fallback_delay) for details.\n\n#### udp_disable_domain_unmapping\n\nIf enabled, for UDP proxy requests addressed to a domain,\nthe original packet address will be sent in the response instead of the mapped domain.\n\nThis option is used for compatibility with clients that\ndo not support receiving UDP packets with domain addresses, such as Surge.\n\n#### udp_connect\n\nIf enabled, attempts to connect UDP connection to the destination instead of listen.\n\n#### udp_timeout\n\nTimeout for UDP connections.\n\nSetting a larger value than the UDP timeout in inbounds will have no effect.\n\nDefault value for protocol sniffed connections:\n\n| Timeout | Protocol             |\n|---------|----------------------|\n| `10s`   | `dns`, `ntp`, `stun` |\n| `30s`   | `quic`, `dtls`       |\n\nIf no protocol is sniffed, the following ports will be recognized as protocols by default:\n\n| Port | Protocol |\n|------|----------|\n| 53   | `dns`    |\n| 123  | `ntp`    |\n| 443  | `quic`   |\n| 3478 | `stun`   |\n\n#### tls_fragment\n\n!!! question \"Since sing-box 1.12.0\"\n\nFragment TLS handshakes to bypass firewalls.\n\nThis feature is intended to circumvent simple firewalls based on **plaintext packet matching**,\nand should not be used to circumvent real censorship.\n\nDue to poor performance, try `tls_record_fragment` first, and only apply to server names known to be blocked.\n\nOn Linux, Apple platforms, (administrator privileges required) Windows,\nthe wait time can be automatically detected. Otherwise, it will fall back to\nwaiting for a fixed time specified by `tls_fragment_fallback_delay`.\n\nIn addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,\nbecause the target is considered to be local or behind a transparent proxy.\n\n#### tls_fragment_fallback_delay\n\n!!! question \"Since sing-box 1.12.0\"\n\nThe fallback value used when TLS segmentation cannot automatically determine the wait time.\n\n`500ms` is used by default.\n\n#### tls_record_fragment\n\n!!! question \"Since sing-box 1.12.0\"\n\nFragment TLS handshake into multiple TLS records to bypass firewalls.\n\n### sniff\n\n```json\n{\n  \"action\": \"sniff\",\n  \"sniffer\": [],\n  \"timeout\": \"\"\n}\n```\n\n`sniff` performs protocol sniffing on connections.\n\nFor deprecated `inbound.sniff` options, it is considered to `sniff()` performed before routing.\n\n#### sniffer\n\nEnabled sniffers.\n\nAll sniffers enabled by default.\n\nAvailable protocol values an be found on in [Protocol Sniff](../sniff/)\n\n#### timeout\n\nTimeout for sniffing.\n\n`300ms` is used by default.\n\n### resolve\n\n```json\n{\n  \"action\": \"resolve\",\n  \"server\": \"\",\n  \"strategy\": \"\",\n  \"disable_cache\": false,\n  \"rewrite_ttl\": null,\n  \"client_subnet\": null\n}\n```\n\n`resolve` resolve request destination from domain to IP addresses.\n\n#### server\n\nSpecifies DNS server tag to use instead of selecting through DNS routing.\n\n#### strategy\n\nDNS resolution strategy, available values are: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.\n\n`dns.strategy` will be used by default.\n\n#### disable_cache\n\n!!! question \"Since sing-box 1.12.0\"\n\nDisable cache and save cache in this query.\n\n#### rewrite_ttl\n\n!!! question \"Since sing-box 1.12.0\"\n\nRewrite TTL in DNS responses.\n\n#### client_subnet\n\n!!! question \"Since sing-box 1.12.0\"\n\nAppend a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.\n\nIf value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.\n\nWill overrides `dns.client_subnet`.\n"
  },
  {
    "path": "docs/configuration/route/rule_action.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [bypass](#bypass)  \n    :material-alert: [reject](#reject)\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-plus: [tls_fragment](#tls_fragment)  \n    :material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)  \n    :material-plus: [tls_record_fragment](#tls_record_fragment)  \n    :material-plus: [resolve.disable_cache](#disable_cache)  \n    :material-plus: [resolve.rewrite_ttl](#rewrite_ttl)  \n    :material-plus: [resolve.client_subnet](#client_subnet)\n\n## 最终动作\n\n### route\n\n```json\n{\n  \"action\": \"route\", // 默认\n  \"outbound\": \"\",\n  \n  ... // route-options 字段\n}\n```\n\n`route` 继承了将连接路由到指定出站的经典规则动作。\n\n#### outbound\n\n==必填==\n\n目标出站的标签。\n\n#### route-options 字段\n\n参阅下方的 `route-options` 字段。\n\n### bypass\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux，且需要启用 `auto_redirect`。\n\n```json\n{\n  \"action\": \"bypass\",\n  \"outbound\": \"\",\n\n  ... // route-options 字段\n}\n```\n\n`bypass` 在预匹配中为 auto redirect 连接在内核层面绕过 sing-box。\n\n对于非 auto redirect 连接和已建立的连接，如果指定了 `outbound`，行为与 `route` 相同；否则规则将被跳过。\n\n#### outbound\n\n目标出站的标签。\n\n如果未指定，规则仅在来自 auto redirect 的[预匹配](/zh/configuration/shared/pre-match/)中匹配，在其他场景中将被跳过。\n\n#### route-options 字段\n\n参阅下方的 `route-options` 字段。\n\n### reject\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    自 sing-box 1.13.0 起，您可以通过 `reject` 动作拒绝（或直接回复）ICMP 回显（ping）请求。\n\n```json\n{\n  \"action\": \"reject\",\n  \"method\": \"default\",  // 默认\n  \"no_drop\": false\n}\n```\n\n`reject` 拒绝连接。\n\n如果尚未执行 `sniff` 操作，则将使用指定方法拒绝 tun 连接。\n\n对于非 tun 连接和已建立的连接，将直接关闭。\n\n#### method\n\n对于 TCP 和 UDP 连接：\n\n- `default`: 对于 TCP 连接回复 RST，对于 UDP 包回复 ICMP 端口不可达。\n- `drop`: 丢弃数据包。\n\n对于 ICMP 回显请求：\n\n- `default`: 回复 ICMP 主机不可达。\n- `drop`: 丢弃数据包。\n- `reply`: 回复以 ICMP 回显应答。\n\n#### no_drop\n\n如果未启用，则 30 秒内触发 50 次后，`method` 将被暂时覆盖为 `drop`。\n\n当 `method` 设为 `drop` 时不可用。\n\n### hijack-dns\n\n```json\n{\n  \"action\": \"hijack-dns\"\n}\n```\n\n`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。\n\n## 非最终动作\n\n### route-options\n\n```json\n{\n  \"action\": \"route-options\",\n  \"override_address\": \"\",\n  \"override_port\": 0,\n  \"network_strategy\": \"\",\n  \"fallback_delay\": \"\",\n  \"udp_disable_domain_unmapping\": false,\n  \"udp_connect\": false,\n  \"udp_timeout\": \"\"\n}\n```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签\n\n`route-options` 为路由设置选项。\n\n#### override_address\n\n覆盖目标地址。\n\n#### override_port\n\n覆盖目标端口。\n\n#### network_strategy\n\n详情参阅 [拨号字段](/zh/configuration/shared/dial/#network_strategy)。\n\n仅当出站为 `direct` 且 `outbound.bind_interface`, `outbound.inet4_bind_address`\n且 `outbound.inet6_bind_address` 未设置时生效。\n\n#### network_type\n\n详情参阅 [拨号字段](/zh/configuration/shared/dial/#network_type)。\n\n#### fallback_network_type\n\n详情参阅 [拨号字段](/zh/configuration/shared/dial/#fallback_network_type)。\n\n#### fallback_delay\n\n详情参阅 [拨号字段](/zh/configuration/shared/dial/#fallback_delay)。\n\n#### udp_disable_domain_unmapping\n\n如果启用，对于地址为域的 UDP 代理请求，将在响应中发送原始包地址而不是映射的域。\n\n此选项用于兼容不支持接收带有域地址的 UDP 包的客户端，如 Surge。\n\n#### udp_connect\n\n如果启用，将尝试将 UDP 连接 connect 到目标而不是 listen。\n\n#### udp_timeout\n\nUDP 连接超时时间。\n\n设置比入站 UDP 超时更大的值将无效。\n\n已探测协议连接的默认值：\n\n| 超时    | 协议                   |\n|-------|----------------------|\n| `10s` | `dns`, `ntp`, `stun` |\n| `30s` | `quic`, `dtls`       |\n\n如果没有探测到协议，以下端口将默认识别为协议：\n\n| 端口   | 协议     |\n|------|--------|\n| 53   | `dns`  |\n| 123  | `ntp`  |\n| 443  | `quic` |\n| 3478 | `stun` |\n\n#### tls_fragment\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n通过分段 TLS 握手数据包来绕过防火墙检测。\n\n此功能旨在规避基于**明文数据包匹配**的简单防火墙，不应该用于规避真的审查。\n\n由于性能不佳，请首先尝试 `tls_record_fragment`，且仅应用于已知被阻止的服务器名称。\n\n在 Linux、Apple 平台和需要管理员权限的 Windows 系统上，可自动检测等待时间。\n若无法自动检测，将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。\n\n此外，若实际等待时间小于 20 毫秒，同样会回退至固定等待时间模式，因为此时判定目标处于本地或透明代理之后。\n\n#### tls_fragment_fallback_delay\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n当 TLS 分片功能无法自动判定等待时间时使用的回退值。\n\n默认使用 `500ms`。\n\n#### tls_record_fragment\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。\n\n### sniff\n\n```json\n{\n  \"action\": \"sniff\",\n  \"sniffer\": [],\n  \"timeout\": \"\"\n}\n```\n\n`sniff` 对连接执行协议嗅探。\n\n对于已弃用的 `inbound.sniff` 选项，被视为在路由之前执行的 `sniff`。\n\n#### sniffer\n\n启用的探测器。\n\n默认启用所有探测器。\n\n可用的协议值可以在 [协议嗅探](../sniff/) 中找到。\n\n#### timeout\n\n探测超时时间。\n\n默认使用 300ms。\n\n### resolve\n\n```json\n{\n  \"action\": \"resolve\",\n  \"server\": \"\",\n  \"strategy\": \"\",\n  \"disable_cache\": false,\n  \"rewrite_ttl\": null,\n  \"client_subnet\": null\n}\n```\n\n`resolve` 将请求的目标从域名解析为 IP 地址。\n\n#### server\n\n指定要使用的 DNS 服务器的标签，而不是通过 DNS 路由进行选择。\n\n#### strategy\n\nDNS 解析策略，可用值有：`prefer_ipv4`、`prefer_ipv6`、`ipv4_only`、`ipv6_only`。\n\n默认使用 `dns.strategy`。\n\n#### disable_cache\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n在此查询中禁用缓存。\n\n#### rewrite_ttl\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n重写 DNS 回应中的 TTL。\n\n#### client_subnet\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n默认情况下，将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。\n\n如果值是 IP 地址而不是前缀，则会自动附加 `/32` 或 `/128`。\n\n将覆盖 `dns.client_subnet`.\n"
  },
  {
    "path": "docs/configuration/route/sniff.md",
    "content": "!!! quote \"Changes in sing-box 1.10.0\"\n\n    :material-plus: QUIC client type detect support for QUIC  \n    :material-plus: Chromium support for QUIC  \n    :material-plus: BitTorrent support  \n    :material-plus: DTLS support  \n    :material-plus: SSH support  \n    :material-plus: RDP support\n\nIf enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed.\n\n#### Supported Protocols\n\n| Network |   Protocol   | Domain Name |      Client      |\n|:-------:|:------------:|:-----------:|:----------------:|\n|   TCP   |    `http`    |    Host     |        /         |\n|   TCP   |    `tls`     | Server Name |        /         |\n|   UDP   |    `quic`    | Server Name | QUIC Client Type |\n|   UDP   |    `stun`    |      /      |        /         |\n| TCP/UDP |    `dns`     |      /      |        /         |\n| TCP/UDP | `bittorrent` |      /      |        /         |\n|   UDP   |    `dtls`    |      /      |        /         |\n|   TCP   |    `ssh`     |      /      | SSH Client Name  |\n|   TCP   |    `rdp`     |      /      |        /         |\n|   UDP   |    `ntp`     |      /      |        /         |\n\n|       QUIC Client        |    Type    |\n|:------------------------:|:----------:|\n|     Chromium/Cronet      | `chromium` |\n| Safari/Apple Network API |  `safari`  |\n| Firefox / uquic firefox  | `firefox`  |\n|  quic-go / uquic chrome  | `quic-go`  |\n"
  },
  {
    "path": "docs/configuration/route/sniff.zh.md",
    "content": "!!! quote \"sing-box 1.10.0 中的更改\"\n\n    :material-plus: QUIC 的 客户端类型探测支持  \n    :material-plus: QUIC 的 Chromium 支持  \n    :material-plus: BitTorrent 支持  \n    :material-plus: DTLS 支持  \n    :material-plus: SSH 支持  \n    :material-plus: RDP 支持\n\n如果在入站中启用，则可以嗅探连接的协议和域名（如果存在）。\n\n#### 支持的协议\n\n|   网络    |      协议      |     域名      |    客户端     |\n|:-------:|:------------:|:-----------:|:----------:|\n|   TCP   |    `http`    |    Host     |     /      |\n|   TCP   |    `tls`     | Server Name |     /      |\n|   UDP   |    `quic`    | Server Name | QUIC 客户端类型 |\n|   UDP   |    `stun`    |      /      |     /      |\n| TCP/UDP |    `dns`     |      /      |     /      |\n| TCP/UDP | `bittorrent` |      /      |     /      |\n|   UDP   |    `dtls`    |      /      |     /      |\n|   TCP   |    `ssh`     |      /      | SSH 客户端名称  |\n|   TCP   |    `rdp`     |      /      |     /      |\n|   UDP   |    `ntp`     |      /      |     /      |\n\n|         QUIC 客户端         |     类型     |\n|:------------------------:|:----------:|\n|     Chromium/Cronet      | `chromium` |\n| Safari/Apple Network API |  `safari`  |\n| Firefox / uquic firefox  | `firefox`  |\n|  quic-go / uquic chrome  | `quic-go`  |\n"
  },
  {
    "path": "docs/configuration/rule-set/adguard.md",
    "content": "!!! question \"Since sing-box 1.10.0\"\n\nsing-box supports some rule-set formats from other projects which cannot be fully translated to sing-box,\ncurrently only AdGuard DNS Filter.\n\nThese formats are not directly supported as source formats,\ninstead you need to convert them to binary rule-set.\n\n## Convert\n\nUse `sing-box rule-set convert --type adguard [--output <file-name>.srs] <file-name>.txt` to convert to binary rule-set.\n\n## Performance\n\nAdGuard keeps all rules in memory and matches them sequentially,\nwhile sing-box chooses high performance and smaller memory usage.\nAs a trade-off, you cannot know which rule item is matched.\n\n## Compatibility\n\nAlmost all rules in [AdGuardSDNSFilter](https://github.com/AdguardTeam/AdGuardSDNSFilter)\nand rules in rule-sets listed in [adguard-filter-list](https://github.com/ppfeufer/adguard-filter-list)\nare supported.\n\n## Supported formats\n\n### AdGuard Filter\n\n#### Basic rule syntax\n\n| Syntax | Supported        |\n|--------|------------------|\n| `@@`   | :material-check: | \n| `\\|\\|` | :material-check: | \n| `\\|`   | :material-check: |\n| `^`    | :material-check: |\n| `*`    | :material-check: |\n\n#### Host syntax\n\n| Syntax      | Example                  | Supported                |\n|-------------|--------------------------|--------------------------|\n| Scheme      | `https://`               | :material-alert: Ignored |\n| Domain Host | `example.org`            | :material-check:         |\n| IP Host     | `1.1.1.1`, `10.0.0.`     | :material-close:         |\n| Regexp      | `/regexp/`               | :material-check:         |\n| Port        | `example.org:80`         | :material-close:         |\n| Path        | `example.org/path/ad.js` | :material-close:         |\n\n#### Modifier syntax\n\n| Modifier              | Supported                |\n|-----------------------|--------------------------|\n| `$important`          | :material-check:         |\n| `$dnsrewrite=0.0.0.0` | :material-alert: Ignored |\n| Any other modifiers   | :material-close:         |\n\n### Hosts\n\nOnly items with `0.0.0.0` IP addresses will be accepted.\n\n### Simple\n\nWhen all rule lines are valid domains, they are treated as simple line-by-line domain rules which,\nlike hosts, only match the exact same domain."
  },
  {
    "path": "docs/configuration/rule-set/adguard.zh.md",
    "content": "!!! question \"自 sing-box 1.10.0 起\"\n\nsing-box 支持其他项目的一些规则集格式，这些格式无法完全转换为 sing-box，\n目前只有 AdGuard DNS Filter。\n\n这些格式不直接作为源格式支持，\n而是需要将它们转换为二进制规则集。\n\n## 转换\n\n使用 `sing-box rule-set convert --type adguard [--output <file-name>.srs] <file-name>.txt` 以转换为二进制规则集。\n\n## 性能\n\nAdGuard 将所有规则保存在内存中并按顺序匹配，\n而 sing-box 选择高性能和较小的内存使用量。\n作为权衡，您无法知道匹配了哪个规则项。\n\n## 兼容性\n\n[AdGuardSDNSFilter](https://github.com/AdguardTeam/AdGuardSDNSFilter)\n中的几乎所有规则以及 [adguard-filter-list](https://github.com/ppfeufer/adguard-filter-list)\n中列出的规则集中的规则均受支持。\n\n## 支持的格式\n\n### AdGuard Filter\n\n#### 基本规则语法\n\n| 语法     | 支持               |\n|--------|------------------|\n| `@@`   | :material-check: | \n| `\\|\\|` | :material-check: | \n| `\\|`   | :material-check: |\n| `^`    | :material-check: |\n| `*`    | :material-check: |\n\n#### 主机语法\n\n| 语法          | 示例                       | 支持                       |\n|-------------|--------------------------|--------------------------|\n| Scheme      | `https://`               | :material-alert: Ignored |\n| Domain Host | `example.org`            | :material-check:         |\n| IP Host     | `1.1.1.1`, `10.0.0.`     | :material-close:         |\n| Regexp      | `/regexp/`               | :material-check:         |\n| Port        | `example.org:80`         | :material-close:         |\n| Path        | `example.org/path/ad.js` | :material-close:         |\n\n#### 描述符语法\n\n| 描述符                   | 支持                       |\n|-----------------------|--------------------------|\n| `$important`          | :material-check:         |\n| `$dnsrewrite=0.0.0.0` | :material-alert: Ignored |\n| 任何其他描述符               | :material-close:         |\n\n### Hosts\n\n只有 IP 地址为 `0.0.0.0` 的条目将被接受。\n\n### 简易\n\n当所有行都是有效域时，它们被视为简单的逐行域规则， 与 hosts 一样，只匹配完全相同的域。"
  },
  {
    "path": "docs/configuration/rule-set/headless-rule.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [network_interface_address](#network_interface_address)  \n    :material-plus: [default_interface_address](#default_interface_address)\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-plus: [network_type](#network_type)  \n    :material-plus: [network_is_expensive](#network_is_expensive)  \n    :material-plus: [network_is_constrained](#network_is_constrained)\n\n### Structure\n\n!!! question \"Since sing-box 1.8.0\"\n\n```json\n{\n  \"rules\": [\n    {\n      \"query_type\": [\n        \"A\",\n        \"HTTPS\",\n        32768\n      ],\n      \"network\": [\n        \"tcp\"\n      ],\n      \"domain\": [\n        \"test.com\"\n      ],\n      \"domain_suffix\": [\n        \".cn\"\n      ],\n      \"domain_keyword\": [\n        \"test\"\n      ],\n      \"domain_regex\": [\n        \"^stun\\\\..+\"\n      ],\n      \"source_ip_cidr\": [\n        \"10.0.0.0/24\",\n        \"192.168.0.1\"\n      ],\n      \"ip_cidr\": [\n        \"10.0.0.0/24\",\n        \"192.168.0.1\"\n      ],\n      \"source_port\": [\n        12345\n      ],\n      \"source_port_range\": [\n        \"1000:2000\",\n        \":3000\",\n        \"4000:\"\n      ],\n      \"port\": [\n        80,\n        443\n      ],\n      \"port_range\": [\n        \"1000:2000\",\n        \":3000\",\n        \"4000:\"\n      ],\n      \"process_name\": [\n        \"curl\"\n      ],\n      \"process_path\": [\n        \"/usr/bin/curl\"\n      ],\n      \"process_path_regex\": [\n        \"^/usr/bin/.+\"\n      ],\n      \"package_name\": [\n        \"com.termux\"\n      ],\n      \"network_type\": [\n        \"wifi\"\n      ],\n      \"network_is_expensive\": false,\n      \"network_is_constrained\": false,\n      \"network_interface_address\": {\n        \"wifi\": [\n          \"2000::/3\"\n        ]\n      },\n      \"default_interface_address\": [\n        \"2000::/3\"\n      ],\n      \"wifi_ssid\": [\n        \"My WIFI\"\n      ],\n      \"wifi_bssid\": [\n        \"00:00:00:00:00:00\"\n      ],\n      \"invert\": false\n    },\n    {\n      \"type\": \"logical\",\n      \"mode\": \"and\",\n      \"rules\": [],\n      \"invert\": false\n    }\n  ]\n}\n```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n### Default Fields\n\n!!! note \"\"\n\n    The default rule uses the following matching logic:  \n    (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `ip_cidr`) &&  \n    (`port` || `port_range`) &&  \n    (`source_port` || `source_port_range`) &&  \n    `other fields`\n\n#### query_type\n\nDNS query type. Values can be integers or type name strings.\n\n#### network\n\n`tcp` or `udp`.\n\n#### domain\n\nMatch full domain.\n\n#### domain_suffix\n\nMatch domain suffix.\n\n#### domain_keyword\n\nMatch domain using keyword.\n\n#### domain_regex\n\nMatch domain using regular expression.\n\n#### source_ip_cidr\n\nMatch source IP CIDR.\n\n#### ip_cidr\n\n!!! info \"\"\n\n    `ip_cidr` is an alias for `source_ip_cidr` when `rule_set_ipcidr_match_source` enabled in route/DNS rules.\n\nMatch IP CIDR.\n\n#### source_port\n\nMatch source port.\n\n#### source_port_range\n\nMatch source port range.\n\n#### port\n\nMatch port.\n\n#### port_range\n\nMatch port range.\n\n#### process_name\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch process name.\n\n#### process_path\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch process path.\n\n#### process_path_regex\n\n!!! question \"Since sing-box 1.10.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch process path using regular expression.\n\n#### package_name\n\nMatch android package name.\n\n#### network_type\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms.\n\nMatch network type.\n\nAvailable values: `wifi`, `cellular`, `ethernet` and `other`.\n\n#### network_is_expensive\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms.\n\nMatch if network is considered Metered (on Android) or considered expensive,\nsuch as Cellular or a Personal Hotspot (on Apple platforms).\n\n#### network_is_constrained\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Apple platforms.\n\nMatch if network is in Low Data Mode.\n\n#### network_interface_address\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms.\n\nMatches network interface (same values as `network_type`) address.\n\n#### default_interface_address\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux, Windows, and macOS.\n\nMatch default interface address.\n\n#### wifi_ssid\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms.\n\nMatch WiFi SSID.\n\n#### wifi_bssid\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms.\n\nMatch WiFi BSSID.\n\n#### invert\n\nInvert match result.\n\n### Logical Fields\n\n#### type\n\n`logical`\n\n#### mode\n\n==Required==\n\n`and` or `or`\n\n#### rules\n\n==Required==\n\nIncluded rules.\n"
  },
  {
    "path": "docs/configuration/rule-set/headless-rule.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [network_interface_address](#network_interface_address)  \n    :material-plus: [default_interface_address](#default_interface_address)\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-plus: [network_type](#network_type)  \n    :material-plus: [network_is_expensive](#network_is_expensive)  \n    :material-plus: [network_is_constrained](#network_is_constrained)\n\n### 结构\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n```json\n{\n  \"rules\": [\n    {\n      \"query_type\": [\n        \"A\",\n        \"HTTPS\",\n        32768\n      ],\n      \"network\": [\n        \"tcp\"\n      ],\n      \"domain\": [\n        \"test.com\"\n      ],\n      \"domain_suffix\": [\n        \".cn\"\n      ],\n      \"domain_keyword\": [\n        \"test\"\n      ],\n      \"domain_regex\": [\n        \"^stun\\\\..+\"\n      ],\n      \"source_ip_cidr\": [\n        \"10.0.0.0/24\",\n        \"192.168.0.1\"\n      ],\n      \"ip_cidr\": [\n        \"10.0.0.0/24\",\n        \"192.168.0.1\"\n      ],\n      \"source_port\": [\n        12345\n      ],\n      \"source_port_range\": [\n        \"1000:2000\",\n        \":3000\",\n        \"4000:\"\n      ],\n      \"port\": [\n        80,\n        443\n      ],\n      \"port_range\": [\n        \"1000:2000\",\n        \":3000\",\n        \"4000:\"\n      ],\n      \"process_name\": [\n        \"curl\"\n      ],\n      \"process_path\": [\n        \"/usr/bin/curl\"\n      ],\n      \"process_path_regex\": [\n        \"^/usr/bin/.+\"\n      ],\n      \"package_name\": [\n        \"com.termux\"\n      ],\n      \"network_type\": [\n        \"wifi\"\n      ],\n      \"network_is_expensive\": false,\n      \"network_is_constrained\": false,\n      \"network_interface_address\": {\n        \"wifi\": [\n          \"2000::/3\"\n        ]\n      },\n      \"default_interface_address\": [\n        \"2000::/3\"\n      ],\n      \"wifi_ssid\": [\n        \"My WIFI\"\n      ],\n      \"wifi_bssid\": [\n        \"00:00:00:00:00:00\"\n      ],\n      \"invert\": false\n    },\n    {\n      \"type\": \"logical\",\n      \"mode\": \"and\",\n      \"rules\": [],\n      \"invert\": false\n    }\n  ]\n}\n```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签。\n\n### Default Fields\n\n!!! note \"\"\n\n    默认规则使用以下匹配逻辑:  \n    (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `ip_cidr`) &&  \n    (`port` || `port_range`) &&  \n    (`source_port` || `source_port_range`) &&  \n    `other fields`\n\n#### query_type\n\nDNS 查询类型。值可以为整数或者类型名称字符串。\n\n#### network\n\n`tcp` 或 `udp`。\n\n#### domain\n\n匹配完整域名。\n\n#### domain_suffix\n\n匹配域名后缀。\n\n#### domain_keyword\n\n匹配域名关键字。\n\n#### domain_regex\n\n匹配域名正则表达式。\n\n#### source_ip_cidr\n\n匹配源 IP CIDR。\n\n#### ip_cidr\n\n匹配 IP CIDR。\n\n#### source_port\n\n匹配源端口。\n\n#### source_port_range\n\n匹配源端口范围。\n\n#### port\n\n匹配端口。\n\n#### port_range\n\n匹配端口范围。\n\n#### process_name\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS。\n\n匹配进程名称。\n\n#### process_path\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n匹配进程路径。\n\n#### process_path_regex\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n使用正则表达式匹配进程路径。\n\n#### package_name\n\n匹配 Android 应用包名。\n\n#### network_type\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端中支持。\n\n匹配网络类型。\n\nAvailable values: `wifi`, `cellular`, `ethernet` and `other`.\n\n#### network_is_expensive\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端中支持。\n\n匹配如果网络被视为计费 (在 Android) 或被视为昂贵，\n像蜂窝网络或个人热点 (在 Apple 平台)。\n\n#### network_is_constrained\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Apple 平台图形客户端中支持。\n\n匹配如果网络在低数据模式下。\n\n#### network_interface_address\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端中支持。\n\n匹配网络接口（可用值同 `network_type`）地址。\n\n#### default_interface_address\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux、Windows 和 macOS.\n\n匹配默认接口地址。\n\n#### wifi_ssid\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端中支持。\n\n匹配 WiFi SSID。\n\n#### wifi_bssid\n\n!!! quote \"\"\n\n    仅在 Android 与 Apple 平台图形客户端中支持。\n\n#### invert\n\n反选匹配结果。\n\n### 逻辑字段\n\n#### type\n\n`logical`\n\n#### mode\n\n==必填==\n\n`and` 或 `or`\n\n#### rules\n\n==必填==\n\n包括的规则。"
  },
  {
    "path": "docs/configuration/rule-set/index.md",
    "content": "!!! quote \"Changes in sing-box 1.10.0\"\n\n    :material-plus: `type: inline`\n\n# rule-set\n\n!!! question \"Since sing-box 1.8.0\"\n\n### Structure\n\n=== \"Inline\"\n\n    !!! question \"Since sing-box 1.10.0\"\n\n    ```json\n    {\n      \"type\": \"inline\", // optional\n      \"tag\": \"\",\n      \"rules\": []\n    }\n    ```\n\n=== \"Local File\"\n\n    ```json\n    {\n      \"type\": \"local\",\n      \"tag\": \"\",\n      \"format\": \"source\", // or binary\n      \"path\": \"\"\n    }\n    ```\n\n=== \"Remote File\"\n\n    !!! info \"\"\n    \n        Remote rule-set will be cached if `experimental.cache_file.enabled`.\n\n    ```json\n    {\n      \"type\": \"remote\",\n      \"tag\": \"\",\n      \"format\": \"source\", // or binary\n      \"url\": \"\",\n      \"download_detour\": \"\", // optional\n      \"update_interval\": \"\" // optional\n    }\n    ```\n\n### Fields\n\n#### type\n\n==Required==\n\nType of rule-set, `local` or `remote`.\n\n#### tag\n\n==Required==\n\nTag of rule-set.\n\n### Inline Fields\n\n!!! question \"Since sing-box 1.10.0\"\n\n#### rules\n\n==Required==\n\nList of [Headless Rule](./headless-rule/).\n\n### Local or Remote Fields\n\n#### format\n\n==Required==\n\nFormat of rule-set file, `source` or `binary`.\n\nOptional when `path` or `url` uses `json` or `srs` as extension.\n\n### Local Fields\n\n#### path\n\n==Required==\n\n!!! note \"\"\n\n    Will be automatically reloaded if file modified since sing-box 1.10.0.\n\nFile path of rule-set.\n\n### Remote Fields\n\n#### url\n\n==Required==\n\nDownload URL of rule-set.\n\n#### download_detour\n\nTag of the outbound to download rule-set.\n\nDefault outbound will be used if empty.\n\n#### update_interval\n\nUpdate interval of rule-set.\n\n`1d` will be used if empty.\n"
  },
  {
    "path": "docs/configuration/rule-set/index.zh.md",
    "content": "!!! quote \"sing-box 1.10.0 中的更改\"\n\n    :material-plus: `type: inline`\n\n# 规则集\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n### 结构\n\n=== \"内联\"\n\n    !!! question \"自 sing-box 1.10.0 起\"\n\n    ```json\n    {\n      \"type\": \"inline\", // 可选\n      \"tag\": \"\",\n      \"rules\": []\n    }\n    ```\n\n=== \"本地文件\"\n\n    ```json\n    {\n      \"type\": \"local\",\n      \"tag\": \"\",\n      \"format\": \"source\", // or binary\n      \"path\": \"\"\n    }\n    ```\n\n=== \"远程文件\"\n\n    !!! info \"\"\n    \n        远程规则集将被缓存如果 `experimental.cache_file.enabled` 已启用。\n\n    ```json\n    {\n      \"type\": \"remote\",\n      \"tag\": \"\",\n      \"format\": \"source\", // or binary\n      \"url\": \"\",\n      \"download_detour\": \"\", // 可选\n      \"update_interval\": \"\" // 可选\n    }\n    ```\n\n### 字段\n\n#### type\n\n==必填==\n\n规则集类型， `local` 或 `remote`。\n\n#### tag\n\n==必填==\n\n规则集的标签。\n\n### 内联字段\n\n!!! question \"自 sing-box 1.10.0 起\"\n\n#### rules\n\n==必填==\n\n一组 [无头规则](./headless-rule/).\n\n### 本地或远程字段\n\n#### format\n\n==必填==\n\n规则集格式， `source` 或 `binary`。\n\n当 `path` 或 `url` 使用 `json` 或 `srs` 作为扩展名时可选。\n\n### 本地字段\n\n#### path\n\n==必填==\n\n!!! note \"\"\n\n    自 sing-box 1.10.0 起，文件更改时将自动重新加载。\n\n规则集的文件路径。\n\n### 远程字段\n\n#### url\n\n==必填==\n\n规则集的下载 URL。\n\n#### download_detour\n\n用于下载规则集的出站的标签。\n\n如果为空，将使用默认出站。\n\n#### update_interval\n\n规则集的更新间隔。\n\n默认使用 `1d`。\n"
  },
  {
    "path": "docs/configuration/rule-set/source-format.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: version `4`\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-plus: version `3`\n\n!!! quote \"Changes in sing-box 1.10.0\"\n\n    :material-plus: version `2`\n\n!!! question \"Since sing-box 1.8.0\"\n\n### Structure\n\n```json\n{\n  \"version\": 3,\n  \"rules\": []\n}\n```\n\n### Compile\n\nUse `sing-box rule-set compile [--output <file-name>.srs] <file-name>.json` to compile source to binary rule-set.\n\n### Fields\n\n#### version\n\n==Required==\n\nVersion of rule-set.\n\n* 1: sing-box 1.8.0: Initial rule-set version.\n* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.\n* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.\n* 4: sing-box 1.13.0: Added `network_interface_address` and `default_interface_address` rule items.\n\n#### rules\n\n==Required==\n\nList of [Headless Rule](../headless-rule/).\n"
  },
  {
    "path": "docs/configuration/rule-set/source-format.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: version `4`\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-plus: version `3`\n\n!!! quote \"sing-box 1.10.0 中的更改\"\n\n    :material-plus: version `2`\n\n!!! question \"自 sing-box 1.8.0 起\"\n\n### 结构\n\n```json\n{\n  \"version\": 3,\n  \"rules\": []\n}\n```\n\n### 编译\n\n使用 `sing-box rule-set compile [--output <file-name>.srs] <file-name>.json` 以编译源文件为二进制规则集。\n\n### 字段\n\n#### version\n\n==必填==\n\n规则集版本。\n\n* 1: sing-box 1.8.0: 初始规则集版本。\n* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。\n* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。\n* 4: sing-box 1.13.0: 添加了 `network_interface_address` 和 `default_interface_address` 规则项。\n\n#### rules\n\n==必填==\n\n一组 [无头规则](../headless-rule/).\n"
  },
  {
    "path": "docs/configuration/service/ccm.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.13.0\"\n\n# CCM\n\nCCM (Claude Code Multiplexer) service is a multiplexing service that allows you to access your local Claude Code subscription remotely through custom tokens.\n\nIt handles OAuth authentication with Claude's API on your local machine while allowing remote Claude Code to authenticate using Auth Tokens via the `ANTHROPIC_AUTH_TOKEN` environment variable.\n\n### Structure\n\n```json\n{\n  \"type\": \"ccm\",\n\n  ... // Listen Fields\n\n  \"credential_path\": \"\",\n  \"usages_path\": \"\",\n  \"users\": [],\n  \"headers\": {},\n  \"detour\": \"\",\n  \"tls\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### credential_path\n\nPath to the Claude Code OAuth credentials file.\n\nIf not specified, defaults to:\n- `$CLAUDE_CONFIG_DIR/.credentials.json` if `CLAUDE_CONFIG_DIR` environment variable is set\n- `~/.claude/.credentials.json` otherwise\n\nOn macOS, credentials are read from the system keychain first, then fall back to the file if unavailable.\n\nRefreshed tokens are automatically written back to the same location.\n\n#### usages_path\n\nPath to the file for storing aggregated API usage statistics.\n\nUsage tracking is disabled if not specified.\n\nWhen enabled, the service tracks and saves comprehensive statistics including:\n- Request counts\n- Token usage (input, output, cache read, cache creation)\n- Calculated costs in USD based on Claude API pricing\n\nStatistics are organized by model, context window (200k standard vs 1M premium), and optionally by user when authentication is enabled.\n\nThe statistics file is automatically saved every minute and upon service shutdown.\n\n#### users\n\nList of authorized users for token authentication.\n\nIf empty, no authentication is required.\n\nObject format:\n\n```json\n{\n  \"name\": \"\",\n  \"token\": \"\"\n}\n```\n\nObject fields:\n\n- `name`: Username identifier for tracking purposes.\n- `token`: Bearer token for authentication. Claude Code authenticates by setting the `ANTHROPIC_AUTH_TOKEN` environment variable to their token value.\n\n#### headers\n\nCustom HTTP headers to send to the Claude API.\n\nThese headers will override any existing headers with the same name.\n\n#### detour\n\nOutbound tag for connecting to the Claude API.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound).\n\n### Example\n\n#### Server\n\n```json\n{\n  \"services\": [\n    {\n      \"type\": \"ccm\",\n      \"listen\": \"0.0.0.0\",\n      \"listen_port\": 8080,\n      \"usages_path\": \"./claude-usages.json\",\n      \"users\": [\n        {\n          \"name\": \"alice\",\n          \"token\": \"ak-ccm-hello-world\"\n        },\n        {\n          \"name\": \"bob\",\n          \"token\": \"ak-ccm-hello-bob\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n#### Client\n\n```bash\nexport ANTHROPIC_BASE_URL=\"http://127.0.0.1:8080\"\nexport ANTHROPIC_AUTH_TOKEN=\"ak-ccm-hello-world\"\n\nclaude\n```\n"
  },
  {
    "path": "docs/configuration/service/ccm.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n# CCM\n\nCCM（Claude Code 多路复用器）服务是一个多路复用服务，允许您通过自定义令牌远程访问本地的 Claude Code 订阅。\n\n它在本地机器上处理与 Claude API 的 OAuth 身份验证，同时允许远程 Claude Code 通过 `ANTHROPIC_AUTH_TOKEN` 环境变量使用认证令牌进行身份验证。\n\n### 结构\n\n```json\n{\n  \"type\": \"ccm\",\n\n  ... // 监听字段\n\n  \"credential_path\": \"\",\n  \"usages_path\": \"\",\n  \"users\": [],\n  \"headers\": {},\n  \"detour\": \"\",\n  \"tls\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。\n\n### 字段\n\n#### credential_path\n\nClaude Code OAuth 凭据文件的路径。\n\n如果未指定，默认值为：\n- 如果设置了 `CLAUDE_CONFIG_DIR` 环境变量，则使用 `$CLAUDE_CONFIG_DIR/.credentials.json`\n- 否则使用 `~/.claude/.credentials.json`\n\n在 macOS 上，首先从系统钥匙串读取凭据，如果不可用则回退到文件。\n\n刷新的令牌会自动写回相同位置。\n\n#### usages_path\n\n用于存储聚合 API 使用统计信息的文件路径。\n\n如果未指定，使用跟踪将被禁用。\n\n启用后，服务会跟踪并保存全面的统计信息，包括：\n- 请求计数\n- 令牌使用量（输入、输出、缓存读取、缓存创建）\n- 基于 Claude API 定价计算的美元成本\n\n统计信息按模型、上下文窗口（200k 标准版 vs 1M 高级版）以及可选的用户（启用身份验证时）进行组织。\n\n统计文件每分钟自动保存一次，并在服务关闭时保存。\n\n#### users\n\n用于令牌身份验证的授权用户列表。\n\n如果为空，则不需要身份验证。\n\n对象格式：\n\n```json\n{\n  \"name\": \"\",\n  \"token\": \"\"\n}\n```\n\n对象字段：\n\n- `name`：用于跟踪的用户名标识符。\n- `token`：用于身份验证的 Bearer 令牌。Claude Code 通过设置 `ANTHROPIC_AUTH_TOKEN` 环境变量为其令牌值进行身份验证。\n\n#### headers\n\n发送到 Claude API 的自定义 HTTP 头。\n\n这些头会覆盖同名的现有头。\n\n#### detour\n\n用于连接 Claude API 的出站标签。\n\n#### tls\n\nTLS 配置，参阅 [TLS](/zh/configuration/shared/tls/#入站)。\n\n### 示例\n\n#### 服务端\n\n```json\n{\n  \"services\": [\n    {\n      \"type\": \"ccm\",\n      \"listen\": \"0.0.0.0\",\n      \"listen_port\": 8080,\n      \"usages_path\": \"./claude-usages.json\",\n      \"users\": [\n        {\n          \"name\": \"alice\",\n          \"token\": \"ak-ccm-hello-world\"\n        },\n        {\n          \"name\": \"bob\",\n          \"token\": \"ak-ccm-hello-bob\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n#### 客户端\n\n```bash\nexport ANTHROPIC_BASE_URL=\"http://127.0.0.1:8080\"\nexport ANTHROPIC_AUTH_TOKEN=\"ak-ccm-hello-world\"\n\nclaude\n```\n"
  },
  {
    "path": "docs/configuration/service/derp.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# DERP\n\nDERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).\n\n### Structure\n\n```json\n{\n  \"type\": \"derp\",\n  \n  ... // Listen Fields\n\n  \"tls\": {},\n  \"config_path\": \"\",\n  \"verify_client_endpoint\": [],\n  \"verify_client_url\": [],\n  \"home\": \"\",\n  \"mesh_with\": [],\n  \"mesh_psk\": \"\",\n  \"mesh_psk_file\": \"\",\n  \"stun\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound).\n\n#### config_path\n\n==Required==\n\nDerper configuration file path.\n\nExample: `derper.key`\n\n#### verify_client_endpoint\n\nTailscale endpoints tags to verify clients.\n\n#### verify_client_url\n\nURL to verify clients.\n\nObject format:\n\n```json\n{\n  \"url\": \"https://my-headscale.com/verify\",\n  \n  ... // Dial Fields\n}\n```\n\nSetting Array value to a string `__URL__` is equivalent to configuring:\n\n```json\n{ \"url\": __URL__ }\n```\n\n#### home\n\nWhat to serve at the root path. It may be left empty (the default, for a default homepage), `blank` for a blank page, or a URL to redirect to\n\n#### mesh_with\n\nMesh with other DERP servers.\n\nObject format:\n\n```json\n{\n  \"server\": \"\",\n  \"server_port\": \"\",\n  \"host\": \"\",\n  \"tls\": {},\n  \n  ... // Dial Fields\n}\n```\n\nObject fields:\n\n- `server`: **Required** DERP server address.\n- `server_port`: **Required** DERP server port.\n- `host`: Custom DERP hostname.\n- `tls`: [TLS](/configuration/shared/tls/#outbound)\n- `Dial Fields`: [Dial Fields](/configuration/shared/dial/)\n\n#### mesh_psk\n\nPre-shared key for DERP mesh.\n\n#### mesh_psk_file\n\nPre-shared key file for DERP mesh.\n\n#### stun\n\nSTUN server listen options.\n\nObject format:\n\n```json\n{\n  \"enabled\": true,\n  \n  ... // Listen Fields\n}\n```\n\nObject fields:\n\n- `enabled`: **Required** Enable STUN server.\n- `listen`: **Required** STUN server listen address, default to `::`.\n- `listen_port`: **Required** STUN server listen port, default to `3478`.\n- `other Listen Fields`: [Listen Fields](/configuration/shared/listen/)\n\nSetting `stun` value to a number `__PORT__` is equivalent to configuring:\n\n```json\n{ \"enabled\": true, \"listen_port\": __PORT__ }\n```\n"
  },
  {
    "path": "docs/configuration/service/derp.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# DERP\n\nDERP 服务是一个 Tailscale DERP 服务器，类似于 [derper](https://pkg.go.dev/tailscale.com/cmd/derper)。\n\n### 结构\n\n```json\n{\n  \"type\": \"derp\",\n\n  ... // 监听字段\n\n  \"tls\": {},\n  \"config_path\": \"\",\n  \"verify_client_endpoint\": [],\n  \"verify_client_url\": [],\n  \"home\": \"\",\n  \"mesh_with\": [],\n  \"mesh_psk\": \"\",\n  \"mesh_psk_file\": \"\",\n  \"stun\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。\n\n### 字段\n\n#### tls\n\nTLS 配置，参阅 [TLS](/zh/configuration/shared/tls/#入站)。\n\n#### config_path\n\n==必填==\n\nDerper 配置文件路径。\n\n示例：`derper.key`\n\n#### verify_client_endpoint\n\n用于验证客户端的 Tailscale 端点标签。\n\n#### verify_client_url\n\n用于验证客户端的 URL。\n\n对象格式：\n\n```json\n{\n  \"url\": \"https://my-headscale.com/verify\",\n\n  ... // 拨号字段\n}\n```\n\n将数组值设置为字符串 `__URL__` 等同于配置：\n\n```json\n{ \"url\": __URL__ }\n```\n\n#### home\n\n在根路径提供的内容。可以留空（默认值，显示默认主页）、`blank` 显示空白页面，或一个重定向的 URL。\n\n#### mesh_with\n\n与其他 DERP 服务器组网。\n\n对象格式：\n\n```json\n{\n  \"server\": \"\",\n  \"server_port\": \"\",\n  \"host\": \"\",\n  \"tls\": {},\n\n  ... // 拨号字段\n}\n```\n\n对象字段：\n\n- `server`：**必填** DERP 服务器地址。\n- `server_port`：**必填** DERP 服务器端口。\n- `host`：自定义 DERP 主机名。\n- `tls`：[TLS](/zh/configuration/shared/tls/#出站)\n- `拨号字段`：[拨号字段](/zh/configuration/shared/dial/)\n\n#### mesh_psk\n\nDERP 组网的预共享密钥。\n\n#### mesh_psk_file\n\nDERP 组网的预共享密钥文件。\n\n#### stun\n\nSTUN 服务器监听选项。\n\n对象格式：\n\n```json\n{\n  \"enabled\": true,\n\n  ... // 监听字段\n}\n```\n\n对象字段：\n\n- `enabled`：**必填** 启用 STUN 服务器。\n- `listen`：**必填** STUN 服务器监听地址，默认为 `::`。\n- `listen_port`：**必填** STUN 服务器监听端口，默认为 `3478`。\n- `其他监听字段`：[监听字段](/zh/configuration/shared/listen/)\n\n将 `stun` 值设置为数字 `__PORT__` 等同于配置：\n\n```json\n{ \"enabled\": true, \"listen_port\": __PORT__ }\n```"
  },
  {
    "path": "docs/configuration/service/index.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# Service\n\n### Structure\n\n```json\n{\n  \"services\": [\n    {\n      \"type\": \"\",\n      \"tag\": \"\"\n    }\n  ]\n}\n```\n\n### Fields\n\n| Type       | Format                 |\n|------------|------------------------|\n| `ccm`      | [CCM](./ccm)           |\n| `derp`     | [DERP](./derp)         |\n| `ocm`      | [OCM](./ocm)           |\n| `resolved` | [Resolved](./resolved) |\n| `ssm-api`  | [SSM API](./ssm-api)   |\n\n#### tag\n\nThe tag of the endpoint.\n"
  },
  {
    "path": "docs/configuration/service/index.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# 服务\n\n### 结构\n\n```json\n{\n  \"services\": [\n    {\n      \"type\": \"\",\n      \"tag\": \"\"\n    }\n  ]\n}\n```\n\n### 字段\n\n| 类型       | 格式                   |\n|-----------|------------------------|\n| `ccm`     | [CCM](./ccm)           |\n| `derp`    | [DERP](./derp)         |\n| `ocm`     | [OCM](./ocm)           |\n| `resolved`| [Resolved](./resolved) |\n| `ssm-api` | [SSM API](./ssm-api)   |\n\n#### tag\n\n端点的标签。"
  },
  {
    "path": "docs/configuration/service/ocm.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.13.0\"\n\n# OCM\n\nOCM (OpenAI Codex Multiplexer) service is a multiplexing service that allows you to access your local OpenAI Codex subscription remotely through custom tokens.\n\nIt handles OAuth authentication with OpenAI's API on your local machine while allowing remote clients to authenticate using custom tokens.\n\n### Structure\n\n```json\n{\n  \"type\": \"ocm\",\n\n  ... // Listen Fields\n\n  \"credential_path\": \"\",\n  \"usages_path\": \"\",\n  \"users\": [],\n  \"headers\": {},\n  \"detour\": \"\",\n  \"tls\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### credential_path\n\nPath to the OpenAI OAuth credentials file.\n\nIf not specified, defaults to:\n- `$CODEX_HOME/auth.json` if `CODEX_HOME` environment variable is set\n- `~/.codex/auth.json` otherwise\n\nRefreshed tokens are automatically written back to the same location.\n\n#### usages_path\n\nPath to the file for storing aggregated API usage statistics.\n\nUsage tracking is disabled if not specified.\n\nWhen enabled, the service tracks and saves comprehensive statistics including:\n- Request counts\n- Token usage (input, output, cached)\n- Calculated costs in USD based on OpenAI API pricing\n\nStatistics are organized by model and optionally by user when authentication is enabled.\n\nThe statistics file is automatically saved every minute and upon service shutdown.\n\n#### users\n\nList of authorized users for token authentication.\n\nIf empty, no authentication is required.\n\nObject format:\n\n```json\n{\n  \"name\": \"\",\n  \"token\": \"\"\n}\n```\n\nObject fields:\n\n- `name`: Username identifier for tracking purposes.\n- `token`: Bearer token for authentication. Clients authenticate by setting the `Authorization: Bearer <token>` header.\n\n#### headers\n\nCustom HTTP headers to send to the OpenAI API.\n\nThese headers will override any existing headers with the same name.\n\n#### detour\n\nOutbound tag for connecting to the OpenAI API.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound).\n\n### Example\n\n#### Server\n\n```json\n{\n  \"services\": [\n    {\n      \"type\": \"ocm\",\n      \"listen\": \"127.0.0.1\",\n      \"listen_port\": 8080\n    }\n  ]\n}\n```\n\n#### Client\n\nAdd to `~/.codex/config.toml`:\n\n```toml\n# profile = \"ocm\"                # set as default profile\n\n[model_providers.ocm]\nname = \"OCM Proxy\"\nbase_url = \"http://127.0.0.1:8080/v1\"\nsupports_websockets = true\n\n[profiles.ocm]\nmodel_provider = \"ocm\"\n# model = \"gpt-5.4\"              # if the latest model is not yet publicly released\n# model_reasoning_effort = \"xhigh\"\n```\n\nThen run:\n\n```bash\ncodex --profile ocm\n```\n\n### Example with Authentication\n\n#### Server\n\n```json\n{\n  \"services\": [\n    {\n      \"type\": \"ocm\",\n      \"listen\": \"0.0.0.0\",\n      \"listen_port\": 8080,\n      \"usages_path\": \"./codex-usages.json\",\n      \"users\": [\n        {\n          \"name\": \"alice\",\n          \"token\": \"sk-ocm-hello-world\"\n        },\n        {\n          \"name\": \"bob\",\n          \"token\": \"sk-ocm-hello-bob\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n#### Client\n\nAdd to `~/.codex/config.toml`:\n\n```toml\n# profile = \"ocm\"                # set as default profile\n\n[model_providers.ocm]\nname = \"OCM Proxy\"\nbase_url = \"http://127.0.0.1:8080/v1\"\nsupports_websockets = true\nexperimental_bearer_token = \"sk-ocm-hello-world\"\n\n[profiles.ocm]\nmodel_provider = \"ocm\"\n# model = \"gpt-5.4\"              # if the latest model is not yet publicly released\n# model_reasoning_effort = \"xhigh\"\n```\n\nThen run:\n\n```bash\ncodex --profile ocm\n```\n"
  },
  {
    "path": "docs/configuration/service/ocm.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n# OCM\n\nOCM（OpenAI Codex 多路复用器）服务是一个多路复用服务，允许您通过自定义令牌远程访问本地的 OpenAI Codex 订阅。\n\n它在本地机器上处理与 OpenAI API 的 OAuth 身份验证，同时允许远程客户端使用自定义令牌进行身份验证。\n\n### 结构\n\n```json\n{\n  \"type\": \"ocm\",\n\n  ... // 监听字段\n\n  \"credential_path\": \"\",\n  \"usages_path\": \"\",\n  \"users\": [],\n  \"headers\": {},\n  \"detour\": \"\",\n  \"tls\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。\n\n### 字段\n\n#### credential_path\n\nOpenAI OAuth 凭据文件的路径。\n\n如果未指定，默认值为：\n- 如果设置了 `CODEX_HOME` 环境变量，则使用 `$CODEX_HOME/auth.json`\n- 否则使用 `~/.codex/auth.json`\n\n刷新的令牌会自动写回相同位置。\n\n#### usages_path\n\n用于存储聚合 API 使用统计信息的文件路径。\n\n如果未指定，使用跟踪将被禁用。\n\n启用后，服务会跟踪并保存全面的统计信息，包括：\n- 请求计数\n- 令牌使用量（输入、输出、缓存）\n- 基于 OpenAI API 定价计算的美元成本\n\n统计信息按模型以及可选的用户（启用身份验证时）进行组织。\n\n统计文件每分钟自动保存一次，并在服务关闭时保存。\n\n#### users\n\n用于令牌身份验证的授权用户列表。\n\n如果为空，则不需要身份验证。\n\n对象格式：\n\n```json\n{\n  \"name\": \"\",\n  \"token\": \"\"\n}\n```\n\n对象字段：\n\n- `name`：用于跟踪的用户名标识符。\n- `token`：用于身份验证的 Bearer 令牌。客户端通过设置 `Authorization: Bearer <token>` 头进行身份验证。\n\n#### headers\n\n发送到 OpenAI API 的自定义 HTTP 头。\n\n这些头会覆盖同名的现有头。\n\n#### detour\n\n用于连接 OpenAI API 的出站标签。\n\n#### tls\n\nTLS 配置，参阅 [TLS](/zh/configuration/shared/tls/#入站)。\n\n### 示例\n\n#### 服务端\n\n```json\n{\n  \"services\": [\n    {\n      \"type\": \"ocm\",\n      \"listen\": \"127.0.0.1\",\n      \"listen_port\": 8080\n    }\n  ]\n}\n```\n\n#### 客户端\n\n在 `~/.codex/config.toml` 中添加：\n\n```toml\n# profile = \"ocm\"                # 设为默认配置\n\n\n[model_providers.ocm]\nname = \"OCM Proxy\"\nbase_url = \"http://127.0.0.1:8080/v1\"\nsupports_websockets = true\n\n[profiles.ocm]\nmodel_provider = \"ocm\"\n# model = \"gpt-5.4\"              # 如果最新模型尚未公开发布\n# model_reasoning_effort = \"xhigh\"\n```\n\n然后运行：\n\n```bash\ncodex --profile ocm\n```\n\n### 带身份验证的示例\n\n#### 服务端\n\n```json\n{\n  \"services\": [\n    {\n      \"type\": \"ocm\",\n      \"listen\": \"0.0.0.0\",\n      \"listen_port\": 8080,\n      \"usages_path\": \"./codex-usages.json\",\n      \"users\": [\n        {\n          \"name\": \"alice\",\n          \"token\": \"sk-ocm-hello-world\"\n        },\n        {\n          \"name\": \"bob\",\n          \"token\": \"sk-ocm-hello-bob\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n#### 客户端\n\n在 `~/.codex/config.toml` 中添加：\n\n```toml\n# profile = \"ocm\"                # 设为默认配置\n\n[model_providers.ocm]\nname = \"OCM Proxy\"\nbase_url = \"http://127.0.0.1:8080/v1\"\nsupports_websockets = true\nexperimental_bearer_token = \"sk-ocm-hello-world\"\n\n[profiles.ocm]\nmodel_provider = \"ocm\"\n# model = \"gpt-5.4\"              # 如果最新模型尚未公开发布\n# model_reasoning_effort = \"xhigh\"\n```\n\n然后运行：\n\n```bash\ncodex --profile ocm\n```\n"
  },
  {
    "path": "docs/configuration/service/resolved.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# Resolved\n\nResolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs\n(e.g. NetworkManager) and provide DNS resolution.\n\nSee also: [Resolved DNS Server](/configuration/dns/server/resolved/)\n\n### Structure\n\n```json\n{\n  \"type\": \"resolved\",\n  \n  ... // Listen Fields\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### listen\n\n==Required==\n\nListen address.\n\n`127.0.0.53` will be used by default.\n\n#### listen_port\n\n==Required==\n\nListen port.\n\n`53` will be used by default.\n"
  },
  {
    "path": "docs/configuration/service/resolved.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# Resolved\n\nResolved 服务是一个伪造的 systemd-resolved DBUS 服务，用于从其他程序\n（如 NetworkManager）接收 DNS 设置并提供 DNS 解析。\n\n另请参阅：[Resolved DNS 服务器](/zh/configuration/dns/server/resolved/)\n\n### 结构\n\n```json\n{\n  \"type\": \"resolved\",\n\n  ... // 监听字段\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。\n\n### 字段\n\n#### listen\n\n==必填==\n\n监听地址。\n\n默认使用 `127.0.0.53`。\n\n#### listen_port\n\n==必填==\n\n监听端口。\n\n默认使用 `53`。"
  },
  {
    "path": "docs/configuration/service/ssm-api.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"Since sing-box 1.12.0\"\n\n# SSM API\n\nSSM API service is a RESTful API server for managing Shadowsocks servers.\n\nSee https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2023-1-shadowsocks-server-management-api-v1.md\n\n### Structure\n\n```json\n{\n  \"type\": \"ssm-api\",\n  \n  ... // Listen Fields\n  \n  \"servers\": {},\n  \"cache_path\": \"\",\n  \"tls\": {}\n}\n```\n\n### Listen Fields\n\nSee [Listen Fields](/configuration/shared/listen/) for details.\n\n### Fields\n\n#### servers\n\n==Required==\n\nA mapping Object from HTTP endpoints to [Shadowsocks Inbound](/configuration/inbound/shadowsocks) tags.\n\nSelected Shadowsocks inbounds must be configured with [managed](/configuration/inbound/shadowsocks#managed) enabled.\n\nExample:\n\n```json\n{\n  \"servers\": {\n    \"/\": \"ss-in\"\n  }\n}\n```\n\n#### cache_path\n\nIf set, when the server is about to stop, traffic and user state will be saved to the specified JSON file\nto be restored on the next startup.\n\n#### tls\n\nTLS configuration, see [TLS](/configuration/shared/tls/#inbound).\n"
  },
  {
    "path": "docs/configuration/service/ssm-api.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n# SSM API\n\nSSM API 服务是一个用于管理 Shadowsocks 服务器的 RESTful API 服务器。\n\n参阅 https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2023-1-shadowsocks-server-management-api-v1.md\n\n### 结构\n\n```json\n{\n  \"type\": \"ssm-api\",\n\n  ... // 监听字段\n\n  \"servers\": {},\n  \"cache_path\": \"\",\n  \"tls\": {}\n}\n```\n\n### 监听字段\n\n参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。\n\n### 字段\n\n#### servers\n\n==必填==\n\n从 HTTP 端点到 [Shadowsocks 入站](/zh/configuration/inbound/shadowsocks) 标签的映射对象。\n\n选定的 Shadowsocks 入站必须配置启用 [managed](/zh/configuration/inbound/shadowsocks#managed)。\n\n示例：\n\n```json\n{\n  \"servers\": {\n    \"/\": \"ss-in\"\n  }\n}\n```\n\n#### cache_path\n\n如果设置，当服务器即将停止时，流量和用户状态将保存到指定的 JSON 文件中，\n以便在下次启动时恢复。\n\n#### tls\n\nTLS 配置，参阅 [TLS](/zh/configuration/shared/tls/#入站)。"
  },
  {
    "path": "docs/configuration/shared/dial.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive)  \n    :material-plus: [tcp_keep_alive](#tcp_keep_alive)  \n    :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval)  \n    :material-plus: [bind_address_no_port](#bind_address_no_port)\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-plus: [domain_resolver](#domain_resolver)  \n    :material-delete-clock: [domain_strategy](#domain_strategy)  \n    :material-plus: [netns](#netns)\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-plus: [network_strategy](#network_strategy)  \n    :material-alert: [fallback_delay](#fallback_delay)  \n    :material-alert: [network_type](#network_type)  \n    :material-alert: [fallback_network_type](#fallback_network_type)\n\n### Structure\n\n```json\n{\n  \"detour\": \"\",\n  \"bind_interface\": \"\",\n  \"inet4_bind_address\": \"\",\n  \"inet6_bind_address\": \"\",\n  \"bind_address_no_port\": false,\n  \"routing_mark\": 0,\n  \"reuse_addr\": false,\n  \"netns\": \"\",\n  \"connect_timeout\": \"\",\n  \"tcp_fast_open\": false,\n  \"tcp_multi_path\": false,\n  \"disable_tcp_keep_alive\": false,\n  \"tcp_keep_alive\": \"\",\n  \"tcp_keep_alive_interval\": \"\",\n  \"udp_fragment\": false,\n\n  \"domain_resolver\": \"\", // or {}\n  \"network_strategy\": \"\",\n  \"network_type\": [],\n  \"fallback_network_type\": [],\n  \"fallback_delay\": \"\",\n\n  // Deprecated\n  \n  \"domain_strategy\": \"\"\n}\n```\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n### Fields\n\n#### detour\n\nThe tag of the upstream outbound.\n\nIf enabled, all other fields will be ignored.\n\n#### bind_interface\n\nThe network interface to bind to.\n\n#### inet4_bind_address\n\nThe IPv4 address to bind to.\n\n#### inet6_bind_address\n\nThe IPv6 address to bind to.\n\n#### bind_address_no_port\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux.\n\nDo not reserve a port when binding to a source address.\n\nThis allows reusing the same source port for multiple connections if the full 4-tuple (source IP, source port, destination IP, destination port) remains unique.\n\n#### routing_mark\n\n!!! quote \"\"\n\n    Only supported on Linux.\n\nSet netfilter routing mark.\n\nIntegers (e.g. `1234`) and string hexadecimals (e.g. `\"0x1234\"`) are supported.\n\n#### reuse_addr\n\nReuse listener address.\n\n#### netns\n\n!!! question \"Since sing-box 1.12.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux.\n\nSet network namespace, name or path.\n\n#### connect_timeout\n\nConnect timeout, in golang's Duration format.\n\nA duration string is a possibly signed sequence of\ndecimal numbers, each with optional fraction and a unit suffix,\nsuch as \"300ms\", \"-1.5h\" or \"2h45m\".\nValid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".\n\n#### tcp_fast_open\n\nEnable TCP Fast Open.\n\n#### tcp_multi_path\n\n!!! warning \"\"\n\n    Go 1.21 required.\n\nEnable TCP Multi Path.\n\n#### disable_tcp_keep_alive\n\n!!! question \"Since sing-box 1.13.0\"\n\nDisable TCP keep alive.\n\n#### tcp_keep_alive\n\n!!! question \"Since sing-box 1.13.0\"\n\n    Default value changed from `10m` to `5m`.\n\nTCP keep alive initial period.\n\n`5m` will be used by default.\n\n#### tcp_keep_alive_interval\n\n!!! question \"Since sing-box 1.13.0\"\n\nTCP keep alive interval.\n\n`75s` will be used by default.\n\n#### udp_fragment\n\nEnable UDP fragmentation.\n\n#### domain_resolver\n\n!!! warning \"\"\n\n    `outbound` DNS rule items are deprecated and will be removed in sing-box 1.14.0, so this item will be required for outbound/endpoints using domain name in server address since sing-box 1.14.0.\n\n!!! info \"\"\n\n    `domain_resolver` or `route.default_domain_resolver` is optional when only one DNS server is configured.\n\nSet domain resolver to use for resolving domain names.\n\nThis option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field.\n\nSetting this option directly to a string is equivalent to setting `server` of this options.\n\n| Outbound/Endpoints | Effected domains         |\n|--------------------|--------------------------|\n| `direct`           | Domain in request        | \n| others             | Domain in server address |\n\n#### network_strategy\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.\n\nStrategy for selecting network interfaces.\n\nAvailable values:\n\n- `default` (default): Connect to default network or networks specified in `network_type` sequentially.\n- `hybrid`: Connect to all networks or networks specified in `network_type` concurrently.\n- `fallback`: Connect to default network or preferred networks specified in `network_type` concurrently, and try fallback networks when unavailable or timeout.\n\nFor fallback, when preferred interfaces fails or times out,\nit will enter a 15s fast fallback state (Connect to all preferred and fallback networks concurrently),\nand exit immediately if preferred networks recover.\n\nConflicts with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`.\n\n#### network_type\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.\n\nNetwork types to use when using `default` or `hybrid` network strategy or\npreferred network types to use when using `fallback` network strategy.\n\nAvailable values: `wifi`, `cellular`, `ethernet`, `other`.\n\nDevice's default network is used by default.\n\n#### fallback_network_type\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.\n\nFallback network types when preferred networks are unavailable or timeout when using `fallback` network strategy.\n\nAll other networks expect preferred are used by default.\n\n#### fallback_delay\n\n!!! question \"Since sing-box 1.11.0\"\n\n!!! quote \"\"\n\n    Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.\n\nThe length of time to wait before spawning a RFC 6555 Fast Fallback connection.\n\nFor `domain_strategy`, is the amount of time to wait for connection to succeed before assuming\nthat IPv4/IPv6 is misconfigured and falling back to other type of addresses.\n\nFor `network_strategy`, is the amount of time to wait for connection to succeed before falling\nback to other interfaces.\n\nOnly take effect when `domain_strategy` or `network_strategy` is set.\n\n`300ms` is used by default.\n\n#### domain_strategy\n\n!!! failure \"Deprecated in sing-box 1.12.0\"\n\n    `domain_strategy` is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver).\n\nAvailable values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.\n\nIf set, the requested domain name will be resolved to IP before connect.\n\n| Outbound | Effected domains         | Fallback Value                            |\n|----------|--------------------------|-------------------------------------------|\n| `direct` | Domain in request        | Take `inbound.domain_strategy` if not set | \n| others   | Domain in server address | /                                         |\n\n"
  },
  {
    "path": "docs/configuration/shared/dial.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive)  \n    :material-plus: [tcp_keep_alive](#tcp_keep_alive)  \n    :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval)  \n    :material-plus: [bind_address_no_port](#bind_address_no_port)\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-plus: [domain_resolver](#domain_resolver)  \n    :material-delete-clock: [domain_strategy](#domain_strategy)  \n    :material-plus: [netns](#netns)\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-plus: [network_strategy](#network_strategy)  \n    :material-alert: [fallback_delay](#fallback_delay)  \n    :material-alert: [network_type](#network_type)  \n    :material-alert: [fallback_network_type](#fallback_network_type)\n\n### 结构\n\n```json\n{\n  \"detour\": \"\",\n  \"bind_interface\": \"\",\n  \"inet4_bind_address\": \"\",\n  \"inet6_bind_address\": \"\",\n  \"bind_address_no_port\": false,\n  \"routing_mark\": 0,\n  \"reuse_addr\": false,\n  \"netns\": \"\",\n  \"connect_timeout\": \"\",\n  \"tcp_fast_open\": false,\n  \"tcp_multi_path\": false,\n  \"disable_tcp_keep_alive\": false,\n  \"tcp_keep_alive\": \"\",\n  \"tcp_keep_alive_interval\": \"\",\n  \"udp_fragment\": false,\n\n  \"domain_resolver\": \"\", // 或 {}\n  \"network_strategy\": \"\",\n  \"network_type\": [],\n  \"fallback_network_type\": [],\n  \"fallback_delay\": \"\",\n  \n  // 废弃的\n\n  \"domain_strategy\": \"\"\n}\n```\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签\n\n### 字段\n\n#### detour\n\n上游出站的标签。\n\n启用时，其他拨号字段将被忽略。\n\n#### bind_interface\n\n要绑定到的网络接口。\n\n#### inet4_bind_address\n\n要绑定的 IPv4 地址。\n\n#### inet6_bind_address\n\n要绑定的 IPv6 地址。\n\n#### bind_address_no_port\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux。\n\n绑定到源地址时不保留端口。\n\n这允许在完整的四元组（源 IP、源端口、目标 IP、目标端口）保持唯一的情况下，为多个连接复用同一源端口。\n\n#### routing_mark\n\n!!! quote \"\"\n\n    仅支持 Linux。\n\n设置 netfilter 路由标记。\n\n支持数字 (如 `1234`) 和十六进制字符串 (如 `\"0x1234\"`)。\n\n#### reuse_addr\n\n重用监听地址。\n\n#### netns\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux。\n\n设置网络命名空间，名称或路径。\n\n#### connect_timeout\n\n连接超时，采用 golang 的 Duration 格式。\n\n持续时间字符串是一个可能有符号的序列十进制数，每个都有可选的分数和单位后缀， 例如 \"300ms\"、\"-1.5h\" 或 \"2h45m\"。\n有效时间单位为 \"ns\"、\"us\"（或 \"µs\"）、\"ms\"、\"s\"、\"m\"、\"h\"。\n\n#### tcp_fast_open\n\n启用 TCP Fast Open。\n\n#### tcp_multi_path\n\n!!! warning \"\"\n\n    需要 Go 1.21。\n\n启用 TCP Multi Path。\n\n#### disable_tcp_keep_alive\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n禁用 TCP keep alive。\n\n#### tcp_keep_alive\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n    默认值从 `10m` 更改为 `5m`。\n\nTCP keep alive 初始周期。\n\n默认使用 `5m`。\n\n#### tcp_keep_alive_interval\n\n!!! question \"自 sing-box 1.13.0 起\"\n\nTCP keep alive 间隔。\n\n默认使用 `75s`。\n\n#### udp_fragment\n\n启用 UDP 分段。\n\n#### domain_resolver\n\n!!! warning \"\"\n\n    `outbound` DNS 规则项已弃用，且将在 sing-box 1.14.0 中被移除。因此，从 sing-box 1.14.0 版本开始，所有在服务器地址中使用域名的出站/端点均需配置此项。\n\n!!! info \"\"\n\n    当只有一个 DNS 服务器已配置时，`domain_resolver` 或 `route.default_domain_resolver` 是可选的。 \n\n用于设置解析域名的域名解析器。\n\n此选项的格式与 [路由 DNS 规则动作](/zh/configuration/dns/rule_action/#route) 相同，但不包含 `action` 字段。  \n\n若直接将此选项设置为字符串，则等同于设置该选项的 `server` 字段。\n\n| 出站/端点       | 受影响的域名                |\n|----------------|---------------------------|\n| `direct`       | 请求中的域名                | \n| 其他类型        | 服务器地址中的域名           |\n\n#### network_strategy\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 iOS 平台图形客户端中支持，并且需要 `route.auto_detect_interface`。\n\n用于选择网络接口的策略。\n\n可用值：\n\n- `default`（默认值）：按顺序连接默认网络或 `network_type` 中指定的网络。\n- `hybrid`：同时连接所有网络或 `network_type` 中指定的网络。\n- `fallback`：同时连接默认网络或 `network_type` 中指定的首选网络，当不可用或超时时尝试回退网络。\n\n对于回退模式，当首选接口失败或超时时，\n将进入15秒的快速回退状态（同时连接所有首选和回退网络），\n如果首选网络恢复，则立即退出。\n\n与 `bind_interface`, `bind_inet4_address` 和 `bind_inet6_address` 冲突。\n\n#### network_type\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 iOS 平台图形客户端中支持，并且需要 `route.auto_detect_interface`。\n\n当使用 `default` 或 `hybrid` 网络策略时要使用的网络类型，或当使用 `fallback` 网络策略时要使用的首选网络类型。\n\n可用值：`wifi`, `cellular`, `ethernet`, `other`。\n\n默认使用设备默认网络。\n\n#### fallback_network_type\n\n!!! question \"自 sing-box 1.11.0 起\"\n\n!!! quote \"\"\n\n    仅在 Android 与 iOS 平台图形客户端中支持，并且需要 `route.auto_detect_interface`。\n\n当使用 `fallback` 网络策略时，在首选网络不可用或超时的情况下要使用的回退网络类型。\n\n默认使用除首选网络外的所有其他网络。\n\n#### fallback_delay\n\n在生成 RFC 6555 快速回退连接之前等待的时间长度。\n\n对于 `domain_strategy`，是在假设之前等待 IPv6 成功的时间量如果设置了 \"prefer_ipv4\"，则 IPv6 配置错误并回退到 IPv4。\n\n对于 `network_strategy`，对于 `network_strategy`，是在回退到其他接口之前等待连接成功的时间。\n\n仅当 `domain_strategy` 或 `network_strategy` 已设置时生效。\n\n默认使用 `300ms`。\n\n#### domain_strategy\n\n!!! failure \"已在 sing-box 1.12.0 废弃\"\n\n    `domain_strategy` 已废弃且将在 sing-box 1.14.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移出站域名策略选项到域名解析器)。\n\n可选值：`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。\n\n如果设置，域名将在请求发出之前解析为 IP。\n\n| 出站       | 受影响的域名    | 默认回退值                     |\n|----------|-----------|---------------------------|\n| `direct` | 请求中的域名    | `inbound.domain_strategy` | \n| others   | 服务器地址中的域名 | /                         |"
  },
  {
    "path": "docs/configuration/shared/dns01_challenge.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [alidns.security_token](#security_token)  \n    :material-plus: [cloudflare.zone_token](#zone_token)  \n    :material-plus: [acmedns](#acmedns)\n\n### Structure\n\n```json\n{\n  \"provider\": \"\",\n\n  ... // Provider Fields\n}\n```\n\n### Provider Fields\n\n#### Alibaba Cloud DNS\n\n```json\n{\n  \"provider\": \"alidns\",\n  \"access_key_id\": \"\",\n  \"access_key_secret\": \"\",\n  \"region_id\": \"\",\n  \"security_token\": \"\"\n}\n```\n\n##### security_token\n\n!!! question \"Since sing-box 1.13.0\"\n\nThe Security Token for STS temporary credentials.\n\n#### Cloudflare\n\n```json\n{\n  \"provider\": \"cloudflare\",\n  \"api_token\": \"\",\n  \"zone_token\": \"\"\n}\n```\n\n##### zone_token\n\n!!! question \"Since sing-box 1.13.0\"\n\nOptional API token with `Zone:Read` permission.\n\nWhen provided, allows `api_token` to be scoped to a single zone.\n\n#### ACME-DNS\n\n!!! question \"Since sing-box 1.13.0\"\n\n```json\n{\n  \"provider\": \"acmedns\",\n  \"username\": \"\",\n  \"password\": \"\",\n  \"subdomain\": \"\",\n  \"server_url\": \"\"\n}\n```\n\nSee [ACME-DNS](https://github.com/joohoi/acme-dns) for details.\n"
  },
  {
    "path": "docs/configuration/shared/dns01_challenge.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [alidns.security_token](#security_token)  \n    :material-plus: [cloudflare.zone_token](#zone_token)  \n    :material-plus: [acmedns](#acmedns)\n\n### 结构\n\n```json\n{\n  \"provider\": \"\",\n\n  ... // 提供商字段\n}\n```\n\n### 提供商字段\n\n#### Alibaba Cloud DNS\n\n```json\n{\n  \"provider\": \"alidns\",\n  \"access_key_id\": \"\",\n  \"access_key_secret\": \"\",\n  \"region_id\": \"\",\n  \"security_token\": \"\"\n}\n```\n\n##### security_token\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n用于 STS 临时凭证的安全令牌。\n\n#### Cloudflare\n\n```json\n{\n  \"provider\": \"cloudflare\",\n  \"api_token\": \"\",\n  \"zone_token\": \"\"\n}\n```\n\n##### zone_token\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n具有 `Zone:Read` 权限的可选 API 令牌。\n\n提供后可将 `api_token` 限定到单个区域。\n\n#### ACME-DNS\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n```json\n{\n  \"provider\": \"acmedns\",\n  \"username\": \"\",\n  \"password\": \"\",\n  \"subdomain\": \"\",\n  \"server_url\": \"\"\n}\n```\n\n参阅 [ACME-DNS](https://github.com/joohoi/acme-dns)。\n"
  },
  {
    "path": "docs/configuration/shared/listen.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive)  \n    :material-alert: [tcp_keep_alive](#tcp_keep_alive)\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-plus: [netns](#netns)  \n    :material-plus: [bind_interface](#bind_interface)  \n    :material-plus: [routing_mark](#routing_mark)  \n    :material-plus: [reuse_addr](#reuse_addr)\n\n!!! quote \"Changes in sing-box 1.11.0\"\n\n    :material-delete-clock: [sniff](#sniff)  \n    :material-delete-clock: [sniff_override_destination](#sniff_override_destination)  \n    :material-delete-clock: [sniff_timeout](#sniff_timeout)  \n    :material-delete-clock: [domain_strategy](#domain_strategy)  \n    :material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping)\n\n### Structure\n\n```json\n{\n  \"listen\": \"\",\n  \"listen_port\": 0,\n  \"bind_interface\": \"\",\n  \"routing_mark\": 0,\n  \"reuse_addr\": false,\n  \"netns\": \"\",\n  \"tcp_fast_open\": false,\n  \"tcp_multi_path\": false,\n  \"disable_tcp_keep_alive\": false,\n  \"tcp_keep_alive\": \"\",\n  \"tcp_keep_alive_interval\": \"\",\n  \"udp_fragment\": false,\n  \"udp_timeout\": \"\",\n  \"detour\": \"\",\n\n  // Deprecated\n  \n  \"sniff\": false,\n  \"sniff_override_destination\": false,\n  \"sniff_timeout\": \"\",\n  \"domain_strategy\": \"\",\n  \"udp_disable_domain_unmapping\": false\n}\n```\n\n### Fields\n\n#### listen\n\n==Required==\n\nListen address.\n\n#### listen_port\n\nListen port.\n\n#### bind_interface\n\n!!! question \"Since sing-box 1.12.0\"\n\nThe network interface to bind to.\n\n#### routing_mark\n\n!!! question \"Since sing-box 1.12.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux.\n\nSet netfilter routing mark.\n\nIntegers (e.g. `1234`) and string hexadecimals (e.g. `\"0x1234\"`) are supported.\n\n#### reuse_addr\n\n!!! question \"Since sing-box 1.12.0\"\n\nReuse listener address.\n\n#### netns\n\n!!! question \"Since sing-box 1.12.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux.\n\nSet network namespace, name or path.\n\n#### tcp_fast_open\n\nEnable TCP Fast Open.\n\n#### tcp_multi_path\n\n!!! warning \"\"\n\n    Go 1.21 required.\n\nEnable TCP Multi Path.\n\n#### disable_tcp_keep_alive\n\n!!! question \"Since sing-box 1.13.0\"\n\nDisable TCP keep alive.\n\n#### tcp_keep_alive\n\n!!! question \"Since sing-box 1.13.0\"\n\n    Default value changed from `10m` to `5m`.\n\nTCP keep alive initial period.\n\n`5m` will be used by default.\n\n#### tcp_keep_alive_interval\n\nTCP keep alive interval.\n\n`75s` will be used by default.\n\n#### udp_fragment\n\nEnable UDP fragmentation.\n\n#### udp_timeout\n\nUDP NAT expiration time.\n\n`5m` will be used by default.\n\n#### detour\n\nIf set, connections will be forwarded to the specified inbound.\n\nRequires target inbound support, see [Injectable](/configuration/inbound/#fields).\n\n#### sniff\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).\n\nEnable sniffing.\n\nSee [Protocol Sniff](/configuration/route/sniff/) for details.\n\n#### sniff_override_destination\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Inbound fields are deprecated and will be removed in sing-box 1.13.0.\n\nOverride the connection destination address with the sniffed domain.\n\nIf the domain name is invalid (like tor), this will not work.\n\n#### sniff_timeout\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).\n\nTimeout for sniffing.\n\n`300ms` is used by default.\n\n#### domain_strategy\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).\n\nOne of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.\n\nIf set, the requested domain name will be resolved to IP before routing.\n\nIf `sniff_override_destination` is in effect, its value will be taken as a fallback.\n\n#### udp_disable_domain_unmapping\n\n!!! failure \"Deprecated in sing-box 1.11.0\"\n\n    Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).\n\nIf enabled, for UDP proxy requests addressed to a domain, \nthe original packet address will be sent in the response instead of the mapped domain.\n\nThis option is used for compatibility with clients that \ndo not support receiving UDP packets with domain addresses, such as Surge.\n"
  },
  {
    "path": "docs/configuration/shared/listen.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive)  \n    :material-alert: [tcp_keep_alive](#tcp_keep_alive)\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-plus: [netns](#netns)  \n    :material-plus: [bind_interface](#bind_interface)  \n    :material-plus: [routing_mark](#routing_mark)  \n    :material-plus: [reuse_addr](#reuse_addr)\n\n!!! quote \"sing-box 1.11.0 中的更改\"\n\n    :material-delete-clock: [sniff](#sniff)  \n    :material-delete-clock: [sniff_override_destination](#sniff_override_destination)  \n    :material-delete-clock: [sniff_timeout](#sniff_timeout)  \n    :material-delete-clock: [domain_strategy](#domain_strategy)  \n    :material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping)\n\n### 结构\n\n```json\n{\n  \"listen\": \"\",\n  \"listen_port\": 0,\n  \"bind_interface\": \"\",\n  \"routing_mark\": 0,\n  \"reuse_addr\": false,\n  \"netns\": \"\",\n  \"tcp_fast_open\": false,\n  \"tcp_multi_path\": false,\n  \"disable_tcp_keep_alive\": false,\n  \"tcp_keep_alive\": \"\",\n  \"tcp_keep_alive_interval\": \"\",\n  \"udp_fragment\": false,\n  \"udp_timeout\": \"\",\n  \"detour\": \"\",\n\n  // 废弃的\n  \n  \"sniff\": false,\n  \"sniff_override_destination\": false,\n  \"sniff_timeout\": \"\",\n  \"domain_strategy\": \"\",\n  \"udp_disable_domain_unmapping\": false\n}\n```\n\n### 字段\n\n#### listen\n\n==必填==\n\n监听地址。\n\n#### listen_port\n\n监听端口。\n\n#### bind_interface\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n要绑定到的网络接口。\n\n#### routing_mark\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux。\n\n设置 netfilter 路由标记。\n\n支持数字 (如 `1234`) 和十六进制字符串 (如 `\"0x1234\"`)。\n\n#### reuse_addr\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n重用监听地址。\n\n#### netns\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux。\n\n设置网络命名空间，名称或路径。\n\n#### tcp_fast_open\n\n启用 TCP Fast Open。\n\n#### tcp_multi_path\n\n!!! warning \"\"\n\n    需要 Go 1.21。\n\n启用 TCP Multi Path。\n\n#### disable_tcp_keep_alive\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n禁用 TCP keep alive。\n\n#### tcp_keep_alive\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n    默认值从 `10m` 更改为 `5m`。\n\nTCP keep alive 初始周期。\n\n默认使用 `5m`。\n\n#### tcp_keep_alive_interval\n\nTCP keep alive 间隔。\n\n默认使用 `75s`。\n\n#### udp_fragment\n\n启用 UDP 分段。\n\n#### udp_timeout\n\nUDP NAT 过期时间。\n\n默认使用 `5m`。\n\n#### detour\n\n如果设置，连接将被转发到指定的入站。\n\n需要目标入站支持，参阅 [注入支持](/zh/configuration/inbound/#字段)。\n\n#### sniff\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    入站字段已废弃且将在 sing-box 1.12.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作).\n\n启用协议探测。\n\n参阅 [协议探测](/zh/configuration/route/sniff/)\n\n#### sniff_override_destination\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    入站字段已废弃且将在 sing-box 1.12.0 中被移除。\n\n用探测出的域名覆盖连接目标地址。\n\n如果域名无效（如 Tor），将不生效。\n\n#### sniff_timeout\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    入站字段已废弃且将在 sing-box 1.12.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作).\n\n探测超时时间。\n\n默认使用 300ms。\n\n#### domain_strategy\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    入站字段已废弃且将在 sing-box 1.12.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作).\n\n可选值： `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。\n\n如果设置，请求的域名将在路由之前解析为 IP。\n\n如果 `sniff_override_destination` 生效，它的值将作为后备。\n\n#### udp_disable_domain_unmapping\n\n!!! failure \"已在 sing-box 1.11.0 废弃\"\n\n    入站字段已废弃且将在 sing-box 1.12.0 中被移除，参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作).\n\n如果启用，对于地址为域的 UDP 代理请求，将在响应中发送原始包地址而不是映射的域。\n\n此选项用于兼容不支持接收带有域地址的 UDP 包的客户端，如 Surge。\n"
  },
  {
    "path": "docs/configuration/shared/multiplex.md",
    "content": "### Inbound\n\n```json\n{\n  \"enabled\": true,\n  \"padding\": false,\n  \"brutal\": {}\n}\n```\n\n### Outbound\n\n```json\n{\n  \"enabled\": true,\n  \"protocol\": \"smux\",\n  \"max_connections\": 4,\n  \"min_streams\": 4,\n  \"max_streams\": 0,\n  \"padding\": false,\n  \"brutal\": {}\n}\n```\n\n\n### Inbound Fields\n\n#### enabled\n\nEnable multiplex support.\n\n#### padding\n\nIf enabled, non-padded connections will be rejected.\n\n#### brutal\n\nSee [TCP Brutal](/configuration/shared/tcp-brutal/) for details.\n\n### Outbound Fields\n\n#### enabled\n\nEnable multiplex.\n\n#### protocol\n\nMultiplex protocol.\n\n| Protocol | Description                        |\n|----------|------------------------------------|\n| smux     | https://github.com/xtaci/smux      |\n| yamux    | https://github.com/hashicorp/yamux |\n| h2mux    | https://golang.org/x/net/http2     |\n\nh2mux is used by default.\n\n#### max_connections\n\nMaximum connections.\n\nConflict with `max_streams`.\n\n#### min_streams\n\nMinimum multiplexed streams in a connection before opening a new connection.\n\nConflict with `max_streams`.\n\n#### max_streams\n\nMaximum multiplexed streams in a connection before opening a new connection.\n\nConflict with `max_connections` and `min_streams`.\n\n#### padding\n\n!!! info\n\n    Requires sing-box server version 1.3-beta9 or later.\n\nEnable padding.\n\n#### brutal\n\nSee [TCP Brutal](/configuration/shared/tcp-brutal/) for details.\n"
  },
  {
    "path": "docs/configuration/shared/multiplex.zh.md",
    "content": "### 入站\n\n```json\n{\n  \"enabled\": true,\n  \"padding\": false,\n  \"brutal\": {}\n}\n```\n\n### 出站\n\n```json\n{\n  \"enabled\": true,\n  \"protocol\": \"smux\",\n  \"max_connections\": 4,\n  \"min_streams\": 4,\n  \"max_streams\": 0,\n  \"padding\": false,\n  \"brutal\": {}\n}\n```\n\n### 入站字段\n\n#### enabled\n\n启用多路复用支持。\n\n#### padding\n\n如果启用，将拒绝非填充连接。\n\n#### brutal\n\n参阅 [TCP Brutal](/zh/configuration/shared/tcp-brutal/)。\n\n### 出站字段\n\n#### enabled\n\n启用多路复用。\n\n#### protocol\n\n多路复用协议\n\n| 协议    | 描述                                 |\n|-------|------------------------------------|\n| smux  | https://github.com/xtaci/smux      |\n| yamux | https://github.com/hashicorp/yamux |\n| h2mux | https://golang.org/x/net/http2     |\n\n默认使用 h2mux。\n\n#### max_connections\n\n最大连接数量。\n\n与 `max_streams` 冲突。\n\n#### min_streams\n\n在打开新连接之前，连接中的最小多路复用流数量。\n\n与 `max_streams` 冲突。\n\n#### max_streams\n\n在打开新连接之前，连接中的最大多路复用流数量。\n\n与 `max_connections` 和 `min_streams` 冲突。\n\n#### padding\n\n!!! info\n\n    需要 sing-box 服务器版本 1.3-beta9 或更高。\n\n启用填充。\n\n#### brutal\n\n参阅 [TCP Brutal](/zh/configuration/shared/tcp-brutal/)。"
  },
  {
    "path": "docs/configuration/shared/neighbor.md",
    "content": "---\nicon: material/lan\n---\n\n# Neighbor Resolution\n\nMatch LAN devices by MAC address and hostname using\n[`source_mac_address`](/configuration/route/rule/#source_mac_address) and\n[`source_hostname`](/configuration/route/rule/#source_hostname) rule items.\n\nNeighbor resolution is automatically enabled when these rule items exist.\nUse [`route.find_neighbor`](/configuration/route/#find_neighbor) to force enable it for logging without rules.\n\n## Linux\n\nWorks natively. No special setup required.\n\nHostname resolution requires DHCP lease files,\nautomatically detected from common DHCP servers (dnsmasq, odhcpd, ISC dhcpd, Kea).\nCustom paths can be set via [`route.dhcp_lease_files`](/configuration/route/#dhcp_lease_files).\n\n## Android\n\n!!! quote \"\"\n\n    Only supported in graphical clients.\n\nRequires Android 11 or above and ROOT.\n\nMust use [VPNHotspot](https://github.com/Mygod/VPNHotspot) to share the VPN connection.\nROM built-in features like \"Use VPN for connected devices\" can share VPN\nbut cannot provide MAC address or hostname information.\n\nSet **IP Masquerade Mode** to **None** in VPNHotspot settings.\n\nOnly route/DNS rules are supported. TUN include/exclude routes are not supported.\n\n### Hostname Visibility\n\nHostname is only visible in sing-box if it is visible in VPNHotspot.\nFor Apple devices, change **Private Wi-Fi Address** from **Rotating** to **Fixed** in the Wi-Fi settings\nof the connected network. Non-Apple devices are always visible.\n\n## macOS\n\nRequires the standalone version (macOS system extension).\nThe App Store version can share the VPN as a hotspot but does not support MAC address or hostname reading.\n\nSee [VPN Hotspot](/manual/misc/vpn-hotspot/#macos) for Internet Sharing setup.\n"
  },
  {
    "path": "docs/configuration/shared/neighbor.zh.md",
    "content": "---\nicon: material/lan\n---\n\n# 邻居解析\n\n通过\n[`source_mac_address`](/configuration/route/rule/#source_mac_address) 和\n[`source_hostname`](/configuration/route/rule/#source_hostname) 规则项匹配局域网设备的 MAC 地址和主机名。\n\n当这些规则项存在时，邻居解析自动启用。\n使用 [`route.find_neighbor`](/configuration/route/#find_neighbor) 可在没有规则时强制启用以输出日志。\n\n## Linux\n\n原生支持，无需特殊设置。\n\n主机名解析需要 DHCP 租约文件，\n自动从常见 DHCP 服务器（dnsmasq、odhcpd、ISC dhcpd、Kea）检测。\n可通过 [`route.dhcp_lease_files`](/configuration/route/#dhcp_lease_files) 设置自定义路径。\n\n## Android\n\n!!! quote \"\"\n\n    仅在图形客户端中支持。\n\n需要 Android 11 或以上版本和 ROOT。\n\n必须使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot) 共享 VPN 连接。\nROM 自带的「通过 VPN 共享连接」等功能可以共享 VPN，\n但无法提供 MAC 地址或主机名信息。\n\n在 VPNHotspot 设置中将 **IP 遮掩模式** 设为 **无**。\n\n仅支持路由/DNS 规则。不支持 TUN 的 include/exclude 路由。\n\n### 设备可见性\n\nMAC 地址和主机名仅在 VPNHotspot 中可见时 sing-box 才能读取。\n对于 Apple 设备，需要在所连接网络的 Wi-Fi 设置中将**私有无线局域网地址**从**轮替**改为**固定**。\n非 Apple 设备始终可见。\n\n## macOS\n\n需要独立版本（macOS 系统扩展）。\nApp Store 版本可以共享 VPN 热点但不支持 MAC 地址或主机名读取。\n\n参阅 [VPN 热点](/manual/misc/vpn-hotspot/#macos) 了解互联网共享设置。\n"
  },
  {
    "path": "docs/configuration/shared/pre-match.md",
    "content": "---\nicon: material/new-box\n---\n\n# Pre-match\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [bypass](#bypass)\n\nPre-match is rule matching that runs before the connection is established.\n\n### How it works\n\nWhen TUN receives a connection request, the connection has not yet been established,\nso no connection data can be read. In this phase, sing-box runs the routing rules in pre-match mode.\n\nSince connection data is unavailable, only actions that do not require connection data can be executed.\nWhen a rule matches an action that requires an established connection, pre-match stops at that rule.\n\n### Supported actions\n\n#### reject\n\nReject with TCP RST / ICMP unreachable.\n\nSee [reject](/configuration/route/rule_action/#reject) for details.\n\n#### route\n\nRoute ICMP connections to the specified outbound for direct reply.\n\nSee [route](/configuration/route/rule_action/#route) for details.\n\n#### bypass\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux with `auto_redirect` enabled.\n\nBypass sing-box and connect directly at kernel level.\n\nIf `outbound` is not specified, the rule only matches in pre-match from auto redirect,\nand will be skipped in other contexts.\n\nFor all other contexts, bypass with `outbound` behaves like `route` action.\n\nSee [bypass](/configuration/route/rule_action/#bypass) for details.\n"
  },
  {
    "path": "docs/configuration/shared/pre-match.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n# 预匹配\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [bypass](#bypass)\n\n预匹配是在连接建立之前运行的规则匹配。\n\n### 工作原理\n\n当 TUN 收到连接请求时，连接尚未建立，因此无法读取连接数据。在此阶段，sing-box 在预匹配模式下运行路由规则。\n\n由于连接数据不可用，只有不需要连接数据的动作才能执行。当规则匹配到需要已建立连接的动作时，预匹配将在该规则处停止。\n\n### 支持的动作\n\n#### reject\n\n以 TCP RST / ICMP 不可达拒绝。\n\n详情参阅 [reject](/zh/configuration/route/rule_action/#reject)。\n\n#### route\n\n将 ICMP 连接路由到指定出站以直接回复。\n\n详情参阅 [route](/zh/configuration/route/rule_action/#route)。\n\n#### bypass\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux，且需要启用 `auto_redirect`。\n\n在内核层面绕过 sing-box 直接连接。\n\n如果未指定 `outbound`，规则仅在来自 auto redirect 的预匹配中匹配，在其他场景中将被跳过。\n\n对于其他所有场景，指定了 `outbound` 的 bypass 行为与 `route` 相同。\n\n详情参阅 [bypass](/zh/configuration/route/rule_action/#bypass)。\n"
  },
  {
    "path": "docs/configuration/shared/tcp-brutal.md",
    "content": "### Server Requirements\n\n* Linux\n* `brutal` congestion control algorithm kernel module installed\n\nSee [tcp-brutal](https://github.com/apernet/tcp-brutal) for details.\n\n### Structure\n\n```json\n{\n  \"enabled\": true,\n  \"up_mbps\": 100,\n  \"down_mbps\": 100\n}\n```\n\n### Fields\n\n#### enabled\n\nEnable TCP Brutal congestion control algorithm。\n\n#### up_mbps, down_mbps\n\n==Required==\n\nUpload and download bandwidth, in Mbps."
  },
  {
    "path": "docs/configuration/shared/tcp-brutal.zh.md",
    "content": "### 服务器要求\n\n* Linux\n* `brutal` 拥塞控制算法内核模块已安装\n\n参阅 [tcp-brutal](https://github.com/apernet/tcp-brutal)。\n\n### 结构\n\n```json\n{\n  \"enabled\": true,\n  \"up_mbps\": 100,\n  \"down_mbps\": 100\n}\n```\n\n### 字段\n\n#### enabled\n\n启用 TCP Brutal 拥塞控制算法。\n\n#### up_mbps, down_mbps\n\n==必填==\n\n上传和下载带宽，以 Mbps 为单位。\n"
  },
  {
    "path": "docs/configuration/shared/tls.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: [kernel_tx](#kernel_tx)  \n    :material-plus: [kernel_rx](#kernel_rx)  \n    :material-plus: [curve_preferences](#curve_preferences)  \n    :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)  \n    :material-plus: [client_certificate](#client_certificate)  \n    :material-plus: [client_certificate_path](#client_certificate_path)  \n    :material-plus: [client_key](#client_key)  \n    :material-plus: [client_key_path](#client_key_path)  \n    :material-plus: [client_authentication](#client_authentication)  \n    :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)  \n    :material-plus: [ech.query_server_name](#query_server_name)\n\n!!! quote \"Changes in sing-box 1.12.0\"\n\n    :material-plus: [fragment](#fragment)  \n    :material-plus: [fragment_fallback_delay](#fragment_fallback_delay)  \n    :material-plus: [record_fragment](#record_fragment)  \n    :material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)  \n    :material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)\n\n!!! quote \"Changes in sing-box 1.10.0\"\n\n    :material-alert-decagram: [utls](#utls)\n\n### Inbound\n\n```json\n{\n  \"enabled\": true,\n  \"server_name\": \"\",\n  \"alpn\": [],\n  \"min_version\": \"\",\n  \"max_version\": \"\",\n  \"cipher_suites\": [],\n  \"curve_preferences\": [],\n  \"certificate\": [],\n  \"certificate_path\": \"\",\n  \"client_authentication\": \"\",\n  \"client_certificate\": [],\n  \"client_certificate_path\": [],\n  \"client_certificate_public_key_sha256\": [],\n  \"key\": [],\n  \"key_path\": \"\",\n  \"kernel_tx\": false,\n  \"kernel_rx\": false,\n  \"acme\": {\n    \"domain\": [],\n    \"data_directory\": \"\",\n    \"default_server_name\": \"\",\n    \"email\": \"\",\n    \"provider\": \"\",\n    \"disable_http_challenge\": false,\n    \"disable_tls_alpn_challenge\": false,\n    \"alternative_http_port\": 0,\n    \"alternative_tls_port\": 0,\n    \"external_account\": {\n      \"key_id\": \"\",\n      \"mac_key\": \"\"\n    },\n    \"dns01_challenge\": {}\n  },\n  \"ech\": {\n    \"enabled\": false,\n    \"key\": [],\n    \"key_path\": \"\",\n\n    // Deprecated\n    \n    \"pq_signature_schemes_enabled\": false,\n    \"dynamic_record_sizing_disabled\": false\n  },\n  \"reality\": {\n    \"enabled\": false,\n    \"handshake\": {\n      \"server\": \"google.com\",\n      \"server_port\": 443,\n\n      ... // Dial Fields\n    },\n    \"private_key\": \"UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc\",\n    \"short_id\": [\n      \"0123456789abcdef\"\n    ],\n    \"max_time_difference\": \"1m\"\n  }\n}\n```\n\n### Outbound\n\n```json\n{\n  \"enabled\": true,\n  \"disable_sni\": false,\n  \"server_name\": \"\",\n  \"insecure\": false,\n  \"alpn\": [],\n  \"min_version\": \"\",\n  \"max_version\": \"\",\n  \"cipher_suites\": [],\n  \"curve_preferences\": [],\n  \"certificate\": \"\",\n  \"certificate_path\": \"\",\n  \"certificate_public_key_sha256\": [],\n  \"client_certificate\": [],\n  \"client_certificate_path\": \"\",\n  \"client_key\": [],\n  \"client_key_path\": \"\",\n  \"fragment\": false,\n  \"fragment_fallback_delay\": \"\",\n  \"record_fragment\": false,\n  \"ech\": {\n    \"enabled\": false,\n    \"config\": [],\n    \"config_path\": \"\",\n    \"query_server_name\": \"\",\n\n    // Deprecated\n    \"pq_signature_schemes_enabled\": false,\n    \"dynamic_record_sizing_disabled\": false\n  },\n  \"utls\": {\n    \"enabled\": false,\n    \"fingerprint\": \"\"\n  },\n  \"reality\": {\n    \"enabled\": false,\n    \"public_key\": \"jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0\",\n    \"short_id\": \"0123456789abcdef\"\n  }\n}\n```\n\nTLS version values:\n\n* `1.0`\n* `1.1`\n* `1.2`\n* `1.3`\n\nCipher suite values:\n\n* `TLS_RSA_WITH_AES_128_CBC_SHA`\n* `TLS_RSA_WITH_AES_256_CBC_SHA`\n* `TLS_RSA_WITH_AES_128_GCM_SHA256`\n* `TLS_RSA_WITH_AES_256_GCM_SHA384`\n* `TLS_AES_128_GCM_SHA256`\n* `TLS_AES_256_GCM_SHA384`\n* `TLS_CHACHA20_POLY1305_SHA256`\n* `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`\n* `TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`\n* `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`\n* `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`\n* `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`\n* `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`\n* `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`\n* `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`\n* `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256`\n* `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256`\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n### Fields\n\n#### enabled\n\nEnable TLS.\n\n#### disable_sni\n\n==Client only==\n\nDo not send server name in ClientHello.\n\n#### server_name\n\nUsed to verify the hostname on the returned certificates unless insecure is given.\n\nIt is also included in the client's handshake to support virtual hosting unless it is an IP address.\n\n#### insecure\n\n==Client only==\n\nAccepts any server certificate.\n\n#### alpn\n\nList of supported application level protocols, in order of preference.\n\nIf both peers support ALPN, the selected protocol will be one from this list, and the connection will fail if there is\nno mutually supported protocol.\n\nSee [Application-Layer Protocol Negotiation](https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation).\n\n#### min_version\n\nThe minimum TLS version that is acceptable.\n\nBy default, TLS 1.2 is currently used as the minimum when acting as a\nclient, and TLS 1.0 when acting as a server.\n\n#### max_version\n\nThe maximum TLS version that is acceptable.\n\nBy default, the maximum version is currently TLS 1.3.\n\n#### cipher_suites\n\nList of enabled TLS 1.0–1.2 cipher suites. The order of the list is ignored.\nNote that TLS 1.3 cipher suites are not configurable.\n\nIf empty, a safe default list is used. The default cipher suites might change over time.\n\n#### curve_preferences\n\n!!! question \"Since sing-box 1.13.0\"\n\nSet of supported key exchange mechanisms. The order of the list is ignored, and key exchange mechanisms are chosen\nfrom this list using an internal preference order by Golang.\n\nAvailable values, also the default list:\n\n* `P256`\n* `P384`\n* `P521`\n* `X25519`\n* `X25519MLKEM768`\n\n#### certificate\n\nServer certificates chain line array, in PEM format.\n\n#### certificate_path\n\n!!! note \"\"\n\n    Will be automatically reloaded if file modified.\n\nThe path to server certificate chain, in PEM format.\n\n\n#### certificate_public_key_sha256\n\n!!! question \"Since sing-box 1.13.0\"\n\n==Client only==\n\nList of SHA-256 hashes of server certificate public keys, in base64 format.\n\nTo generate the SHA-256 hash for a certificate's public key, use the following commands:\n\n```bash\n# For a certificate file\nopenssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64\n\n# For a certificate from a remote server\necho | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64\n```\n\n#### client_certificate\n\n!!! question \"Since sing-box 1.13.0\"\n\n==Client only==\n\nClient certificate chain line array, in PEM format.\n\n#### client_certificate_path\n\n!!! question \"Since sing-box 1.13.0\"\n\n==Client only==\n\nThe path to client certificate chain, in PEM format.\n\n#### client_key\n\n!!! question \"Since sing-box 1.13.0\"\n\n==Client only==\n\nClient private key line array, in PEM format.\n\n#### client_key_path\n\n!!! question \"Since sing-box 1.13.0\"\n\n==Client only==\n\nThe path to client private key, in PEM format.\n\n#### key\n\n==Server only==\n\nThe server private key line array, in PEM format.\n\n#### key_path\n\n==Server only==\n\n!!! note \"\"\n\n    Will be automatically reloaded if file modified.\n\nThe path to the server private key, in PEM format.\n\n#### client_authentication\n\n!!! question \"Since sing-box 1.13.0\"\n\n==Server only==\n\nThe type of client authentication to use.\n\nAvailable values:\n\n* `no` (default)\n* `request`\n* `require-any`\n* `verify-if-given`\n* `require-and-verify`\n\nOne of `client_certificate`, `client_certificate_path`, or `client_certificate_public_key_sha256` is required\nif this option is set to `verify-if-given`, or `require-and-verify`.\n\n#### client_certificate\n\n!!! question \"Since sing-box 1.13.0\"\n\n==Server only==\n\nClient certificate chain line array, in PEM format.\n\n#### client_certificate_path\n\n!!! question \"Since sing-box 1.13.0\"\n\n==Server only==\n\n!!! note \"\"\n\n    Will be automatically reloaded if file modified.\n\nList of path to client certificate chain, in PEM format.\n\n#### client_certificate_public_key_sha256\n\n!!! question \"Since sing-box 1.13.0\"\n\n==Server only==\n\nList of SHA-256 hashes of client certificate public keys, in base64 format.\n\nTo generate the SHA-256 hash for a certificate's public key, use the following commands:\n\n```bash\n# For a certificate file\nopenssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64\n\n# For a certificate from a remote server\necho | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64\n```\n\n#### kernel_tx\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux 5.1+, use a newer kernel if possible.\n\n!!! quote \"\"\n\n    Only TLS 1.3 is supported.\n\n!!! warning \"\"\n\n    kTLS TX may only improve performance when `splice(2)` is available (both ends must be TCP or TLS without additional protocols after handshake); otherwise, it will definitely degrade performance.\n\nEnable kernel TLS transmit support.\n\n#### kernel_rx\n\n!!! question \"Since sing-box 1.13.0\"\n\n!!! quote \"\"\n\n    Only supported on Linux 5.1+, use a newer kernel if possible.\n\n!!! quote \"\"\n\n    Only TLS 1.3 is supported.\n\n!!! failure \"\"\n\n    kTLS RX will definitely degrade performance even if `splice(2)` is in use, so enabling it is not recommended.\n\nEnable kernel TLS receive support.\n\n## Custom TLS support\n\n!!! info \"QUIC support\"\n\n    Only ECH is supported in QUIC.\n\n#### utls\n\n==Client only==\n\n!!! failure \"Not Recommended\"\n\n    uTLS has had repeated fingerprinting vulnerabilities discovered by researchers.\n\n    uTLS is a Go library that attempts to imitate browser TLS fingerprints by copying\n    ClientHello structure. However, browsers use completely different TLS stacks\n    (Chrome uses BoringSSL, Firefox uses NSS) with distinct implementation behaviors\n    that cannot be replicated by simply copying the handshake format, making detection possible.\n    Additionally, the library lacks active maintenance and has poor code quality,\n    making it unsuitable for censorship circumvention.\n\n    For TLS fingerprint resistance, use [NaiveProxy](/configuration/inbound/naive/) instead.\n\nuTLS is a fork of \"crypto/tls\", which provides ClientHello fingerprinting resistance.\n\nAvailable fingerprint values:\n\n!!! warning \"Removed since sing-box 1.10.0\"\n\n    Some legacy chrome fingerprints have been removed and will fallback to chrome:\n\n    :material-close: chrome_psk  \n    :material-close: chrome_psk_shuffle  \n    :material-close: chrome_padding_psk_shuffle  \n    :material-close: chrome_pq  \n    :material-close: chrome_pq_psk\n\n* chrome\n* firefox\n* edge\n* safari\n* 360\n* qq\n* ios\n* android\n* random\n* randomized\n\nChrome fingerprint will be used if empty.\n\n### ECH Fields\n\nECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello\nmessage.\n\nThe ECH key and configuration can be generated by `sing-box generate ech-keypair`.\n\n#### pq_signature_schemes_enabled\n\n!!! failure \"Deprecated in sing-box 1.12.0\"\n\n    ECH support has been migrated to use stdlib in sing-box 1.12.0, which does not come with support for PQ signature schemes, so `pq_signature_schemes_enabled` has been deprecated and no longer works.\n\nEnable support for post-quantum peer certificate signature schemes.\n\n#### dynamic_record_sizing_disabled\n\n!!! failure \"Deprecated in sing-box 1.12.0\"\n\n    `dynamic_record_sizing_disabled` has nothing to do with ECH, was added by mistake, has been deprecated and no longer works.\n\nDisables adaptive sizing of TLS records.\n\nWhen true, the largest possible TLS record size is always used.  \nWhen false, the size of TLS records may be adjusted in an attempt to improve latency.\n\n#### key\n\n==Server only==\n\nECH key line array, in PEM format.\n\n#### key_path\n\n==Server only==\n\n!!! note \"\"\n\n    Will be automatically reloaded if file modified.\n\nThe path to ECH key, in PEM format.\n\n#### config\n\n==Client only==\n\nECH configuration line array, in PEM format.\n\nIf empty, load from DNS will be attempted.\n\n#### config_path\n\n==Client only==\n\nThe path to ECH configuration, in PEM format.\n\nIf empty, load from DNS will be attempted.\n\n#### query_server_name\n\n!!! question \"Since sing-box 1.13.0\"\n\n==Client only==\n\nOverrides the domain name used for ECH HTTPS record queries.\n\nIf empty, `server_name` is used for queries.\n\n#### fragment\n\n!!! question \"Since sing-box 1.12.0\"\n\n==Client only==\n\nFragment TLS handshakes to bypass firewalls.\n\nThis feature is intended to circumvent simple firewalls based on **plaintext packet matching**,\nand should not be used to circumvent real censorship.\n\nDue to poor performance, try `record_fragment` first, and only apply to server names known to be blocked.\n\nOn Linux, Apple platforms, (administrator privileges required) Windows,\nthe wait time can be automatically detected. Otherwise, it will fall back to\nwaiting for a fixed time specified by `fragment_fallback_delay`.\n\nIn addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,\nbecause the target is considered to be local or behind a transparent proxy.\n\n#### fragment_fallback_delay\n\n!!! question \"Since sing-box 1.12.0\"\n\n==Client only==\n\nThe fallback value used when TLS segmentation cannot automatically determine the wait time.\n\n`500ms` is used by default.\n\n#### record_fragment\n\n!!! question \"Since sing-box 1.12.0\"\n\n==Client only==\n\nFragment TLS handshake into multiple TLS records to bypass firewalls.\n\n### ACME Fields\n\n#### domain\n\nList of domain.\n\nACME will be disabled if empty.\n\n#### data_directory\n\nThe directory to store ACME data.\n\n`$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic` will be used if empty.\n\n#### default_server_name\n\nServer name to use when choosing a certificate if the ClientHello's ServerName field is empty.\n\n#### email\n\nThe email address to use when creating or selecting an existing ACME server account\n\n#### provider\n\nThe ACME CA provider to use.\n\n| Value                   | Provider      |\n|-------------------------|---------------|\n| `letsencrypt (default)` | Let's Encrypt |\n| `zerossl`               | ZeroSSL       |\n| `https://...`           | Custom        |\n\n#### disable_http_challenge\n\nDisable all HTTP challenges.\n\n#### disable_tls_alpn_challenge\n\nDisable all TLS-ALPN challenges\n\n#### alternative_http_port\n\nThe alternate port to use for the ACME HTTP challenge; if non-empty, this port will be used instead of 80 to spin up a\nlistener for the HTTP challenge.\n\n#### alternative_tls_port\n\nThe alternate port to use for the ACME TLS-ALPN challenge; the system must forward 443 to this port for challenge to\nsucceed.\n\n#### external_account\n\nEAB (External Account Binding) contains information necessary to bind or map an ACME account to some other account known\nby the CA.\n\nExternal account bindings are \"used to associate an ACME account with an existing account in a non-ACME system, such as\na CA customer database.\n\nTo enable ACME account binding, the CA operating the ACME server needs to provide the ACME client with a MAC key and a\nkey identifier, using some mechanism outside of ACME. §7.3.4\n\n#### external_account.key_id\n\nThe key identifier.\n\n#### external_account.mac_key\n\nThe MAC key.\n\n#### dns01_challenge\n\nACME DNS01 challenge field. If configured, other challenge methods will be disabled.\n\nSee [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/) for details.\n\n### Reality Fields\n\n#### handshake\n\n==Server only==\n\n==Required==\n\nHandshake server address and [Dial Fields](/configuration/shared/dial/).\n\n#### private_key\n\n==Server only==\n\n==Required==\n\nPrivate key, generated by `sing-box generate reality-keypair`.\n\n#### public_key\n\n==Client only==\n\n==Required==\n\nPublic key, generated by `sing-box generate reality-keypair`.\n\n#### short_id\n\n==Required==\n\nA hexadecimal string with zero to eight digits.\n\n#### max_time_difference\n\n==Server only==\n\nThe maximum time difference between the server and the client.\n\nCheck disabled if empty."
  },
  {
    "path": "docs/configuration/shared/tls.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: [kernel_tx](#kernel_tx)  \n    :material-plus: [kernel_rx](#kernel_rx)  \n    :material-plus: [curve_preferences](#curve_preferences)  \n    :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)  \n    :material-plus: [client_certificate](#client_certificate)  \n    :material-plus: [client_certificate_path](#client_certificate_path)  \n    :material-plus: [client_key](#client_key)  \n    :material-plus: [client_key_path](#client_key_path)  \n    :material-plus: [client_authentication](#client_authentication)  \n    :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)  \n    :material-plus: [ech.query_server_name](#query_server_name)\n\n!!! quote \"sing-box 1.12.0 中的更改\"\n\n    :material-plus: [fragment](#fragment)  \n    :material-plus: [fragment_fallback_delay](#fragment_fallback_delay)  \n    :material-plus: [record_fragment](#record_fragment)  \n    :material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)  \n    :material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)\n\n!!! quote \"sing-box 1.10.0 中的更改\"\n\n    :material-alert-decagram: [utls](#utls)\n\n### 入站\n\n```json\n{\n  \"enabled\": true,\n  \"server_name\": \"\",\n  \"alpn\": [],\n  \"min_version\": \"\",\n  \"max_version\": \"\",\n  \"cipher_suites\": [],\n  \"curve_preferences\": [],\n  \"certificate\": [],\n  \"certificate_path\": \"\",\n  \"client_authentication\": \"\",\n  \"client_certificate\": [],\n  \"client_certificate_path\": [],\n  \"client_certificate_public_key_sha256\": [],\n  \"key\": [],\n  \"key_path\": \"\",\n  \"kernel_tx\": false,\n  \"kernel_rx\": false,\n  \"acme\": {\n    \"domain\": [],\n    \"data_directory\": \"\",\n    \"default_server_name\": \"\",\n    \"email\": \"\",\n    \"provider\": \"\",\n    \"disable_http_challenge\": false,\n    \"disable_tls_alpn_challenge\": false,\n    \"alternative_http_port\": 0,\n    \"alternative_tls_port\": 0,\n    \"external_account\": {\n      \"key_id\": \"\",\n      \"mac_key\": \"\"\n    },\n    \"dns01_challenge\": {}\n  },\n  \"ech\": {\n    \"enabled\": false,\n    \"key\": [],\n    \"key_path\": \"\",\n\n    // 废弃的\n    \n    \"pq_signature_schemes_enabled\": false,\n    \"dynamic_record_sizing_disabled\": false\n  },\n  \"reality\": {\n    \"enabled\": false,\n    \"handshake\": {\n      \"server\": \"google.com\",\n      \"server_port\": 443,\n      \n      ... // 拨号字段\n    },\n    \"private_key\": \"UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc\",\n    \"short_id\": [\n      \"0123456789abcdef\"\n    ],\n    \"max_time_difference\": \"1m\"\n  }\n}\n```\n\n### 出站\n\n```json\n{\n  \"enabled\": true,\n  \"disable_sni\": false,\n  \"server_name\": \"\",\n  \"insecure\": false,\n  \"alpn\": [],\n  \"min_version\": \"\",\n  \"max_version\": \"\",\n  \"cipher_suites\": [],\n  \"curve_preferences\": [],\n  \"certificate\": \"\",\n  \"certificate_path\": \"\",\n  \"certificate_public_key_sha256\": [],\n  \"client_certificate\": [],\n  \"client_certificate_path\": \"\",\n  \"client_key\": [],\n  \"client_key_path\": \"\",\n  \"fragment\": false,\n  \"fragment_fallback_delay\": \"\",\n  \"record_fragment\": false,\n  \"ech\": {\n    \"enabled\": false,\n    \"config\": [],\n    \"config_path\": \"\",\n    \"query_server_name\": \"\",\n\n    // 废弃的\n    \"pq_signature_schemes_enabled\": false,\n    \"dynamic_record_sizing_disabled\": false\n  },\n  \"utls\": {\n    \"enabled\": false,\n    \"fingerprint\": \"\"\n  },\n  \"reality\": {\n    \"enabled\": false,\n    \"public_key\": \"jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0\",\n    \"short_id\": \"0123456789abcdef\"\n  }\n}\n```\n\nTLS 版本值：\n\n* `1.0`\n* `1.1`\n* `1.2`\n* `1.3`\n\n密码套件值：\n\n* `TLS_RSA_WITH_AES_128_CBC_SHA`\n* `TLS_RSA_WITH_AES_256_CBC_SHA`\n* `TLS_RSA_WITH_AES_128_GCM_SHA256`\n* `TLS_RSA_WITH_AES_256_GCM_SHA384`\n* `TLS_AES_128_GCM_SHA256`\n* `TLS_AES_256_GCM_SHA384`\n* `TLS_CHACHA20_POLY1305_SHA256`\n* `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`\n* `TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`\n* `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`\n* `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`\n* `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`\n* `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`\n* `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`\n* `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`\n* `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256`\n* `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256`\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签\n\n### 字段\n\n#### enabled\n\n启用 TLS\n\n#### disable_sni\n\n==仅客户端==\n\n不要在 ClientHello 中发送服务器名称.\n\n#### server_name\n\n用于验证返回证书上的主机名，除非设置不安全。\n\n它还包含在 ClientHello 中以支持虚拟主机，除非它是 IP 地址。\n\n#### insecure\n\n==仅客户端==\n\n接受任何服务器证书。\n\n#### alpn\n\n支持的应用层协议协商列表，按优先顺序排列。\n\n如果两个对等点都支持 ALPN，则选择的协议将是此列表中的一个，如果没有相互支持的协议则连接将失败。\n\n参阅 [Application-Layer Protocol Negotiation](https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation)。\n\n#### min_version\n\n可接受的最低 TLS 版本。\n\n默认情况下，当前使用 TLS 1.2 作为客户端的最低要求。作为服务器时使用 TLS 1.0。\n\n#### max_version\n\n可接受的最大 TLS 版本。\n\n默认情况下，当前最高版本为 TLS 1.3。\n\n#### cipher_suites\n\n启用的 TLS 1.0–1.2 密码套件列表。列表的顺序被忽略。请注意，TLS 1.3 的密码套件是不可配置的。\n\n如果为空，则使用安全的默认列表。默认密码套件可能会随着时间的推移而改变。\n\n#### curve_preferences\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n支持的密钥交换机制集合。列表的顺序被忽略，密钥交换机制通过 Golang 的内部偏好顺序从此列表中选择。\n\n可用值，同时也是默认列表：\n\n* `P256`\n* `P384`\n* `P521`\n* `X25519`\n* `X25519MLKEM768`\n\n#### certificate\n\n服务器证书链行数组，PEM 格式。\n\n#### certificate_path\n\n!!! note \"\"\n\n    文件更改时将自动重新加载。\n\n服务器证书链路径，PEM 格式。\n\n#### certificate_public_key_sha256\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n==仅客户端==\n\n服务器证书公钥的 SHA-256 哈希列表，base64 格式。\n\n要生成证书公钥的 SHA-256 哈希，请使用以下命令：\n\n```bash\n# 对于证书文件\nopenssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64\n\n# 对于远程服务器的证书\necho | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64\n```\n\n#### client_certificate\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n==仅客户端==\n\n客户端证书链行数组，PEM 格式。\n\n#### client_certificate_path\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n==仅客户端==\n\n客户端证书链路径，PEM 格式。\n\n#### client_key\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n==仅客户端==\n\n客户端私钥行数组，PEM 格式。\n\n#### client_key_path\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n==仅客户端==\n\n客户端私钥路径，PEM 格式。\n\n#### key\n\n==仅服务器==\n\n!!! note \"\"\n\n    文件更改时将自动重新加载。\n\n服务器 PEM 私钥行数组。\n\n#### key_path\n\n==仅服务器==\n\n!!! note \"\"\n\n    文件更改时将自动重新加载。\n\n服务器私钥路径，PEM 格式。\n\n#### client_authentication\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n==仅服务器==\n\n要使用的客户端身份验证类型。\n\n可用值：\n\n* `no`（默认）\n* `request`\n* `require-any`\n* `verify-if-given`\n* `require-and-verify`\n\n如果此选项设置为 `verify-if-given` 或 `require-and-verify`，\n则需要 `client_certificate`、`client_certificate_path` 或 `client_certificate_public_key_sha256` 中的一个。\n\n#### client_certificate\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n==仅服务器==\n\n客户端证书链行数组，PEM 格式。\n\n#### client_certificate_path\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n==仅服务器==\n\n!!! note \"\"\n\n    文件更改时将自动重新加载。\n\n客户端证书链路径列表，PEM 格式。\n\n#### client_certificate_public_key_sha256\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n==仅服务器==\n\n客户端证书公钥的 SHA-256 哈希列表，base64 格式。\n\n要生成证书公钥的 SHA-256 哈希，请使用以下命令：\n\n```bash\n# 对于证书文件\nopenssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64\n\n# 对于远程服务器的证书\necho | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64\n```\n\n#### kernel_tx\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux 5.1+，如果可能，使用较新的内核。\n\n!!! quote \"\"\n\n    仅支持 TLS 1.3。\n\n!!! warning \"\"\n\n    kTLS TX 仅当 `splice(2)` 可用时（两端经过握手后必须为没有附加协议的 TCP 或 TLS）才能提高性能；否则肯定会降低性能。\n\n启用内核 TLS 发送支持。\n\n#### kernel_rx\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n!!! quote \"\"\n\n    仅支持 Linux 5.1+，如果可能，使用较新的内核。\n\n!!! quote \"\"\n\n    仅支持 TLS 1.3。\n\n!!! failure \"\"\n\n    即使使用 `splice(2)`，kTLS RX 也肯定会降低性能，因此不建议启用。\n\n启用内核 TLS 接收支持。\n\n## 自定义 TLS 支持\n\n!!! info \"QUIC 支持\"\n\n    只有 ECH 在 QUIC 中被支持.\n\n#### utls\n\n==仅客户端==\n\n!!! failure \"不推荐\"\n\n    uTLS 已被研究人员多次发现其指纹可被识别的漏洞。\n\n    uTLS 是一个试图通过复制 ClientHello 结构来模仿浏览器 TLS 指纹的 Go 库。\n    然而，浏览器使用完全不同的 TLS 实现（Chrome 使用 BoringSSL，Firefox 使用 NSS），\n    其实现行为无法通过简单复制握手格式来复现，其行为细节必然存在差异，使得检测成为可能。\n    此外，此库缺乏积极维护，且代码质量较差，不建议用于反审查场景。\n\n    如需 TLS 指纹抵抗，请改用 [NaiveProxy](/zh/configuration/inbound/naive/)。\n\nuTLS 是 \"crypto/tls\" 的一个分支，它提供了 ClientHello 指纹识别阻力。\n\n可用的指纹值：\n\n!!! warning \"已在 sing-box 1.10.0 移除\"\n\n    一些旧 chrome 指纹已被删除，并将会退到 chrome：\n\n    :material-close: chrome_psk  \n    :material-close: chrome_psk_shuffle  \n    :material-close: chrome_padding_psk_shuffle  \n    :material-close: chrome_pq  \n    :material-close: chrome_pq_psk\n\n* chrome\n* firefox\n* edge\n* safari\n* 360\n* qq\n* ios\n* android\n* random\n* randomized\n\n默认使用 chrome 指纹。\n\n### ECH 字段\n\nECH (Encrypted Client Hello) 是一个 TLS 扩展，它允许客户端加密其 ClientHello 的第一部分信息。\n\nECH 密钥和配置可以通过 `sing-box generate ech-keypair` 生成。\n\n#### pq_signature_schemes_enabled\n\n!!! failure \"已在 sing-box 1.12.0 废弃\"\n\n    ECH 支持已在 sing-box 1.12.0 迁移至使用标准库，但标准库不支持后量子对等证书签名方案，因此 `pq_signature_schemes_enabled` 已被弃用且不再工作。\n\n启用对后量子对等证书签名方案的支持。\n\n#### dynamic_record_sizing_disabled\n\n!!! failure \"已在 sing-box 1.12.0 废弃\"\n\n    `dynamic_record_sizing_disabled` 与 ECH 无关，是错误添加的，现已弃用且不再工作。\n\n禁用 TLS 记录的自适应大小调整。\n\n当为 true 时，总是使用最大可能的 TLS 记录大小。\n当为 false 时，可能会调整 TLS 记录的大小以尝试改善延迟。\n\n#### key\n\n==仅服务器==\n\nECH 密钥行数组，PEM 格式。\n\n#### key_path\n\n==仅服务器==\n\n!!! note \"\"\n\n    文件更改时将自动重新加载。\n\nECH 密钥路径，PEM 格式。\n\n#### config\n\n==仅客户端==\n\nECH 配置行数组，PEM 格式。\n\n如果为空，将尝试从 DNS 加载。\n\n#### config_path\n\n==仅客户端==\n\nECH 配置路径，PEM 格式。\n\n如果为空，将尝试从 DNS 加载。\n\n#### query_server_name\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n==仅客户端==\n\n覆盖用于 ECH HTTPS 记录查询的域名。\n\n如果为空，使用 `server_name` 查询。\n\n#### fragment\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n==仅客户端==\n\n通过分段 TLS 握手数据包来绕过防火墙。\n\n此功能旨在规避基于**明文数据包匹配**的简单防火墙，不应该用于规避真正的审查。\n\n由于性能不佳，请首先尝试 `record_fragment`，且仅应用于已知被阻止的服务器名称。\n\n在 Linux、Apple 平台和（需要管理员权限的）Windows 系统上，\n可以自动检测等待时间。否则，将回退到\n等待 `fragment_fallback_delay` 指定的固定时间。\n\n此外，如果实际等待时间少于 20ms，也会回退到等待固定时间，\n因为目标被认为是本地的或在透明代理后面。\n\n#### fragment_fallback_delay\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n==仅客户端==\n\n当 TLS 分段无法自动确定等待时间时使用的回退值。\n\n默认使用 `500ms`。\n\n#### record_fragment\n\n!!! question \"自 sing-box 1.12.0 起\"\n\n==仅客户端==\n\n将 TLS 握手分段为多个 TLS 记录以绕过防火墙。\n\n### ACME 字段\n\n#### domain\n\n域名列表。\n\n如果为空则禁用 ACME。\n\n#### data_directory\n\nACME 数据存储目录。\n\n如果为空则使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`。\n\n#### default_server_name\n\n如果 ClientHello 的 ServerName 字段为空，则选择证书时要使用的服务器名称。\n\n#### email\n\n创建或选择现有 ACME 服务器帐户时使用的电子邮件地址。\n\n#### provider\n\n要使用的 ACME CA 供应商。\n\n| 值                  | 供应商           |\n|--------------------|---------------|\n| `letsencrypt (默认)` | Let's Encrypt |\n| `zerossl`          | ZeroSSL       |\n| `https://...`      | 自定义           |\n\n#### disable_http_challenge\n\n禁用所有 HTTP 质询。\n\n#### disable_tls_alpn_challenge\n\n禁用所有 TLS-ALPN 质询。\n\n#### alternative_http_port\n\n用于 ACME HTTP 质询的备用端口；如果非空，将使用此端口而不是 80 来启动 HTTP 质询的侦听器。\n\n#### alternative_tls_port\n\n用于 ACME TLS-ALPN 质询的备用端口； 系统必须将 443 转发到此端口以使质询成功。\n\n#### external_account\n\nEAB（外部帐户绑定）包含将 ACME 帐户绑定或映射到 CA 已知的其他帐户所需的信息。\n\n外部帐户绑定\"用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联，例如 CA 客户数据库。\n\n为了启用 ACME 帐户绑定，运行 ACME 服务器的 CA 需要使用 ACME 之外的某种机制向 ACME 客户端提供 MAC 密钥和密钥标识符。§7.3.4\n\n#### external_account.key_id\n\n密钥标识符。\n\n#### external_account.mac_key\n\nMAC 密钥。\n\n#### dns01_challenge\n\nACME DNS01 验证字段。如果配置，将禁用其他验证方法。\n\n参阅 [DNS01 验证字段](/zh/configuration/shared/dns01_challenge/)。\n\n### Reality 字段\n\n#### handshake\n\n==仅服务器==\n\n==必填==\n\n握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。\n\n#### private_key\n\n==仅服务器==\n\n==必填==\n\n私钥，由 `sing-box generate reality-keypair` 生成。\n\n#### public_key\n\n==仅客户端==\n\n==必填==\n\n公钥，由 `sing-box generate reality-keypair` 生成。\n\n#### short_id\n\n==必填==\n\n一个零到八位的十六进制字符串。\n\n#### max_time_difference\n\n==仅服务器==\n\n服务器和客户端之间的最大时间差。\n\n如果为空则禁用检查。\n"
  },
  {
    "path": "docs/configuration/shared/udp-over-tcp.md",
    "content": "!!! warning \"\"\n\n    It's a proprietary protocol created by SagerNet, not part of shadowsocks.\n\nThe UDP over TCP protocol is used to transmit UDP packets in TCP.\n\n### Structure\n\n```json\n{\n  \"enabled\": true,\n  \"version\": 2\n}\n```\n\n!!! info \"\"\n\n    The structure can be replaced with a boolean value when the version is not specified.\n\n### Fields\n\n#### enabled\n\nEnable the UDP over TCP protocol.\n\n#### version\n\nThe protocol version, `1` or `2`.\n\n2 is used by default.\n\n### Application support\n\n| Project      | UoT v1               | UoT v2               |\n|--------------|----------------------|----------------------|\n| sing-box     | v0 (2022/08/11)      | v1.2-beta9           |\n| Clash.Meta   | v1.12.0 (2022/07/02) | v1.14.3 (2023/03/31) |\n| Shadowrocket | v2.2.12 (2022/08/13) | /                    |\n\n### Protocol details\n\n#### Protocol version 1\n\nThe client requests the magic address to the upper layer proxy protocol to indicate the request: `sp.udp-over-tcp.arpa`\n\n#### Stream format\n\n| ATYP | address  | port  | length | data     |\n|------|----------|-------|--------|----------|\n| u8   | variable | u16be | u16be  | variable |\n\n**ATYP / address / port**: Uses the SOCKS address format, but with different address types:\n\n| ATYP   | Address type |\n|--------|--------------|\n| `0x00` | IPv4 Address |\n| `0x01` | IPv6 Address |\n| `0x02` | Domain Name  |\n\n#### Protocol version 2\n\nProtocol version 2 uses a new magic address: `sp.v2.udp-over-tcp.arpa`\n\n##### Request format\n\n| isConnect | ATYP | address  | port  |\n|-----------|------|----------|-------|\n| u8        | u8   | variable | u16be |\n\n**isConnect**: Set to 1 to indicates that the stream uses the connect format, 0 to disable.\n\n**ATYP / address / port**: Request destination, uses the SOCKS address format.\n\n##### Connect stream format\n\n| length | data     |\n|--------|----------|\n| u16be  | variable |\n\n##### Non-connect stream format\n\nAs the same as the stream format in protocol version 1."
  },
  {
    "path": "docs/configuration/shared/udp-over-tcp.zh.md",
    "content": "!!! warning \"\"\n\n    这是 SagerNet 创建的专有协议，不是 shadowsocks 的一部分。\n\nUDP over TCP 协议用于在 TCP 中传输 UDP 数据包。\n\n### 结构\n\n```json\n{\n  \"enabled\": true,\n  \"version\": 2\n}\n```\n\n!!! info \"\"\n\n    当不指定版本时，结构可以用布尔值替换。\n\n### 字段\n\n#### enabled\n\n启用 UDP over TCP 协议。\n\n#### version\n\n协议版本，`1` 或 `2`。\n\n默认使用 2。\n\n### 应用程序支持\n\n| 项目         | UoT v1               | UoT v2               |\n|--------------|----------------------|----------------------|\n| sing-box     | v0 (2022/08/11)      | v1.2-beta9           |\n| Clash.Meta   | v1.12.0 (2022/07/02) | v1.14.3 (2023/03/31) |\n| Shadowrocket | v2.2.12 (2022/08/13) | /                    |\n\n### 协议详情\n\n#### 协议版本 1\n\n客户端向上层代理协议请求魔法地址以表示请求：`sp.udp-over-tcp.arpa`\n\n#### 流格式\n\n| ATYP | 地址     | 端口  | 长度   | 数据     |\n|------|----------|-------|--------|----------|\n| u8   | 可变长   | u16be | u16be  | 可变长   |\n\n**ATYP / 地址 / 端口**：使用 SOCKS 地址格式，但使用不同的地址类型：\n\n| ATYP   | 地址类型  |\n|--------|-----------|\n| `0x00` | IPv4 地址 |\n| `0x01` | IPv6 地址 |\n| `0x02` | 域名      |\n\n#### 协议版本 2\n\n协议版本 2 使用新的魔法地址：`sp.v2.udp-over-tcp.arpa`\n\n##### 请求格式\n\n| isConnect | ATYP | 地址     | 端口  |\n|-----------|------|----------|-------|\n| u8        | u8   | 可变长   | u16be |\n\n**isConnect**：设置为 1 表示流使用连接格式，0 表示禁用。\n\n**ATYP / 地址 / 端口**：请求目标，使用 SOCKS 地址格式。\n\n##### 连接流格式\n\n| 长度   | 数据     |\n|--------|----------|\n| u16be  | 可变长   |\n\n##### 非连接流格式\n\n与协议版本 1 中的流格式相同。"
  },
  {
    "path": "docs/configuration/shared/v2ray-transport.md",
    "content": "V2Ray Transport is a set of private protocols invented by v2ray, and has contaminated the names of other protocols, such\nas `trojan-grpc` in clash.\n\n### Structure\n\n```json\n{\n  \"type\": \"\"\n}\n```\n\nAvailable transports:\n\n* HTTP\n* WebSocket\n* QUIC\n* gRPC\n* HTTPUpgrade\n\n!!! warning \"Difference from v2ray-core\"\n\n    * No TCP transport, plain HTTP is merged into the HTTP transport.\n    * No mKCP transport.\n    * No DomainSocket transport.\n\n!!! note \"\"\n\n    You can ignore the JSON Array [] tag when the content is only one item\n\n### HTTP\n\n```json\n{\n  \"type\": \"http\",\n  \"host\": [],\n  \"path\": \"\",\n  \"method\": \"\",\n  \"headers\": {},\n  \"idle_timeout\": \"15s\",\n  \"ping_timeout\": \"15s\"\n}\n```\n\n!!! warning \"Difference from v2ray-core\"\n\n    TLS is not enforced. If TLS is not configured, plain HTTP 1.1 is used.\n\n#### host\n\nList of host domain.\n\nThe client will choose randomly and the server will verify if not empty.\n\n#### path\n\n!!! warning\n\n    V2Ray's documentation says that the path between the server and the client must be consistent, \n    but the actual code allows the client to add any suffix to the path.\n    sing-box uses the same behavior as V2Ray, but note that the behavior does not exist in `WebSocket` and `HTTPUpgrade` transport.\n\nPath of HTTP request.\n\nThe server will verify.\n\n#### method\n\nMethod of HTTP request.\n\nThe server will verify if not empty.\n\n#### headers\n\nExtra headers of HTTP request.\n\nThe server will write in response if not empty.\n\n#### idle_timeout\n\nIn HTTP2 server:\n\nSpecifies the time until idle clients should be closed with a GOAWAY frame. PING frames are not considered as activity.\n\nIn HTTP2 client:\n\nSpecifies the period of time after which a health check will be performed using a ping frame if no frames have been\nreceived on the connection.Please note that a ping response is considered a received frame, so if there is no other\ntraffic on the connection, the health check will be executed every interval. If the value is zero, no health check will\nbe performed.\n\nZero is used by default.\n\n#### ping_timeout\n\nIn HTTP2 client:\n\nSpecifies the timeout duration after sending a PING frame, within which a response must be received.\nIf a response to the PING frame is not received within the specified timeout duration, the connection will be closed.\nThe default timeout duration is 15 seconds.\n\n### WebSocket\n\n```json\n{\n  \"type\": \"ws\",\n  \"path\": \"\",\n  \"headers\": {},\n  \"max_early_data\": 0,\n  \"early_data_header_name\": \"\"\n}\n```\n\n#### path\n\nPath of HTTP request.\n\nThe server will verify.\n\n#### headers\n\nExtra headers of HTTP request.\n\nThe server will write in response if not empty.\n\n#### max_early_data\n\nAllowed payload size is in the request. Enabled if not zero.\n\n#### early_data_header_name\n\nEarly data is sent in path instead of header by default.\n\nTo be compatible with Xray-core, set this to `Sec-WebSocket-Protocol`.\n\nIt needs to be consistent with the server.\n\n### QUIC\n\n```json\n{\n  \"type\": \"quic\"\n}\n```\n\n!!! warning \"Difference from v2ray-core\"\n\n    No additional encryption support:\n    It's basically duplicate encryption. And Xray-core is not compatible with v2ray-core in here.\n\n### gRPC\n\n!!! note \"\"\n\n    standard gRPC has good compatibility but poor performance and is not included by default, see [Installation](/installation/build-from-source/#build-tags).\n\n```json\n{\n  \"type\": \"grpc\",\n  \"service_name\": \"TunService\",\n  \"idle_timeout\": \"15s\",\n  \"ping_timeout\": \"15s\",\n  \"permit_without_stream\": false\n}\n```\n\n#### service_name\n\nService name of gRPC.\n\n#### idle_timeout\n\nIn standard gRPC server/client:\n\nIf the transport doesn't see any activity after a duration of this time,\nit pings the client to check if the connection is still active.\n\nIn default gRPC server/client:\n\nIt has the same behavior as the corresponding setting in HTTP transport.\n\n#### ping_timeout\n\nIn standard gRPC server/client:\n\nThe timeout that after performing a keepalive check, the client will wait for activity.\nIf no activity is detected, the connection will be closed.\n\nIn default gRPC server/client:\n\nIt has the same behavior as the corresponding setting in HTTP transport.\n\n#### permit_without_stream\n\nIn standard gRPC client:\n\nIf enabled, the client transport sends keepalive pings even with no active connections.\nIf disabled, when there are no active connections, `idle_timeout` and `ping_timeout` will be ignored and no keepalive\npings will be sent.\n\nDisabled by default.\n\n### HTTPUpgrade\n\n```json\n{\n  \"type\": \"httpupgrade\",\n  \"host\": \"\",\n  \"path\": \"\",\n  \"headers\": {}\n}\n```\n\n#### host\n\nHost domain.\n\nThe server will verify if not empty.\n\n#### path\n\nPath of HTTP request.\n\nThe server will verify.\n\n#### headers\n\nExtra headers of HTTP request.\n\nThe server will write in response if not empty.\n"
  },
  {
    "path": "docs/configuration/shared/v2ray-transport.zh.md",
    "content": "V2Ray Transport 是 v2ray 发明的一组私有协议，并污染了其他协议的名称，如 clash 中的 `trojan-grpc`。\n\n### 结构\n\n```json\n{\n  \"type\": \"\"\n}\n```\n\n可用的传输协议：\n\n* HTTP\n* WebSocket\n* QUIC\n* gRPC\n* HTTPUpgrade\n\n!!! warning \"与 v2ray-core 的区别\"\n\n    * 没有 TCP 传输层, 纯 HTTP 已合并到 HTTP 传输层。\n    * 没有 mKCP 传输层。\n    * 没有 DomainSocket 传输层。\n\n!!! note \"\"\n\n    当内容只有一项时，可以忽略 JSON 数组 [] 标签。\n\n### HTTP\n\n```json\n{\n  \"type\": \"http\",\n  \"host\": [],\n  \"path\": \"\",\n  \"method\": \"\",\n  \"headers\": {},\n  \"idle_timeout\": \"15s\",\n  \"ping_timeout\": \"15s\"\n}\n```\n\n!!! warning \"与 v2ray-core 的区别\"\n\n    不强制执行 TLS。如果未配置 TLS，将使用纯 HTTP 1.1。\n\n#### host\n\n主机域名列表。\n\n如果设置，客户端将随机选择，服务器将验证。\n\n#### path\n\n!!! warning\n\n    V2Ray 文档称服务端和客户端的路径必须一致，但实际代码允许客户端向路径添加任何后缀。\n    sing-box 使用与 V2Ray 相同的行为，但请注意，该行为在 `WebSocket` 和 `HTTPUpgrade` 传输层中不存在。\n\nHTTP 请求路径\n\n服务器将验证。\n\n#### method\n\nHTTP 请求方法\n\n如果设置，服务器将验证。\n\n#### headers\n\nHTTP 请求的额外标头\n\n如果设置，服务器将写入响应。\n\n#### idle_timeout\n\n在 HTTP2 服务器中：\n\n指定闲置客户端应在多长时间内使用 GOAWAY 帧关闭。PING 帧不被视为活动。\n\n在 HTTP2 客户端中：\n\n如果连接上没有收到任何帧，指定一段时间后将使用 PING 帧执行健康检查。需要注意的是，PING 响应被视为已接收的帧，因此如果连接上没有其他流量，则健康检查将在每个间隔执行一次。如果值为零，则不会执行健康检查。\n\n默认使用零。\n\n#### ping_timeout\n\n在 HTTP2 客户端中：\n\n指定发送 PING 帧后，在指定的超时时间内必须接收到响应。如果在指定的超时时间内没有收到 PING 帧的响应，则连接将关闭。默认超时持续时间为 15 秒。\n\n### WebSocket\n\n```json\n{\n  \"type\": \"ws\",\n  \"path\": \"\",\n  \"headers\": {},\n  \"max_early_data\": 0,\n  \"early_data_header_name\": \"\"\n}\n```\n\n#### path\n\nHTTP 请求路径\n\n服务器将验证。\n\n#### headers\n\nHTTP 请求的额外标头\n\n如果设置，服务器将写入响应。\n\n#### max_early_data\n\n请求中允许的最大有效负载大小。默认启用。\n\n#### early_data_header_name\n\n默认情况下，早期数据在路径而不是标头中发送。\n\n要与 Xray-core 兼容，请将其设置为 `Sec-WebSocket-Protocol`。\n\n它需要与服务器保持一致。\n\n### QUIC\n\n```json\n{\n  \"type\": \"quic\"\n}\n```\n\n!!! warning \"与 v2ray-core 的区别\"\n\n    没有额外的加密支持：\n    它基本上是重复加密。 并且 Xray-core 在这里与 v2ray-core 不兼容。\n\n### gRPC\n\n!!! note \"\"\n\n    默认安装不包含标准 gRPC (兼容性好，但性能较差), 参阅 [安装](/zh/installation/build-from-source/#构建标记)。\n\n```json\n{\n  \"type\": \"grpc\",\n  \"service_name\": \"TunService\",\n  \"idle_timeout\": \"15s\",\n  \"ping_timeout\": \"15s\",\n  \"permit_without_stream\": false\n}\n```\n\n#### service_name\n\ngRPC 服务名称。\n\n#### idle_timeout\n\n在标准 gRPC 服务器/客户端：\n\n如果传输在此时间段后没有看到任何活动，它会向客户端发送 ping 请求以检查连接是否仍然活动。\n\n在默认 gRPC 服务器/客户端：\n\n它的行为与 HTTP 传输层中的相应设置相同。\n\n#### ping_timeout\n\n在标准 gRPC 服务器/客户端：\n\n经过一段时间之后，客户端将执行 keepalive 检查并等待活动。如果没有检测到任何活动，则会关闭连接。\n\n在默认 gRPC 服务器/客户端：\n\n它的行为与 HTTP 传输层中的相应设置相同。\n\n#### permit_without_stream\n\n在标准 gRPC 客户端：\n\n如果启用，客户端传输即使没有活动连接也会发送 keepalive ping。如果禁用，则在没有活动连接时，将忽略 `idle_timeout` 和 `ping_timeout`，并且不会发送 keepalive ping。\n\n默认禁用。\n\n### HTTPUpgrade\n\n```json\n{\n  \"type\": \"httpupgrade\",\n  \"host\": \"\",\n  \"path\": \"\",\n  \"headers\": {}\n}\n```\n\n#### host\n\n主机域名。\n\n服务器将验证。\n\n#### path\n\nHTTP 请求路径\n\n服务器将验证。\n\n#### headers\n\nHTTP 请求的额外标头。\n\n如果设置，服务器将写入响应。\n"
  },
  {
    "path": "docs/configuration/shared/wifi-state.md",
    "content": "---\nicon: material/new-box\n---\n\n# Wi-Fi State\n\n!!! quote \"Changes in sing-box 1.13.0\"\n\n    :material-plus: Linux support  \n    :material-plus: Windows support\n\nsing-box can monitor Wi-Fi state to enable routing rules based on `wifi_ssid` and `wifi_bssid`.\n\n### Platform Support\n\n| Platform        | Support          | Notes                    |\n|-----------------|------------------|--------------------------|\n| Android         | :material-check: | In graphical client      |\n| Apple platforms | :material-check: | In graphical clients     |\n| Linux           | :material-check: | Requires supported daemon |\n| Windows         | :material-check: | WLAN API                 |\n| Others          | :material-close: |                          |\n\n### Linux\n\n!!! question \"Since sing-box 1.13.0\"\n\nThe following backends are supported and will be auto-detected in order of priority:\n\n| Backend          | Interface   |\n|------------------|-------------|\n| NetworkManager   | D-Bus       |\n| IWD              | D-Bus       |\n| wpa_supplicant   | Unix socket |\n| ConnMan          | D-Bus       |\n\n### Windows\n\n!!! question \"Since sing-box 1.13.0\"\n\nUses Windows WLAN API.\n"
  },
  {
    "path": "docs/configuration/shared/wifi-state.zh.md",
    "content": "---\nicon: material/new-box\n---\n\n# Wi-Fi 状态\n\n!!! quote \"sing-box 1.13.0 中的更改\"\n\n    :material-plus: Linux 支持  \n    :material-plus: Windows 支持\n\nsing-box 可以监控 Wi-Fi 状态，以启用基于 `wifi_ssid` 和 `wifi_bssid` 的路由规则。\n\n### 平台支持\n\n| 平台            | 支持              | 备注           |\n|-----------------|------------------|----------------|\n| Android         | :material-check: | 仅图形客户端    |\n| Apple 平台      | :material-check: | 仅图形客户端    |\n| Linux           | :material-check: | 需要支持的守护进程 |\n| Windows         | :material-check: | WLAN API       |\n| 其他            | :material-close: |                |\n\n### Linux\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n支持以下后端，将按优先级顺序自动探测：\n\n| 后端              | 接口         |\n|------------------|-------------|\n| NetworkManager   | D-Bus       |\n| IWD              | D-Bus       |\n| wpa_supplicant   | Unix socket |\n| ConnMan          | D-Bus       |\n\n### Windows\n\n!!! question \"自 sing-box 1.13.0 起\"\n\n使用 Windows WLAN API。\n"
  },
  {
    "path": "docs/deprecated.md",
    "content": "---\nicon: material/delete-alert\n---\n\n# Deprecated Feature List\n\n## 1.12.0\n\n#### Legacy DNS server formats\n\nDNS servers are refactored,\ncheck [Migration](../migration/#migrate-to-new-dns-servers).\n\nCompatibility for old formats will be removed in sing-box 1.14.0.\n\n#### `outbound` DNS rule item\n\nLegacy `outbound` DNS rules are deprecated\nand can be replaced by dial fields,\ncheck [Migration](../migration/#migrate-outbound-dns-rule-items-to-domain-resolver).\n\n#### Legacy ECH fields\n\nECH support has been migrated to use stdlib in sing-box 1.12.0,\nwhich does not come with support for PQ signature schemes,\nso `pq_signature_schemes_enabled` has been deprecated and no longer works.\n\nAlso, `dynamic_record_sizing_disabled` has nothing to do with ECH,\nwas added by mistake, has been deprecated and no longer works.\n\nThese fields will be removed in sing-box 1.13.0.\n\n## 1.11.0\n\n#### Legacy special outbounds\n\nLegacy special outbounds (`block` / `dns`) are deprecated\nand can be replaced by rule actions,\ncheck [Migration](../migration/#migrate-legacy-special-outbounds-to-rule-actions).\n\nOld fields will be removed in sing-box 1.13.0.\n\n#### Legacy inbound fields\n\nLegacy inbound fields （`inbound.<sniff/domain_strategy/...>` are deprecated\nand can be replaced by rule actions,\ncheck [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions).\n\nOld fields will be removed in sing-box 1.13.0.\n\n#### Destination override fields in direct outbound\n\nDestination override fields (`override_address` / `override_port`) in direct outbound are deprecated\nand can be replaced by rule actions,\ncheck [Migration](../migration/#migrate-destination-override-fields-to-route-options).\n\n#### WireGuard outbound\n\nWireGuard outbound is deprecated and can be replaced by endpoint,\ncheck [Migration](../migration/#migrate-wireguard-outbound-to-endpoint).\n\nOld outbound will be removed in sing-box 1.13.0.\n\n#### GSO option in TUN\n\nGSO has no advantages for transparent proxy scenarios, is deprecated and no longer works in TUN.\n\nOld fields will be removed in sing-box 1.13.0.\n\n## 1.10.0\n\n#### TUN address fields are merged\n\n`inet4_address` and `inet6_address` are merged into `address`,\n`inet4_route_address` and `inet6_route_address` are merged into `route_address`,\n`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.\n\nOld fields will be removed in sing-box 1.12.0.\n\n#### Match source rule items are renamed\n\n`rule_set_ipcidr_match_source` route and DNS rule items are renamed to\n`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.\n\n#### Drop support for go1.18 and go1.19\n\nDue to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.\n\n## 1.8.0\n\n#### Cache file and related features in Clash API\n\n`cache_file` and related features in Clash API is migrated to independent `cache_file` options,\ncheck [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-options).\n\n#### GeoIP\n\nGeoIP is deprecated and will be removed in sing-box 1.12.0.\n\nThe maxmind GeoIP National Database, as an IP classification database,\nis not entirely suitable for traffic bypassing,\nand all existing implementations suffer from high memory usage and difficult management.\n\nsing-box 1.8.0 introduces [rule-set](/configuration/rule-set/), which can completely replace GeoIP,\ncheck [Migration](/migration/#migrate-geoip-to-rule-sets).\n\n#### Geosite\n\nGeosite is deprecated and will be removed in sing-box 1.12.0.\n\nGeosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution,\nsuffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management.\n\nsing-box 1.8.0 introduces [rule-set](/configuration/rule-set/), which can completely replace Geosite,\ncheck [Migration](/migration/#migrate-geosite-to-rule-sets).\n\n## 1.6.0\n\nThe following features will be marked deprecated in 1.5.0 and removed entirely in 1.6.0.\n\n#### ShadowsocksR\n\nShadowsocksR support has never been enabled by default, since the most commonly used proxy sales panel in the\nillegal industry stopped using this protocol, it does not make sense to continue to maintain it.\n\n#### Proxy Protocol\n\nProxy Protocol is added by Pull Request, has problems, is only used by the backend of HTTP multiplexers such as nginx,\nis intrusive, and is meaningless for proxy purposes.\n"
  },
  {
    "path": "docs/deprecated.zh.md",
    "content": "---\nicon: material/delete-alert\n---\n\n# 废弃功能列表\n\n#### 旧的 DNS 服务器格式\n\nDNS 服务器已重构，\n参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式).\n\n对旧格式的兼容性将在 sing-box 1.14.0 中被移除。\n\n#### `outbound` DNS 规则项\n\n旧的 `outbound` DNS 规则已废弃，\n且可被拨号字段代替，\n参阅 [迁移指南](/zh/migration/#迁移-outbound-dns-规则项到域解析选项).\n\n#### 旧的 ECH 字段\n\nECH 支持已在 sing-box 1.12.0 迁移至使用标准库，但标准库不支持后量子对等证书签名方案，\n因此 `pq_signature_schemes_enabled` 已被弃用且不再工作。\n\n另外，`dynamic_record_sizing_disabled` 与 ECH 无关，是错误添加的，现已弃用且不再工作。\n\n相关字段将在 sing-box 1.13.0 中被移除。\n\n## 1.11.0\n\n#### 旧的特殊出站\n\n旧的特殊出站（`block` / `dns`）已废弃且可以通过规则动作替代，\n参阅 [迁移指南](/zh/migration/#迁移旧的特殊出站到规则动作)。\n\n旧字段将在 sing-box 1.13.0 中被移除。\n\n#### 旧的入站字段\n\n旧的入站字段（`inbound.<sniff/domain_strategy/...>`）已废弃且可以通过规则动作替代，\n参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作)。\n\n旧字段将在 sing-box 1.13.0 中被移除。\n\n#### direct 出站中的目标地址覆盖字段\n\ndirect 出站中的目标地址覆盖字段（`override_address` / `override_port`）已废弃且可以通过规则动作替代，\n参阅 [迁移指南](/zh/migration/#迁移-direct-出站中的目标地址覆盖字段到路由字段)。\n\n旧字段将在 sing-box 1.13.0 中被移除。\n\n#### WireGuard 出站\n\nWireGuard 出站已废弃且可以通过端点替代，\n参阅 [迁移指南](/zh/migration/#迁移-wireguard-出站到端点)。\n\n旧出站将在 sing-box 1.13.0 中被移除。\n\n#### TUN 的 GSO 字段\n\nGSO 对透明代理场景没有优势，已废弃且在 TUN 中不再起作用。\n\n旧字段将在 sing-box 1.13.0 中被移除。\n\n## 1.10.0\n\n#### Match source 规则项已重命名\n\n`rule_set_ipcidr_match_source` 路由和 DNS 规则项已被重命名为\n`rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。\n\n#### TUN 地址字段已合并\n\n`inet4_address` 和 `inet6_address` 已合并为 `address`，\n`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`，\n`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。\n\n旧字段将在 sing-box 1.11.0 中被移除。\n\n#### 移除对 go1.18 和 go1.19 的支持\n\n由于维护困难，sing-box 1.10.0 要求至少 Go 1.20 才能编译。\n\n## 1.8.0\n\n#### Clash API 中的 Cache file 及相关功能\n\nClash API 中的 `cache_file` 及相关功能已废弃且已迁移到独立的 `cache_file` 设置，\n参阅 [迁移指南](/zh/migration/#将缓存文件从-clash-api-迁移到独立选项)。\n\n#### GeoIP\n\nGeoIP 已废弃且将在 sing-box 1.12.0 中被移除。\n\nmaxmind GeoIP 国家数据库作为 IP 分类数据库，不完全适合流量绕过，\n且现有的实现均存在内存使用大与管理困难的问题。\n\nsing-box 1.8.0 引入了[规则集](/zh/configuration/rule-set/)，\n可以完全替代 GeoIP， 参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。\n\n#### Geosite\n\nGeosite 已废弃且将在 sing-box 1.12.0 中被移除。\n\nGeosite，即由 V2Ray 维护的 domain-list-community 项目，作为早期流量绕过解决方案，\n存在着包括缺少维护、规则不准确和管理困难内的大量问题。\n\nsing-box 1.8.0 引入了[规则集](/zh/configuration/rule-set/)，\n可以完全替代 Geosite，参阅 [迁移指南](/zh/migration/#迁移-geosite-到规则集)。\n\n## 1.6.0\n\n下列功能已在 1.5.0 中标记为已弃用，并在 1.6.0 中完全删除。\n\n#### ShadowsocksR\n\nShadowsocksR 支持从未默认启用，自从常用的黑产代理销售面板停止使用该协议，继续维护它是没有意义的。\n\n#### Proxy Protocol\n\nProxy Protocol 支持由 Pull Request 添加，存在问题且仅由 HTTP 多路复用器（如 nginx）的后端使用，具有侵入性，对于代理目的毫无意义。\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\ndescription: Welcome to the wiki page for the sing-box project.\n---\n\n# :material-home: Home\n\nWelcome to the wiki page for the sing-box project.\n\nThe universal proxy platform.\n\n## License\n\n```\nCopyright (C) 2022 by nekohasekai <contact-sagernet@sekai.icu>\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n\nIn addition, no derivative work may use the name or imply association\nwith this application without prior consent.\n```\n"
  },
  {
    "path": "docs/index.zh.md",
    "content": "---\ndescription: 欢迎来到该 sing-box 项目的文档页。\n---\n\n# :material-home: 开始\n\n欢迎来到该 sing-box 项目的文档页。\n\n通用代理平台。\n\n## 授权\n\n```\nCopyright (C) 2022 by nekohasekai <contact-sagernet@sekai.icu>\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n\nIn addition, no derivative work may use the name or imply association\nwith this application without prior consent.\n```\n"
  },
  {
    "path": "docs/installation/build-from-source.md",
    "content": "---\nicon: material/file-code\n---\n\n# Build from source\n\n## :material-graph: Requirements\n\n### sing-box 1.11\n\n* Go 1.23.1 - ~\n\n### sing-box 1.10\n\n* Go 1.20.0 - ~\n\n### sing-box 1.9\n\n* Go 1.18.5 - 1.22.x\n* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled\n\n## :material-fast-forward: Simple Build\n\n```bash\nmake\n```\n\nOr build and install binary to `$GOBIN`:\n\n```bash\nmake install\n```\n\n## :material-cog: Custom Build\n\n```bash\nTAGS=\"tag_a tag_b\" make\n```\n\nor\n\n```bash\ngo build -tags \"tag_a tag_b\" ./cmd/sing-box\n```\n\n## :material-folder-settings: Build Tags\n\n| Build Tag                          | Enabled by default   | Description                                                                                                                                                                                                                                                                                                                    |\n|------------------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `with_quic`                        | :material-check:     | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). |\n| `with_grpc`                        | :material-close:️    | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc).                                                                                                                                                                                                                      |\n| `with_dhcp`                        | :material-check:     | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/).                                                                                                                                                                                                                                                 |\n| `with_wireguard`                   | :material-check:     | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/).                                                                                                                                                                                                                                    |\n| `with_utls`                        | :material-check:     | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls).                                                                                                                                                                                          |\n| `with_acme`                        | :material-check:     | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/).                                                                                                                                                                                                                                         |\n| `with_clash_api`                   | :material-check:     | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields).                                                                                                                                                                                                                                |\n| `with_v2ray_api`                   | :material-close:️    | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields).                                                                                                                                                                                                                                |\n| `with_gvisor`                      | :material-check:     | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface).                                                                                                                                                                   |\n| `with_embedded_tor` (CGO required) | :material-close:️    | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/).                                                                                                                                                                                                                                             |\n| `with_tailscale`                   | :material-check:     | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale).                                                                                                                                                                                                                                     |\n| `with_ccm`                         | :material-check:     | Build with Claude Code Multiplexer service support.                                                                                                                                                                                                                                                                            |\n| `with_ocm`                         | :material-check:     | Build with OpenAI Codex Multiplexer service support.                                                                                                                                                                                                                                                                           |\n| `with_naive_outbound`              | :material-check:     | Build with NaiveProxy outbound support, see [NaiveProxy outbound](/configuration/outbound/naive/).                                                                                                                                                                                                                             |\n| `badlinkname`                      | :material-check:     | Enable `go:linkname` access to internal standard library functions. Required because the Go standard library does not expose many low-level APIs needed by this project, and reimplementing them externally is impractical. Used for kTLS (kernel TLS offload) and raw TLS record manipulation.                                 |\n| `tfogo_checklinkname0`             | :material-check:     | Companion to `badlinkname`. Go 1.23+ enforces `go:linkname` restrictions via the linker; this tag signals the build uses `-checklinkname=0` to bypass that enforcement.                                                                                                                                                       |\n\nIt is not recommended to change the default build tag list unless you really know what you are adding.\n\n## :material-wrench: Linker Flags\n\nThe following `-ldflags` are used in official builds:\n\n| Flag                                                        | Description                                                                                                                                                                                                             |\n|-------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `-X 'internal/godebug.defaultGODEBUG=multipathtcp=0'`      | Go 1.24 enabled Multipath TCP for listeners by default (`multipathtcp=2`). This may cause errors on low-level sockets, and sing-box has its own MPTCP control (`tcp_multi_path` option). This flag disables the Go default. |\n| `-checklinkname=0`                                          | Go 1.23+ linker rejects unauthorized `go:linkname` usage. This flag disables the check, required together with the `badlinkname` build tag.                                                                            |\n\n## :material-package-variant: For Downstream Packagers\n\nThe default build tag lists and linker flags are available as files in the repository for downstream packagers to reference directly:\n\n| File | Description |\n|------|-------------|\n| `release/DEFAULT_BUILD_TAGS` | Default for Linux (common architectures), Darwin, and Android. |\n| `release/DEFAULT_BUILD_TAGS_WINDOWS` | Default for Windows (includes `with_purego`). |\n| `release/DEFAULT_BUILD_TAGS_OTHERS` | Default for other platforms (no `with_naive_outbound`). |\n| `release/LDFLAGS` | Required linker flags (see above). |\n\n## :material-layers: with_naive_outbound\n\nNaiveProxy outbound requires special build configurations depending on your target platform.\n\n### Supported Platforms\n\n| Platform        | Architectures                                          | Mode   | Requirements                                                    |\n|-----------------|--------------------------------------------------------|--------|-----------------------------------------------------------------|\n| Linux           | amd64, arm64                                           | purego | None (library included in official releases)                    |\n| Linux           | 386, amd64, arm, arm64, mipsle, mips64le, riscv64, loong64 | CGO    | Chromium toolchain, glibc >= 2.31 (loong64: >= 2.36) at runtime |\n| Linux (musl)    | 386, amd64, arm, arm64, mipsle, riscv64, loong64       | CGO    | Chromium toolchain                                              |\n| Windows         | amd64, arm64                                           | purego | None (library included in official releases)                    |\n| Apple platforms | *                                                      | CGO    | Xcode                                                           |\n| Android         | *                                                      | CGO    | Android NDK                                                     |\n\n### Windows\n\nUse `with_purego` tag.\n\nFor official releases, `libcronet.dll` is included in the archive. For self-built binaries, download from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) and place in the same directory as `sing-box.exe` or in a directory listed in `PATH`.\n\n### Linux (purego, amd64/arm64 only)\n\nUse `with_purego` tag.\n\nFor official releases, `libcronet.so` is included in the archive. For self-built binaries, download from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) and place in the same directory as sing-box binary or in system library path.\n\n### Linux (CGO)\n\nSee [cronet-go](https://github.com/sagernet/cronet-go#linux-build-instructions).\n\n- **glibc build**: Requires glibc >= 2.31 at runtime\n- **musl build**: Use `with_musl` tag, statically linked, no runtime requirements\n\n### Apple platforms / Android\n\nSee [cronet-go](https://github.com/sagernet/cronet-go).\n"
  },
  {
    "path": "docs/installation/build-from-source.zh.md",
    "content": "---\nicon: material/file-code\n---\n\n# 从源代码构建\n\n## :material-graph: 要求\n\n### sing-box 1.11\n\n* Go 1.23.1 - ~\n\n### sing-box 1.10\n\n* Go 1.20.0 - ~\n* Go 1.21.0 - ~ with tag `with_ech` enabled\n\n### sing-box 1.9\n\n* Go 1.18.5 - 1.22.x\n* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled\n* Go 1.21.0 - 1.22.x with tag `with_ech` enabled\n\n您可以从 https://go.dev/doc/install 下载并安装 Go，推荐使用最新版本。\n\n## :material-fast-forward: 快速开始\n\n```bash\nmake\n```\n\n或者构建二进制文件并将其安装到 `$GOBIN`：\n\n```bash\nmake install\n```\n\n## :material-cog: 自定义构建\n\n```bash\nTAGS=\"tag_a tag_b\" make\n```\n\nor\n\n```bash\ngo build -tags \"tag_a tag_b\" ./cmd/sing-box\n```\n\n## :material-folder-settings: 构建标记\n\n| 构建标记                               | 默认启动              | 说明                                                                                                                                                                                                                                                                                                                             |\n|------------------------------------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `with_quic`                        | :material-check:  | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/zh/configuration/dns/server/), [Naive inbound](/zh/configuration/inbound/naive/), [Hysteria Inbound](/zh/configuration/inbound/hysteria/), [Hysteria Outbound](/zh/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/zh/configuration/shared/v2ray-transport#quic). |\n| `with_grpc`                        | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/zh/configuration/shared/v2ray-transport#grpc).                                                                                                                                                                                                                      |\n| `with_dhcp`                        | :material-check:  | Build with DHCP support, see [DHCP DNS transport](/zh/configuration/dns/server/).                                                                                                                                                                                                                                                 |\n| `with_wireguard`                   | :material-check:  | Build with WireGuard support, see [WireGuard outbound](/zh/configuration/outbound/wireguard/).                                                                                                                                                                                                                                    |\n| `with_utls`                        | :material-check:  | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/zh/configuration/shared/tls#utls).                                                                                                                                                                                          |\n| `with_acme`                        | :material-check:  | Build with ACME TLS certificate issuer support, see [TLS](/zh/configuration/shared/tls/).                                                                                                                                                                                                                                         |\n| `with_clash_api`                   | :material-check:  | Build with Clash API support, see [Experimental](/zh/configuration/experimental#clash-api-fields).                                                                                                                                                                                                                                |\n| `with_v2ray_api`                   | :material-close:️ | Build with V2Ray API support, see [Experimental](/zh/configuration/experimental#v2ray-api-fields).                                                                                                                                                                                                                                |\n| `with_gvisor`                      | :material-check:  | Build with gVisor support, see [Tun inbound](/zh/configuration/inbound/tun#stack) and [WireGuard outbound](/zh/configuration/outbound/wireguard#system_interface).                                                                                                                                                                   |\n| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/zh/configuration/outbound/tor/).                                                                                                                                                                                                                                             |\n| `with_tailscale`                   | :material-check:  | 构建 Tailscale 支持，参阅 [Tailscale 端点](/zh/configuration/endpoint/tailscale)。                                                                                                                                                                                                                                                         |\n| `with_ccm`                         | :material-check:  | 构建 Claude Code Multiplexer 服务支持。                                                                                                                                                                                                                                                                                              |\n| `with_ocm`                         | :material-check:  | 构建 OpenAI Codex Multiplexer 服务支持。                                                                                                                                                                                                                                                                                             |\n| `with_naive_outbound`              | :material-check:  | 构建 NaiveProxy 出站支持，参阅 [NaiveProxy 出站](/zh/configuration/outbound/naive/)。                                                                                                                                                                                                                                                         |\n| `badlinkname`                      | :material-check:  | 启用 `go:linkname` 以访问标准库内部函数。Go 标准库未提供本项目需要的许多底层 API，且在外部重新实现不切实际。用于 kTLS（内核 TLS 卸载）和原始 TLS 记录操作。                                                                                                                                                                                                                           |\n| `tfogo_checklinkname0`             | :material-check:  | `badlinkname` 的伴随标记。Go 1.23+ 链接器强制限制 `go:linkname` 使用；此标记表示构建使用 `-checklinkname=0` 以绕过该限制。                                                                                                                                                                                                                                |\n\n除非您确实知道您正在启用什么，否则不建议更改默认构建标签列表。\n\n## :material-wrench: 链接器标志\n\n以下 `-ldflags` 在官方构建中使用：\n\n| 标志                                                          | 说明                                                                                                                                                         |\n|-------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `-X 'internal/godebug.defaultGODEBUG=multipathtcp=0'`      | Go 1.24 默认为监听器启用 Multipath TCP（`multipathtcp=2`）。这可能在底层 socket 上导致错误，且 sing-box 有自己的 MPTCP 控制（`tcp_multi_path` 选项）。此标志禁用 Go 的默认行为。                             |\n| `-checklinkname=0`                                          | Go 1.23+ 链接器拒绝未授权的 `go:linkname` 使用。此标志禁用该检查，需要与 `badlinkname` 构建标记一起使用。                                                                                   |\n\n## :material-package-variant: 下游打包者\n\n默认构建标签列表和链接器标志以文件形式存放在仓库中，供下游打包者直接引用：\n\n| 文件 | 说明 |\n|------|------|\n| `release/DEFAULT_BUILD_TAGS` | Linux（常见架构）、Darwin 和 Android 的默认标签。 |\n| `release/DEFAULT_BUILD_TAGS_WINDOWS` | Windows 的默认标签（包含 `with_purego`）。 |\n| `release/DEFAULT_BUILD_TAGS_OTHERS` | 其他平台的默认标签（不含 `with_naive_outbound`）。 |\n| `release/LDFLAGS` | 必需的链接器标志（参见上文）。 |\n\n## :material-layers: with_naive_outbound\n\nNaiveProxy 出站需要根据目标平台进行特殊的构建配置。\n\n### 支持的平台\n\n| 平台           | 架构                                                       | 模式     | 要求                                                  |\n|--------------|----------------------------------------------------------|--------|-----------------------------------------------------|\n| Linux        | amd64, arm64                                             | purego | 无（官方发布版本已包含库文件）                                     |\n| Linux        | 386, amd64, arm, arm64, mipsle, mips64le, riscv64, loong64 | CGO    | Chromium 工具链，运行时需要 glibc >= 2.31（loong64: >= 2.36） |\n| Linux (musl) | 386, amd64, arm, arm64, mipsle, riscv64, loong64          | CGO    | Chromium 工具链                                        |\n| Windows      | amd64, arm64                                             | purego | 无（官方发布版本已包含库文件）                                     |\n| Apple 平台     | *                                                        | CGO    | Xcode                                               |\n| Android      | *                                                        | CGO    | Android NDK                                          |\n\n### Windows\n\n使用 `with_purego` 标记。\n\n官方发布版本已包含 `libcronet.dll`。自行构建时，从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载并放置在 `sing-box.exe` 相同目录或 `PATH` 中的任意目录。\n\n### Linux (purego, 仅 amd64/arm64)\n\n使用 `with_purego` 标记。\n\n官方发布版本已包含 `libcronet.so`。自行构建时，从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载并放置在 sing-box 二进制文件相同目录或系统库路径中。\n\n### Linux (CGO)\n\n参阅 [cronet-go](https://github.com/sagernet/cronet-go#linux-build-instructions)。\n\n- **glibc 构建**：运行时需要 glibc >= 2.31\n- **musl 构建**：使用 `with_musl` 标记，静态链接，无运行时要求\n\n### Apple 平台 / Android\n\n参阅 [cronet-go](https://github.com/sagernet/cronet-go)。\n"
  },
  {
    "path": "docs/installation/docker.md",
    "content": "---\nicon: material/docker\n---\n\n# Docker\n\n## :material-console: Command\n\n```bash\ndocker run -d \\\n  -v /etc/sing-box:/etc/sing-box/ \\\n  --name=sing-box \\\n  --restart=always \\\n  ghcr.io/sagernet/sing-box \\\n  -D /var/lib/sing-box \\\n  -C /etc/sing-box/ run\n```\n\n## :material-box-shadow: Compose\n\n```yaml\nversion: \"3.8\"\nservices:\n  sing-box:\n    image: ghcr.io/sagernet/sing-box\n    container_name: sing-box\n    restart: always\n    volumes:\n      - /etc/sing-box:/etc/sing-box/\n    command: -D /var/lib/sing-box -C /etc/sing-box/ run\n```\n"
  },
  {
    "path": "docs/installation/docker.zh.md",
    "content": "---\nicon: material/docker\n---\n\n# Docker\n\n## :material-console: 命令\n\n```bash\ndocker run -d \\\n  -v /etc/sing-box:/etc/sing-box/ \\\n  --name=sing-box \\\n  --restart=always \\\n  ghcr.io/sagernet/sing-box \\\n  -D /var/lib/sing-box \\\n  -C /etc/sing-box/ run\n```\n\n## :material-box-shadow: Compose\n\n```yaml\nversion: \"3.8\"\nservices:\n  sing-box:\n    image: ghcr.io/sagernet/sing-box\n    container_name: sing-box\n    restart: always\n    volumes:\n      - /etc/sing-box:/etc/sing-box/\n    command: -D /var/lib/sing-box -C /etc/sing-box/ run\n```\n"
  },
  {
    "path": "docs/installation/package-manager.md",
    "content": "---\nicon: material/package\n---\n\n# Package Manager\n\n## :material-tram: Repository Installation\n\n=== \":material-debian: Debian / APT\"\n\n    ```bash\n    sudo mkdir -p /etc/apt/keyrings &&\n       sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&\n       sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&\n       echo '\n    Types: deb\n    URIs: https://deb.sagernet.org/\n    Suites: *\n    Components: *\n    Enabled: yes\n    Signed-By: /etc/apt/keyrings/sagernet.asc\n    ' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&\n       sudo apt-get update &&\n       sudo apt-get install sing-box # or sing-box-beta\n    ```\n\n=== \":material-redhat: Redhat / DNF 5\"\n\n    ```bash\n    sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&\n    sudo dnf install sing-box # or sing-box-beta\n    ```\n\n=== \":material-redhat: Redhat / DNF 4\"\n\n    ```bash\n    sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&\n    sudo dnf -y install dnf-plugins-core &&\n    sudo dnf install sing-box # or sing-box-beta\n    ```\n\n## :material-download-box: Manual Installation\n\nThe script download and install the latest package from GitHub releases\nfor deb or rpm based Linux distributions, ArchLinux and OpenWrt.\n\n```shell\ncurl -fsSL https://sing-box.app/install.sh | sh\n```\n\nor latest beta:\n\n```shell\ncurl -fsSL https://sing-box.app/install.sh | sh -s -- --beta\n```\n\nor specific version:\n\n```shell\ncurl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>\n```\n\n## :material-book-lock-open: Managed Installation\n\n=== \":material-linux: Linux\"\n\n    | Type     | Platform      | Command                      | Link                                                                                                          |\n    |----------|---------------|------------------------------|---------------------------------------------------------------------------------------------------------------|\n    | AUR      | Arch Linux    | `? -S sing-box`              | [![AUR package](https://repology.org/badge/version-for-repo/aur/sing-box.svg)][aur]                           |\n    | nixpkgs  | NixOS         | `nix-env -iA nixos.sing-box` | [![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/sing-box.svg)][nixpkgs] |\n    | Homebrew | macOS / Linux | `brew install sing-box`      | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew]                |\n    | APK      | Alpine        | `apk add sing-box`           | [![Alpine Linux Edge package](https://repology.org/badge/version-for-repo/alpine_edge/sing-box.svg)][alpine]  |\n    | DEB      | AOSC          | `apt install sing-box`       | [![AOSC package](https://repology.org/badge/version-for-repo/aosc/sing-box.svg)][aosc]                        |\n\n=== \":material-apple: macOS\"\n\n    | Type     | Platform | Command                 | Link                                                                                           |\n    |----------|----------|-------------------------|------------------------------------------------------------------------------------------------|\n    | Homebrew | macOS    | `brew install sing-box` | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew] |\n\n=== \":material-microsoft-windows: Windows\"\n\n    | Type       | Platform | Command                   | Link                                                                                                |\n    |------------|----------|---------------------------|-----------------------------------------------------------------------------------------------------|\n    | Scoop      | Windows  | `scoop install sing-box`  | [![Scoop package](https://repology.org/badge/version-for-repo/scoop/sing-box.svg)][scoop]           |\n    | Chocolatey | Windows  | `choco install sing-box`  | [![Chocolatey package](https://repology.org/badge/version-for-repo/chocolatey/sing-box.svg)][choco] |\n    | winget     | Windows  | `winget install sing-box` | [![winget package](https://repology.org/badge/version-for-repo/winget/sing-box.svg)][winget]        |\n\n=== \":material-android: Android\"\n\n    | Type   | Platform | Command            | Link                                                                                         |\n    |--------|----------|--------------------|----------------------------------------------------------------------------------------------|\n    | Termux | Android  | `pkg add sing-box` | [![Termux package](https://repology.org/badge/version-for-repo/termux/sing-box.svg)][termux] |\n\n=== \":material-freebsd: FreeBSD\"\n\n    | Type       | Platform | Command                | Link                                                                                       |\n    |------------|----------|------------------------|--------------------------------------------------------------------------------------------|\n    | FreshPorts | FreeBSD  | `pkg install sing-box` | [![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/sing-box.svg)][ports] |\n\n## :material-alert: Problematic Sources\n\n| Type       | Platform | Link                                                                                      | Promblem(s)                             |\n|------------|----------|-------------------------------------------------------------------------------------------|-----------------------------------------|\n| DEB        | AOSC     | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | Problematic build tag list modification |\n| Homebrew   | /        | [homebrew-core][brew]                                                                     | Problematic build tag list modification |\n| Termux     | Android  | [termux-packages][termux]                                                                 | Problematic build tag list modification |\n| FreshPorts | FreeBSD  | [FreeBSD ports][ports]                                                                    | Old Go  (go1.20)                        |\n\nIf you are a user of them, please report issues to them:\n\n1. Please do not modify release build tags without full understanding of the related functionality: enabling non-default\n   labels may result in decreased performance; the lack of default labels may cause user confusion.\n2. sing-box supports compiling with some older Go versions, but it is not recommended (especially versions that are no\n   longer supported by Go).\n\n## :material-book-multiple: Service Management\n\nFor Linux systems with [systemd][systemd], usually the installation already includes a sing-box service,\nyou can manage the service using the following command:\n\n| Operation | Command                                       |\n|-----------|-----------------------------------------------|\n| Enable    | `sudo systemctl enable sing-box`              |\n| Disable   | `sudo systemctl disable sing-box`             |\n| Start     | `sudo systemctl start sing-box`               |\n| Stop      | `sudo systemctl stop sing-box`                |\n| Kill      | `sudo systemctl kill sing-box`                |\n| Restart   | `sudo systemctl restart sing-box`             |\n| Logs      | `sudo journalctl -u sing-box --output cat -e` |\n| New Logs  | `sudo journalctl -u sing-box --output cat -f` |\n\n[alpine]: https://pkgs.alpinelinux.org/packages?name=sing-box\n\n[aur]: https://aur.archlinux.org/packages/sing-box\n\n[nixpkgs]: https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/tools/networking/sing-box/default.nix\n\n[brew]: https://formulae.brew.sh/formula/sing-box\n\n[openwrt]: https://github.com/openwrt/packages/tree/master/net/sing-box\n\n[immortalwrt]: https://github.com/immortalwrt/packages/tree/master/net/sing-box\n\n[choco]: https://chocolatey.org/packages/sing-box\n\n[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/sing-box.json\n\n[winget]: https://github.com/microsoft/winget-pkgs/tree/master/manifests/s/SagerNet/sing-box\n\n[termux]: https://github.com/termux/termux-packages/tree/master/packages/sing-box\n\n[ports]: https://www.freshports.org/net/sing-box\n\n[aosc]: https://packages.aosc.io/packages/sing-box\n\n[systemd]: https://systemd.io/\n"
  },
  {
    "path": "docs/installation/package-manager.zh.md",
    "content": "---\nicon: material/package\n---\n\n# 包管理器\n\n## :material-tram: 仓库安装\n\n=== \":material-debian: Debian / APT\"\n\n    ```bash\n    sudo mkdir -p /etc/apt/keyrings &&\n       sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&\n       sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&\n       echo '\n    Types: deb\n    URIs: https://deb.sagernet.org/\n    Suites: *\n    Components: *\n    Enabled: yes\n    Signed-By: /etc/apt/keyrings/sagernet.asc\n    ' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&\n       sudo apt-get update &&\n       sudo apt-get install sing-box # or sing-box-beta\n    ```\n\n=== \":material-redhat: Redhat / DNF 5\"\n\n    ```bash\n    sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&\n    sudo dnf install sing-box # or sing-box-beta\n    ```\n\n=== \":material-redhat: Redhat / DNF 4\"\n\n    ```bash\n    sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&\n    sudo dnf -y install dnf-plugins-core &&\n    sudo dnf install sing-box # or sing-box-beta\n    ```\n\n## :material-download-box: 手动安装\n\n该脚本从 GitHub 发布中下载并安装最新的软件包，适用于基于 deb 或 rpm 的 Linux 发行版、ArchLinux 和 OpenWrt。\n\n```shell\ncurl -fsSL https://sing-box.app/install.sh | sh\n```\n\n或最新测试版：\n\n```shell\ncurl -fsSL https://sing-box.app/install.sh | sh -s -- --beta\n```\n\n或指定版本：\n\n```shell\ncurl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>\n```\n\n## :material-book-lock-open: 托管安装\n\n=== \":material-linux: Linux\"\n\n    | 类型       | 平台            | 命令                           | 链接                                                                                                            |\n    |----------|---------------|------------------------------|---------------------------------------------------------------------------------------------------------------|\n    | AUR      | Arch Linux    | `? -S sing-box`              | [![AUR package](https://repology.org/badge/version-for-repo/aur/sing-box.svg)][aur]                           |\n    | nixpkgs  | NixOS         | `nix-env -iA nixos.sing-box` | [![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/sing-box.svg)][nixpkgs] |\n    | Homebrew | macOS / Linux | `brew install sing-box`      | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew]                |\n    | APK      | Alpine        | `apk add sing-box`           | [![Alpine Linux Edge package](https://repology.org/badge/version-for-repo/alpine_edge/sing-box.svg)][alpine]  |\n    | DEB      | AOSC          | `apt install sing-box`       | [![AOSC package](https://repology.org/badge/version-for-repo/aosc/sing-box.svg)][aosc]                        |\n\n=== \":material-apple: macOS\"\n\n    | 类型       | 平台    | 命令                      | 链接                                                                                             |\n    |----------|-------|-------------------------|------------------------------------------------------------------------------------------------|\n    | Homebrew | macOS | `brew install sing-box` | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew] |\n\n=== \":material-microsoft-windows: Windows\"\n\n    | 类型         | 平台      | 命令                        | 链接                                                                                                  |\n    |------------|---------|---------------------------|-----------------------------------------------------------------------------------------------------|\n    | Scoop      | Windows | `scoop install sing-box`  | [![Scoop package](https://repology.org/badge/version-for-repo/scoop/sing-box.svg)][scoop]           |\n    | Chocolatey | Windows | `choco install sing-box`  | [![Chocolatey package](https://repology.org/badge/version-for-repo/chocolatey/sing-box.svg)][choco] |\n    | winget     | Windows | `winget install sing-box` | [![winget package](https://repology.org/badge/version-for-repo/winget/sing-box.svg)][winget]        |\n\n=== \":material-android: Android\"\n\n    | 类型     | 平台      | 命令                 | 链接                                                                                           |\n    |--------|---------|--------------------|----------------------------------------------------------------------------------------------|\n    | Termux | Android | `pkg add sing-box` | [![Termux package](https://repology.org/badge/version-for-repo/termux/sing-box.svg)][termux] |\n\n=== \":material-freebsd: FreeBSD\"\n\n    | 类型         | 平台      | 命令                     | 链接                                                                                         |\n    |------------|---------|------------------------|--------------------------------------------------------------------------------------------|\n    | FreshPorts | FreeBSD | `pkg install sing-box` | [![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/sing-box.svg)][ports] |\n\n## :material-alert: 存在问题的源\n\n| 类型         | 平台      | 链接                                                                                        | 原因              |\n|------------|---------|-------------------------------------------------------------------------------------------|-----------------|\n| DEB        | AOSC    | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | 存在问题的构建标志列表修改   |\n| Homebrew   | /       | [homebrew-core][brew]                                                                     | 存在问题的构建标志列表修改   |\n| Termux     | Android | [termux-packages][termux]                                                                 | 存在问题的构建标志列表修改   |\n| FreshPorts | FreeBSD | [FreeBSD ports][ports]                                                                    | 太旧的 Go (go1.20) |\n\n如果您是其用户，请向他们报告问题：\n\n1. 在未完全了解相关功能的情况下，请勿修改发布版本标签：启用非默认标签可能会导致性能下降；缺少默认标签可能会引起用户混淆。\n2. sing-box 支持使用一些较旧的 Go 版本进行编译，但不推荐使用（特别是已不再受 Go 支持的版本）。\n\n## :material-book-multiple: 服务管理\n\n对于带有 [systemd][systemd] 的 Linux 系统，通常安装已经包含 sing-box 服务，\n您可以使用以下命令管理服务：\n\n| 行动   | 命令                                            |\n|------|-----------------------------------------------|\n| 启用   | `sudo systemctl enable sing-box`              |\n| 禁用   | `sudo systemctl disable sing-box`             |\n| 启动   | `sudo systemctl start sing-box`               |\n| 停止   | `sudo systemctl stop sing-box`                |\n| 强行停止 | `sudo systemctl kill sing-box`                |\n| 重新启动 | `sudo systemctl restart sing-box`             |\n| 查看日志 | `sudo journalctl -u sing-box --output cat -e` |\n| 实时日志 | `sudo journalctl -u sing-box --output cat -f` |\n\n[alpine]: https://pkgs.alpinelinux.org/packages?name=sing-box\n\n[aur]: https://aur.archlinux.org/packages/sing-box\n\n[nixpkgs]: https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/tools/networking/sing-box/default.nix\n\n[brew]: https://formulae.brew.sh/formula/sing-box\n\n[choco]: https://chocolatey.org/packages/sing-box\n\n[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/sing-box.json\n\n[winget]: https://github.com/microsoft/winget-pkgs/tree/master/manifests/s/SagerNet/sing-box\n\n[termux]: https://github.com/termux/termux-packages/tree/master/packages/sing-box\n\n[ports]: https://www.freshports.org/net/sing-box\n\n[aosc]: https://packages.aosc.io/packages/sing-box\n\n[systemd]: https://systemd.io/\n"
  },
  {
    "path": "docs/installation/tools/arch-install.sh",
    "content": "#!/bin/bash\n\nset -e -o pipefail\n\nARCH_RAW=$(uname -m)\ncase \"${ARCH_RAW}\" in\n    'x86_64')    ARCH='amd64';;\n    'x86' | 'i686' | 'i386')     ARCH='386';;\n    'aarch64' | 'arm64') ARCH='arm64';;\n    'armv7l')   ARCH='armv7';;\n    's390x')    ARCH='s390x';;\n    *)          echo \"Unsupported architecture: ${ARCH_RAW}\"; exit 1;;\nesac\n\nVERSION=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest \\\n    | grep tag_name \\\n    | cut -d \":\" -f2 \\\n    | sed 's/\\\"//g;s/\\,//g;s/\\ //g;s/v//')\n\ncurl -Lo sing-box.pkg.tar.zst \"https://github.com/SagerNet/sing-box/releases/download/v${VERSION}/sing-box_${VERSION}_linux_${ARCH}.pkg.tar.zst\"\nsudo pacman -U sing-box.pkg.tar.zst\nrm sing-box.pkg.tar.zst\n"
  },
  {
    "path": "docs/installation/tools/deb-install.sh",
    "content": "#!/bin/bash\n\nset -e -o pipefail\n\nARCH_RAW=$(uname -m)\ncase \"${ARCH_RAW}\" in\n    'x86_64')    ARCH='amd64';;\n    'x86' | 'i686' | 'i386')     ARCH='386';;\n    'aarch64' | 'arm64') ARCH='arm64';;\n    'armv7l')   ARCH='armv7';;\n    's390x')    ARCH='s390x';;\n    *)          echo \"Unsupported architecture: ${ARCH_RAW}\"; exit 1;;\nesac\n\nVERSION=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest \\\n    | grep tag_name \\\n    | cut -d \":\" -f2 \\\n    | sed 's/\\\"//g;s/\\,//g;s/\\ //g;s/v//')\n\ncurl -Lo sing-box.deb \"https://github.com/SagerNet/sing-box/releases/download/v${VERSION}/sing-box_${VERSION}_linux_${ARCH}.deb\"\nsudo dpkg -i sing-box.deb\nrm sing-box.deb\n\n"
  },
  {
    "path": "docs/installation/tools/install.sh",
    "content": "#!/bin/sh\n\ndownload_beta=false\ndownload_version=\"\"\n\nwhile [ $# -gt 0 ]; do\n  case \"$1\" in\n    --beta)\n      download_beta=true\n      shift\n      ;;\n    --version)\n      shift\n      if [ $# -eq 0 ]; then\n        echo \"Missing argument for --version\"\n        echo \"Usage: $0 [--beta] [--version <version>]\"\n        exit 1\n      fi\n      download_version=\"$1\"\n      shift\n      ;;\n    *)\n      echo \"Unknown argument: $1\"\n      echo \"Usage: $0 [--beta] [--version <version>]\"\n      exit 1\n      ;;\n  esac\ndone\n\nif command -v pacman >/dev/null 2>&1; then\n  os=\"linux\"\n  arch=$(uname -m)\n  package_suffix=\".pkg.tar.zst\"\n  package_install=\"pacman -U --noconfirm\"\nelif command -v dpkg >/dev/null 2>&1; then\n  os=\"linux\"\n  arch=$(dpkg --print-architecture)\n  package_suffix=\".deb\"\n  package_install=\"dpkg -i\"\nelif command -v dnf >/dev/null 2>&1; then\n  os=\"linux\"\n  arch=$(uname -m)\n  package_suffix=\".rpm\"\n  package_install=\"dnf install -y\"\nelif command -v rpm >/dev/null 2>&1; then\n  os=\"linux\"\n  arch=$(uname -m)\n  package_suffix=\".rpm\"\n  package_install=\"rpm -i\"\nelif command -v apk >/dev/null 2>&1 && [ -f /etc/os-release ] && grep -q OPENWRT_ARCH /etc/os-release; then\n  os=\"openwrt\"\n  . /etc/os-release\n  arch=\"$OPENWRT_ARCH\"\n  package_suffix=\".apk\"\n  package_install=\"apk add --allow-untrusted\"\nelif command -v apk >/dev/null 2>&1; then\n  os=\"linux\"\n  arch=$(apk --print-arch)\n  package_suffix=\".apk\"\n  package_install=\"apk add --allow-untrusted\"\nelif command -v opkg >/dev/null 2>&1; then\n  os=\"openwrt\"\n  . /etc/os-release\n  arch=\"$OPENWRT_ARCH\"\n  package_suffix=\".ipk\"\n  package_install=\"opkg update && opkg install\"\nelse\n  echo \"Missing supported package manager.\"\n  exit 1\nfi\n\nif [ -z \"$download_version\" ]; then\n  if [ \"$download_beta\" != \"true\" ]; then\n    if [ -n \"$GITHUB_TOKEN\" ]; then\n      latest_release=$(curl -s -H \"Authorization: token ${GITHUB_TOKEN}\" https://api.github.com/repos/SagerNet/sing-box/releases/latest)\n    else\n      latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest)\n    fi\n    curl_exit_status=$?\n    if [ $curl_exit_status -ne 0 ]; then\n      exit $curl_exit_status\n    fi\n    if [ \"$(echo \"$latest_release\" | grep tag_name | wc -l)\" -eq 0 ]; then\n      echo \"$latest_release\"\n      exit 1\n    fi\n    download_version=$(echo \"$latest_release\" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[\", v]//g')\n  else\n    if [ -n \"$GITHUB_TOKEN\" ]; then\n      latest_release=$(curl -s -H \"Authorization: token ${GITHUB_TOKEN}\" https://api.github.com/repos/SagerNet/sing-box/releases)\n    else\n      latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases)\n    fi\n    curl_exit_status=$?\n    if [ $curl_exit_status -ne 0 ]; then\n      exit $curl_exit_status\n    fi\n    if [ \"$(echo \"$latest_release\" | grep tag_name | wc -l)\" -eq 0 ]; then\n      echo \"$latest_release\"\n      exit 1\n    fi\n    download_version=$(echo \"$latest_release\" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[\", v]//g')\n  fi\nfi\n\npackage_name=\"sing-box_${download_version}_${os}_${arch}${package_suffix}\"\npackage_url=\"https://github.com/SagerNet/sing-box/releases/download/v${download_version}/${package_name}\"\n\necho \"Downloading $package_url\"\nif [ -n \"$GITHUB_TOKEN\" ]; then\n  curl --fail -Lo \"$package_name\" -H \"Authorization: token ${GITHUB_TOKEN}\" \"$package_url\"\nelse\n  curl --fail -Lo \"$package_name\" \"$package_url\"\nfi\n\ncurl_exit_status=$?\nif [ $curl_exit_status -ne 0 ]; then\n  exit $curl_exit_status\nfi\n\nif command -v sudo >/dev/null 2>&1; then\n  package_install=\"sudo $package_install\"\nfi\n\necho \"$package_install $package_name\"\nsh -c \"$package_install \\\"$package_name\\\"\"\nrm -f \"$package_name\"\n"
  },
  {
    "path": "docs/installation/tools/rpm-install.sh",
    "content": "#!/bin/bash\n\nset -e -o pipefail\n\nARCH_RAW=$(uname -m)\ncase \"${ARCH_RAW}\" in\n    'x86_64')    ARCH='amd64';;\n    'x86' | 'i686' | 'i386')     ARCH='386';;\n    'aarch64' | 'arm64') ARCH='arm64';;\n    'armv7l')   ARCH='armv7';;\n    's390x')    ARCH='s390x';;\n    *)          echo \"Unsupported architecture: ${ARCH_RAW}\"; exit 1;;\nesac\n\nVERSION=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest \\\n    | grep tag_name \\\n    | cut -d \":\" -f2 \\\n    | sed 's/\\\"//g;s/\\,//g;s/\\ //g;s/v//')\n\ncurl -Lo sing-box.rpm \"https://github.com/SagerNet/sing-box/releases/download/v${VERSION}/sing-box_${VERSION}_linux_${ARCH}.rpm\"\nsudo rpm -i sing-box.rpm\nrm sing-box.rpm\n"
  },
  {
    "path": "docs/installation/tools/sing-box.repo",
    "content": "[sing-box]\nname=sing-box\nbaseurl=https://rpm.sagernet.org/\nenabled=1\nrepo_gpgcheck=1\ngpgcheck=1\ngpgkey=https://sing-box.app/gpg.key\n"
  },
  {
    "path": "docs/manual/misc/tunnelvision.md",
    "content": "---\nicon: material/book-lock-open\n---\n\n# TunnelVision\n\nTunnelVision is an attack that uses DHCP option 121 to set higher priority routes\nso that traffic does not go through the VPN.\n\nReference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-3661\n\n## Status\n\n### Android\n\nAndroid does not handle DHCP option 121 and is not affected.\n\n### Apple platforms\n\nUpdate [sing-box graphical client](/clients/apple/#download) to `1.9.0-rc.16` or newer,\nthen enable `includeAllNetworks` in `Settings` — `Packet Tunnel` and you will be unaffected.\n\nNote: when `includeAllNetworks` is enabled, the default TUN stack is changed to `gvisor`,\nand the `system` and `mixed` stacks are not available.\n\n### Linux\n\nUpdate sing-box to `1.9.0-rc.16` or newer, rules generated by `auto-route` are unaffected.\n\n### Windows\n\nNo solution yet.\n\n## Workarounds\n\n* Don't connect to untrusted networks\n* Relay untrusted network through another device\n* Just ignore it\n"
  },
  {
    "path": "docs/manual/proxy/client.md",
    "content": "---\nicon: material/cellphone-link\n---\n\n# Client\n\n### :material-ray-start: Introduction\n\nFor a long time, the modern usage and principles of proxy clients\nfor graphical operating systems have not been clearly described.\nHowever, we can categorize them into three types:\nsystem proxy, firewall redirection, and virtual interface.\n\n### :material-web-refresh: System Proxy\n\nAlmost all graphical environments support system-level proxies,\nwhich are essentially ordinary HTTP proxies that only support TCP.\n\n| Operating System / Desktop Environment       | System Proxy                         | Application Support |\n|:---------------------------------------------|:-------------------------------------|:--------------------|\n| Windows                                      | :material-check:                     | :material-check:    |\n| macOS                                        | :material-check:                     | :material-check:    |\n| GNOME/KDE                                    | :material-check:                     | :material-check:    |\n| Android                                      | ROOT or adb (permission) is required | :material-check:    |\n| Android/iOS (with sing-box graphical client) | via `tun.platform.http_proxy`        | :material-check:    |\n\nAs one of the most well-known proxy methods, it has many shortcomings:\nmany TCP clients that are not based on HTTP do not check and use the system proxy.\nMoreover, UDP and ICMP traffics bypass the proxy.\n\n```mermaid\nflowchart LR\n    dns[DNS query] -- Is HTTP request? --> proxy[HTTP proxy]\n    dns --> leak[Leak]\n    tcp[TCP connection] -- Is HTTP request? --> proxy\n    tcp -- Check and use HTTP CONNECT? --> proxy\n    tcp --> leak\n    udp[UDP packet] --> leak\n```\n\n### :material-wall-fire: Firewall Redirection\n\nThis type of usage typically relies on the firewall or hook interface provided by the operating system,\nsuch as Windows’ WFP, Linux’s redirect, TProxy and eBPF, and macOS’s pf.\nAlthough it is intrusive and cumbersome to configure,\nit remains popular within the community of amateur proxy open source projects like V2Ray,\ndue to the low technical requirements it imposes on the software.\n\n### :material-expansion-card: Virtual Interface\n\nAll L2/L3 proxies (seriously defined VPNs, such as OpenVPN, WireGuard) are based on virtual network interfaces,\nwhich is also the only way for all L4 proxies to work as VPNs on mobile platforms like Android, iOS.\n\nThe sing-box inherits and develops clash-premium’s TUN inbound (L3 to L4 conversion)\nas the most reasonable method for performing transparent proxying.\n\n```mermaid\nflowchart TB\n    packet[IP Packet]\n    packet --> windows[Windows / macOS]\n    packet --> linux[Linux]\n    tun[TUN interface]\n    windows -. route .-> tun\n    linux -. iproute2 route/rule .-> tun\n    tun --> gvisor[gVisor TUN stack]\n    tun --> system[system TUN stack]\n    assemble([L3 to L4 assemble])\n    gvisor --> assemble\n    system --> assemble\n    assemble --> conn[TCP and UDP connections]\n    conn --> router[sing-box Router]\n    router --> direct[Direct outbound]\n    router --> proxy[Proxy outbounds]\n    router -- DNS hijack --> dns_out[DNS outbound]\n    dns_out --> dns_router[DNS router]\n    dns_router --> router\n    direct --> adi([auto detect interface])\n    proxy --> adi\n    adi --> default[Default network interface in the system]\n    default --> destination[Destination server]\n    default --> proxy_server[Proxy server]\n    proxy_server --> destination\n```\n\n## :material-cellphone-link: Examples\n\n### Basic TUN usage for Chinese users\n\n=== \":material-numeric-4-box: IPv4 only\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"tag\": \"google\",\n            \"type\": \"tls\",\n            \"server\": \"8.8.8.8\"\n          },\n          {\n            \"tag\": \"local\",\n            \"type\": \"udp\",\n            \"server\": \"223.5.5.5\"\n          }\n        ],\n        \"strategy\": \"ipv4_only\"\n      },\n      \"inbounds\": [\n        {\n          \"type\": \"tun\",\n          \"address\": [\"172.19.0.1/30\"],\n          \"auto_route\": true,\n          // \"auto_redirect\": true, // On linux\n          \"strict_route\": true\n        }\n      ],\n      \"outbounds\": [\n        // ...\n        {\n          \"type\": \"direct\",\n          \"tag\": \"direct\"\n        }\n      ],\n      \"route\": {\n        \"rules\": [\n          {\n            \"action\": \"sniff\"\n          },\n          {\n            \"protocol\": \"dns\",\n            \"action\": \"hijack-dns\"\n          },\n          {\n            \"ip_is_private\": true,\n            \"outbound\": \"direct\"\n          }\n        ],\n        \"default_domain_resolver\": \"local\",\n        \"auto_detect_interface\": true\n      }\n    }\n    ```\n\n=== \":material-numeric-6-box: IPv4 & IPv6\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"tag\": \"google\",\n            \"type\": \"tls\",\n            \"server\": \"8.8.8.8\"\n          },\n          {\n            \"tag\": \"local\",\n            \"type\": \"udp\",\n            \"server\": \"223.5.5.5\"\n          }\n        ]\n      },\n      \"inbounds\": [\n        {\n          \"type\": \"tun\",\n          \"address\": [\"172.19.0.1/30\", \"fdfe:dcba:9876::1/126\"],\n          \"auto_route\": true,\n          // \"auto_redirect\": true, // On linux\n          \"strict_route\": true\n        }\n      ],\n      \"outbounds\": [\n        // ...\n        {\n          \"type\": \"direct\",\n          \"tag\": \"direct\"\n        }\n      ],\n      \"route\": {\n        \"rules\": [\n          {\n            \"action\": \"sniff\"\n          },\n          {\n            \"protocol\": \"dns\",\n            \"action\": \"hijack-dns\"\n          },\n          {\n            \"ip_is_private\": true,\n            \"outbound\": \"direct\"\n          }\n        ],\n        \"default_domain_resolver\": \"local\",\n        \"auto_detect_interface\": true\n      }\n    }\n    ```\n\n=== \":material-domain-switch: FakeIP\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"tag\": \"google\",\n            \"type\": \"tls\",\n            \"server\": \"8.8.8.8\"\n          },\n          {\n            \"tag\": \"local\",\n            \"type\": \"udp\",\n            \"server\": \"223.5.5.5\"\n          },\n          {\n            \"tag\": \"remote\",\n            \"type\": \"fakeip\",\n            \"inet4_range\": \"198.18.0.0/15\",\n            \"inet6_range\": \"fc00::/18\"\n          }\n        ],\n        \"rules\": [\n          {\n            \"query_type\": [\n              \"A\",\n              \"AAAA\"\n            ],\n            \"server\": \"remote\"\n          }\n        ],\n        \"independent_cache\": true\n      },\n      \"inbounds\": [\n        {\n          \"type\": \"tun\",\n          \"address\": [\"172.19.0.1/30\",\"fdfe:dcba:9876::1/126\"],\n          \"auto_route\": true,\n          // \"auto_redirect\": true, // On linux\n          \"strict_route\": true\n        }\n      ],\n      \"outbounds\": [\n        // ...\n        {\n          \"type\": \"direct\",\n          \"tag\": \"direct\"\n        }\n      ],\n      \"route\": {\n        \"rules\": [\n          {\n            \"action\": \"sniff\"\n          },\n          {\n            \"protocol\": \"dns\",\n            \"action\": \"hijack-dns\"\n          },\n          {\n            \"ip_is_private\": true,\n            \"outbound\": \"direct\"\n          }\n        ],\n        \"default_domain_resolver\": \"local\",\n        \"auto_detect_interface\": true\n      }\n    }\n    ```\n\n### Traffic bypass usage for Chinese users\n\n=== \":material-dns: DNS rules\"\n\n    === \":material-shield-off: With DNS leaks\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"tag\": \"google\",\n                \"type\": \"tls\",\n                \"server\": \"8.8.8.8\"\n              },\n              {\n                \"tag\": \"local\",\n                \"type\": \"https\",\n                \"server\": \"223.5.5.5\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"rule_set\": \"geosite-geolocation-cn\",\n                \"server\": \"local\"\n              },\n              {\n                \"type\": \"logical\",\n                \"mode\": \"and\",\n                \"rules\": [\n                  {\n                    \"rule_set\": \"geosite-geolocation-!cn\",\n                    \"invert\": true\n                  },\n                  {\n                    \"rule_set\": \"geoip-cn\"\n                  }\n                ],\n                \"server\": \"local\"\n              }\n            ]\n          },\n          \"route\": {\n            \"default_domain_resolver\": \"local\",\n            \"rule_set\": [\n              {\n                \"type\": \"remote\",\n                \"tag\": \"geosite-geolocation-cn\",\n                \"format\": \"binary\",\n                \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs\"\n              },\n              {\n                \"type\": \"remote\",\n                \"tag\": \"geosite-geolocation-!cn\",\n                \"format\": \"binary\",\n                \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs\"\n              },\n              {\n                \"type\": \"remote\",\n                \"tag\": \"geoip-cn\",\n                \"format\": \"binary\",\n                \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs\"\n              }\n            ]\n          },\n          \"experimental\": {\n            \"cache_file\": {\n              \"enabled\": true,\n              \"store_rdrc\": true\n            },\n            \"clash_api\": {\n              \"default_mode\": \"Enhanced\"\n            }\n          }\n        }\n        ```\n\n    === \":material-security: Without DNS leaks, but slower\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"tag\": \"google\",\n                \"type\": \"tls\",\n                \"server\": \"8.8.8.8\"\n              },\n              {\n                \"tag\": \"local\",\n                \"type\": \"https\",\n                \"server\": \"223.5.5.5\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"rule_set\": \"geosite-geolocation-cn\",\n                \"server\": \"local\"\n              },\n              {\n                \"type\": \"logical\",\n                \"mode\": \"and\",\n                \"rules\": [\n                  {\n                    \"rule_set\": \"geosite-geolocation-!cn\",\n                    \"invert\": true\n                  },\n                  {\n                    \"rule_set\": \"geoip-cn\"\n                  }\n                ],\n                \"server\": \"google\",\n                \"client_subnet\": \"114.114.114.114/24\" // Any China client IP address\n              }\n            ]\n          },\n          \"route\": {\n            \"default_domain_resolver\": \"local\",\n            \"rule_set\": [\n              {\n                \"type\": \"remote\",\n                \"tag\": \"geosite-geolocation-cn\",\n                \"format\": \"binary\",\n                \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs\"\n              },\n              {\n                \"type\": \"remote\",\n                \"tag\": \"geosite-geolocation-!cn\",\n                \"format\": \"binary\",\n                \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs\"\n              },\n              {\n                \"type\": \"remote\",\n                \"tag\": \"geoip-cn\",\n                \"format\": \"binary\",\n                \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs\"\n              }\n            ]\n          },\n          \"experimental\": {\n            \"cache_file\": {\n              \"enabled\": true,\n              \"store_rdrc\": true\n            },\n            \"clash_api\": {\n              \"default_mode\": \"Enhanced\"\n            }\n          }\n        }\n        ```\n\n=== \":material-router-network: Route rules\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"direct\",\n          \"tag\": \"direct\"\n        }\n      ],\n      \"route\": {\n        \"rules\": [\n          {\n            \"action\": \"sniff\"\n          },\n          {\n            \"type\": \"logical\",\n            \"mode\": \"or\",\n            \"rules\": [\n              {\n                \"protocol\": \"dns\"\n              },\n              {\n                \"port\": 53\n              }\n            ],\n            \"action\": \"hijack-dns\"\n          },\n          {\n            \"ip_is_private\": true,\n            \"outbound\": \"direct\"\n          },\n          {\n            \"type\": \"logical\",\n            \"mode\": \"or\",\n            \"rules\": [\n              {\n                \"port\": 853\n              },\n              {\n                \"network\": \"udp\",\n                \"port\": 443\n              },\n              {\n                \"protocol\": \"stun\"\n              }\n            ],\n            \"action\": \"reject\"\n          },\n          {\n            \"rule_set\": \"geosite-geolocation-cn\",\n            \"outbound\": \"direct\"\n          },\n          {\n            \"type\": \"logical\",\n            \"mode\": \"and\",\n            \"rules\": [\n              {\n                \"rule_set\": \"geoip-cn\"\n              },\n              {\n                \"rule_set\": \"geosite-geolocation-!cn\",\n                \"invert\": true\n              }\n            ],\n            \"outbound\": \"direct\"\n          }\n        ],\n        \"rule_set\": [\n          {\n            \"type\": \"remote\",\n            \"tag\": \"geoip-cn\",\n            \"format\": \"binary\",\n            \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs\"\n          },\n          {\n            \"type\": \"remote\",\n            \"tag\": \"geosite-geolocation-cn\",\n            \"format\": \"binary\",\n            \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs\"\n          }\n        ]\n      }\n    }\n    ```\n"
  },
  {
    "path": "docs/manual/proxy/server.md",
    "content": "---\nicon: material/server\n---\n\n# Server\n\nTo use sing-box as a proxy protocol server, you pretty much only need to configure the inbound for that protocol.\n\nThe Proxy Protocol menu below contains descriptions and configuration examples\nof recommended protocols for bypassing GFW.\n"
  },
  {
    "path": "docs/manual/proxy-protocol/hysteria2.md",
    "content": "---\nicon: material/lightning-bolt\n---\n\n# Hysteria 2\n\nHysteria 2 is a simple, Chinese-made protocol based on QUIC.\nThe selling point is Brutal, a congestion control algorithm that\ntries to achieve a user-defined bandwidth despite packet loss.\n\n!!! warning\n\n    Even though GFW rarely blocks UDP-based proxies, such protocols actually have far more obvious characteristics than TCP based proxies.\n\n| Specification                                                             | Resists passive detection | Resists active probes |\n|---------------------------------------------------------------------------|---------------------------|-----------------------|\n| [hysteria.network](https://v2.hysteria.network/docs/developers/Protocol/) | :material-alert:          | :material-check:      |\n\n## :material-text-box-check: Password Generator\n\n| Generate Password          | Action                                                          |\n|----------------------------|-----------------------------------------------------------------|\n| <code id=\"password\"><code> | <button class=\"md-button\" onclick=\"generate()\">Refresh</button> |\n\n<script>\n    function generate() {\n        const array = new Uint8Array(16);\n        window.crypto.getRandomValues(array);\n        document.getElementById(\"password\").textContent = btoa(String.fromCharCode.apply(null, array));\n    }\n    generate();\n</script>\n\n## :material-alert: Difference from official Hysteria\n\nThe official program supports an authentication method called **userpass**,\nwhich essentially uses a combination of `<username>:<password>` as the actual password,\nwhile sing-box does not provide this alias.\nTo use sing-box with the official program, you need to fill in that combination as the actual password.\n\n## :material-server: Server Example\n\n!!! info \"\"\n\n    Replace `up_mbps` and `down_mbps` values with the actual bandwidth of your server.\n\n=== \":material-harddisk: With local certificate\"\n\n    ```json\n     {\n      \"inbounds\": [\n        {\n          \"type\": \"hysteria2\",\n          \"listen\": \"::\",\n          \"listen_port\": 8080,\n          \"up_mbps\": 100,\n          \"down_mbps\": 100,\n          \"users\": [\n            {\n              \"name\": \"sekai\",\n              \"password\": \"<password>\"\n            }\n          ],\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\",\n            \"key_path\": \"/path/to/key.pem\",\n            \"certificate_path\": \"/path/to/certificate.pem\"\n          }\n        }\n      ]\n    }\n    ```\n\n=== \":material-auto-fix: With ACME\"\n\n    ```json\n     {\n      \"inbounds\": [\n        {\n          \"type\": \"hysteria2\",\n          \"listen\": \"::\",\n          \"listen_port\": 8080,\n          \"up_mbps\": 100,\n          \"down_mbps\": 100,\n          \"users\": [\n            {\n              \"name\": \"sekai\",\n              \"password\": \"<password>\"\n            }\n          ],\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\",\n            \"acme\": {\n              \"domain\": \"example.org\",\n              \"email\": \"admin@example.org\"\n            }\n          }\n        }\n      ]\n    }\n    ```\n\n=== \":material-cloud: With ACME and Cloudflare API\"\n\n    ```json\n     {\n      \"inbounds\": [\n        {\n          \"type\": \"hysteria2\",\n          \"listen\": \"::\",\n          \"listen_port\": 8080,\n          \"up_mbps\": 100,\n          \"down_mbps\": 100,\n          \"users\": [\n            {\n              \"name\": \"sekai\",\n              \"password\": \"<password>\"\n            }\n          ],\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\",\n            \"acme\": {\n              \"domain\": \"example.org\",\n              \"email\": \"admin@example.org\",\n              \"dns01_challenge\": {\n                \"provider\": \"cloudflare\",\n                \"api_token\": \"my_token\"\n              }\n            }\n          }\n        }\n      ]\n    }\n    ```\n\n## :material-cellphone-link: Client Example\n\n!!! info \"\"\n\n    Replace `up_mbps` and `down_mbps` values with the actual bandwidth of your client.\n\n=== \":material-web-check: With valid certificate\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"hysteria2\",\n          \"server\": \"127.0.0.1\",\n          \"server_port\": 8080,\n          \"up_mbps\": 100,\n          \"down_mbps\": 100,\n          \"password\": \"<password>\",\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\"\n          }\n        }\n      ]\n    }\n    ```\n\n=== \":material-check: With self-sign certificate\"\n\n    !!! info \"Tip\"\n        \n        Use `sing-box merge` command to merge configuration and certificate into one file.\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"hysteria2\",\n          \"server\": \"127.0.0.1\",\n          \"server_port\": 8080,\n          \"up_mbps\": 100,\n          \"down_mbps\": 100,\n          \"password\": \"<password>\",\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\",\n            \"certificate_path\": \"/path/to/certificate.pem\"\n          }\n        }\n      ]\n    }\n    ```\n\n=== \":material-alert: Ignore certificate verification\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"hysteria2\",\n          \"server\": \"127.0.0.1\",\n          \"server_port\": 8080,\n          \"up_mbps\": 100,\n          \"down_mbps\": 100,\n          \"password\": \"<password>\",\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\",\n            \"insecure\": true\n          }\n        }\n      ]\n    }\n    ```\n"
  },
  {
    "path": "docs/manual/proxy-protocol/shadowsocks.md",
    "content": "---\nicon: material/send\n---\n\n# Shadowsocks\n\nShadowsocks is the most well-known Chinese-made proxy protocol.\nIt exists in multiple versions, but only AEAD 2022 ciphers \nover TCP with multiplexing is recommended.\n\n| Ciphers        | Specification                                              | Cryptographically sound | Resists passive detection | Resists active probes |\n|----------------|------------------------------------------------------------|-------------------------|---------------------------|-----------------------|\n| Stream Ciphers | [shadowsocks.org](https://shadowsocks.org/doc/stream.html) | :material-alert:        | :material-alert:          | :material-alert:      |\n| AEAD           | [shadowsocks.org](https://shadowsocks.org/doc/aead.html)   | :material-check:        | :material-alert:          | :material-alert:      |\n| AEAD 2022      | [shadowsocks.org](https://shadowsocks.org/doc/sip022.html) | :material-check:        | :material-check:          | :material-help:       |\n\n(We strongly recommend using multiplexing to send UDP traffic over TCP, because\ndoing otherwise is vulnerable to passive detection.)\n\n## :material-text-box-check: Password Generator\n\n| For `2022-blake3-aes-128-gcm` cipher | For other ciphers             | Action                                                          |\n|--------------------------------------|-------------------------------|-----------------------------------------------------------------|\n| <code id=\"password_16\"><code>        | <code id=\"password_32\"><code> | <button class=\"md-button\" onclick=\"generate()\">Refresh</button> |\n\n<script>\n    function generatePassword(element, length) {\n        const array = new Uint8Array(length);\n        window.crypto.getRandomValues(array);\n        document.getElementById(element).textContent = btoa(String.fromCharCode.apply(null, array));\n    }\n    function generate() {\n      generatePassword(\"password_16\", 16);\n      generatePassword(\"password_32\", 32);\n    }\n    generate();\n</script>\n\n## :material-server: Server Example\n\n=== \":material-account: Single-user\"\n\n    ```json\n     {\n      \"inbounds\": [\n        {\n          \"type\": \"shadowsocks\",\n          \"listen\": \"::\",\n          \"listen_port\": 8080,\n          \"network\": \"tcp\",\n          \"method\": \"2022-blake3-aes-128-gcm\",\n          \"password\": \"<password>\",\n          \"multiplex\": {\n            \"enabled\": true\n          }\n        }\n      ]\n    }\n    ```\n\n=== \":material-account-multiple: Multi-user\"\n\n    ```json\n     {\n      \"inbounds\": [\n        {\n          \"type\": \"shadowsocks\",\n          \"listen\": \"::\",\n          \"listen_port\": 8080,\n          \"network\": \"tcp\",\n          \"method\": \"2022-blake3-aes-128-gcm\",\n          \"password\": \"<server_password>\",\n          \"users\": [\n            {\n              \"name\": \"sekai\",\n              \"password\": \"<user_password>\"\n            }\n          ],\n          \"multiplex\": {\n            \"enabled\": true\n          }\n        }\n      ]\n    }\n    ```\n\n## :material-cellphone-link: Client Example\n\n=== \":material-account: Single-user\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"shadowsocks\",\n          \"server\": \"127.0.0.1\",\n          \"server_port\": 8080,\n          \"method\": \"2022-blake3-aes-128-gcm\",\n          \"password\": \"<pasword>\",\n          \"multiplex\": {\n            \"enabled\": true\n          }\n        }\n      ]\n    }\n    ```\n\n=== \":material-account-multiple: Multi-user\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"shadowsocks\",\n          \"server\": \"127.0.0.1\",\n          \"server_port\": 8080,\n          \"method\": \"2022-blake3-aes-128-gcm\",\n          \"password\": \"<server_pasword>:<user_password>\",\n          \"multiplex\": {\n            \"enabled\": true\n          }\n        }\n      ]\n    }\n    ```\n"
  },
  {
    "path": "docs/manual/proxy-protocol/trojan.md",
    "content": "---\nicon: material/horse\n---\n\n# Trojan\n\nTrojan is the most commonly used TLS proxy made in China. It can be used in various combinations.\n\n| Protocol and implementation combination | Specification                                                        | Resists passive detection | Resists active probes |\n|-----------------------------------------|----------------------------------------------------------------------|---------------------------|-----------------------|\n| Origin / trojan-gfw                     | [trojan-gfw.github.io](https://trojan-gfw.github.io/trojan/protocol) | :material-check:          | :material-check:      |\n| Basic Go implementation                 | /                                                                    | :material-alert:          | :material-check:      |\n| with privates transport by V2Ray        | No formal definition                                                 | :material-alert:          | :material-alert:      |\n| with uTLS enabled                       | No formal definition                                                 | :material-help:           | :material-check:      |\n\n## :material-text-box-check: Password Generator\n\n| Generate Password          | Action                                                          |\n|----------------------------|-----------------------------------------------------------------|\n| <code id=\"password\"><code> | <button class=\"md-button\" onclick=\"generate()\">Refresh</button> |\n\n<script>\n    function generate() {\n        const array = new Uint8Array(16);\n        window.crypto.getRandomValues(array);\n        document.getElementById(\"password\").textContent = btoa(String.fromCharCode.apply(null, array));\n    }\n    generate();\n</script>\n\n## :material-server: Server Example\n\n=== \":material-harddisk: With local certificate\"\n\n    ```json\n    {\n      \"inbounds\": [\n        {\n          \"type\": \"trojan\",\n          \"listen\": \"::\",\n          \"listen_port\": 8080,\n          \"users\": [\n            {\n              \"name\": \"example\",\n              \"password\": \"password\"\n            }\n          ],\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\",\n            \"key_path\": \"/path/to/key.pem\",\n            \"certificate_path\": \"/path/to/certificate.pem\"\n          },\n          \"multiplex\": {\n            \"enabled\": true\n          }\n        }\n      ]\n    }\n    ```\n\n=== \":material-auto-fix: With ACME\"\n\n    ```json\n    {\n      \"inbounds\": [\n        {\n          \"type\": \"trojan\",\n          \"listen\": \"::\",\n          \"listen_port\": 8080,\n          \"users\": [\n            {\n              \"name\": \"example\",\n              \"password\": \"password\"\n            }\n          ],\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\",\n            \"acme\": {\n              \"domain\": \"example.org\",\n              \"email\": \"admin@example.org\"\n            }\n          },\n          \"multiplex\": {\n            \"enabled\": true\n          }\n        }\n      ]\n    }\n    ```\n\n=== \":material-cloud: With ACME and Cloudflare API\"\n\n    ```json\n    {\n      \"inbounds\": [\n        {\n          \"type\": \"trojan\",\n          \"listen\": \"::\",\n          \"listen_port\": 8080,\n          \"users\": [\n            {\n              \"name\": \"example\",\n              \"password\": \"password\"\n            }\n          ],\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\",\n            \"acme\": {\n              \"domain\": \"example.org\",\n              \"email\": \"admin@example.org\",\n              \"dns01_challenge\": {\n                \"provider\": \"cloudflare\",\n                \"api_token\": \"my_token\"\n              }\n            }\n          },\n          \"multiplex\": {\n            \"enabled\": true\n          }\n        }\n      ]\n    }\n    ```\n\n## :material-cellphone-link: Client Example\n\n=== \":material-web-check: With valid certificate\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"trojan\",\n          \"server\": \"127.0.0.1\",\n          \"server_port\": 8080,\n          \"password\": \"password\",\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\"\n          },\n          \"multiplex\": {\n            \"enabled\": true\n          }\n        }\n      ]\n    }\n    ```\n\n=== \":material-check: With self-sign certificate\"\n\n    !!! info \"Tip\"\n        \n        Use `sing-box merge` command to merge configuration and certificate into one file.\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"trojan\",\n          \"server\": \"127.0.0.1\",\n          \"server_port\": 8080,\n          \"password\": \"password\",\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\",\n            \"certificate_path\": \"/path/to/certificate.pem\"\n          },\n          \"multiplex\": {\n            \"enabled\": true\n          }\n        }\n      ]\n    }\n    ```\n\n=== \":material-alert: Ignore certificate verification\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"trojan\",\n          \"server\": \"127.0.0.1\",\n          \"server_port\": 8080,\n          \"password\": \"password\",\n          \"tls\": {\n            \"enabled\": true,\n            \"server_name\": \"example.org\",\n            \"insecure\": true\n          },\n          \"multiplex\": {\n            \"enabled\": true\n          }\n        }\n      ]\n    }\n    ```\n"
  },
  {
    "path": "docs/migration.md",
    "content": "---\nicon: material/arrange-bring-forward\n---\n\n## 1.12.0\n\n### Migrate to new DNS server formats\n\nDNS servers are refactored for better performance and scalability.\n\n!!! info \"References\"\n\n    [DNS Server](/configuration/dns/server/) /\n    [Legacy DNS Server](/configuration/dns/server/legacy/)\n\n=== \"Local\"\n\n    === \":material-card-remove: Deprecated\"\n        \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"local\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"local\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"TCP\"\n\n    === \":material-card-remove: Deprecated\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"tcp://1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"tcp\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"UDP\"\n\n    === \":material-card-remove: Deprecated\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"udp\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"TLS\"\n\n    === \":material-card-remove: Deprecated\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"tls://1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"tls\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"HTTPS\"\n\n    === \":material-card-remove: Deprecated\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"https://1.1.1.1/dns-query\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"https\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"QUIC\"\n\n    === \":material-card-remove: Deprecated\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"quic://1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"quic\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"HTTP3\"\n\n    === \":material-card-remove: Deprecated\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"h3://1.1.1.1/dns-query\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"h3\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"DHCP\"\n\n    === \":material-card-remove: Deprecated\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"dhcp://auto\"\n              },\n              {\n                \"address\": \"dhcp://en0\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"dhcp\",\n              },\n              {\n                \"type\": \"dhcp\",\n                \"interface\": \"en0\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"FakeIP\"\n\n    === \":material-card-remove: Deprecated\"\n        \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"1.1.1.1\"\n              },\n              {\n                \"address\": \"fakeip\",\n                \"tag\": \"fakeip\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"query_type\": [\n                  \"A\",\n                  \"AAAA\"\n                ],\n                \"server\": \"fakeip\"\n              }\n            ],\n            \"fakeip\": {\n              \"enabled\": true,\n              \"inet4_range\": \"198.18.0.0/15\",\n              \"inet6_range\": \"fc00::/18\"\n            }\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"udp\",\n                \"server\": \"1.1.1.1\"\n              },\n              {\n                \"type\": \"fakeip\",\n                \"tag\": \"fakeip\",\n                \"inet4_range\": \"198.18.0.0/15\",\n                \"inet6_range\": \"fc00::/18\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"query_type\": [\n                  \"A\",\n                  \"AAAA\"\n                ],\n                \"server\": \"fakeip\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"RCode\"\n\n    === \":material-card-remove: Deprecated\"\n        \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"rcode://refused\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n        \n        ```json\n        {\n          \"dns\": {\n            \"rules\": [\n              {\n                \"domain\": [\n                  \"example.com\"\n                ],\n                // other rules\n                \n                \"action\": \"predefined\",\n                \"rcode\": \"REFUSED\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"Servers with domain address\"\n\n    === \":material-card-remove: Deprecated\"\n        \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"https://dns.google/dns-query\",\n                \"address_resolver\": \"google\"\n              },\n              {\n                \"tag\": \"google\",\n                \"address\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"https\",\n                \"server\": \"dns.google\",\n                \"domain_resolver\": \"google\"\n              },\n              {\n                \"type\": \"udp\",\n                \"tag\": \"google\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"Servers with strategy\"\n\n    === \":material-card-remove: Deprecated\"\n            \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"1.1.1.1\",\n                \"strategy\": \"ipv4_only\"\n              },\n              {\n                \"tag\": \"google\",\n                \"address\": \"8.8.8.8\",\n                \"strategy\": \"prefer_ipv6\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"domain\": \"google.com\",\n                \"server\": \"google\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"udp\",\n                \"server\": \"1.1.1.1\"\n              },\n              {\n                \"type\": \"udp\",\n                \"tag\": \"google\",\n                \"server\": \"8.8.8.8\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"domain\": \"google.com\",\n                \"server\": \"google\",\n                \"strategy\": \"prefer_ipv6\"\n              }\n            ],\n            \"strategy\": \"ipv4_only\"\n          }\n        }\n        ```\n\n=== \"Servers with client subnet\"\n\n    === \":material-card-remove: Deprecated\"\n        \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"1.1.1.1\"\n              },\n              {\n                \"tag\": \"google\",\n                \"address\": \"8.8.8.8\",\n                \"client_subnet\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"udp\",\n                \"server\": \"1.1.1.1\"\n              },\n              {\n                \"type\": \"udp\",\n                \"tag\": \"google\",\n                \"server\": \"8.8.8.8\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"domain\": \"google.com\",\n                \"server\": \"google\",\n                \"client_subnet\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n### Migrate outbound DNS rule items to domain resolver\n\nThe legacy outbound DNS rules are deprecated and can be replaced by new domain resolver options.\n\n!!! info \"References\"\n\n    [DNS rule](/configuration/dns/rule/#outbound) /\n    [Dial Fields](/configuration/shared/dial/#domain_resolver) /\n    [Route](/configuration/route/#domain_resolver)\n\n=== \":material-card-remove: Deprecated\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"address\": \"local\",\n            \"tag\": \"local\"\n          }\n        ],\n        \"rules\": [\n          {\n            \"outbound\": \"any\",\n            \"server\": \"local\"\n          }\n        ]\n      },\n      \"outbounds\": [\n        {\n          \"type\": \"socks\",\n          \"server\": \"example.org\",\n          \"server_port\": 2080\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: New\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"local\",\n            \"tag\": \"local\"\n          }\n        ]\n      },\n      \"outbounds\": [\n        {\n          \"type\": \"socks\",\n          \"server\": \"example.org\",\n          \"server_port\": 2080,\n          \"domain_resolver\": {\n            \"server\": \"local\",\n            \"rewrite_ttl\": 60,\n            \"client_subnet\": \"1.1.1.1\"\n          },\n          // or \"domain_resolver\": \"local\",\n        }\n      ],\n      \n      // or\n    \n      \"route\": {\n        \"default_domain_resolver\": {\n          \"server\": \"local\",\n          \"rewrite_ttl\": 60,\n          \"client_subnet\": \"1.1.1.1\"\n        }\n      }\n    }\n    ```\n\n### Migrate outbound domain strategy option to domain resolver\n\n!!! info \"References\"\n\n    [Dial Fields](/configuration/shared/dial/#domain_strategy)\n\nThe `domain_strategy` option in Dial Fields has been deprecated and can be replaced with the new domain resolver option.\n\nNote that due to the use of Dial Fields by some of the new DNS servers introduced in sing-box 1.12,\nsome people mistakenly believe that `domain_strategy` is the same feature as in the legacy DNS servers.\n\n=== \":material-card-remove: Deprecated\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"socks\",\n          \"server\": \"example.org\",\n          \"server_port\": 2080,\n          \"domain_strategy\": \"prefer_ipv4\",\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: New\"\n\n    ```json\n     {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"local\",\n            \"tag\": \"local\"\n          }\n        ]\n      },\n      \"outbounds\": [\n        {\n          \"type\": \"socks\",\n          \"server\": \"example.org\",\n          \"server_port\": 2080,\n          \"domain_resolver\": {\n            \"server\": \"local\",\n            \"strategy\": \"prefer_ipv4\"\n          }\n        }\n      ]\n    }\n    ```\n\n## 1.11.0\n\n### Migrate legacy special outbounds to rule actions\n\nLegacy special outbounds are deprecated and can be replaced by rule actions.\n\n!!! info \"References\"\n\n    [Rule Action](/configuration/route/rule_action/) / \n    [Block](/configuration/outbound/block/) / \n    [DNS](/configuration/outbound/dns)\n\n=== \"Block\"\n\n    === \":material-card-remove: Deprecated\"\n    \n        ```json\n        {\n          \"outbounds\": [\n            {\n              \"type\": \"block\",\n              \"tag\": \"block\"\n            }\n          ],\n          \"route\": {\n            \"rules\": [\n              {\n                ...,\n                \n                \"outbound\": \"block\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"route\": {\n            \"rules\": [\n              {\n                ...,\n                \n                \"action\": \"reject\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"DNS\"\n\n    === \":material-card-remove: Deprecated\"\n    \n        ```json\n        {\n          \"inbound\": [\n            {\n              ...,\n              \n              \"sniff\": true\n            }\n          ],\n          \"outbounds\": [\n            {\n              \"tag\": \"dns\",\n              \"type\": \"dns\"\n            }\n          ],\n          \"route\": {\n            \"rules\": [\n              {\n                \"protocol\": \"dns\",\n                \"outbound\": \"dns\"\n              }\n            ]\n          }\n        }\n        ```\n    \n    === \":material-card-multiple: New\"\n    \n        ```json\n        {\n          \"route\": {\n            \"rules\": [\n              {\n                \"action\": \"sniff\"\n              },\n              {\n                \"protocol\": \"dns\",\n                \"action\": \"hijack-dns\"\n              }\n            ]\n          }\n        }\n        ```\n\n### Migrate legacy inbound fields to rule actions\n\nInbound fields are deprecated and can be replaced by rule actions.\n\n!!! info \"References\"\n\n    [Listen Fields](/configuration/shared/listen/) /\n    [Rule](/configuration/route/rule/) / \n    [Rule Action](/configuration/route/rule_action/) / \n    [DNS Rule](/configuration/dns/rule/) / \n    [DNS Rule Action](/configuration/dns/rule_action/)\n\n=== \":material-card-remove: Deprecated\"\n\n    ```json\n    {\n      \"inbounds\": [\n        {\n          \"type\": \"mixed\",\n          \"sniff\": true,\n          \"sniff_timeout\": \"1s\",\n          \"domain_strategy\": \"prefer_ipv4\"\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: New\"\n\n    ```json\n    {\n      \"inbounds\": [\n        {\n          \"type\": \"mixed\",\n          \"tag\": \"in\"\n        }\n      ],\n      \"route\": {\n        \"rules\": [\n          {\n            \"inbound\": \"in\",\n            \"action\": \"resolve\",\n            \"strategy\": \"prefer_ipv4\"\n          },\n          {\n            \"inbound\": \"in\",\n            \"action\": \"sniff\",\n            \"timeout\": \"1s\"\n          }\n        ]\n      }\n    }\n    ```\n\n### Migrate destination override fields to route options\n\nDestination override fields in direct outbound are deprecated and can be replaced by route options.\n\n!!! info \"References\"\n\n    [Rule Action](/configuration/route/rule_action/) /\n    [Direct](/configuration/outbound/direct/)\n\n=== \":material-card-remove: Deprecated\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"direct\",\n          \"override_address\": \"1.1.1.1\",\n          \"override_port\": 443\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: New\"\n\n    ```json\n    {\n      \"route\": {\n        \"rules\": [\n          {\n            \"action\": \"route-options\", // or route\n            \"override_address\": \"1.1.1.1\",\n            \"override_port\": 443\n          }\n        ]\n      }\n    ```\n\n### Migrate WireGuard outbound to endpoint\n\nWireGuard outbound is deprecated and can be replaced by endpoint.\n\n!!! info \"References\"\n\n    [Endpoint](/configuration/endpoint/) /\n    [WireGuard Endpoint](/configuration/endpoint/wireguard/) /\n    [WireGuard Outbound](/configuration/outbound/wireguard/)\n\n=== \":material-card-remove: Deprecated\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"wireguard\",\n          \"tag\": \"wg-out\",\n\n          \"server\": \"127.0.0.1\",\n          \"server_port\": 10001,\n          \"system_interface\": true,\n          \"gso\": true,\n          \"interface_name\": \"wg0\",\n          \"local_address\": [\n            \"10.0.0.1/32\"\n          ],\n          \"private_key\": \"<private_key>\",\n          \"peer_public_key\": \"<peer_public_key>\",\n          \"pre_shared_key\": \"<pre_shared_key>\",\n          \"reserved\": [0, 0, 0],\n          \"mtu\": 1408\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: New\"\n\n    ```json\n    {\n      \"endpoints\": [\n        {\n          \"type\": \"wireguard\",\n          \"tag\": \"wg-ep\",\n          \"system\": true,\n          \"name\": \"wg0\",\n          \"mtu\": 1408,\n          \"address\": [\n            \"10.0.0.2/32\"\n          ],\n          \"private_key\": \"<private_key>\",\n          \"listen_port\": 10000,\n          \"peers\": [\n            {\n              \"address\": \"127.0.0.1\",\n              \"port\": 10001,\n              \"public_key\": \"<peer_public_key>\",\n              \"pre_shared_key\": \"<pre_shared_key>\",\n              \"allowed_ips\": [\n                \"0.0.0.0/0\"\n              ],\n              \"persistent_keepalive_interval\": 30,\n              \"reserved\": [0, 0, 0]\n            }\n          ]\n        }\n      ]\n    }\n    ```\n\n## 1.10.0\n\n### TUN address fields are merged\n\n`inet4_address` and `inet6_address` are merged into `address`,\n`inet4_route_address` and `inet6_route_address` are merged into `route_address`,\n`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.\n\n!!! info \"References\"\n\n    [TUN](/configuration/inbound/tun/)\n\n=== \":material-card-remove: Deprecated\"\n\n    ```json\n    {\n      \"inbounds\": [\n        {\n          \"type\": \"tun\",\n          \"inet4_address\": \"172.19.0.1/30\",\n          \"inet6_address\": \"fdfe:dcba:9876::1/126\",\n          \"inet4_route_address\": [\n            \"0.0.0.0/1\",\n            \"128.0.0.0/1\"\n          ],\n          \"inet6_route_address\": [\n            \"::/1\",\n            \"8000::/1\"\n          ],\n          \"inet4_route_exclude_address\": [\n            \"192.168.0.0/16\"\n          ],\n          \"inet6_route_exclude_address\": [\n            \"fc00::/7\"\n          ]\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: New\"\n\n    ```json\n    {\n      \"inbounds\": [\n        {\n          \"type\": \"tun\",\n          \"address\": [\n            \"172.19.0.1/30\",\n            \"fdfe:dcba:9876::1/126\"\n          ],\n          \"route_address\": [\n            \"0.0.0.0/1\",\n            \"128.0.0.0/1\",\n            \"::/1\",\n            \"8000::/1\"\n          ],\n          \"route_exclude_address\": [\n            \"192.168.0.0/16\",\n            \"fc00::/7\"\n          ]\n        }\n      ]\n    }\n    ```\n\n## 1.9.5\n\n### Bundle Identifier updates in Apple platform clients\n\nDue to problems with our old Apple developer account,\nwe can only change Bundle Identifiers to re-list sing-box apps,\nwhich means the data will not be automatically inherited.\n\nFor iOS, you need to back up your old data yourself (if you still have access to it);  \nfor tvOS, you need to re-import profiles from your iPhone or iPad or create it manually;  \nfor macOS, you can migrate the data folder using the following command:\n\n```bash\ncd ~/Library/Group\\ Containers && \\ \n  mv group.io.nekohasekai.sfa group.io.nekohasekai.sfavt\n```\n\n## 1.9.0\n\n### `domain_suffix` behavior update\n\nFor historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.\n\nsing-box 1.9.0 modifies the behavior of `domain_suffix`: If the rule value is prefixed with `.`,\nthe behavior is unchanged, otherwise it matches `(domain|.+\\.domain)` instead.\n\n### `process_path` format update on Windows\n\nThe `process_path` rule of sing-box is inherited from Clash,\nthe original code uses the local system's path format (e.g. `\\Device\\HarddiskVolume1\\folder\\program.exe`),\nbut when the device has multiple disks, the HarddiskVolume serial number is not stable.\n\nsing-box 1.9.0 make QueryFullProcessImageNameW output a Win32 path (such as `C:\\folder\\program.exe`),\nwhich will disrupt the existing `process_path` use cases in Windows.\n\n## 1.8.0\n\n### :material-close-box: Migrate cache file from Clash API to independent options\n\n!!! info \"References\"\n\n    [Clash API](/configuration/experimental/clash-api/) / \n    [Cache File](/configuration/experimental/cache-file/)\n\n=== \":material-card-remove: Deprecated\"\n\n    ```json\n    {\n      \"experimental\": {\n        \"clash_api\": {\n          \"cache_file\": \"cache.db\", // default value\n          \"cahce_id\": \"my_profile2\",\n          \"store_mode\": true,\n          \"store_selected\": true,\n          \"store_fakeip\": true\n        }\n      }\n    }\n    ```\n\n=== \":material-card-multiple: New\"\n\n    ```json\n    {\n      \"experimental\"  : {\n        \"cache_file\": {\n          \"enabled\": true,\n          \"path\": \"cache.db\", // default value\n          \"cache_id\": \"my_profile2\",\n          \"store_fakeip\": true\n        }\n      }\n    }\n    ```\n\n### :material-checkbox-intermediate: Migrate GeoIP to rule-sets\n\n!!! info \"References\"\n\n    [GeoIP](/configuration/route/geoip/) / \n    [Route](/configuration/route/) / \n    [Route Rule](/configuration/route/rule/) / \n    [DNS Rule](/configuration/dns/rule/) / \n    [rule-set](/configuration/rule-set/)\n\n!!! tip\n\n    `sing-box geoip` commands can help you convert custom GeoIP into rule-sets.\n\n=== \":material-card-remove: Deprecated\"\n\n    ```json\n    {\n      \"route\": {\n        \"rules\": [\n          {\n            \"geoip\": \"private\",\n            \"outbound\": \"direct\"\n          },\n          {\n            \"geoip\": \"cn\",\n            \"outbound\": \"direct\"\n          },\n          {\n            \"source_geoip\": \"cn\",\n            \"outbound\": \"block\"\n          }\n        ],\n        \"geoip\": {\n          \"download_detour\": \"proxy\"\n        }\n      }\n    }\n    ```\n\n=== \":material-card-multiple: New\"\n\n    ```json\n    {\n      \"route\": {\n        \"rules\": [\n          {\n            \"ip_is_private\": true,\n            \"outbound\": \"direct\"\n          },\n          {\n            \"rule_set\": \"geoip-cn\",\n            \"outbound\": \"direct\"\n          },\n          {\n            \"rule_set\": \"geoip-us\",\n            \"rule_set_ipcidr_match_source\": true,\n            \"outbound\": \"block\"\n          }\n        ],\n        \"rule_set\": [\n          {\n            \"tag\": \"geoip-cn\",\n            \"type\": \"remote\",\n            \"format\": \"binary\",\n            \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs\",\n            \"download_detour\": \"proxy\"\n          },\n          {\n            \"tag\": \"geoip-us\",\n            \"type\": \"remote\",\n            \"format\": \"binary\",\n            \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-us.srs\",\n            \"download_detour\": \"proxy\"\n          }\n        ]\n      },\n      \"experimental\": {\n        \"cache_file\": {\n          \"enabled\": true // required to save rule-set cache\n        }\n      }\n    }\n    ```\n\n### :material-checkbox-intermediate: Migrate Geosite to rule-sets\n\n!!! info \"References\"\n\n    [Geosite](/configuration/route/geosite/) / \n    [Route](/configuration/route/) / \n    [Route Rule](/configuration/route/rule/) / \n    [DNS Rule](/configuration/dns/rule/) / \n    [rule-set](/configuration/rule-set/)\n\n!!! tip\n\n    `sing-box geosite` commands can help you convert custom Geosite into rule-sets.\n\n=== \":material-card-remove: Deprecated\"\n\n    ```json\n    {\n      \"route\": {\n        \"rules\": [\n          {\n            \"geosite\": \"cn\",\n            \"outbound\": \"direct\"\n          }\n        ],\n        \"geosite\": {\n          \"download_detour\": \"proxy\"\n        }\n      }\n    }\n    ```\n\n=== \":material-card-multiple: New\"\n\n    ```json\n    {\n      \"route\": {\n        \"rules\": [\n          {\n            \"rule_set\": \"geosite-cn\",\n            \"outbound\": \"direct\"\n          }\n        ],\n        \"rule_set\": [\n          {\n            \"tag\": \"geosite-cn\",\n            \"type\": \"remote\",\n            \"format\": \"binary\",\n            \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs\",\n            \"download_detour\": \"proxy\"\n          }\n        ]\n      },\n      \"experimental\": {\n        \"cache_file\": {\n          \"enabled\": true // required to save rule-set cache\n        }\n      }\n    }\n    ```\n"
  },
  {
    "path": "docs/migration.zh.md",
    "content": "---\nicon: material/arrange-bring-forward\n---\n\n## 1.12.0\n\n### 迁移到新的 DNS 服务器格式\n\nDNS 服务器已经重构。\n\n!!! info \"引用\"\n\n    [DNS 服务器](/zh/configuration/dns/server/) /\n    [旧 DNS 服务器](/zh/configuration/dns/server/legacy/)\n\n=== \"Local\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"local\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"local\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"TCP\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"tcp://1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"tcp\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"UDP\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"udp\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"TLS\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"tls://1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"tls\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"HTTPS\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"https://1.1.1.1/dns-query\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"https\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"QUIC\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"quic://1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"quic\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"HTTP3\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"h3://1.1.1.1/dns-query\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"h3\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"DHCP\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"dhcp://auto\"\n              },\n              {\n                \"address\": \"dhcp://en0\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"dhcp\",\n              },\n              {\n                \"type\": \"dhcp\",\n                \"interface\": \"en0\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"FakeIP\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"1.1.1.1\"\n              },\n              {\n                \"address\": \"fakeip\",\n                \"tag\": \"fakeip\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"query_type\": [\n                  \"A\",\n                  \"AAAA\"\n                ],\n                \"server\": \"fakeip\"\n              }\n            ],\n            \"fakeip\": {\n              \"enabled\": true,\n              \"inet4_range\": \"198.18.0.0/15\",\n              \"inet6_range\": \"fc00::/18\"\n            }\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"udp\",\n                \"server\": \"1.1.1.1\"\n              },\n              {\n                \"type\": \"fakeip\",\n                \"tag\": \"fakeip\",\n                \"inet4_range\": \"198.18.0.0/15\",\n                \"inet6_range\": \"fc00::/18\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"query_type\": [\n                  \"A\",\n                  \"AAAA\"\n                ],\n                \"server\": \"fakeip\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"RCode\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"rcode://refused\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"rules\": [\n              {\n                \"domain\": [\n                  \"example.com\"\n                ],\n                // 其它规则\n                \n                \"action\": \"predefined\",\n                \"rcode\": \"REFUSED\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"带有域名地址的服务器\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"https://dns.google/dns-query\",\n                \"address_resolver\": \"google\"\n              },\n              {\n                \"tag\": \"google\",\n                \"address\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"https\",\n                \"server\": \"dns.google\",\n                \"domain_resolver\": \"google\"\n              },\n              {\n                \"type\": \"udp\",\n                \"tag\": \"google\",\n                \"server\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"带有域策略的服务器\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"1.1.1.1\",\n                \"strategy\": \"ipv4_only\"\n              },\n              {\n                \"tag\": \"google\",\n                \"address\": \"8.8.8.8\",\n                \"strategy\": \"prefer_ipv6\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"domain\": \"google.com\",\n                \"server\": \"google\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"udp\",\n                \"server\": \"1.1.1.1\"\n              },\n              {\n                \"type\": \"udp\",\n                \"tag\": \"google\",\n                \"server\": \"8.8.8.8\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"domain\": \"google.com\",\n                \"server\": \"google\",\n                \"strategy\": \"prefer_ipv6\"\n              }\n            ],\n            \"strategy\": \"ipv4_only\"\n          }\n        }\n        ```\n\n=== \"带有客户端子网的服务器\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"address\": \"1.1.1.1\"\n              },\n              {\n                \"tag\": \"google\",\n                \"address\": \"8.8.8.8\",\n                \"client_subnet\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"dns\": {\n            \"servers\": [\n              {\n                \"type\": \"udp\",\n                \"server\": \"1.1.1.1\"\n              },\n              {\n                \"type\": \"udp\",\n                \"tag\": \"google\",\n                \"server\": \"8.8.8.8\"\n              }\n            ],\n            \"rules\": [\n              {\n                \"domain\": \"google.com\",\n                \"server\": \"google\",\n                \"client_subnet\": \"1.1.1.1\"\n              }\n            ]\n          }\n        }\n        ```\n\n### 迁移 outbound DNS 规则项到域解析选项\n\n旧的 `outbound` DNS 规则已废弃，且可新的域解析选项代替。\n\n!!! info \"参考\"\n\n    [DNS 规则](/zh/configuration/dns/rule/#outbound) /\n    [拨号字段](/zh/configuration/shared/dial/#domain_resolver) /\n    [路由](/zh/configuration/route/#default_domain_resolver)\n\n=== \":material-card-remove: 废弃的\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"address\": \"local\",\n            \"tag\": \"local\"\n          }\n        ],\n        \"rules\": [\n          {\n            \"outbound\": \"any\",\n            \"server\": \"local\"\n          }\n        ]\n      },\n      \"outbounds\": [\n        {\n          \"type\": \"socks\",\n          \"server\": \"example.org\",\n          \"server_port\": 2080\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: 新的\"\n\n    ```json\n    {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"local\",\n            \"tag\": \"local\"\n          }\n        ]\n      },\n      \"outbounds\": [\n        {\n          \"type\": \"socks\",\n          \"server\": \"example.org\",\n          \"server_port\": 2080,\n          \"domain_resolver\": {\n            \"server\": \"local\",\n            \"rewrite_ttl\": 60,\n            \"client_subnet\": \"1.1.1.1\"\n          },\n          // 或 \"domain_resolver\": \"local\",\n        }\n      ],\n\n      // 或\n\n      \"route\": {\n        \"default_domain_resolver\": {\n          \"server\": \"local\",\n          \"rewrite_ttl\": 60,\n          \"client_subnet\": \"1.1.1.1\"\n        }\n      }\n    }\n    ```\n\n### 迁移出站域名策略选项到域名解析器\n\n拨号字段中的 `domain_strategy` 选项已被弃用，可以用新的域名解析器选项替代。\n\n请注意，由于 sing-box 1.12 中引入的一些新 DNS 服务器使用了拨号字段，一些人错误地认为 `domain_strategy` 与旧 DNS 服务器中的功能相同。\n\n!!! info \"参考\"\n\n    [拨号字段](/zh/configuration/shared/dial/#domain_strategy)\n\n=== \":material-card-remove: 弃用的\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"socks\",\n          \"server\": \"example.org\",\n          \"server_port\": 2080,\n          \"domain_strategy\": \"prefer_ipv4\",\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: 新的\"\n\n    ```json\n     {\n      \"dns\": {\n        \"servers\": [\n          {\n            \"type\": \"local\",\n            \"tag\": \"local\"\n          }\n        ]\n      },\n      \"outbounds\": [\n        {\n          \"type\": \"socks\",\n          \"server\": \"example.org\",\n          \"server_port\": 2080,\n          \"domain_resolver\": {\n            \"server\": \"local\",\n            \"strategy\": \"prefer_ipv4\"\n          }\n        }\n      ]\n    }\n    ```\n\n## 1.11.0\n\n### 迁移旧的特殊出站到规则动作\n\n旧的特殊出站已被弃用，且可以被规则动作替代。\n\n!!! info \"参考\"\n\n    [规则动作](/zh/configuration/route/rule_action/) /\n    [Block](/zh/configuration/outbound/block/) / \n    [DNS](/zh/configuration/outbound/dns)\n\n=== \"Block\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"outbounds\": [\n            {\n              \"type\": \"block\",\n              \"tag\": \"block\"\n            }\n          ],\n          \"route\": {\n            \"rules\": [\n              {\n                ...,\n\n                \"outbound\": \"block\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"route\": {\n            \"rules\": [\n              {\n                ...,\n\n                \"action\": \"reject\"\n              }\n            ]\n          }\n        }\n        ```\n\n=== \"DNS\"\n\n    === \":material-card-remove: 弃用的\"\n\n        ```json\n        {\n          \"inbound\": [\n            {\n              ...,\n\n              \"sniff\": true\n            }\n          ],\n          \"outbounds\": [\n            {\n              \"tag\": \"dns\",\n              \"type\": \"dns\"\n            }\n          ],\n          \"route\": {\n            \"rules\": [\n              {\n                \"protocol\": \"dns\",\n                \"outbound\": \"dns\"\n              }\n            ]\n          }\n        }\n        ```\n\n    === \":material-card-multiple: 新的\"\n\n        ```json\n        {\n          \"route\": {\n            \"rules\": [\n              {\n                \"action\": \"sniff\"\n              },\n              {\n                \"protocol\": \"dns\",\n                \"action\": \"hijack-dns\"\n              }\n            ]\n          }\n        }\n        ```\n\n### 迁移旧的入站字段到规则动作\n\n入站选项已被弃用，且可以被规则动作替代。\n\n!!! info \"参考\"\n\n    [监听字段](/zh/configuration/shared/listen/) /\n    [规则](/zh/configuration/route/rule/) /\n    [规则动作](/zh/configuration/route/rule_action/) /\n    [DNS 规则](/zh/configuration/dns/rule/) /\n    [DNS 规则动作](/zh/configuration/dns/rule_action/)\n\n=== \":material-card-remove: 弃用的\"\n\n    ```json\n    {\n      \"inbounds\": [\n        {\n          \"type\": \"mixed\",\n          \"sniff\": true,\n          \"sniff_timeout\": \"1s\",\n          \"domain_strategy\": \"prefer_ipv4\"\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: 新的\"\n\n    ```json\n    {\n      \"inbounds\": [\n        {\n          \"type\": \"mixed\",\n          \"tag\": \"in\"\n        }\n      ],\n      \"route\": {\n        \"rules\": [\n          {\n            \"inbound\": \"in\",\n            \"action\": \"resolve\",\n            \"strategy\": \"prefer_ipv4\"\n          },\n          {\n            \"inbound\": \"in\",\n            \"action\": \"sniff\",\n            \"timeout\": \"1s\"\n          }\n        ]\n      }\n    }\n    ```\n\n### 迁移 direct 出站中的目标地址覆盖字段到路由字段\n\ndirect 出站中的目标地址覆盖字段已废弃，且可以被路由字段替代。\n\n!!! info \"参考\"\n\n    [Rule Action](/zh/configuration/route/rule_action/) /\n    [Direct](/zh/configuration/outbound/direct/)\n\n=== \":material-card-remove: 弃用的\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"direct\",\n          \"override_address\": \"1.1.1.1\",\n          \"override_port\": 443\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: 新的\"\n\n    ```json\n    {\n      \"route\": {\n        \"rules\": [\n          {\n            \"action\": \"route-options\", // 或 route\n            \"override_address\": \"1.1.1.1\",\n            \"override_port\": 443\n          }\n        ]\n      }\n    }\n    ```\n\n### 迁移 WireGuard 出站到端点\n\nWireGuard 出站已被弃用，且可以被端点替代。\n\n!!! info \"参考\"\n\n    [端点](/zh/configuration/endpoint/) /\n    [WireGuard 端点](/zh/configuration/endpoint/wireguard/) / \n    [WireGuard 出站](/zh/configuration/outbound/wireguard/)\n\n=== \":material-card-remove: 弃用的\"\n\n    ```json\n    {\n      \"outbounds\": [\n        {\n          \"type\": \"wireguard\",\n          \"tag\": \"wg-out\",\n\n          \"server\": \"127.0.0.1\",\n          \"server_port\": 10001,\n          \"system_interface\": true,\n          \"gso\": true,\n          \"interface_name\": \"wg0\",\n          \"local_address\": [\n            \"10.0.0.1/32\"\n          ],\n          \"private_key\": \"<private_key>\",\n          \"peer_public_key\": \"<peer_public_key>\",\n          \"pre_shared_key\": \"<pre_shared_key>\",\n          \"reserved\": [0, 0, 0],\n          \"mtu\": 1408\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: 新的\"\n\n    ```json\n    {\n      \"endpoints\": [\n        {\n          \"type\": \"wireguard\",\n          \"tag\": \"wg-ep\",\n          \"system\": true,\n          \"name\": \"wg0\",\n          \"mtu\": 1408,\n          \"address\": [\n            \"10.0.0.2/32\"\n          ],\n          \"private_key\": \"<private_key>\",\n          \"listen_port\": 10000,\n          \"peers\": [\n            {\n              \"address\": \"127.0.0.1\",\n              \"port\": 10001,\n              \"public_key\": \"<peer_public_key>\",\n              \"pre_shared_key\": \"<pre_shared_key>\",\n              \"allowed_ips\": [\n                \"0.0.0.0/0\"\n              ],\n              \"persistent_keepalive_interval\": 30,\n              \"reserved\": [0, 0, 0]\n            }\n          ]\n        }\n      ]\n    }\n    ```\n\n## 1.10.0\n\n### TUN 地址字段已合并\n\n`inet4_address` 和 `inet6_address` 已合并为 `address`，\n`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`，\n`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。\n\n!!! info \"参考\"\n\n    [TUN](/zh/configuration/inbound/tun/)\n\n=== \":material-card-remove: 弃用的\"\n\n    ```json\n    {\n      \"inbounds\": [\n        {\n          \"type\": \"tun\",\n          \"inet4_address\": \"172.19.0.1/30\",\n          \"inet6_address\": \"fdfe:dcba:9876::1/126\",\n          \"inet4_route_address\": [\n            \"0.0.0.0/1\",\n            \"128.0.0.0/1\"\n          ],\n          \"inet6_route_address\": [\n            \"::/1\",\n            \"8000::/1\"\n          ],\n          \"inet4_route_exclude_address\": [\n            \"192.168.0.0/16\"\n          ],\n          \"inet6_route_exclude_address\": [\n            \"fc00::/7\"\n          ]\n        }\n      ]\n    }\n    ```\n\n=== \":material-card-multiple: 新的\"\n\n    ```json\n    {\n      \"inbounds\": [\n        {\n          \"type\": \"tun\",\n          \"address\": [\n            \"172.19.0.1/30\",\n            \"fdfe:dcba:9876::1/126\"\n          ],\n          \"route_address\": [\n            \"0.0.0.0/1\",\n            \"128.0.0.0/1\",\n            \"::/1\",\n            \"8000::/1\"\n          ],\n          \"route_exclude_address\": [\n            \"192.168.0.0/16\",\n            \"fc00::/7\"\n          ]\n        }\n      ]\n    }\n    ```\n\n## 1.9.5\n\n### Apple 平台客户端的 Bundle Identifier 更新\n\n由于我们旧的苹果开发者账户存在问题，我们只能通过更新 Bundle Identifiers\n来重新上架 sing-box 应用， 这意味着数据不会自动继承。\n\n对于 iOS，您需要自行备份旧的数据（如果您仍然可以访问）；  \n对于 Apple tvOS，您需要从 iPhone 或 iPad 重新导入配置或者手动创建；  \n对于 macOS，您可以使用以下命令迁移数据文件夹：\n\n```bash\ncd ~/Library/Group\\ Containers && \\ \n  mv group.io.nekohasekai.sfa group.io.nekohasekai.sfavt\n```\n\n## 1.9.0\n\n### `domain_suffix` 行为更新\n\n由于历史原因，sing-box 的 `domain_suffix` 规则匹配字面前缀，而不与其他项目相同。\n\nsing-box 1.9.0 修改了 `domain_suffix` 的行为：如果规则值以 `.` 为前缀则行为不变，否则改为匹配 `(domain|.+\\.domain)`。\n\n### 对 Windows 上 `process_path` 格式的更新\n\nsing-box 的 `process_path` 规则继承自Clash，\n原始代码使用本地系统的路径格式（例如 `\\Device\\HarddiskVolume1\\folder\\program.exe`），\n但是当设备有多个硬盘时，该 HarddiskVolume 系列号并不稳定。\n\nsing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径（如 `C:\\folder\\program.exe`），\n这将会破坏现有的 Windows `process_path` 用例。\n\n## 1.8.0\n\n### :material-close-box: 将缓存文件从 Clash API 迁移到独立选项\n\n!!! info \"参考\"\n\n    [Clash API](/zh/configuration/experimental/clash-api/) / \n    [Cache File](/zh/configuration/experimental/cache-file/)\n\n=== \":material-card-remove: 弃用的\"\n\n    ```json\n    {\n      \"experimental\": {\n        \"clash_api\": {\n          \"cache_file\": \"cache.db\", // 默认值\n          \"cahce_id\": \"my_profile2\",\n          \"store_mode\": true,\n          \"store_selected\": true,\n          \"store_fakeip\": true\n        }\n      }\n    }\n    ```\n\n=== \":material-card-multiple: 新的\"\n\n    ```json\n    {\n      \"experimental\"  : {\n        \"cache_file\": {\n          \"enabled\": true,\n          \"path\": \"cache.db\", // 默认值\n          \"cache_id\": \"my_profile2\",\n          \"store_fakeip\": true\n        }\n      }\n    }\n    ```\n\n### :material-checkbox-intermediate: 迁移 GeoIP 到规则集\n\n!!! info \"参考\"\n\n    [GeoIP](/zh/configuration/route/geoip/) / \n    [路由](/zh/configuration/route/) / \n    [路由规则](/zh/configuration/route/rule/) / \n    [DNS 规则](/zh/configuration/dns/rule/) / \n    [规则集](/zh/configuration/rule-set/)\n\n!!! tip\n\n    `sing-box geoip` 命令可以帮助您将自定义 GeoIP 转换为规则集。\n\n=== \":material-card-remove: 弃用的\"\n\n    ```json\n    {\n      \"route\": {\n        \"rules\": [\n          {\n            \"geoip\": \"private\",\n            \"outbound\": \"direct\"\n          },\n          {\n            \"geoip\": \"cn\",\n            \"outbound\": \"direct\"\n          },\n          {\n            \"source_geoip\": \"cn\",\n            \"outbound\": \"block\"\n          }\n        ],\n        \"geoip\": {\n          \"download_detour\": \"proxy\"\n        }\n      }\n    }\n    ```\n\n=== \":material-card-multiple: 新的\"\n\n    ```json\n    {\n      \"route\": {\n        \"rules\": [\n          {\n            \"ip_is_private\": true,\n            \"outbound\": \"direct\"\n          },\n          {\n            \"rule_set\": \"geoip-cn\",\n            \"outbound\": \"direct\"\n          },\n          {\n            \"rule_set\": \"geoip-us\",\n            \"rule_set_ipcidr_match_source\": true,\n            \"outbound\": \"block\"\n          }\n        ],\n        \"rule_set\": [\n          {\n            \"tag\": \"geoip-cn\",\n            \"type\": \"remote\",\n            \"format\": \"binary\",\n            \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs\",\n            \"download_detour\": \"proxy\"\n          },\n          {\n            \"tag\": \"geoip-us\",\n            \"type\": \"remote\",\n            \"format\": \"binary\",\n            \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-us.srs\",\n            \"download_detour\": \"proxy\"\n          }\n        ]\n      },\n      \"experimental\": {\n        \"cache_file\": {\n          \"enabled\": true // required to save rule-set cache\n        }\n      }\n    }\n    ```\n\n### :material-checkbox-intermediate: 迁移 Geosite 到规则集\n\n!!! info \"参考\"\n\n    [Geosite](/zh/configuration/route/geosite/) / \n    [路由](/zh/configuration/route/) / \n    [路由规则](/zh/configuration/route/rule/) / \n    [DNS 规则](/zh/configuration/dns/rule/) / \n    [规则集](/zh/configuration/rule-set/)\n\n!!! tip\n\n    `sing-box geosite` 命令可以帮助您将自定义 Geosite 转换为规则集。\n\n=== \":material-card-remove: 弃用的\"\n\n    ```json\n    {\n      \"route\": {\n        \"rules\": [\n          {\n            \"geosite\": \"cn\",\n            \"outbound\": \"direct\"\n          }\n        ],\n        \"geosite\": {\n          \"download_detour\": \"proxy\"\n        }\n      }\n    }\n    ```\n\n=== \":material-card-multiple: 新的\"\n\n    ```json\n    {\n      \"route\": {\n        \"rules\": [\n          {\n            \"rule_set\": \"geosite-cn\",\n            \"outbound\": \"direct\"\n          }\n        ],\n        \"rule_set\": [\n          {\n            \"tag\": \"geosite-cn\",\n            \"type\": \"remote\",\n            \"format\": \"binary\",\n            \"url\": \"https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs\",\n            \"download_detour\": \"proxy\"\n          }\n        ]\n      },\n      \"experimental\": {\n        \"cache_file\": {\n          \"enabled\": true // required to save rule-set cache\n        }\n      }\n    }\n    ```\n"
  },
  {
    "path": "docs/sponsors.md",
    "content": "---\nicon: material/hand-coin\n---\n\n# Sponsors\n\nDo you or your friends use sing-box?\n\nYou can help keep the project bug-free and feature rich by sponsoring\nthe project maintainer via [GitHub Sponsors](https://github.com/sponsors/nekohasekai).\n\n![](https://nekohasekai.github.io/sponsor-images/sponsors.svg)\n\n## Commercial Sponsors\n\n> [Warp](https://go.warp.dev/sing-box), Built for coding with multiple AI agents.\n\n[![](https://github.com/warpdotdev/brand-assets/raw/refs/heads/main/Github/Sponsor/Warp-Github-LG-02.png)](https://go.warp.dev/sing-box)\n\n## Special Sponsors\n\n> Viral Tech, Inc.\n\nHelping us re-list sing-box apps on the Apple Store.\n\n---\n\n> [JetBrains](https://www.jetbrains.com)\n\nFree license for the amazing IDEs.\n\n[![JetBrains logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://www.jetbrains.com)\n"
  },
  {
    "path": "docs/support.md",
    "content": "---\nicon: material/forum\n---\n\n# Support\n\n| Channel                       | Link                                        |\n| :---------------------------- | :------------------------------------------ |\n| GitHub Issues                 | https://github.com/SagerNet/sing-box/issues |\n| Telegram notification channel | https://t.me/yapnc                          |\n| Telegram user group           | https://t.me/yapug                          |\n| Email                         | contact@sagernet.org                        |\n"
  },
  {
    "path": "docs/support.zh.md",
    "content": "---\nicon: material/forum\n---\n\n# 支持\n\n| 通道              | 链接                                        |\n| :---------------- | :------------------------------------------ |\n| GitHub Issues     | https://github.com/SagerNet/sing-box/issues |\n| Telegram 通知频道 | https://t.me/yapnc                          |\n| Telegram 用户组   | https://t.me/yapug                          |\n| 邮件              | contact@sagernet.org                        |\n\n"
  },
  {
    "path": "experimental/cachefile/cache.go",
    "content": "package cachefile\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/bbolt\"\n\tbboltErrors \"github.com/sagernet/bbolt/errors\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/service/filemanager\"\n)\n\nvar (\n\tbucketSelected = []byte(\"selected\")\n\tbucketExpand   = []byte(\"group_expand\")\n\tbucketMode     = []byte(\"clash_mode\")\n\tbucketRuleSet  = []byte(\"rule_set\")\n\n\tbucketNameList = []string{\n\t\tstring(bucketSelected),\n\t\tstring(bucketExpand),\n\t\tstring(bucketMode),\n\t\tstring(bucketRuleSet),\n\t\tstring(bucketRDRC),\n\t}\n\n\tcacheIDDefault = []byte(\"default\")\n)\n\nvar _ adapter.CacheFile = (*CacheFile)(nil)\n\ntype CacheFile struct {\n\tctx               context.Context\n\tpath              string\n\tcacheID           []byte\n\tstoreFakeIP       bool\n\tstoreRDRC         bool\n\trdrcTimeout       time.Duration\n\tDB                *bbolt.DB\n\tresetAccess       sync.Mutex\n\tsaveMetadataTimer *time.Timer\n\tsaveFakeIPAccess  sync.RWMutex\n\tsaveDomain        map[netip.Addr]string\n\tsaveAddress4      map[string]netip.Addr\n\tsaveAddress6      map[string]netip.Addr\n\tsaveRDRCAccess    sync.RWMutex\n\tsaveRDRC          map[saveRDRCCacheKey]bool\n}\n\ntype saveRDRCCacheKey struct {\n\tTransportName string\n\tQuestionName  string\n\tQType         uint16\n}\n\nfunc New(ctx context.Context, options option.CacheFileOptions) *CacheFile {\n\tvar path string\n\tif options.Path != \"\" {\n\t\tpath = options.Path\n\t} else {\n\t\tpath = \"cache.db\"\n\t}\n\tvar cacheIDBytes []byte\n\tif options.CacheID != \"\" {\n\t\tcacheIDBytes = append([]byte{0}, []byte(options.CacheID)...)\n\t}\n\tvar rdrcTimeout time.Duration\n\tif options.StoreRDRC {\n\t\tif options.RDRCTimeout > 0 {\n\t\t\trdrcTimeout = time.Duration(options.RDRCTimeout)\n\t\t} else {\n\t\t\trdrcTimeout = 7 * 24 * time.Hour\n\t\t}\n\t}\n\treturn &CacheFile{\n\t\tctx:          ctx,\n\t\tpath:         filemanager.BasePath(ctx, path),\n\t\tcacheID:      cacheIDBytes,\n\t\tstoreFakeIP:  options.StoreFakeIP,\n\t\tstoreRDRC:    options.StoreRDRC,\n\t\trdrcTimeout:  rdrcTimeout,\n\t\tsaveDomain:   make(map[netip.Addr]string),\n\t\tsaveAddress4: make(map[string]netip.Addr),\n\t\tsaveAddress6: make(map[string]netip.Addr),\n\t\tsaveRDRC:     make(map[saveRDRCCacheKey]bool),\n\t}\n}\n\nfunc (c *CacheFile) Name() string {\n\treturn \"cache-file\"\n}\n\nfunc (c *CacheFile) Dependencies() []string {\n\treturn nil\n}\n\nfunc (c *CacheFile) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateInitialize {\n\t\treturn nil\n\t}\n\tconst fileMode = 0o666\n\toptions := bbolt.Options{Timeout: time.Second}\n\tvar (\n\t\tdb  *bbolt.DB\n\t\terr error\n\t)\n\tfor i := 0; i < 10; i++ {\n\t\tdb, err = bbolt.Open(c.path, fileMode, &options)\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\t\tif errors.Is(err, bboltErrors.ErrTimeout) {\n\t\t\tcontinue\n\t\t}\n\t\tif E.IsMulti(err, bboltErrors.ErrInvalid, bboltErrors.ErrChecksum, bboltErrors.ErrVersionMismatch) {\n\t\t\trmErr := os.Remove(c.path)\n\t\t\tif rmErr != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = filemanager.Chown(c.ctx, c.path)\n\tif err != nil {\n\t\tdb.Close()\n\t\treturn E.Cause(err, \"platform chown\")\n\t}\n\terr = db.Batch(func(tx *bbolt.Tx) error {\n\t\treturn tx.ForEach(func(name []byte, b *bbolt.Bucket) error {\n\t\t\tif name[0] == 0 {\n\t\t\t\treturn b.ForEachBucket(func(k []byte) error {\n\t\t\t\t\tbucketName := string(k)\n\t\t\t\t\tif !(common.Contains(bucketNameList, bucketName)) {\n\t\t\t\t\t\t_ = b.DeleteBucket(name)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tbucketName := string(name)\n\t\t\t\tif !(common.Contains(bucketNameList, bucketName) || strings.HasPrefix(bucketName, fakeipBucketPrefix)) {\n\t\t\t\t\t_ = tx.DeleteBucket(name)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t})\n\tif err != nil {\n\t\tdb.Close()\n\t\treturn err\n\t}\n\tc.DB = db\n\treturn nil\n}\n\nfunc (c *CacheFile) Close() error {\n\tif c.DB == nil {\n\t\treturn nil\n\t}\n\treturn c.DB.Close()\n}\n\nfunc (c *CacheFile) view(fn func(tx *bbolt.Tx) error) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tc.resetDB()\n\t\t\terr = E.New(\"database corrupted: \", r)\n\t\t}\n\t}()\n\treturn c.DB.View(fn)\n}\n\nfunc (c *CacheFile) batch(fn func(tx *bbolt.Tx) error) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tc.resetDB()\n\t\t\terr = E.New(\"database corrupted: \", r)\n\t\t}\n\t}()\n\treturn c.DB.Batch(fn)\n}\n\nfunc (c *CacheFile) update(fn func(tx *bbolt.Tx) error) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tc.resetDB()\n\t\t\terr = E.New(\"database corrupted: \", r)\n\t\t}\n\t}()\n\treturn c.DB.Update(fn)\n}\n\nfunc (c *CacheFile) resetDB() {\n\tc.resetAccess.Lock()\n\tdefer c.resetAccess.Unlock()\n\tc.DB.Close()\n\tos.Remove(c.path)\n\tdb, err := bbolt.Open(c.path, 0o666, &bbolt.Options{Timeout: time.Second})\n\tif err == nil {\n\t\t_ = filemanager.Chown(c.ctx, c.path)\n\t\tc.DB = db\n\t}\n}\n\nfunc (c *CacheFile) StoreFakeIP() bool {\n\treturn c.storeFakeIP\n}\n\nfunc (c *CacheFile) LoadMode() string {\n\tvar mode string\n\tc.view(func(t *bbolt.Tx) error {\n\t\tbucket := t.Bucket(bucketMode)\n\t\tif bucket == nil {\n\t\t\treturn nil\n\t\t}\n\t\tvar modeBytes []byte\n\t\tif len(c.cacheID) > 0 {\n\t\t\tmodeBytes = bucket.Get(c.cacheID)\n\t\t} else {\n\t\t\tmodeBytes = bucket.Get(cacheIDDefault)\n\t\t}\n\t\tmode = string(modeBytes)\n\t\treturn nil\n\t})\n\treturn mode\n}\n\nfunc (c *CacheFile) StoreMode(mode string) error {\n\treturn c.batch(func(t *bbolt.Tx) error {\n\t\tbucket, err := t.CreateBucketIfNotExists(bucketMode)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(c.cacheID) > 0 {\n\t\t\treturn bucket.Put(c.cacheID, []byte(mode))\n\t\t} else {\n\t\t\treturn bucket.Put(cacheIDDefault, []byte(mode))\n\t\t}\n\t})\n}\n\nfunc (c *CacheFile) bucket(t *bbolt.Tx, key []byte) *bbolt.Bucket {\n\tif c.cacheID == nil {\n\t\treturn t.Bucket(key)\n\t}\n\tbucket := t.Bucket(c.cacheID)\n\tif bucket == nil {\n\t\treturn nil\n\t}\n\treturn bucket.Bucket(key)\n}\n\nfunc (c *CacheFile) createBucket(t *bbolt.Tx, key []byte) (*bbolt.Bucket, error) {\n\tif c.cacheID == nil {\n\t\treturn t.CreateBucketIfNotExists(key)\n\t}\n\tbucket, err := t.CreateBucketIfNotExists(c.cacheID)\n\tif bucket == nil {\n\t\treturn nil, err\n\t}\n\treturn bucket.CreateBucketIfNotExists(key)\n}\n\nfunc (c *CacheFile) LoadSelected(group string) string {\n\tvar selected string\n\tc.view(func(t *bbolt.Tx) error {\n\t\tbucket := c.bucket(t, bucketSelected)\n\t\tif bucket == nil {\n\t\t\treturn nil\n\t\t}\n\t\tselectedBytes := bucket.Get([]byte(group))\n\t\tif len(selectedBytes) > 0 {\n\t\t\tselected = string(selectedBytes)\n\t\t}\n\t\treturn nil\n\t})\n\treturn selected\n}\n\nfunc (c *CacheFile) StoreSelected(group, selected string) error {\n\treturn c.batch(func(t *bbolt.Tx) error {\n\t\tbucket, err := c.createBucket(t, bucketSelected)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn bucket.Put([]byte(group), []byte(selected))\n\t})\n}\n\nfunc (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) {\n\tc.view(func(t *bbolt.Tx) error {\n\t\tbucket := c.bucket(t, bucketExpand)\n\t\tif bucket == nil {\n\t\t\treturn nil\n\t\t}\n\t\texpandBytes := bucket.Get([]byte(group))\n\t\tif len(expandBytes) == 1 {\n\t\t\tisExpand = expandBytes[0] == 1\n\t\t\tloaded = true\n\t\t}\n\t\treturn nil\n\t})\n\treturn\n}\n\nfunc (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error {\n\treturn c.batch(func(t *bbolt.Tx) error {\n\t\tbucket, err := c.createBucket(t, bucketExpand)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif isExpand {\n\t\t\treturn bucket.Put([]byte(group), []byte{1})\n\t\t} else {\n\t\t\treturn bucket.Put([]byte(group), []byte{0})\n\t\t}\n\t})\n}\n\nfunc (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary {\n\tvar savedSet adapter.SavedBinary\n\terr := c.view(func(t *bbolt.Tx) error {\n\t\tbucket := c.bucket(t, bucketRuleSet)\n\t\tif bucket == nil {\n\t\t\treturn os.ErrNotExist\n\t\t}\n\t\tsetBinary := bucket.Get([]byte(tag))\n\t\tif len(setBinary) == 0 {\n\t\t\treturn os.ErrInvalid\n\t\t}\n\t\treturn savedSet.UnmarshalBinary(setBinary)\n\t})\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &savedSet\n}\n\nfunc (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error {\n\treturn c.batch(func(t *bbolt.Tx) error {\n\t\tbucket, err := c.createBucket(t, bucketRuleSet)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsetBinary, err := set.MarshalBinary()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn bucket.Put([]byte(tag), setBinary)\n\t})\n}\n"
  },
  {
    "path": "experimental/cachefile/fakeip.go",
    "content": "package cachefile\n\nimport (\n\t\"net/netip\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/bbolt\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n)\n\nconst fakeipBucketPrefix = \"fakeip_\"\n\nvar (\n\tbucketFakeIP        = []byte(fakeipBucketPrefix + \"address\")\n\tbucketFakeIPDomain4 = []byte(fakeipBucketPrefix + \"domain4\")\n\tbucketFakeIPDomain6 = []byte(fakeipBucketPrefix + \"domain6\")\n\tkeyMetadata         = []byte(fakeipBucketPrefix + \"metadata\")\n)\n\nfunc (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata {\n\tvar metadata adapter.FakeIPMetadata\n\terr := c.batch(func(tx *bbolt.Tx) error {\n\t\tbucket := tx.Bucket(bucketFakeIP)\n\t\tif bucket == nil {\n\t\t\treturn os.ErrNotExist\n\t\t}\n\t\tmetadataBinary := bucket.Get(keyMetadata)\n\t\tif len(metadataBinary) == 0 {\n\t\t\treturn os.ErrInvalid\n\t\t}\n\t\terr := bucket.Delete(keyMetadata)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn metadata.UnmarshalBinary(metadataBinary)\n\t})\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &metadata\n}\n\nfunc (c *CacheFile) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error {\n\treturn c.batch(func(tx *bbolt.Tx) error {\n\t\tbucket, err := tx.CreateBucketIfNotExists(bucketFakeIP)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tmetadataBinary, err := metadata.MarshalBinary()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn bucket.Put(keyMetadata, metadataBinary)\n\t})\n}\n\nfunc (c *CacheFile) FakeIPSaveMetadataAsync(metadata *adapter.FakeIPMetadata) {\n\tif c.saveMetadataTimer == nil {\n\t\tc.saveMetadataTimer = time.AfterFunc(C.FakeIPMetadataSaveInterval, func() {\n\t\t\t_ = c.FakeIPSaveMetadata(metadata)\n\t\t})\n\t} else {\n\t\tc.saveMetadataTimer.Reset(C.FakeIPMetadataSaveInterval)\n\t}\n}\n\nfunc (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error {\n\treturn c.batch(func(tx *bbolt.Tx) error {\n\t\tbucket, err := tx.CreateBucketIfNotExists(bucketFakeIP)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\toldDomain := bucket.Get(address.AsSlice())\n\t\terr = bucket.Put(address.AsSlice(), []byte(domain))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif address.Is4() {\n\t\t\tbucket, err = tx.CreateBucketIfNotExists(bucketFakeIPDomain4)\n\t\t} else {\n\t\t\tbucket, err = tx.CreateBucketIfNotExists(bucketFakeIPDomain6)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif oldDomain != nil {\n\t\t\tif err := bucket.Delete(oldDomain); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn bucket.Put([]byte(domain), address.AsSlice())\n\t})\n}\n\nfunc (c *CacheFile) FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger) {\n\tc.saveFakeIPAccess.Lock()\n\tif oldDomain, loaded := c.saveDomain[address]; loaded {\n\t\tif address.Is4() {\n\t\t\tdelete(c.saveAddress4, oldDomain)\n\t\t} else {\n\t\t\tdelete(c.saveAddress6, oldDomain)\n\t\t}\n\t}\n\tc.saveDomain[address] = domain\n\tif address.Is4() {\n\t\tc.saveAddress4[domain] = address\n\t} else {\n\t\tc.saveAddress6[domain] = address\n\t}\n\tc.saveFakeIPAccess.Unlock()\n\tgo func() {\n\t\terr := c.FakeIPStore(address, domain)\n\t\tif err != nil {\n\t\t\tlogger.Warn(\"save FakeIP cache: \", err)\n\t\t}\n\t\tc.saveFakeIPAccess.Lock()\n\t\tdelete(c.saveDomain, address)\n\t\tif address.Is4() {\n\t\t\tdelete(c.saveAddress4, domain)\n\t\t} else {\n\t\t\tdelete(c.saveAddress6, domain)\n\t\t}\n\t\tc.saveFakeIPAccess.Unlock()\n\t}()\n}\n\nfunc (c *CacheFile) FakeIPLoad(address netip.Addr) (string, bool) {\n\tc.saveFakeIPAccess.RLock()\n\tcachedDomain, cached := c.saveDomain[address]\n\tc.saveFakeIPAccess.RUnlock()\n\tif cached {\n\t\treturn cachedDomain, true\n\t}\n\tvar domain string\n\t_ = c.view(func(tx *bbolt.Tx) error {\n\t\tbucket := tx.Bucket(bucketFakeIP)\n\t\tif bucket == nil {\n\t\t\treturn nil\n\t\t}\n\t\tdomain = string(bucket.Get(address.AsSlice()))\n\t\treturn nil\n\t})\n\treturn domain, domain != \"\"\n}\n\nfunc (c *CacheFile) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bool) {\n\tvar (\n\t\tcachedAddress netip.Addr\n\t\tcached        bool\n\t)\n\tc.saveFakeIPAccess.RLock()\n\tif !isIPv6 {\n\t\tcachedAddress, cached = c.saveAddress4[domain]\n\t} else {\n\t\tcachedAddress, cached = c.saveAddress6[domain]\n\t}\n\tc.saveFakeIPAccess.RUnlock()\n\tif cached {\n\t\treturn cachedAddress, true\n\t}\n\tvar address netip.Addr\n\t_ = c.view(func(tx *bbolt.Tx) error {\n\t\tvar bucket *bbolt.Bucket\n\t\tif isIPv6 {\n\t\t\tbucket = tx.Bucket(bucketFakeIPDomain6)\n\t\t} else {\n\t\t\tbucket = tx.Bucket(bucketFakeIPDomain4)\n\t\t}\n\t\tif bucket == nil {\n\t\t\treturn nil\n\t\t}\n\t\taddress = M.AddrFromIP(bucket.Get([]byte(domain)))\n\t\treturn nil\n\t})\n\treturn address, address.IsValid()\n}\n\nfunc (c *CacheFile) FakeIPReset() error {\n\treturn c.batch(func(tx *bbolt.Tx) error {\n\t\terr := tx.DeleteBucket(bucketFakeIP)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = tx.DeleteBucket(bucketFakeIPDomain4)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn tx.DeleteBucket(bucketFakeIPDomain6)\n\t})\n}\n"
  },
  {
    "path": "experimental/cachefile/rdrc.go",
    "content": "package cachefile\n\nimport (\n\t\"encoding/binary\"\n\t\"time\"\n\n\t\"github.com/sagernet/bbolt\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\nvar bucketRDRC = []byte(\"rdrc2\")\n\nfunc (c *CacheFile) StoreRDRC() bool {\n\treturn c.storeRDRC\n}\n\nfunc (c *CacheFile) RDRCTimeout() time.Duration {\n\treturn c.rdrcTimeout\n}\n\nfunc (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) {\n\tc.saveRDRCAccess.RLock()\n\trejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName, qType}]\n\tc.saveRDRCAccess.RUnlock()\n\tif cached {\n\t\treturn\n\t}\n\tkey := buf.Get(2 + len(qName))\n\tbinary.BigEndian.PutUint16(key, qType)\n\tcopy(key[2:], qName)\n\tdefer buf.Put(key)\n\tvar deleteCache bool\n\terr := c.view(func(tx *bbolt.Tx) error {\n\t\tbucket := c.bucket(tx, bucketRDRC)\n\t\tif bucket == nil {\n\t\t\treturn nil\n\t\t}\n\t\tbucket = bucket.Bucket([]byte(transportName))\n\t\tif bucket == nil {\n\t\t\treturn nil\n\t\t}\n\t\tcontent := bucket.Get(key)\n\t\tif content == nil {\n\t\t\treturn nil\n\t\t}\n\t\texpiresAt := time.Unix(int64(binary.BigEndian.Uint64(content)), 0)\n\t\tif time.Now().After(expiresAt) {\n\t\t\tdeleteCache = true\n\t\t\treturn nil\n\t\t}\n\t\trejected = true\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\tif deleteCache {\n\t\tc.update(func(tx *bbolt.Tx) error {\n\t\t\tbucket := c.bucket(tx, bucketRDRC)\n\t\t\tif bucket == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tbucket = bucket.Bucket([]byte(transportName))\n\t\t\tif bucket == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn bucket.Delete(key)\n\t\t})\n\t}\n\treturn\n}\n\nfunc (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) error {\n\treturn c.batch(func(tx *bbolt.Tx) error {\n\t\tbucket, err := c.createBucket(tx, bucketRDRC)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbucket, err = bucket.CreateBucketIfNotExists([]byte(transportName))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tkey := buf.Get(2 + len(qName))\n\t\tbinary.BigEndian.PutUint16(key, qType)\n\t\tcopy(key[2:], qName)\n\t\tdefer buf.Put(key)\n\t\texpiresAt := buf.Get(8)\n\t\tdefer buf.Put(expiresAt)\n\t\tbinary.BigEndian.PutUint64(expiresAt, uint64(time.Now().Add(c.rdrcTimeout).Unix()))\n\t\treturn bucket.Put(key, expiresAt)\n\t})\n}\n\nfunc (c *CacheFile) SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) {\n\tsaveKey := saveRDRCCacheKey{transportName, qName, qType}\n\tc.saveRDRCAccess.Lock()\n\tc.saveRDRC[saveKey] = true\n\tc.saveRDRCAccess.Unlock()\n\tgo func() {\n\t\terr := c.SaveRDRC(transportName, qName, qType)\n\t\tif err != nil {\n\t\t\tlogger.Warn(\"save RDRC: \", err)\n\t\t}\n\t\tc.saveRDRCAccess.Lock()\n\t\tdelete(c.saveRDRC, saveKey)\n\t\tc.saveRDRCAccess.Unlock()\n\t}()\n}\n"
  },
  {
    "path": "experimental/clashapi/api_meta.go",
    "content": "package clashapi\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"runtime/debug\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/ws\"\n\t\"github.com/sagernet/ws/wsutil\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n\t\"github.com/go-chi/render\"\n)\n\n// API created by Clash.Meta\n\nfunc (s *Server) setupMetaAPI(r chi.Router) {\n\tif s.logDebug {\n\t\tr := chi.NewRouter()\n\t\tr.Put(\"/gc\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tdebug.FreeOSMemory()\n\t\t})\n\t\tr.Mount(\"/\", middleware.Profiler())\n\t}\n\tr.Get(\"/memory\", memory(s.ctx, s.trafficManager))\n\tr.Mount(\"/group\", groupRouter(s))\n\tr.Mount(\"/upgrade\", upgradeRouter(s))\n}\n\ntype Memory struct {\n\tInuse   uint64 `json:\"inuse\"`\n\tOSLimit uint64 `json:\"oslimit\"` // maybe we need it in the future\n}\n\nfunc memory(ctx context.Context, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tvar conn net.Conn\n\t\tif r.Header.Get(\"Upgrade\") == \"websocket\" {\n\t\t\tvar err error\n\t\t\tconn, _, _, err = ws.UpgradeHTTP(r, w)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer conn.Close()\n\t\t}\n\n\t\tif conn == nil {\n\t\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t\trender.Status(r, http.StatusOK)\n\t\t}\n\n\t\ttick := time.NewTicker(time.Second)\n\t\tdefer tick.Stop()\n\t\tbuf := &bytes.Buffer{}\n\t\tvar err error\n\t\tfirst := true\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-tick.C:\n\t\t\t}\n\t\t\tbuf.Reset()\n\n\t\t\tinuse := trafficManager.Snapshot().Memory\n\n\t\t\t// make chat.js begin with zero\n\t\t\t// this is shit var,but we need output 0 for first time\n\t\t\tif first {\n\t\t\t\tfirst = false\n\t\t\t\tinuse = 0\n\t\t\t}\n\t\t\tif err := json.NewEncoder(buf).Encode(Memory{\n\t\t\t\tInuse:   inuse,\n\t\t\t\tOSLimit: 0,\n\t\t\t}); err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif conn == nil {\n\t\t\t\t_, err = w.Write(buf.Bytes())\n\t\t\t\tw.(http.Flusher).Flush()\n\t\t\t} else {\n\t\t\t\terr = wsutil.WriteServerText(conn, buf.Bytes())\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "experimental/clashapi/api_meta_group.go",
    "content": "package clashapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/urltest\"\n\t\"github.com/sagernet/sing-box/protocol/group\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/batch\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc groupRouter(server *Server) http.Handler {\n\tr := chi.NewRouter()\n\tr.Get(\"/\", getGroups(server))\n\tr.Route(\"/{name}\", func(r chi.Router) {\n\t\tr.Use(parseProxyName, findProxyByName(server))\n\t\tr.Get(\"/\", getGroup(server))\n\t\tr.Get(\"/delay\", getGroupDelay(server))\n\t})\n\treturn r\n}\n\nfunc getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tgroups := common.Map(common.Filter(server.outbound.Outbounds(), func(it adapter.Outbound) bool {\n\t\t\t_, isGroup := it.(adapter.OutboundGroup)\n\t\t\treturn isGroup\n\t\t}), func(it adapter.Outbound) *badjson.JSONObject {\n\t\t\treturn proxyInfo(server, it)\n\t\t})\n\t\trender.JSON(w, r, render.M{\n\t\t\t\"proxies\": groups,\n\t\t})\n\t}\n}\n\nfunc getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tproxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)\n\t\tif _, ok := proxy.(adapter.OutboundGroup); ok {\n\t\t\trender.JSON(w, r, proxyInfo(server, proxy))\n\t\t\treturn\n\t\t}\n\t\trender.Status(r, http.StatusNotFound)\n\t\trender.JSON(w, r, ErrNotFound)\n\t}\n}\n\nfunc getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tproxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)\n\t\toutboundGroup, ok := proxy.(adapter.OutboundGroup)\n\t\tif !ok {\n\t\t\trender.Status(r, http.StatusNotFound)\n\t\t\trender.JSON(w, r, ErrNotFound)\n\t\t\treturn\n\t\t}\n\n\t\tquery := r.URL.Query()\n\t\turl := query.Get(\"url\")\n\t\tif strings.HasPrefix(url, \"http://\") {\n\t\t\turl = \"\"\n\t\t}\n\t\ttimeout, err := strconv.ParseInt(query.Get(\"timeout\"), 10, 32)\n\t\tif err != nil {\n\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\trender.JSON(w, r, ErrBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*time.Duration(timeout))\n\t\tdefer cancel()\n\n\t\tvar result map[string]uint16\n\t\tif urlTestGroup, isURLTestGroup := outboundGroup.(adapter.URLTestGroup); isURLTestGroup {\n\t\t\tresult, err = urlTestGroup.URLTest(ctx)\n\t\t} else {\n\t\t\toutbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {\n\t\t\t\titOutbound, _ := server.outbound.Outbound(it)\n\t\t\t\treturn itOutbound\n\t\t\t}))\n\t\t\tb, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))\n\t\t\tchecked := make(map[string]bool)\n\t\t\tresult = make(map[string]uint16)\n\t\t\tvar resultAccess sync.Mutex\n\t\t\tfor _, detour := range outbounds {\n\t\t\t\ttag := detour.Tag()\n\t\t\t\trealTag := group.RealTag(detour)\n\t\t\t\tif checked[realTag] {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tchecked[realTag] = true\n\t\t\t\tp, loaded := server.outbound.Outbound(realTag)\n\t\t\t\tif !loaded {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tb.Go(realTag, func() (any, error) {\n\t\t\t\t\tt, err := urltest.URLTest(ctx, url, p)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tserver.logger.Debug(\"outbound \", tag, \" unavailable: \", err)\n\t\t\t\t\t\tserver.urlTestHistory.DeleteURLTestHistory(realTag)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tserver.logger.Debug(\"outbound \", tag, \" available: \", t, \"ms\")\n\t\t\t\t\t\tserver.urlTestHistory.StoreURLTestHistory(realTag, &adapter.URLTestHistory{\n\t\t\t\t\t\t\tTime:  time.Now(),\n\t\t\t\t\t\t\tDelay: t,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tresultAccess.Lock()\n\t\t\t\t\t\tresult[tag] = t\n\t\t\t\t\t\tresultAccess.Unlock()\n\t\t\t\t\t}\n\t\t\t\t\treturn nil, nil\n\t\t\t\t})\n\t\t\t}\n\t\t\tb.Wait()\n\t\t}\n\n\t\tif err != nil {\n\t\t\trender.Status(r, http.StatusGatewayTimeout)\n\t\t\trender.JSON(w, r, newError(err.Error()))\n\t\t\treturn\n\t\t}\n\n\t\trender.JSON(w, r, result)\n\t}\n}\n"
  },
  {
    "path": "experimental/clashapi/api_meta_upgrade.go",
    "content": "package clashapi\n\nimport (\n\t\"net/http\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc upgradeRouter(server *Server) http.Handler {\n\tr := chi.NewRouter()\n\tr.Post(\"/ui\", updateExternalUI(server))\n\treturn r\n}\n\nfunc updateExternalUI(server *Server) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tif server.externalUI == \"\" {\n\t\t\trender.Status(r, http.StatusNotFound)\n\t\t\trender.JSON(w, r, newError(\"external UI not enabled\"))\n\t\t\treturn\n\t\t}\n\t\tserver.logger.Info(\"upgrading external UI\")\n\t\terr := server.downloadExternalUI()\n\t\tif err != nil {\n\t\t\tserver.logger.Error(E.Cause(err, \"upgrade external ui\"))\n\t\t\trender.Status(r, http.StatusInternalServerError)\n\t\t\trender.JSON(w, r, newError(err.Error()))\n\t\t\treturn\n\t\t}\n\t\tserver.logger.Info(\"updated external UI\")\n\t\trender.JSON(w, r, render.M{\"status\": \"ok\"})\n\t}\n}\n"
  },
  {
    "path": "experimental/clashapi/cache.go",
    "content": "package clashapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc cacheRouter(ctx context.Context) http.Handler {\n\tr := chi.NewRouter()\n\tr.Post(\"/fakeip/flush\", flushFakeip(ctx))\n\tr.Post(\"/dns/flush\", flushDNS(ctx))\n\treturn r\n}\n\nfunc flushFakeip(ctx context.Context) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tcacheFile := service.FromContext[adapter.CacheFile](ctx)\n\t\tif cacheFile != nil {\n\t\t\terr := cacheFile.FakeIPReset()\n\t\t\tif err != nil {\n\t\t\t\trender.Status(r, http.StatusInternalServerError)\n\t\t\t\trender.JSON(w, r, newError(err.Error()))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\trender.NoContent(w, r)\n\t}\n}\n\nfunc flushDNS(ctx context.Context) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tdnsRouter := service.FromContext[adapter.DNSRouter](ctx)\n\t\tif dnsRouter != nil {\n\t\t\tdnsRouter.ClearCache()\n\t\t}\n\t\trender.NoContent(w, r)\n\t}\n}\n"
  },
  {
    "path": "experimental/clashapi/common.go",
    "content": "package clashapi\n\nimport (\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\n// When name is composed of a partial escape string, Golang does not unescape it\nfunc getEscapeParam(r *http.Request, paramName string) string {\n\tparam := chi.URLParam(r, paramName)\n\tif newParam, err := url.PathUnescape(param); err == nil {\n\t\tparam = newParam\n\t}\n\treturn param\n}\n"
  },
  {
    "path": "experimental/clashapi/configs.go",
    "content": "package clashapi\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc configRouter(server *Server, logFactory log.Factory) http.Handler {\n\tr := chi.NewRouter()\n\tr.Get(\"/\", getConfigs(server, logFactory))\n\tr.Put(\"/\", updateConfigs)\n\tr.Patch(\"/\", patchConfigs(server))\n\treturn r\n}\n\ntype configSchema struct {\n\tPort        int    `json:\"port\"`\n\tSocksPort   int    `json:\"socks-port\"`\n\tRedirPort   int    `json:\"redir-port\"`\n\tTProxyPort  int    `json:\"tproxy-port\"`\n\tMixedPort   int    `json:\"mixed-port\"`\n\tAllowLan    bool   `json:\"allow-lan\"`\n\tBindAddress string `json:\"bind-address\"`\n\tMode        string `json:\"mode\"`\n\t// sing-box added\n\tModeList []string       `json:\"mode-list\"`\n\tLogLevel string         `json:\"log-level\"`\n\tIPv6     bool           `json:\"ipv6\"`\n\tTun      map[string]any `json:\"tun\"`\n}\n\nfunc getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tlogLevel := logFactory.Level()\n\t\tif logLevel == log.LevelTrace {\n\t\t\tlogLevel = log.LevelDebug\n\t\t} else if logLevel < log.LevelError {\n\t\t\tlogLevel = log.LevelError\n\t\t}\n\t\trender.JSON(w, r, &configSchema{\n\t\t\tMode:        server.mode,\n\t\t\tModeList:    server.modeList,\n\t\t\tBindAddress: \"*\",\n\t\t\tLogLevel:    log.FormatLevel(logLevel),\n\t\t})\n\t}\n}\n\nfunc patchConfigs(server *Server) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tvar newConfig configSchema\n\t\terr := render.DecodeJSON(r.Body, &newConfig)\n\t\tif err != nil {\n\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\trender.JSON(w, r, ErrBadRequest)\n\t\t\treturn\n\t\t}\n\t\tif newConfig.Mode != \"\" {\n\t\t\tserver.SetMode(newConfig.Mode)\n\t\t}\n\t\trender.NoContent(w, r)\n\t}\n}\n\nfunc updateConfigs(w http.ResponseWriter, r *http.Request) {\n\trender.NoContent(w, r)\n}\n"
  },
  {
    "path": "experimental/clashapi/connections.go",
    "content": "package clashapi\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/ws\"\n\t\"github.com/sagernet/ws/wsutil\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n\t\"github.com/gofrs/uuid/v5\"\n)\n\nfunc connectionRouter(ctx context.Context, router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler {\n\tr := chi.NewRouter()\n\tr.Get(\"/\", getConnections(ctx, trafficManager))\n\tr.Delete(\"/\", closeAllConnections(router, trafficManager))\n\tr.Delete(\"/{id}\", closeConnection(trafficManager))\n\treturn r\n}\n\nfunc getConnections(ctx context.Context, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Header.Get(\"Upgrade\") != \"websocket\" {\n\t\t\tsnapshot := trafficManager.Snapshot()\n\t\t\trender.JSON(w, r, snapshot)\n\t\t\treturn\n\t\t}\n\n\t\tconn, _, _, err := ws.UpgradeHTTP(r, w)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tdefer conn.Close()\n\n\t\tintervalStr := r.URL.Query().Get(\"interval\")\n\t\tinterval := 1000\n\t\tif intervalStr != \"\" {\n\t\t\tt, err := strconv.Atoi(intervalStr)\n\t\t\tif err != nil {\n\t\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\t\trender.JSON(w, r, ErrBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tinterval = t\n\t\t}\n\n\t\tbuf := &bytes.Buffer{}\n\t\tsendSnapshot := func() error {\n\t\t\tbuf.Reset()\n\t\t\tsnapshot := trafficManager.Snapshot()\n\t\t\tif err := json.NewEncoder(buf).Encode(snapshot); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn wsutil.WriteServerText(conn, buf.Bytes())\n\t\t}\n\n\t\tif err = sendSnapshot(); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\ttick := time.NewTicker(time.Millisecond * time.Duration(interval))\n\t\tdefer tick.Stop()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-tick.C:\n\t\t\t}\n\t\t\tif err = sendSnapshot(); err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc closeConnection(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tid := uuid.FromStringOrNil(chi.URLParam(r, \"id\"))\n\t\tsnapshot := trafficManager.Snapshot()\n\t\tfor _, c := range snapshot.Connections {\n\t\t\tif id == c.Metadata().ID {\n\t\t\t\tc.Close()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\trender.NoContent(w, r)\n\t}\n}\n\nfunc closeAllConnections(router adapter.Router, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tsnapshot := trafficManager.Snapshot()\n\t\tfor _, c := range snapshot.Connections {\n\t\t\tc.Close()\n\t\t}\n\t\trouter.ResetNetwork()\n\t\trender.NoContent(w, r)\n\t}\n}\n"
  },
  {
    "path": "experimental/clashapi/ctxkeys.go",
    "content": "package clashapi\n\nvar (\n\tCtxKeyProxyName    = contextKey(\"proxy name\")\n\tCtxKeyProviderName = contextKey(\"provider name\")\n\tCtxKeyProxy        = contextKey(\"proxy\")\n\tCtxKeyProvider     = contextKey(\"provider\")\n)\n\ntype contextKey string\n\nfunc (c contextKey) String() string {\n\treturn \"clash context key \" + string(c)\n}\n"
  },
  {
    "path": "experimental/clashapi/dns.go",
    "content": "package clashapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n\t\"github.com/miekg/dns\"\n)\n\nfunc dnsRouter(router adapter.DNSRouter) http.Handler {\n\tr := chi.NewRouter()\n\tr.Get(\"/query\", queryDNS(router))\n\treturn r\n}\n\nfunc queryDNS(router adapter.DNSRouter) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tname := r.URL.Query().Get(\"name\")\n\t\tqTypeStr := r.URL.Query().Get(\"type\")\n\t\tif qTypeStr == \"\" {\n\t\t\tqTypeStr = \"A\"\n\t\t}\n\n\t\tqType, exist := dns.StringToType[qTypeStr]\n\t\tif !exist {\n\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\trender.JSON(w, r, newError(\"invalid query type\"))\n\t\t\treturn\n\t\t}\n\n\t\tctx, cancel := context.WithTimeout(context.Background(), C.DNSTimeout)\n\t\tdefer cancel()\n\n\t\tmsg := dns.Msg{}\n\t\tmsg.SetQuestion(dns.Fqdn(name), qType)\n\t\tresp, err := router.Exchange(ctx, &msg, adapter.DNSQueryOptions{})\n\t\tif err != nil {\n\t\t\trender.Status(r, http.StatusInternalServerError)\n\t\t\trender.JSON(w, r, newError(err.Error()))\n\t\t\treturn\n\t\t}\n\n\t\tresponseData := render.M{\n\t\t\t\"Status\":   resp.Rcode,\n\t\t\t\"Question\": resp.Question,\n\t\t\t\"Server\":   \"internal\",\n\t\t\t\"TC\":       resp.Truncated,\n\t\t\t\"RD\":       resp.RecursionDesired,\n\t\t\t\"RA\":       resp.RecursionAvailable,\n\t\t\t\"AD\":       resp.AuthenticatedData,\n\t\t\t\"CD\":       resp.CheckingDisabled,\n\t\t}\n\n\t\trr2Json := func(rr dns.RR) render.M {\n\t\t\theader := rr.Header()\n\t\t\treturn render.M{\n\t\t\t\t\"name\": header.Name,\n\t\t\t\t\"type\": header.Rrtype,\n\t\t\t\t\"TTL\":  header.Ttl,\n\t\t\t\t\"data\": rr.String()[len(header.String()):],\n\t\t\t}\n\t\t}\n\n\t\tif len(resp.Answer) > 0 {\n\t\t\tresponseData[\"Answer\"] = common.Map(resp.Answer, rr2Json)\n\t\t}\n\t\tif len(resp.Ns) > 0 {\n\t\t\tresponseData[\"Authority\"] = common.Map(resp.Ns, rr2Json)\n\t\t}\n\t\tif len(resp.Extra) > 0 {\n\t\t\tresponseData[\"Additional\"] = common.Map(resp.Extra, rr2Json)\n\t\t}\n\n\t\trender.JSON(w, r, responseData)\n\t}\n}\n"
  },
  {
    "path": "experimental/clashapi/errors.go",
    "content": "package clashapi\n\nvar (\n\tErrUnauthorized   = newError(\"Unauthorized\")\n\tErrBadRequest     = newError(\"Body invalid\")\n\tErrForbidden      = newError(\"Forbidden\")\n\tErrNotFound       = newError(\"Resource not found\")\n\tErrRequestTimeout = newError(\"Timeout\")\n)\n\n// HTTPError is custom HTTP error for API\ntype HTTPError struct {\n\tMessage string `json:\"message\"`\n}\n\nfunc (e *HTTPError) Error() string {\n\treturn e.Message\n}\n\nfunc newError(msg string) *HTTPError {\n\treturn &HTTPError{Message: msg}\n}\n"
  },
  {
    "path": "experimental/clashapi/profile.go",
    "content": "package clashapi\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc profileRouter() http.Handler {\n\tr := chi.NewRouter()\n\tr.Get(\"/tracing\", subscribeTracing)\n\treturn r\n}\n\nfunc subscribeTracing(w http.ResponseWriter, r *http.Request) {\n\t// if !profile.Tracing.Load() {\n\trender.Status(r, http.StatusNotFound)\n\trender.JSON(w, r, ErrNotFound)\n\t//return\n\t//}\n\n\t/*wsConn, err := upgrader.Upgrade(w, r, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tch := make(chan map[string]any, 1024)\n\tsub := event.Subscribe()\n\tdefer event.UnSubscribe(sub)\n\tbuf := &bytes.Buffer{}\n\n\tgo func() {\n\t\tfor elm := range sub {\n\t\t\tselect {\n\t\t\tcase ch <- elm:\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tfor elm := range ch {\n\t\tbuf.Reset()\n\t\tif err := json.NewEncoder(buf).Encode(elm); err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif err := wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()); err != nil {\n\t\t\tbreak\n\t\t}\n\t}*/\n}\n"
  },
  {
    "path": "experimental/clashapi/provider.go",
    "content": "package clashapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc proxyProviderRouter() http.Handler {\n\tr := chi.NewRouter()\n\tr.Get(\"/\", getProviders)\n\n\tr.Route(\"/{name}\", func(r chi.Router) {\n\t\tr.Use(parseProviderName, findProviderByName)\n\t\tr.Get(\"/\", getProvider)\n\t\tr.Put(\"/\", updateProvider)\n\t\tr.Get(\"/healthcheck\", healthCheckProvider)\n\t})\n\treturn r\n}\n\nfunc getProviders(w http.ResponseWriter, r *http.Request) {\n\trender.JSON(w, r, render.M{\n\t\t\"providers\": render.M{},\n\t})\n}\n\nfunc getProvider(w http.ResponseWriter, r *http.Request) {\n\t/*provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider)\n\trender.JSON(w, r, provider)*/\n\trender.NoContent(w, r)\n}\n\nfunc updateProvider(w http.ResponseWriter, r *http.Request) {\n\t/*provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider)\n\tif err := provider.Update(); err != nil {\n\t\trender.Status(r, http.StatusServiceUnavailable)\n\t\trender.JSON(w, r, newError(err.Error()))\n\t\treturn\n\t}*/\n\trender.NoContent(w, r)\n}\n\nfunc healthCheckProvider(w http.ResponseWriter, r *http.Request) {\n\t/*provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider)\n\tprovider.HealthCheck()*/\n\trender.NoContent(w, r)\n}\n\nfunc parseProviderName(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tname := getEscapeParam(r, \"name\")\n\t\tctx := context.WithValue(r.Context(), CtxKeyProviderName, name)\n\t\tnext.ServeHTTP(w, r.WithContext(ctx))\n\t})\n}\n\nfunc findProviderByName(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t/*name := r.Context().Value(CtxKeyProviderName).(string)\n\t\tproviders := tunnel.ProxyProviders()\n\t\tprovider, exist := providers[name]\n\t\tif !exist {*/\n\t\trender.Status(r, http.StatusNotFound)\n\t\trender.JSON(w, r, ErrNotFound)\n\t\t//return\n\t\t//}\n\n\t\t// ctx := context.WithValue(r.Context(), CtxKeyProvider, provider)\n\t\t// next.ServeHTTP(w, r.WithContext(ctx))\n\t})\n}\n"
  },
  {
    "path": "experimental/clashapi/proxies.go",
    "content": "package clashapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/urltest\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/protocol/group\"\n\t\"github.com/sagernet/sing/common\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc proxyRouter(server *Server, router adapter.Router) http.Handler {\n\tr := chi.NewRouter()\n\tr.Get(\"/\", getProxies(server))\n\n\tr.Route(\"/{name}\", func(r chi.Router) {\n\t\tr.Use(parseProxyName, findProxyByName(server))\n\t\tr.Get(\"/\", getProxy(server))\n\t\tr.Get(\"/delay\", getProxyDelay(server))\n\t\tr.Put(\"/\", updateProxy)\n\t})\n\treturn r\n}\n\nfunc parseProxyName(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tname := getEscapeParam(r, \"name\")\n\t\tctx := context.WithValue(r.Context(), CtxKeyProxyName, name)\n\t\tnext.ServeHTTP(w, r.WithContext(ctx))\n\t})\n}\n\nfunc findProxyByName(server *Server) func(next http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tname := r.Context().Value(CtxKeyProxyName).(string)\n\t\t\tproxy, exist := server.outbound.Outbound(name)\n\t\t\tif !exist {\n\t\t\t\trender.Status(r, http.StatusNotFound)\n\t\t\t\trender.JSON(w, r, ErrNotFound)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tctx := context.WithValue(r.Context(), CtxKeyProxy, proxy)\n\t\t\tnext.ServeHTTP(w, r.WithContext(ctx))\n\t\t})\n\t}\n}\n\nfunc proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {\n\tvar info badjson.JSONObject\n\tvar clashType string\n\tswitch detour.Type() {\n\tcase C.TypeBlock:\n\t\tclashType = \"Reject\"\n\tdefault:\n\t\tclashType = C.ProxyDisplayName(detour.Type())\n\t}\n\tinfo.Put(\"type\", clashType)\n\tinfo.Put(\"name\", detour.Tag())\n\tinfo.Put(\"udp\", common.Contains(detour.Network(), N.NetworkUDP))\n\tdelayHistory := server.urlTestHistory.LoadURLTestHistory(adapter.OutboundTag(detour))\n\tif delayHistory != nil {\n\t\tinfo.Put(\"history\", []*adapter.URLTestHistory{delayHistory})\n\t} else {\n\t\tinfo.Put(\"history\", []*adapter.URLTestHistory{})\n\t}\n\tif group, isGroup := detour.(adapter.OutboundGroup); isGroup {\n\t\tinfo.Put(\"now\", group.Now())\n\t\tinfo.Put(\"all\", group.All())\n\t}\n\treturn &info\n}\n\nfunc getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tvar proxyMap badjson.JSONObject\n\t\toutbounds := common.Filter(server.outbound.Outbounds(), func(detour adapter.Outbound) bool {\n\t\t\treturn detour.Tag() != \"\"\n\t\t})\n\t\toutbounds = append(outbounds, common.Map(common.Filter(server.endpoint.Endpoints(), func(detour adapter.Endpoint) bool {\n\t\t\treturn detour.Tag() != \"\"\n\t\t}), func(it adapter.Endpoint) adapter.Outbound {\n\t\t\treturn it\n\t\t})...)\n\n\t\tallProxies := make([]string, 0, len(outbounds))\n\n\t\tfor _, detour := range outbounds {\n\t\t\tswitch detour.Type() {\n\t\t\tcase C.TypeDirect, C.TypeBlock, C.TypeDNS:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tallProxies = append(allProxies, detour.Tag())\n\t\t}\n\n\t\tdefaultTag := server.outbound.Default().Tag()\n\n\t\tsort.SliceStable(allProxies, func(i, j int) bool {\n\t\t\treturn allProxies[i] == defaultTag\n\t\t})\n\n\t\t// fix clash dashboard\n\t\tproxyMap.Put(\"GLOBAL\", map[string]any{\n\t\t\t\"type\":    \"Fallback\",\n\t\t\t\"name\":    \"GLOBAL\",\n\t\t\t\"udp\":     true,\n\t\t\t\"history\": []*adapter.URLTestHistory{},\n\t\t\t\"all\":     allProxies,\n\t\t\t\"now\":     defaultTag,\n\t\t})\n\n\t\tfor i, detour := range outbounds {\n\t\t\tvar tag string\n\t\t\tif detour.Tag() == \"\" {\n\t\t\t\ttag = F.ToString(i)\n\t\t\t} else {\n\t\t\t\ttag = detour.Tag()\n\t\t\t}\n\t\t\tproxyMap.Put(tag, proxyInfo(server, detour))\n\t\t}\n\t\tvar responseMap badjson.JSONObject\n\t\tresponseMap.Put(\"proxies\", &proxyMap)\n\t\tresponse, err := responseMap.MarshalJSON()\n\t\tif err != nil {\n\t\t\trender.Status(r, http.StatusInternalServerError)\n\t\t\trender.JSON(w, r, newError(err.Error()))\n\t\t\treturn\n\t\t}\n\t\tw.Write(response)\n\t}\n}\n\nfunc getProxy(server *Server) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tproxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)\n\t\tresponse, err := proxyInfo(server, proxy).MarshalJSON()\n\t\tif err != nil {\n\t\t\trender.Status(r, http.StatusInternalServerError)\n\t\t\trender.JSON(w, r, newError(err.Error()))\n\t\t\treturn\n\t\t}\n\t\tw.Write(response)\n\t}\n}\n\ntype UpdateProxyRequest struct {\n\tName string `json:\"name\"`\n}\n\nfunc updateProxy(w http.ResponseWriter, r *http.Request) {\n\treq := UpdateProxyRequest{}\n\tif err := render.DecodeJSON(r.Body, &req); err != nil {\n\t\trender.Status(r, http.StatusBadRequest)\n\t\trender.JSON(w, r, ErrBadRequest)\n\t\treturn\n\t}\n\n\tproxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)\n\tselector, ok := proxy.(*group.Selector)\n\tif !ok {\n\t\trender.Status(r, http.StatusBadRequest)\n\t\trender.JSON(w, r, newError(\"Must be a Selector\"))\n\t\treturn\n\t}\n\n\tif !selector.SelectOutbound(req.Name) {\n\t\trender.Status(r, http.StatusBadRequest)\n\t\trender.JSON(w, r, newError(\"Selector update error: not found\"))\n\t\treturn\n\t}\n\n\trender.NoContent(w, r)\n}\n\nfunc getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tquery := r.URL.Query()\n\t\turl := query.Get(\"url\")\n\t\tif strings.HasPrefix(url, \"http://\") {\n\t\t\turl = \"\"\n\t\t}\n\t\ttimeout, err := strconv.ParseInt(query.Get(\"timeout\"), 10, 16)\n\t\tif err != nil {\n\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\trender.JSON(w, r, ErrBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tproxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)\n\t\tctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))\n\t\tdefer cancel()\n\n\t\tdelay, err := urltest.URLTest(ctx, url, proxy)\n\t\tdefer func() {\n\t\t\trealTag := group.RealTag(proxy)\n\t\t\tif err != nil {\n\t\t\t\tserver.urlTestHistory.DeleteURLTestHistory(realTag)\n\t\t\t} else {\n\t\t\t\tserver.urlTestHistory.StoreURLTestHistory(realTag, &adapter.URLTestHistory{\n\t\t\t\t\tTime:  time.Now(),\n\t\t\t\t\tDelay: delay,\n\t\t\t\t})\n\t\t\t}\n\t\t}()\n\n\t\tif ctx.Err() != nil {\n\t\t\trender.Status(r, http.StatusGatewayTimeout)\n\t\t\trender.JSON(w, r, ErrRequestTimeout)\n\t\t\treturn\n\t\t}\n\n\t\tif err != nil || delay == 0 {\n\t\t\trender.Status(r, http.StatusServiceUnavailable)\n\t\t\trender.JSON(w, r, newError(\"An error occurred in the delay test\"))\n\t\t\treturn\n\t\t}\n\n\t\trender.JSON(w, r, render.M{\n\t\t\t\"delay\": delay,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "experimental/clashapi/ruleprovider.go",
    "content": "package clashapi\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc ruleProviderRouter() http.Handler {\n\tr := chi.NewRouter()\n\tr.Get(\"/\", getRuleProviders)\n\n\tr.Route(\"/{name}\", func(r chi.Router) {\n\t\tr.Use(parseProviderName, findRuleProviderByName)\n\t\tr.Get(\"/\", getRuleProvider)\n\t\tr.Put(\"/\", updateRuleProvider)\n\t})\n\treturn r\n}\n\nfunc getRuleProviders(w http.ResponseWriter, r *http.Request) {\n\trender.JSON(w, r, render.M{\n\t\t\"providers\": []string{},\n\t})\n}\n\nfunc getRuleProvider(w http.ResponseWriter, r *http.Request) {\n\t// provider := r.Context().Value(CtxKeyProvider).(provider.RuleProvider)\n\t// render.JSON(w, r, provider)\n\trender.NoContent(w, r)\n}\n\nfunc updateRuleProvider(w http.ResponseWriter, r *http.Request) {\n\t/*provider := r.Context().Value(CtxKeyProvider).(provider.RuleProvider)\n\tif err := provider.Update(); err != nil {\n\t\trender.Status(r, http.StatusServiceUnavailable)\n\t\trender.JSON(w, r, newError(err.Error()))\n\t\treturn\n\t}*/\n\trender.NoContent(w, r)\n}\n\nfunc findRuleProviderByName(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t/*name := r.Context().Value(CtxKeyProviderName).(string)\n\t\tproviders := tunnel.RuleProviders()\n\t\tprovider, exist := providers[name]\n\t\tif !exist {*/\n\t\trender.Status(r, http.StatusNotFound)\n\t\trender.JSON(w, r, ErrNotFound)\n\t\t//return\n\t\t//}\n\n\t\t// ctx := context.WithValue(r.Context(), CtxKeyProvider, provider)\n\t\t// next.ServeHTTP(w, r.WithContext(ctx))\n\t})\n}\n"
  },
  {
    "path": "experimental/clashapi/rules.go",
    "content": "package clashapi\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc ruleRouter(router adapter.Router) http.Handler {\n\tr := chi.NewRouter()\n\tr.Get(\"/\", getRules(router))\n\treturn r\n}\n\ntype Rule struct {\n\tType    string `json:\"type\"`\n\tPayload string `json:\"payload\"`\n\tProxy   string `json:\"proxy\"`\n}\n\nfunc getRules(router adapter.Router) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\trawRules := router.Rules()\n\n\t\tvar rules []Rule\n\t\tfor _, rule := range rawRules {\n\t\t\trules = append(rules, Rule{\n\t\t\t\tType:    rule.Type(),\n\t\t\t\tPayload: rule.String(),\n\t\t\t\tProxy:   rule.Action().String(),\n\t\t\t})\n\t\t}\n\t\trender.JSON(w, r, render.M{\n\t\t\t\"rules\": rules,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "experimental/clashapi/script.go",
    "content": "package clashapi\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc scriptRouter() http.Handler {\n\tr := chi.NewRouter()\n\tr.Post(\"/\", testScript)\n\tr.Patch(\"/\", patchScript)\n\treturn r\n}\n\n/*type TestScriptRequest struct {\n\tScript   *string    `json:\"script\"`\n\tMetadata C.Metadata `json:\"metadata\"`\n}*/\n\nfunc testScript(w http.ResponseWriter, r *http.Request) {\n\t/*\treq := TestScriptRequest{}\n\t\tif err := render.DecodeJSON(r.Body, &req); err != nil {\n\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\trender.JSON(w, r, ErrBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tfn := tunnel.ScriptFn()\n\t\tif req.Script == nil && fn == nil {\n\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\trender.JSON(w, r, newError(\"should send `script`\"))\n\t\t\treturn\n\t\t}\n\n\t\tif !req.Metadata.Valid() {\n\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\trender.JSON(w, r, newError(\"metadata not valid\"))\n\t\t\treturn\n\t\t}\n\n\t\tif req.Script != nil {\n\t\t\tvar err error\n\t\t\tfn, err = script.ParseScript(*req.Script)\n\t\t\tif err != nil {\n\t\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\t\trender.JSON(w, r, newError(err.Error()))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tctx, _ := script.MakeContext(tunnel.ProxyProviders(), tunnel.RuleProviders())\n\n\t\tthread := &starlark.Thread{}\n\t\tret, err := starlark.Call(thread, fn, starlark.Tuple{ctx, script.MakeMetadata(&req.Metadata)}, nil)\n\t\tif err != nil {\n\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\trender.JSON(w, r, newError(err.Error()))\n\t\t\treturn\n\t\t}\n\n\t\telm, ok := ret.(starlark.String)\n\t\tif !ok {\n\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\trender.JSON(w, r, \"script fn must return a string\")\n\t\t\treturn\n\t\t}\n\n\t\trender.JSON(w, r, render.M{\n\t\t\t\"result\": string(elm),\n\t\t})*/\n\trender.Status(r, http.StatusBadRequest)\n\trender.JSON(w, r, newError(\"not implemented\"))\n}\n\ntype PatchScriptRequest struct {\n\tScript string `json:\"script\"`\n}\n\nfunc patchScript(w http.ResponseWriter, r *http.Request) {\n\t/*req := PatchScriptRequest{}\n\tif err := render.DecodeJSON(r.Body, &req); err != nil {\n\t\trender.Status(r, http.StatusBadRequest)\n\t\trender.JSON(w, r, ErrBadRequest)\n\t\treturn\n\t}\n\n\tfn, err := script.ParseScript(req.Script)\n\tif err != nil {\n\t\trender.Status(r, http.StatusBadRequest)\n\t\trender.JSON(w, r, newError(err.Error()))\n\t\treturn\n\t}\n\n\ttunnel.UpdateScript(fn)*/\n\trender.NoContent(w, r)\n}\n"
  },
  {
    "path": "experimental/clashapi/server.go",
    "content": "package clashapi\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/sagernet/cors\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/urltest\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/experimental\"\n\t\"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/observable\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/filemanager\"\n\t\"github.com/sagernet/ws\"\n\t\"github.com/sagernet/ws/wsutil\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc init() {\n\texperimental.RegisterClashServerConstructor(NewServer)\n}\n\nvar _ adapter.ClashServer = (*Server)(nil)\n\ntype Server struct {\n\tctx            context.Context\n\trouter         adapter.Router\n\tdnsRouter      adapter.DNSRouter\n\toutbound       adapter.OutboundManager\n\tendpoint       adapter.EndpointManager\n\tlogger         log.Logger\n\thttpServer     *http.Server\n\ttrafficManager *trafficontrol.Manager\n\turlTestHistory adapter.URLTestHistoryStorage\n\tlogDebug       bool\n\n\tmode           string\n\tmodeList       []string\n\tmodeUpdateHook *observable.Subscriber[struct{}]\n\n\texternalController       bool\n\texternalUI               string\n\texternalUIDownloadURL    string\n\texternalUIDownloadDetour string\n}\n\nfunc NewServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {\n\ttrafficManager := trafficontrol.NewManager()\n\tchiRouter := chi.NewRouter()\n\ts := &Server{\n\t\tctx:       ctx,\n\t\trouter:    service.FromContext[adapter.Router](ctx),\n\t\tdnsRouter: service.FromContext[adapter.DNSRouter](ctx),\n\t\toutbound:  service.FromContext[adapter.OutboundManager](ctx),\n\t\tendpoint:  service.FromContext[adapter.EndpointManager](ctx),\n\t\tlogger:    logFactory.NewLogger(\"clash-api\"),\n\t\thttpServer: &http.Server{\n\t\t\tAddr:    options.ExternalController,\n\t\t\tHandler: chiRouter,\n\t\t},\n\t\ttrafficManager:           trafficManager,\n\t\tlogDebug:                 logFactory.Level() >= log.LevelDebug,\n\t\tmodeList:                 options.ModeList,\n\t\texternalController:       options.ExternalController != \"\",\n\t\texternalUIDownloadURL:    options.ExternalUIDownloadURL,\n\t\texternalUIDownloadDetour: options.ExternalUIDownloadDetour,\n\t}\n\ts.urlTestHistory = service.FromContext[adapter.URLTestHistoryStorage](ctx)\n\tif s.urlTestHistory == nil {\n\t\ts.urlTestHistory = urltest.NewHistoryStorage()\n\t}\n\tdefaultMode := \"Rule\"\n\tif options.DefaultMode != \"\" {\n\t\tdefaultMode = options.DefaultMode\n\t}\n\tif !common.Contains(s.modeList, defaultMode) {\n\t\ts.modeList = append([]string{defaultMode}, s.modeList...)\n\t}\n\ts.mode = defaultMode\n\t//goland:noinspection GoDeprecation\n\t//nolint:staticcheck\n\tif options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != \"\" || options.CacheID != \"\" {\n\t\treturn nil, E.New(\"cache_file and related fields in Clash API is deprecated in sing-box 1.8.0, use experimental.cache_file instead.\")\n\t}\n\tallowedOrigins := options.AccessControlAllowOrigin\n\tif len(allowedOrigins) == 0 {\n\t\tallowedOrigins = []string{\"*\"}\n\t}\n\tcors := cors.New(cors.Options{\n\t\tAllowedOrigins:      allowedOrigins,\n\t\tAllowedMethods:      []string{\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"},\n\t\tAllowedHeaders:      []string{\"Content-Type\", \"Authorization\"},\n\t\tAllowPrivateNetwork: options.AccessControlAllowPrivateNetwork,\n\t\tMaxAge:              300,\n\t})\n\tchiRouter.Use(cors.Handler)\n\tchiRouter.Group(func(r chi.Router) {\n\t\tr.Use(authentication(options.Secret))\n\t\tr.Get(\"/\", hello(options.ExternalUI != \"\"))\n\t\tr.Get(\"/logs\", getLogs(s.ctx, logFactory))\n\t\tr.Get(\"/traffic\", traffic(s.ctx, trafficManager))\n\t\tr.Get(\"/version\", version)\n\t\tr.Mount(\"/configs\", configRouter(s, logFactory))\n\t\tr.Mount(\"/proxies\", proxyRouter(s, s.router))\n\t\tr.Mount(\"/rules\", ruleRouter(s.router))\n\t\tr.Mount(\"/connections\", connectionRouter(s.ctx, s.router, trafficManager))\n\t\tr.Mount(\"/providers/proxies\", proxyProviderRouter())\n\t\tr.Mount(\"/providers/rules\", ruleProviderRouter())\n\t\tr.Mount(\"/script\", scriptRouter())\n\t\tr.Mount(\"/profile\", profileRouter())\n\t\tr.Mount(\"/cache\", cacheRouter(ctx))\n\t\tr.Mount(\"/dns\", dnsRouter(s.dnsRouter))\n\n\t\ts.setupMetaAPI(r)\n\t})\n\tif options.ExternalUI != \"\" {\n\t\ts.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))\n\t\tchiRouter.Group(func(r chi.Router) {\n\t\t\tr.Get(\"/ui\", http.RedirectHandler(\"/ui/\", http.StatusMovedPermanently).ServeHTTP)\n\t\t\tr.Handle(\"/ui/*\", http.StripPrefix(\"/ui/\", http.FileServer(Dir(s.externalUI))))\n\t\t})\n\t}\n\treturn s, nil\n}\n\nfunc (s *Server) Name() string {\n\treturn \"clash server\"\n}\n\nfunc (s *Server) Start(stage adapter.StartStage) error {\n\tswitch stage {\n\tcase adapter.StartStateStart:\n\t\tcacheFile := service.FromContext[adapter.CacheFile](s.ctx)\n\t\tif cacheFile != nil {\n\t\t\tmode := cacheFile.LoadMode()\n\t\t\tif common.Any(s.modeList, func(it string) bool {\n\t\t\t\treturn strings.EqualFold(it, mode)\n\t\t\t}) {\n\t\t\t\ts.mode = mode\n\t\t\t}\n\t\t}\n\tcase adapter.StartStateStarted:\n\t\tif s.externalController {\n\t\t\ts.checkAndDownloadExternalUI()\n\t\t\tvar (\n\t\t\t\tlistener net.Listener\n\t\t\t\terr      error\n\t\t\t)\n\t\t\tfor i := 0; i < 3; i++ {\n\t\t\t\tlistener, err = net.Listen(\"tcp\", s.httpServer.Addr)\n\t\t\t\tif runtime.GOOS == \"android\" && errors.Is(err, syscall.EADDRINUSE) {\n\t\t\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"external controller listen error\")\n\t\t\t}\n\t\t\ts.logger.Info(\"restful api listening at \", listener.Addr())\n\t\t\tgo func() {\n\t\t\t\terr = s.httpServer.Serve(listener)\n\t\t\t\tif err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\t\t\t\ts.logger.Error(\"external controller serve error: \", err)\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Server) Close() error {\n\treturn common.Close(\n\t\tcommon.PtrOrNil(s.httpServer),\n\t\ts.trafficManager,\n\t\ts.urlTestHistory,\n\t)\n}\n\nfunc (s *Server) Mode() string {\n\treturn s.mode\n}\n\nfunc (s *Server) ModeList() []string {\n\treturn s.modeList\n}\n\nfunc (s *Server) SetModeUpdateHook(hook *observable.Subscriber[struct{}]) {\n\ts.modeUpdateHook = hook\n}\n\nfunc (s *Server) SetMode(newMode string) {\n\tif !common.Contains(s.modeList, newMode) {\n\t\tnewMode = common.Find(s.modeList, func(it string) bool {\n\t\t\treturn strings.EqualFold(it, newMode)\n\t\t})\n\t}\n\tif !common.Contains(s.modeList, newMode) {\n\t\treturn\n\t}\n\tif newMode == s.mode {\n\t\treturn\n\t}\n\ts.mode = newMode\n\tif s.modeUpdateHook != nil {\n\t\ts.modeUpdateHook.Emit(struct{}{})\n\t}\n\ts.dnsRouter.ClearCache()\n\tcacheFile := service.FromContext[adapter.CacheFile](s.ctx)\n\tif cacheFile != nil {\n\t\terr := cacheFile.StoreMode(newMode)\n\t\tif err != nil {\n\t\t\ts.logger.Error(E.Cause(err, \"save mode\"))\n\t\t}\n\t}\n\ts.logger.Info(\"updated mode: \", newMode)\n}\n\nfunc (s *Server) HistoryStorage() adapter.URLTestHistoryStorage {\n\treturn s.urlTestHistory\n}\n\nfunc (s *Server) TrafficManager() *trafficontrol.Manager {\n\treturn s.trafficManager\n}\n\nfunc (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {\n\treturn trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outbound, matchedRule, matchOutbound)\n}\n\nfunc (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn {\n\treturn trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outbound, matchedRule, matchOutbound)\n}\n\nfunc authentication(serverSecret string) func(next http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif serverSecret == \"\" {\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Browser websocket not support custom header\n\t\t\tif r.Header.Get(\"Upgrade\") == \"websocket\" && r.URL.Query().Get(\"token\") != \"\" {\n\t\t\t\ttoken := r.URL.Query().Get(\"token\")\n\t\t\t\tif token != serverSecret {\n\t\t\t\t\trender.Status(r, http.StatusUnauthorized)\n\t\t\t\t\trender.JSON(w, r, ErrUnauthorized)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\theader := r.Header.Get(\"Authorization\")\n\t\t\tbearer, token, found := strings.Cut(header, \" \")\n\n\t\t\thasInvalidHeader := bearer != \"Bearer\"\n\t\t\thasInvalidSecret := !found || token != serverSecret\n\t\t\tif hasInvalidHeader || hasInvalidSecret {\n\t\t\t\trender.Status(r, http.StatusUnauthorized)\n\t\t\t\trender.JSON(w, r, ErrUnauthorized)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tnext.ServeHTTP(w, r)\n\t\t}\n\t\treturn http.HandlerFunc(fn)\n\t}\n}\n\nfunc hello(redirect bool) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tcontentType := r.Header.Get(\"Content-Type\")\n\t\tif !redirect || contentType == \"application/json\" {\n\t\t\trender.JSON(w, r, render.M{\"hello\": \"clash\"})\n\t\t} else {\n\t\t\thttp.Redirect(w, r, \"/ui/\", http.StatusTemporaryRedirect)\n\t\t}\n\t}\n}\n\ntype Traffic struct {\n\tUp   int64 `json:\"up\"`\n\tDown int64 `json:\"down\"`\n}\n\nfunc traffic(ctx context.Context, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tvar conn net.Conn\n\t\tif r.Header.Get(\"Upgrade\") == \"websocket\" {\n\t\t\tvar err error\n\t\t\tconn, _, _, err = ws.UpgradeHTTP(r, w)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer conn.Close()\n\t\t}\n\n\t\tif conn == nil {\n\t\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t\trender.Status(r, http.StatusOK)\n\t\t}\n\n\t\ttick := time.NewTicker(time.Second)\n\t\tdefer tick.Stop()\n\t\tbuf := &bytes.Buffer{}\n\t\tuploadTotal, downloadTotal := trafficManager.Total()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-tick.C:\n\t\t\t}\n\t\t\tbuf.Reset()\n\t\t\tuploadTotalNew, downloadTotalNew := trafficManager.Total()\n\t\t\terr := json.NewEncoder(buf).Encode(Traffic{\n\t\t\t\tUp:   uploadTotalNew - uploadTotal,\n\t\t\t\tDown: downloadTotalNew - downloadTotal,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif conn == nil {\n\t\t\t\t_, err = w.Write(buf.Bytes())\n\t\t\t\tw.(http.Flusher).Flush()\n\t\t\t} else {\n\t\t\t\terr = wsutil.WriteServerText(conn, buf.Bytes())\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tuploadTotal = uploadTotalNew\n\t\t\tdownloadTotal = downloadTotalNew\n\t\t}\n\t}\n}\n\ntype Log struct {\n\tType    string `json:\"type\"`\n\tPayload string `json:\"payload\"`\n}\n\nfunc getLogs(ctx context.Context, logFactory log.ObservableFactory) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tlevelText := r.URL.Query().Get(\"level\")\n\t\tif levelText == \"\" {\n\t\t\tlevelText = \"info\"\n\t\t}\n\n\t\tlevel, ok := log.ParseLevel(levelText)\n\t\tif ok != nil {\n\t\t\trender.Status(r, http.StatusBadRequest)\n\t\t\trender.JSON(w, r, ErrBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tsubscription, done, err := logFactory.Subscribe()\n\t\tif err != nil {\n\t\t\trender.Status(r, http.StatusNoContent)\n\t\t\treturn\n\t\t}\n\t\tdefer logFactory.UnSubscribe(subscription)\n\n\t\tvar conn net.Conn\n\t\tif r.Header.Get(\"Upgrade\") == \"websocket\" {\n\t\t\tconn, _, _, err = ws.UpgradeHTTP(r, w)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer conn.Close()\n\t\t}\n\n\t\tif conn == nil {\n\t\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t\trender.Status(r, http.StatusOK)\n\t\t}\n\n\t\tbuf := &bytes.Buffer{}\n\t\tvar logEntry log.Entry\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-done:\n\t\t\t\treturn\n\t\t\tcase logEntry = <-subscription:\n\t\t\t}\n\t\t\tif logEntry.Level > level {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbuf.Reset()\n\t\t\terr = json.NewEncoder(buf).Encode(Log{\n\t\t\t\tType:    log.FormatLevel(logEntry.Level),\n\t\t\t\tPayload: logEntry.Message,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif conn == nil {\n\t\t\t\t_, err = w.Write(buf.Bytes())\n\t\t\t\tw.(http.Flusher).Flush()\n\t\t\t} else {\n\t\t\t\terr = wsutil.WriteServerText(conn, buf.Bytes())\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc version(w http.ResponseWriter, r *http.Request) {\n\trender.JSON(w, r, render.M{\"version\": \"sing-box \" + C.Version, \"premium\": true, \"meta\": true})\n}\n"
  },
  {
    "path": "experimental/clashapi/server_fs.go",
    "content": "package clashapi\n\nimport \"net/http\"\n\ntype Dir http.Dir\n\nfunc (d Dir) Open(name string) (http.File, error) {\n\tfile, err := http.Dir(d).Open(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &fileWrapper{file}, nil\n}\n\n// workaround for #2345 #2596\ntype fileWrapper struct {\n\thttp.File\n}\n"
  },
  {
    "path": "experimental/clashapi/server_resources.go",
    "content": "package clashapi\n\nimport (\n\t\"archive/zip\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\t\"github.com/sagernet/sing/service/filemanager\"\n)\n\nfunc (s *Server) checkAndDownloadExternalUI() {\n\tif s.externalUI == \"\" {\n\t\treturn\n\t}\n\tentries, err := os.ReadDir(s.externalUI)\n\tif err != nil {\n\t\tos.MkdirAll(s.externalUI, 0o755)\n\t}\n\tif len(entries) == 0 {\n\t\terr = s.downloadExternalUI()\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"download external ui error: \", err)\n\t\t}\n\t}\n}\n\nfunc (s *Server) downloadExternalUI() error {\n\tvar downloadURL string\n\tif s.externalUIDownloadURL != \"\" {\n\t\tdownloadURL = s.externalUIDownloadURL\n\t} else {\n\t\tdownloadURL = \"https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip\"\n\t}\n\tvar detour adapter.Outbound\n\tif s.externalUIDownloadDetour != \"\" {\n\t\toutbound, loaded := s.outbound.Outbound(s.externalUIDownloadDetour)\n\t\tif !loaded {\n\t\t\treturn E.New(\"detour outbound not found: \", s.externalUIDownloadDetour)\n\t\t}\n\t\tdetour = outbound\n\t} else {\n\t\toutbound := s.outbound.Default()\n\t\tdetour = outbound\n\t}\n\ts.logger.Info(\"downloading external ui using outbound/\", detour.Type(), \"[\", detour.Tag(), \"]\")\n\thttpClient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tForceAttemptHTTP2:   true,\n\t\t\tTLSHandshakeTimeout: C.TCPTimeout,\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\treturn detour.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t\t\t},\n\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\tTime:    ntp.TimeFuncFromContext(s.ctx),\n\t\t\t\tRootCAs: adapter.RootPoolFromContext(s.ctx),\n\t\t\t},\n\t\t},\n\t}\n\tdefer httpClient.CloseIdleConnections()\n\tresponse, err := httpClient.Get(downloadURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer response.Body.Close()\n\tif response.StatusCode != http.StatusOK {\n\t\treturn E.New(\"download external ui failed: \", response.Status)\n\t}\n\terr = s.downloadZIP(response.Body, s.externalUI)\n\tif err != nil {\n\t\tremoveAllInDirectory(s.externalUI)\n\t}\n\treturn err\n}\n\nfunc (s *Server) downloadZIP(body io.Reader, output string) error {\n\ttempFile, err := filemanager.CreateTemp(s.ctx, \"external-ui.zip\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.Remove(tempFile.Name())\n\t_, err = io.Copy(tempFile, body)\n\ttempFile.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\treader, err := zip.OpenReader(tempFile.Name())\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer reader.Close()\n\ttrimDir := zipIsInSingleDirectory(reader.File)\n\tfor _, file := range reader.File {\n\t\tif file.FileInfo().IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tpathElements := strings.Split(file.Name, \"/\")\n\t\tif trimDir {\n\t\t\tpathElements = pathElements[1:]\n\t\t}\n\t\tsaveDirectory := output\n\t\tif len(pathElements) > 1 {\n\t\t\tsaveDirectory = filepath.Join(saveDirectory, filepath.Join(pathElements[:len(pathElements)-1]...))\n\t\t}\n\t\terr = os.MkdirAll(saveDirectory, 0o755)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsavePath := filepath.Join(saveDirectory, pathElements[len(pathElements)-1])\n\t\terr = downloadZIPEntry(s.ctx, file, savePath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc downloadZIPEntry(ctx context.Context, zipFile *zip.File, savePath string) error {\n\tsaveFile, err := filemanager.Create(ctx, savePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer saveFile.Close()\n\treader, err := zipFile.Open()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer reader.Close()\n\treturn common.Error(io.Copy(saveFile, reader))\n}\n\nfunc removeAllInDirectory(directory string) {\n\tdirEntries, err := os.ReadDir(directory)\n\tif err != nil {\n\t\treturn\n\t}\n\tfor _, dirEntry := range dirEntries {\n\t\tos.RemoveAll(filepath.Join(directory, dirEntry.Name()))\n\t}\n}\n\nfunc zipIsInSingleDirectory(files []*zip.File) bool {\n\tvar singleDirectory string\n\tfor _, file := range files {\n\t\tif file.FileInfo().IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tpathElements := strings.Split(file.Name, \"/\")\n\t\tif len(pathElements) == 0 {\n\t\t\treturn false\n\t\t}\n\t\tif singleDirectory == \"\" {\n\t\t\tsingleDirectory = pathElements[0]\n\t\t} else if singleDirectory != pathElements[0] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "experimental/clashapi/trafficontrol/manager.go",
    "content": "package trafficontrol\n\nimport (\n\t\"runtime\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/common/compatible\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/observable\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\n\t\"github.com/gofrs/uuid/v5\"\n)\n\ntype ConnectionEventType int\n\nconst (\n\tConnectionEventNew ConnectionEventType = iota\n\tConnectionEventUpdate\n\tConnectionEventClosed\n)\n\ntype ConnectionEvent struct {\n\tType          ConnectionEventType\n\tID            uuid.UUID\n\tMetadata      *TrackerMetadata\n\tUplinkDelta   int64\n\tDownlinkDelta int64\n\tClosedAt      time.Time\n}\n\nconst closedConnectionsLimit = 1000\n\ntype Manager struct {\n\tuploadTotal   atomic.Int64\n\tdownloadTotal atomic.Int64\n\n\tconnections             compatible.Map[uuid.UUID, Tracker]\n\tclosedConnectionsAccess sync.Mutex\n\tclosedConnections       list.List[TrackerMetadata]\n\tmemory                  uint64\n\n\teventSubscriber *observable.Subscriber[ConnectionEvent]\n}\n\nfunc NewManager() *Manager {\n\treturn &Manager{}\n}\n\nfunc (m *Manager) SetEventHook(subscriber *observable.Subscriber[ConnectionEvent]) {\n\tm.eventSubscriber = subscriber\n}\n\nfunc (m *Manager) Join(c Tracker) {\n\tmetadata := c.Metadata()\n\tm.connections.Store(metadata.ID, c)\n\tif m.eventSubscriber != nil {\n\t\tm.eventSubscriber.Emit(ConnectionEvent{\n\t\t\tType:     ConnectionEventNew,\n\t\t\tID:       metadata.ID,\n\t\t\tMetadata: metadata,\n\t\t})\n\t}\n}\n\nfunc (m *Manager) Leave(c Tracker) {\n\tmetadata := c.Metadata()\n\t_, loaded := m.connections.LoadAndDelete(metadata.ID)\n\tif loaded {\n\t\tclosedAt := time.Now()\n\t\tmetadata.ClosedAt = closedAt\n\t\tmetadataCopy := *metadata\n\t\tm.closedConnectionsAccess.Lock()\n\t\tif m.closedConnections.Len() >= closedConnectionsLimit {\n\t\t\tm.closedConnections.PopFront()\n\t\t}\n\t\tm.closedConnections.PushBack(metadataCopy)\n\t\tm.closedConnectionsAccess.Unlock()\n\t\tif m.eventSubscriber != nil {\n\t\t\tm.eventSubscriber.Emit(ConnectionEvent{\n\t\t\t\tType:     ConnectionEventClosed,\n\t\t\t\tID:       metadata.ID,\n\t\t\t\tMetadata: &metadataCopy,\n\t\t\t\tClosedAt: closedAt,\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc (m *Manager) PushUploaded(size int64) {\n\tm.uploadTotal.Add(size)\n}\n\nfunc (m *Manager) PushDownloaded(size int64) {\n\tm.downloadTotal.Add(size)\n}\n\nfunc (m *Manager) Total() (up int64, down int64) {\n\treturn m.uploadTotal.Load(), m.downloadTotal.Load()\n}\n\nfunc (m *Manager) ConnectionsLen() int {\n\treturn m.connections.Len()\n}\n\nfunc (m *Manager) Connections() []*TrackerMetadata {\n\tvar connections []*TrackerMetadata\n\tm.connections.Range(func(_ uuid.UUID, value Tracker) bool {\n\t\tconnections = append(connections, value.Metadata())\n\t\treturn true\n\t})\n\treturn connections\n}\n\nfunc (m *Manager) ClosedConnections() []*TrackerMetadata {\n\tm.closedConnectionsAccess.Lock()\n\tvalues := m.closedConnections.Array()\n\tm.closedConnectionsAccess.Unlock()\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tconnections := make([]*TrackerMetadata, len(values))\n\tfor i := range values {\n\t\tconnections[i] = &values[i]\n\t}\n\treturn connections\n}\n\nfunc (m *Manager) Connection(id uuid.UUID) Tracker {\n\tconnection, loaded := m.connections.Load(id)\n\tif !loaded {\n\t\treturn nil\n\t}\n\treturn connection\n}\n\nfunc (m *Manager) Snapshot() *Snapshot {\n\tvar connections []Tracker\n\tm.connections.Range(func(_ uuid.UUID, value Tracker) bool {\n\t\tif value.Metadata().OutboundType != C.TypeDNS {\n\t\t\tconnections = append(connections, value)\n\t\t}\n\t\treturn true\n\t})\n\n\tvar memStats runtime.MemStats\n\truntime.ReadMemStats(&memStats)\n\tm.memory = memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased\n\n\treturn &Snapshot{\n\t\tUpload:      m.uploadTotal.Load(),\n\t\tDownload:    m.downloadTotal.Load(),\n\t\tConnections: connections,\n\t\tMemory:      m.memory,\n\t}\n}\n\nfunc (m *Manager) ResetStatistic() {\n\tm.uploadTotal.Store(0)\n\tm.downloadTotal.Store(0)\n}\n\ntype Snapshot struct {\n\tDownload    int64\n\tUpload      int64\n\tConnections []Tracker\n\tMemory      uint64\n}\n\nfunc (s *Snapshot) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(map[string]any{\n\t\t\"downloadTotal\": s.Download,\n\t\t\"uploadTotal\":   s.Upload,\n\t\t\"connections\":   common.Map(s.Connections, func(t Tracker) *TrackerMetadata { return t.Metadata() }),\n\t\t\"memory\":        s.Memory,\n\t})\n}\n"
  },
  {
    "path": "experimental/clashapi/trafficontrol/tracker.go",
    "content": "package trafficontrol\n\nimport (\n\t\"net\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"github.com/gofrs/uuid/v5\"\n)\n\ntype TrackerMetadata struct {\n\tID           uuid.UUID\n\tMetadata     adapter.InboundContext\n\tCreatedAt    time.Time\n\tClosedAt     time.Time\n\tUpload       *atomic.Int64\n\tDownload     *atomic.Int64\n\tChain        []string\n\tRule         adapter.Rule\n\tOutbound     string\n\tOutboundType string\n}\n\nfunc (t TrackerMetadata) MarshalJSON() ([]byte, error) {\n\tvar inbound string\n\tif t.Metadata.Inbound != \"\" {\n\t\tinbound = t.Metadata.InboundType + \"/\" + t.Metadata.Inbound\n\t} else {\n\t\tinbound = t.Metadata.InboundType\n\t}\n\tvar domain string\n\tif t.Metadata.Domain != \"\" {\n\t\tdomain = t.Metadata.Domain\n\t} else {\n\t\tdomain = t.Metadata.Destination.Fqdn\n\t}\n\tvar processPath string\n\tif t.Metadata.ProcessInfo != nil {\n\t\tif t.Metadata.ProcessInfo.ProcessPath != \"\" {\n\t\t\tprocessPath = t.Metadata.ProcessInfo.ProcessPath\n\t\t} else if t.Metadata.ProcessInfo.AndroidPackageName != \"\" {\n\t\t\tprocessPath = t.Metadata.ProcessInfo.AndroidPackageName\n\t\t}\n\t\tif processPath == \"\" {\n\t\t\tif t.Metadata.ProcessInfo.UserId != -1 {\n\t\t\t\tprocessPath = F.ToString(t.Metadata.ProcessInfo.UserId)\n\t\t\t}\n\t\t} else if t.Metadata.ProcessInfo.UserName != \"\" {\n\t\t\tprocessPath = F.ToString(processPath, \" (\", t.Metadata.ProcessInfo.UserName, \")\")\n\t\t} else if t.Metadata.ProcessInfo.UserId != -1 {\n\t\t\tprocessPath = F.ToString(processPath, \" (\", t.Metadata.ProcessInfo.UserId, \")\")\n\t\t}\n\t}\n\tvar rule string\n\tif t.Rule != nil {\n\t\trule = F.ToString(t.Rule, \" => \", t.Rule.Action())\n\t} else {\n\t\trule = \"final\"\n\t}\n\treturn json.Marshal(map[string]any{\n\t\t\"id\": t.ID,\n\t\t\"metadata\": map[string]any{\n\t\t\t\"network\":         t.Metadata.Network,\n\t\t\t\"type\":            inbound,\n\t\t\t\"sourceIP\":        t.Metadata.Source.Addr,\n\t\t\t\"destinationIP\":   t.Metadata.Destination.Addr,\n\t\t\t\"sourcePort\":      F.ToString(t.Metadata.Source.Port),\n\t\t\t\"destinationPort\": F.ToString(t.Metadata.Destination.Port),\n\t\t\t\"host\":            domain,\n\t\t\t\"dnsMode\":         \"normal\",\n\t\t\t\"processPath\":     processPath,\n\t\t},\n\t\t\"upload\":      t.Upload.Load(),\n\t\t\"download\":    t.Download.Load(),\n\t\t\"start\":       t.CreatedAt,\n\t\t\"chains\":      t.Chain,\n\t\t\"rule\":        rule,\n\t\t\"rulePayload\": \"\",\n\t})\n}\n\ntype Tracker interface {\n\tMetadata() *TrackerMetadata\n\tClose() error\n}\n\ntype TCPConn struct {\n\tN.ExtendedConn\n\tmetadata TrackerMetadata\n\tmanager  *Manager\n}\n\nfunc (tt *TCPConn) Metadata() *TrackerMetadata {\n\treturn &tt.metadata\n}\n\nfunc (tt *TCPConn) Close() error {\n\ttt.manager.Leave(tt)\n\treturn tt.ExtendedConn.Close()\n}\n\nfunc (tt *TCPConn) Upstream() any {\n\treturn tt.ExtendedConn\n}\n\nfunc (tt *TCPConn) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (tt *TCPConn) WriterReplaceable() bool {\n\treturn true\n}\n\nfunc NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *TCPConn {\n\tid, _ := uuid.NewV4()\n\tvar (\n\t\tchain        []string\n\t\tnext         string\n\t\toutbound     string\n\t\toutboundType string\n\t)\n\tif matchOutbound != nil {\n\t\tnext = matchOutbound.Tag()\n\t} else {\n\t\tnext = outboundManager.Default().Tag()\n\t}\n\tfor {\n\t\tdetour, loaded := outboundManager.Outbound(next)\n\t\tif !loaded {\n\t\t\tbreak\n\t\t}\n\t\tchain = append(chain, next)\n\t\toutbound = detour.Tag()\n\t\toutboundType = detour.Type()\n\t\tgroup, isGroup := detour.(adapter.OutboundGroup)\n\t\tif !isGroup {\n\t\t\tbreak\n\t\t}\n\t\tnext = group.Now()\n\t}\n\tupload := new(atomic.Int64)\n\tdownload := new(atomic.Int64)\n\ttracker := &TCPConn{\n\t\tExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {\n\t\t\tupload.Add(n)\n\t\t\tmanager.PushUploaded(n)\n\t\t}}, []N.CountFunc{func(n int64) {\n\t\t\tdownload.Add(n)\n\t\t\tmanager.PushDownloaded(n)\n\t\t}}),\n\t\tmetadata: TrackerMetadata{\n\t\t\tID:           id,\n\t\t\tMetadata:     metadata,\n\t\t\tCreatedAt:    time.Now(),\n\t\t\tUpload:       upload,\n\t\t\tDownload:     download,\n\t\t\tChain:        common.Reverse(chain),\n\t\t\tRule:         matchRule,\n\t\t\tOutbound:     outbound,\n\t\t\tOutboundType: outboundType,\n\t\t},\n\t\tmanager: manager,\n\t}\n\tmanager.Join(tracker)\n\treturn tracker\n}\n\ntype UDPConn struct {\n\tN.PacketConn `json:\"-\"`\n\tmetadata     TrackerMetadata\n\tmanager      *Manager\n}\n\nfunc (ut *UDPConn) Metadata() *TrackerMetadata {\n\treturn &ut.metadata\n}\n\nfunc (ut *UDPConn) Close() error {\n\tut.manager.Leave(ut)\n\treturn ut.PacketConn.Close()\n}\n\nfunc (ut *UDPConn) Upstream() any {\n\treturn ut.PacketConn\n}\n\nfunc (ut *UDPConn) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (ut *UDPConn) WriterReplaceable() bool {\n\treturn true\n}\n\nfunc NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *UDPConn {\n\tid, _ := uuid.NewV4()\n\tvar (\n\t\tchain        []string\n\t\tnext         string\n\t\toutbound     string\n\t\toutboundType string\n\t)\n\tif matchOutbound != nil {\n\t\tnext = matchOutbound.Tag()\n\t} else {\n\t\tnext = outboundManager.Default().Tag()\n\t}\n\tfor {\n\t\tdetour, loaded := outboundManager.Outbound(next)\n\t\tif !loaded {\n\t\t\tbreak\n\t\t}\n\t\tchain = append(chain, next)\n\t\toutbound = detour.Tag()\n\t\toutboundType = detour.Type()\n\t\tgroup, isGroup := detour.(adapter.OutboundGroup)\n\t\tif !isGroup {\n\t\t\tbreak\n\t\t}\n\t\tnext = group.Now()\n\t}\n\tupload := new(atomic.Int64)\n\tdownload := new(atomic.Int64)\n\ttrackerConn := &UDPConn{\n\t\tPacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) {\n\t\t\tupload.Add(n)\n\t\t\tmanager.PushUploaded(n)\n\t\t}}, []N.CountFunc{func(n int64) {\n\t\t\tdownload.Add(n)\n\t\t\tmanager.PushDownloaded(n)\n\t\t}}),\n\t\tmetadata: TrackerMetadata{\n\t\t\tID:           id,\n\t\t\tMetadata:     metadata,\n\t\t\tCreatedAt:    time.Now(),\n\t\t\tUpload:       upload,\n\t\t\tDownload:     download,\n\t\t\tChain:        common.Reverse(chain),\n\t\t\tRule:         matchRule,\n\t\t\tOutbound:     outbound,\n\t\t\tOutboundType: outboundType,\n\t\t},\n\t\tmanager: manager,\n\t}\n\tmanager.Join(trackerConn)\n\treturn trackerConn\n}\n"
  },
  {
    "path": "experimental/clashapi.go",
    "content": "package experimental\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"sort\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n)\n\ntype ClashServerConstructor = func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error)\n\nvar clashServerConstructor ClashServerConstructor\n\nfunc RegisterClashServerConstructor(constructor ClashServerConstructor) {\n\tclashServerConstructor = constructor\n}\n\nfunc NewClashServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {\n\tif clashServerConstructor == nil {\n\t\treturn nil, os.ErrInvalid\n\t}\n\treturn clashServerConstructor(ctx, logFactory, options)\n}\n\nfunc CalculateClashModeList(options option.Options) []string {\n\tvar clashModes []string\n\tclashModes = append(clashModes, extraClashModeFromRule(common.PtrValueOrDefault(options.Route).Rules)...)\n\tclashModes = append(clashModes, extraClashModeFromDNSRule(common.PtrValueOrDefault(options.DNS).Rules)...)\n\tclashModes = common.FilterNotDefault(common.Uniq(clashModes))\n\tpredefinedOrder := []string{\n\t\t\"Rule\", \"Global\", \"Direct\",\n\t}\n\tvar newClashModes []string\n\tfor _, mode := range clashModes {\n\t\tif !common.Contains(predefinedOrder, mode) {\n\t\t\tnewClashModes = append(newClashModes, mode)\n\t\t}\n\t}\n\tsort.Strings(newClashModes)\n\tfor _, mode := range predefinedOrder {\n\t\tif common.Contains(clashModes, mode) {\n\t\t\tnewClashModes = append(newClashModes, mode)\n\t\t}\n\t}\n\treturn newClashModes\n}\n\nfunc extraClashModeFromRule(rules []option.Rule) []string {\n\tvar clashMode []string\n\tfor _, rule := range rules {\n\t\tswitch rule.Type {\n\t\tcase C.RuleTypeDefault:\n\t\t\tif rule.DefaultOptions.ClashMode != \"\" {\n\t\t\t\tclashMode = append(clashMode, rule.DefaultOptions.ClashMode)\n\t\t\t}\n\t\tcase C.RuleTypeLogical:\n\t\t\tclashMode = append(clashMode, extraClashModeFromRule(rule.LogicalOptions.Rules)...)\n\t\t}\n\t}\n\treturn clashMode\n}\n\nfunc extraClashModeFromDNSRule(rules []option.DNSRule) []string {\n\tvar clashMode []string\n\tfor _, rule := range rules {\n\t\tswitch rule.Type {\n\t\tcase C.RuleTypeDefault:\n\t\t\tif rule.DefaultOptions.ClashMode != \"\" {\n\t\t\t\tclashMode = append(clashMode, rule.DefaultOptions.ClashMode)\n\t\t\t}\n\t\tcase C.RuleTypeLogical:\n\t\t\tclashMode = append(clashMode, extraClashModeFromDNSRule(rule.LogicalOptions.Rules)...)\n\t\t}\n\t}\n\treturn clashMode\n}\n"
  },
  {
    "path": "experimental/deprecated/constants.go",
    "content": "package deprecated\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/sagernet/sing-box/common/badversion\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/experimental/locale\"\n\tF \"github.com/sagernet/sing/common/format\"\n\n\t\"golang.org/x/mod/semver\"\n)\n\ntype Note struct {\n\tName              string\n\tDescription       string\n\tDeprecatedVersion string\n\tScheduledVersion  string\n\tEnvName           string\n\tMigrationLink     string\n}\n\nfunc (n Note) Impending() bool {\n\tif n.ScheduledVersion == \"\" {\n\t\treturn false\n\t}\n\tif !semver.IsValid(\"v\" + C.Version) {\n\t\treturn false\n\t}\n\tversionCurrent := badversion.Parse(C.Version)\n\tversionMinor := badversion.Parse(n.ScheduledVersion).Minor - versionCurrent.Minor\n\tif versionCurrent.PreReleaseIdentifier == \"\" && versionMinor < 0 {\n\t\tpanic(\"invalid deprecated note: \" + n.Name)\n\t}\n\treturn versionMinor <= 1\n}\n\nfunc (n Note) Message() string {\n\tif n.MigrationLink != \"\" {\n\t\treturn fmt.Sprintf(locale.Current().DeprecatedMessage, n.Description, n.DeprecatedVersion, n.ScheduledVersion)\n\t} else {\n\t\treturn fmt.Sprintf(locale.Current().DeprecatedMessageNoLink, n.Description, n.DeprecatedVersion, n.ScheduledVersion)\n\t}\n}\n\nfunc (n Note) MessageWithLink() string {\n\tif n.MigrationLink != \"\" {\n\t\treturn F.ToString(\n\t\t\tn.Description, \" is deprecated in sing-box \", n.DeprecatedVersion,\n\t\t\t\" and will be removed in sing-box \", n.ScheduledVersion, \", checkout documentation for migration: \", n.MigrationLink,\n\t\t)\n\t} else {\n\t\treturn F.ToString(\n\t\t\tn.Description, \" is deprecated in sing-box \", n.DeprecatedVersion,\n\t\t\t\" and will be removed in sing-box \", n.ScheduledVersion, \".\",\n\t\t)\n\t}\n}\n\nvar OptionLegacyDNSTransport = Note{\n\tName:              \"legacy-dns-transport\",\n\tDescription:       \"legacy DNS servers\",\n\tDeprecatedVersion: \"1.12.0\",\n\tScheduledVersion:  \"1.14.0\",\n\tEnvName:           \"LEGACY_DNS_SERVERS\",\n\tMigrationLink:     \"https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats\",\n}\n\nvar OptionLegacyDNSFakeIPOptions = Note{\n\tName:              \"legacy-dns-fakeip-options\",\n\tDescription:       \"legacy DNS fakeip options\",\n\tDeprecatedVersion: \"1.12.0\",\n\tScheduledVersion:  \"1.14.0\",\n\tEnvName:           \"LEGACY_DNS_FAKEIP_OPTIONS\",\n\tMigrationLink:     \"https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats\",\n}\n\nvar OptionOutboundDNSRuleItem = Note{\n\tName:              \"outbound-dns-rule-item\",\n\tDescription:       \"outbound DNS rule item\",\n\tDeprecatedVersion: \"1.12.0\",\n\tScheduledVersion:  \"1.14.0\",\n\tEnvName:           \"OUTBOUND_DNS_RULE_ITEM\",\n\tMigrationLink:     \"https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver\",\n}\n\nvar OptionMissingDomainResolver = Note{\n\tName:              \"missing-domain-resolver\",\n\tDescription:       \"missing `route.default_domain_resolver` or `domain_resolver` in dial fields\",\n\tDeprecatedVersion: \"1.12.0\",\n\tScheduledVersion:  \"1.14.0\",\n\tEnvName:           \"MISSING_DOMAIN_RESOLVER\",\n\tMigrationLink:     \"https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver\",\n}\n\nvar OptionLegacyDomainStrategyOptions = Note{\n\tName:              \"legacy-domain-strategy-options\",\n\tDescription:       \"legacy domain strategy options\",\n\tDeprecatedVersion: \"1.12.0\",\n\tScheduledVersion:  \"1.14.0\",\n\tEnvName:           \"LEGACY_DOMAIN_STRATEGY_OPTIONS\",\n\tMigrationLink:     \"https://sing-box.sagernet.org/migration/#migrate-domain-strategy-options\",\n}\n\nvar Options = []Note{\n\tOptionLegacyDNSTransport,\n\tOptionLegacyDNSFakeIPOptions,\n\tOptionOutboundDNSRuleItem,\n\tOptionMissingDomainResolver,\n\tOptionLegacyDomainStrategyOptions,\n}\n"
  },
  {
    "path": "experimental/deprecated/manager.go",
    "content": "package deprecated\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing/service\"\n)\n\ntype Manager interface {\n\tReportDeprecated(feature Note)\n}\n\nfunc Report(ctx context.Context, feature Note) {\n\tmanager := service.FromContext[Manager](ctx)\n\tif manager == nil {\n\t\treturn\n\t}\n\tmanager.ReportDeprecated(feature)\n}\n"
  },
  {
    "path": "experimental/deprecated/stderr.go",
    "content": "package deprecated\n\nimport (\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\ntype stderrManager struct {\n\tlogger   logger.Logger\n\treported map[string]bool\n}\n\nfunc NewStderrManager(logger logger.Logger) Manager {\n\treturn &stderrManager{\n\t\tlogger:   logger,\n\t\treported: make(map[string]bool),\n\t}\n}\n\nfunc (f *stderrManager) ReportDeprecated(feature Note) {\n\tif f.reported[feature.Name] {\n\t\treturn\n\t}\n\tf.reported[feature.Name] = true\n\tif !feature.Impending() {\n\t\tf.logger.Warn(feature.MessageWithLink())\n\t\treturn\n\t}\n\tif feature.EnvName != \"\" {\n\t\tenable, enableErr := strconv.ParseBool(os.Getenv(\"ENABLE_DEPRECATED_\" + feature.EnvName))\n\t\tif enableErr == nil && enable {\n\t\t\tf.logger.Warn(feature.MessageWithLink())\n\t\t\treturn\n\t\t}\n\t\tf.logger.Error(feature.MessageWithLink())\n\t\tf.logger.Fatal(\"to continuing using this feature, set environment variable ENABLE_DEPRECATED_\" + feature.EnvName + \"=true\")\n\t} else {\n\t\tf.logger.Error(feature.MessageWithLink())\n\t}\n}\n"
  },
  {
    "path": "experimental/libbox/build_info.go",
    "content": "//go:build android\n\npackage libbox\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\t\"debug/buildinfo\"\n\t\"io\"\n\t\"runtime/debug\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing/common\"\n)\n\nconst (\n\tandroidVPNCoreTypeOpenVPN     = \"OpenVPN\"\n\tandroidVPNCoreTypeShadowsocks = \"Shadowsocks\"\n\tandroidVPNCoreTypeClash       = \"Clash\"\n\tandroidVPNCoreTypeV2Ray       = \"V2Ray\"\n\tandroidVPNCoreTypeWireGuard   = \"WireGuard\"\n\tandroidVPNCoreTypeSingBox     = \"sing-box\"\n\tandroidVPNCoreTypeUnknown     = \"Unknown\"\n)\n\ntype AndroidVPNType struct {\n\tCoreType  string\n\tCorePath  string\n\tGoVersion string\n}\n\nfunc ReadAndroidVPNType(publicSourceDirList StringIterator) (*AndroidVPNType, error) {\n\tapkPathList := iteratorToArray[string](publicSourceDirList)\n\tvar lastError error\n\tfor _, apkPath := range apkPathList {\n\t\tandroidVPNType, err := readAndroidVPNType(apkPath)\n\t\tif androidVPNType == nil {\n\t\t\tif err != nil {\n\t\t\t\tlastError = err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\treturn androidVPNType, nil\n\t}\n\treturn nil, lastError\n}\n\nfunc readAndroidVPNType(publicSourceDir string) (*AndroidVPNType, error) {\n\treader, err := zip.OpenReader(publicSourceDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer reader.Close()\n\tvar lastError error\n\tfor _, file := range reader.File {\n\t\tif !strings.HasPrefix(file.Name, \"lib/\") {\n\t\t\tcontinue\n\t\t}\n\t\tvpnType, err := readAndroidVPNTypeEntry(file)\n\t\tif err != nil {\n\t\t\tlastError = err\n\t\t\tcontinue\n\t\t}\n\t\treturn vpnType, nil\n\t}\n\tfor _, file := range reader.File {\n\t\tif !strings.HasPrefix(file.Name, \"lib/\") {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.Contains(file.Name, androidVPNCoreTypeOpenVPN) || strings.Contains(file.Name, \"ovpn\") {\n\t\t\treturn &AndroidVPNType{CoreType: androidVPNCoreTypeOpenVPN}, nil\n\t\t}\n\t\tif strings.Contains(file.Name, androidVPNCoreTypeShadowsocks) {\n\t\t\treturn &AndroidVPNType{CoreType: androidVPNCoreTypeShadowsocks}, nil\n\t\t}\n\t}\n\treturn nil, lastError\n}\n\nfunc readAndroidVPNTypeEntry(zipFile *zip.File) (*AndroidVPNType, error) {\n\treadCloser, err := zipFile.Open()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlibContent := make([]byte, zipFile.UncompressedSize64)\n\t_, err = io.ReadFull(readCloser, libContent)\n\treadCloser.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbuildInfo, err := buildinfo.Read(bytes.NewReader(libContent))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar vpnType AndroidVPNType\n\tvpnType.GoVersion = buildInfo.GoVersion\n\tif !strings.HasPrefix(vpnType.GoVersion, \"go\") {\n\t\tvpnType.GoVersion = \"obfuscated\"\n\t} else {\n\t\tvpnType.GoVersion = vpnType.GoVersion[2:]\n\t}\n\tvpnType.CoreType = androidVPNCoreTypeUnknown\n\tif len(buildInfo.Deps) == 0 {\n\t\tvpnType.CoreType = \"obfuscated\"\n\t\treturn &vpnType, nil\n\t}\n\n\tdependencies := make(map[string]bool)\n\tdependencies[buildInfo.Path] = true\n\tfor _, module := range buildInfo.Deps {\n\t\tdependencies[module.Path] = true\n\t\tif module.Replace != nil {\n\t\t\tdependencies[module.Replace.Path] = true\n\t\t}\n\t}\n\tfor dependency := range dependencies {\n\t\tpkgType, loaded := determinePkgType(dependency)\n\t\tif loaded {\n\t\t\tvpnType.CoreType = pkgType\n\t\t}\n\t}\n\tif vpnType.CoreType == androidVPNCoreTypeUnknown {\n\t\tfor dependency := range dependencies {\n\t\t\tpkgType, loaded := determinePkgTypeSecondary(dependency)\n\t\t\tif loaded {\n\t\t\t\tvpnType.CoreType = pkgType\n\t\t\t\treturn &vpnType, nil\n\t\t\t}\n\t\t}\n\t}\n\tif vpnType.CoreType != androidVPNCoreTypeUnknown {\n\t\tvpnType.CorePath, _ = determineCorePath(buildInfo, vpnType.CoreType)\n\t\treturn &vpnType, nil\n\t}\n\tif dependencies[\"github.com/golang/protobuf\"] && dependencies[\"github.com/v2fly/ss-bloomring\"] {\n\t\tvpnType.CoreType = androidVPNCoreTypeV2Ray\n\t\treturn &vpnType, nil\n\t}\n\treturn &vpnType, nil\n}\n\nfunc determinePkgType(pkgName string) (string, bool) {\n\tpkgNameLower := strings.ToLower(pkgName)\n\tif strings.Contains(pkgNameLower, \"clash\") {\n\t\treturn androidVPNCoreTypeClash, true\n\t}\n\tif strings.Contains(pkgNameLower, \"v2ray\") || strings.Contains(pkgNameLower, \"xray\") {\n\t\treturn androidVPNCoreTypeV2Ray, true\n\t}\n\n\tif strings.Contains(pkgNameLower, \"sing-box\") {\n\t\treturn androidVPNCoreTypeSingBox, true\n\t}\n\treturn \"\", false\n}\n\nfunc determinePkgTypeSecondary(pkgName string) (string, bool) {\n\tpkgNameLower := strings.ToLower(pkgName)\n\tif strings.Contains(pkgNameLower, \"wireguard\") {\n\t\treturn androidVPNCoreTypeWireGuard, true\n\t}\n\treturn \"\", false\n}\n\nfunc determineCorePath(pkgInfo *buildinfo.BuildInfo, pkgType string) (string, bool) {\n\tswitch pkgType {\n\tcase androidVPNCoreTypeClash:\n\t\treturn determineCorePathForPkgs(pkgInfo, []string{\"github.com/Dreamacro/clash\"}, []string{\"clash\"})\n\tcase androidVPNCoreTypeV2Ray:\n\t\tif v2rayVersion, loaded := determineCorePathForPkgs(pkgInfo, []string{\n\t\t\t\"github.com/v2fly/v2ray-core\",\n\t\t\t\"github.com/v2fly/v2ray-core/v4\",\n\t\t\t\"github.com/v2fly/v2ray-core/v5\",\n\t\t}, []string{\n\t\t\t\"v2ray\",\n\t\t}); loaded {\n\t\t\treturn v2rayVersion, true\n\t\t}\n\t\tif xrayVersion, loaded := determineCorePathForPkgs(pkgInfo, []string{\n\t\t\t\"github.com/xtls/xray-core\",\n\t\t}, []string{\n\t\t\t\"xray\",\n\t\t}); loaded {\n\t\t\treturn xrayVersion, true\n\t\t}\n\t\treturn \"\", false\n\tcase androidVPNCoreTypeSingBox:\n\t\treturn determineCorePathForPkgs(pkgInfo, []string{\"github.com/sagernet/sing-box\"}, []string{\"sing-box\"})\n\tcase androidVPNCoreTypeWireGuard:\n\t\treturn determineCorePathForPkgs(pkgInfo, []string{\"golang.zx2c4.com/wireguard\"}, []string{\"wireguard\"})\n\tdefault:\n\t\treturn \"\", false\n\t}\n}\n\nfunc determineCorePathForPkgs(pkgInfo *buildinfo.BuildInfo, pkgs []string, names []string) (string, bool) {\n\tfor _, pkg := range pkgs {\n\t\tif pkgInfo.Path == pkg {\n\t\t\treturn pkg, true\n\t\t}\n\t\tstrictDependency := common.Find(pkgInfo.Deps, func(module *debug.Module) bool {\n\t\t\treturn module.Path == pkg\n\t\t})\n\t\tif strictDependency != nil {\n\t\t\tif isValidVersion(strictDependency.Version) {\n\t\t\t\treturn strictDependency.Path + \" \" + strictDependency.Version, true\n\t\t\t} else {\n\t\t\t\treturn strictDependency.Path, true\n\t\t\t}\n\t\t}\n\t}\n\tfor _, name := range names {\n\t\tif strings.Contains(pkgInfo.Path, name) {\n\t\t\treturn pkgInfo.Path, true\n\t\t}\n\t\tlooseDependency := common.Find(pkgInfo.Deps, func(module *debug.Module) bool {\n\t\t\treturn strings.Contains(module.Path, name) || (module.Replace != nil && strings.Contains(module.Replace.Path, name))\n\t\t})\n\t\tif looseDependency != nil {\n\t\t\treturn looseDependency.Path, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nfunc isValidVersion(version string) bool {\n\tif version == \"(devel)\" {\n\t\treturn false\n\t}\n\tif strings.Contains(version, \"v0.0.0\") {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "experimental/libbox/command.go",
    "content": "package libbox\n\nconst (\n\tCommandLog int32 = iota\n\tCommandStatus\n\tCommandGroup\n\tCommandClashMode\n\tCommandConnections\n)\n"
  },
  {
    "path": "experimental/libbox/command_client.go",
    "content": "package libbox\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/daemon\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\ntype CommandClient struct {\n\thandler     CommandClientHandler\n\tgrpcConn    *grpc.ClientConn\n\tgrpcClient  daemon.StartedServiceClient\n\toptions     CommandClientOptions\n\tctx         context.Context\n\tcancel      context.CancelFunc\n\tclientMutex sync.RWMutex\n\tstandalone  bool\n}\n\ntype CommandClientOptions struct {\n\tcommands       []int32\n\tStatusInterval int64\n}\n\nfunc (o *CommandClientOptions) AddCommand(command int32) {\n\to.commands = append(o.commands, command)\n}\n\ntype CommandClientHandler interface {\n\tConnected()\n\tDisconnected(message string)\n\tSetDefaultLogLevel(level int32)\n\tClearLogs()\n\tWriteLogs(messageList LogIterator)\n\tWriteStatus(message *StatusMessage)\n\tWriteGroups(message OutboundGroupIterator)\n\tInitializeClashMode(modeList StringIterator, currentMode string)\n\tUpdateClashMode(newMode string)\n\tWriteConnectionEvents(events *ConnectionEvents)\n}\n\ntype LogEntry struct {\n\tLevel   int32\n\tMessage string\n}\n\ntype LogIterator interface {\n\tLen() int32\n\tHasNext() bool\n\tNext() *LogEntry\n}\n\ntype XPCDialer interface {\n\tDialXPC() (int32, error)\n}\n\nvar sXPCDialer XPCDialer\n\nfunc SetXPCDialer(dialer XPCDialer) {\n\tsXPCDialer = dialer\n}\n\nfunc NewStandaloneCommandClient() *CommandClient {\n\treturn &CommandClient{standalone: true}\n}\n\nfunc NewCommandClient(handler CommandClientHandler, options *CommandClientOptions) *CommandClient {\n\treturn &CommandClient{\n\t\thandler: handler,\n\t\toptions: common.PtrValueOrDefault(options),\n\t}\n}\n\nfunc unaryClientAuthInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {\n\tif sCommandServerSecret != \"\" {\n\t\tctx = metadata.AppendToOutgoingContext(ctx, \"x-command-secret\", sCommandServerSecret)\n\t}\n\treturn invoker(ctx, method, req, reply, cc, opts...)\n}\n\nfunc streamClientAuthInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {\n\tif sCommandServerSecret != \"\" {\n\t\tctx = metadata.AppendToOutgoingContext(ctx, \"x-command-secret\", sCommandServerSecret)\n\t}\n\treturn streamer(ctx, desc, cc, method, opts...)\n}\n\nconst (\n\tcommandClientDialAttempts  = 10\n\tcommandClientDialBaseDelay = 100 * time.Millisecond\n\tcommandClientDialStepDelay = 50 * time.Millisecond\n)\n\nfunc commandClientDialDelay(attempt int) time.Duration {\n\treturn commandClientDialBaseDelay + time.Duration(attempt)*commandClientDialStepDelay\n}\n\nfunc dialTarget() (string, func(context.Context, string) (net.Conn, error)) {\n\tif sXPCDialer != nil {\n\t\treturn \"passthrough:///xpc\", func(ctx context.Context, _ string) (net.Conn, error) {\n\t\t\tfileDescriptor, err := sXPCDialer.DialXPC()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn networkConnectionFromFileDescriptor(fileDescriptor)\n\t\t}\n\t}\n\tif sCommandServerListenPort == 0 {\n\t\tsocketPath := filepath.Join(sBasePath, \"command.sock\")\n\t\treturn \"passthrough:///command-socket\", func(ctx context.Context, _ string) (net.Conn, error) {\n\t\t\tvar networkDialer net.Dialer\n\t\t\treturn networkDialer.DialContext(ctx, \"unix\", socketPath)\n\t\t}\n\t}\n\treturn net.JoinHostPort(\"127.0.0.1\", strconv.Itoa(int(sCommandServerListenPort))), nil\n}\n\nfunc networkConnectionFromFileDescriptor(fileDescriptor int32) (net.Conn, error) {\n\tfile := os.NewFile(uintptr(fileDescriptor), \"xpc-command-socket\")\n\tif file == nil {\n\t\treturn nil, E.New(\"invalid file descriptor\")\n\t}\n\tnetworkConnection, err := net.FileConn(file)\n\tif err != nil {\n\t\tfile.Close()\n\t\treturn nil, E.Cause(err, \"create connection from fd\")\n\t}\n\tfile.Close()\n\treturn networkConnection, nil\n}\n\nfunc (c *CommandClient) dialWithRetry(target string, contextDialer func(context.Context, string) (net.Conn, error), retryDial bool) (*grpc.ClientConn, daemon.StartedServiceClient, error) {\n\tvar connection *grpc.ClientConn\n\tvar client daemon.StartedServiceClient\n\tvar lastError error\n\n\tfor attempt := 0; attempt < commandClientDialAttempts; attempt++ {\n\t\tif connection == nil {\n\t\t\toptions := []grpc.DialOption{\n\t\t\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t\t\tgrpc.WithUnaryInterceptor(unaryClientAuthInterceptor),\n\t\t\t\tgrpc.WithStreamInterceptor(streamClientAuthInterceptor),\n\t\t\t}\n\t\t\tif contextDialer != nil {\n\t\t\t\toptions = append(options, grpc.WithContextDialer(contextDialer))\n\t\t\t}\n\t\t\tvar err error\n\t\t\tconnection, err = grpc.NewClient(target, options...)\n\t\t\tif err != nil {\n\t\t\t\tlastError = err\n\t\t\t\tif !retryDial {\n\t\t\t\t\treturn nil, nil, err\n\t\t\t\t}\n\t\t\t\ttime.Sleep(commandClientDialDelay(attempt))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tclient = daemon.NewStartedServiceClient(connection)\n\t\t}\n\t\twaitDuration := commandClientDialDelay(attempt)\n\t\tctx, cancel := context.WithTimeout(context.Background(), waitDuration)\n\t\t_, err := client.GetStartedAt(ctx, &emptypb.Empty{}, grpc.WaitForReady(true))\n\t\tcancel()\n\t\tif err == nil {\n\t\t\treturn connection, client, nil\n\t\t}\n\t\tlastError = err\n\t}\n\n\tif connection != nil {\n\t\tconnection.Close()\n\t}\n\treturn nil, nil, lastError\n}\n\nfunc (c *CommandClient) Connect() error {\n\tc.clientMutex.Lock()\n\tcommon.Close(common.PtrOrNil(c.grpcConn))\n\n\ttarget, contextDialer := dialTarget()\n\tconnection, client, err := c.dialWithRetry(target, contextDialer, true)\n\tif err != nil {\n\t\tc.clientMutex.Unlock()\n\t\treturn err\n\t}\n\tc.grpcConn = connection\n\tc.grpcClient = client\n\tc.ctx, c.cancel = context.WithCancel(context.Background())\n\tc.clientMutex.Unlock()\n\n\tc.handler.Connected()\n\treturn c.dispatchCommands()\n}\n\nfunc (c *CommandClient) ConnectWithFD(fd int32) error {\n\tc.clientMutex.Lock()\n\tcommon.Close(common.PtrOrNil(c.grpcConn))\n\n\tnetworkConnection, err := networkConnectionFromFileDescriptor(fd)\n\tif err != nil {\n\t\tc.clientMutex.Unlock()\n\t\treturn err\n\t}\n\tconnection, client, err := c.dialWithRetry(\"passthrough:///xpc\", func(ctx context.Context, _ string) (net.Conn, error) {\n\t\treturn networkConnection, nil\n\t}, false)\n\tif err != nil {\n\t\tnetworkConnection.Close()\n\t\tc.clientMutex.Unlock()\n\t\treturn err\n\t}\n\tc.grpcConn = connection\n\tc.grpcClient = client\n\tc.ctx, c.cancel = context.WithCancel(context.Background())\n\tc.clientMutex.Unlock()\n\n\tc.handler.Connected()\n\treturn c.dispatchCommands()\n}\n\nfunc (c *CommandClient) dispatchCommands() error {\n\tfor _, command := range c.options.commands {\n\t\tswitch command {\n\t\tcase CommandLog:\n\t\t\tgo c.handleLogStream()\n\t\tcase CommandStatus:\n\t\t\tgo c.handleStatusStream()\n\t\tcase CommandGroup:\n\t\t\tgo c.handleGroupStream()\n\t\tcase CommandClashMode:\n\t\t\tgo c.handleClashModeStream()\n\t\tcase CommandConnections:\n\t\t\tgo c.handleConnectionsStream()\n\t\tdefault:\n\t\t\treturn E.New(\"unknown command: \", command)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *CommandClient) Disconnect() error {\n\tc.clientMutex.Lock()\n\tdefer c.clientMutex.Unlock()\n\tif c.cancel != nil {\n\t\tc.cancel()\n\t}\n\treturn common.Close(common.PtrOrNil(c.grpcConn))\n}\n\nfunc (c *CommandClient) getClientForCall() (daemon.StartedServiceClient, error) {\n\tc.clientMutex.RLock()\n\tif c.grpcClient != nil {\n\t\tdefer c.clientMutex.RUnlock()\n\t\treturn c.grpcClient, nil\n\t}\n\tc.clientMutex.RUnlock()\n\n\tc.clientMutex.Lock()\n\tdefer c.clientMutex.Unlock()\n\n\tif c.grpcClient != nil {\n\t\treturn c.grpcClient, nil\n\t}\n\n\ttarget, contextDialer := dialTarget()\n\tconnection, client, err := c.dialWithRetry(target, contextDialer, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc.grpcConn = connection\n\tc.grpcClient = client\n\tif c.ctx == nil {\n\t\tc.ctx, c.cancel = context.WithCancel(context.Background())\n\t}\n\treturn c.grpcClient, nil\n}\n\nfunc (c *CommandClient) closeConnection() {\n\tc.clientMutex.Lock()\n\tdefer c.clientMutex.Unlock()\n\tif c.grpcConn != nil {\n\t\tc.grpcConn.Close()\n\t\tc.grpcConn = nil\n\t\tc.grpcClient = nil\n\t}\n}\n\nfunc callWithResult[T any](c *CommandClient, call func(client daemon.StartedServiceClient) (T, error)) (T, error) {\n\tclient, err := c.getClientForCall()\n\tif err != nil {\n\t\tvar zero T\n\t\treturn zero, err\n\t}\n\tif c.standalone {\n\t\tdefer c.closeConnection()\n\t}\n\treturn call(client)\n}\n\nfunc (c *CommandClient) getStreamContext() (daemon.StartedServiceClient, context.Context) {\n\tc.clientMutex.RLock()\n\tdefer c.clientMutex.RUnlock()\n\treturn c.grpcClient, c.ctx\n}\n\nfunc (c *CommandClient) handleLogStream() {\n\tclient, ctx := c.getStreamContext()\n\tstream, err := client.SubscribeLog(ctx, &emptypb.Empty{})\n\tif err != nil {\n\t\tc.handler.Disconnected(err.Error())\n\t\treturn\n\t}\n\tdefaultLogLevel, err := client.GetDefaultLogLevel(ctx, &emptypb.Empty{})\n\tif err != nil {\n\t\tc.handler.Disconnected(err.Error())\n\t\treturn\n\t}\n\tc.handler.SetDefaultLogLevel(int32(defaultLogLevel.Level))\n\tfor {\n\t\tlogMessage, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tc.handler.Disconnected(err.Error())\n\t\t\treturn\n\t\t}\n\t\tif logMessage.Reset_ {\n\t\t\tc.handler.ClearLogs()\n\t\t}\n\t\tvar messages []*LogEntry\n\t\tfor _, msg := range logMessage.Messages {\n\t\t\tmessages = append(messages, &LogEntry{\n\t\t\t\tLevel:   int32(msg.Level),\n\t\t\t\tMessage: msg.Message,\n\t\t\t})\n\t\t}\n\t\tc.handler.WriteLogs(newIterator(messages))\n\t}\n}\n\nfunc (c *CommandClient) handleStatusStream() {\n\tclient, ctx := c.getStreamContext()\n\tinterval := c.options.StatusInterval\n\n\tstream, err := client.SubscribeStatus(ctx, &daemon.SubscribeStatusRequest{\n\t\tInterval: interval,\n\t})\n\tif err != nil {\n\t\tc.handler.Disconnected(err.Error())\n\t\treturn\n\t}\n\n\tfor {\n\t\tstatus, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tc.handler.Disconnected(err.Error())\n\t\t\treturn\n\t\t}\n\t\tc.handler.WriteStatus(statusMessageFromGRPC(status))\n\t}\n}\n\nfunc (c *CommandClient) handleGroupStream() {\n\tclient, ctx := c.getStreamContext()\n\n\tstream, err := client.SubscribeGroups(ctx, &emptypb.Empty{})\n\tif err != nil {\n\t\tc.handler.Disconnected(err.Error())\n\t\treturn\n\t}\n\n\tfor {\n\t\tgroups, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tc.handler.Disconnected(err.Error())\n\t\t\treturn\n\t\t}\n\t\tc.handler.WriteGroups(outboundGroupIteratorFromGRPC(groups))\n\t}\n}\n\nfunc (c *CommandClient) handleClashModeStream() {\n\tclient, ctx := c.getStreamContext()\n\n\tmodeStatus, err := client.GetClashModeStatus(ctx, &emptypb.Empty{})\n\tif err != nil {\n\t\tc.handler.Disconnected(err.Error())\n\t\treturn\n\t}\n\n\tif sFixAndroidStack {\n\t\tgo func() {\n\t\t\tc.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode)\n\t\t\tif len(modeStatus.ModeList) == 0 {\n\t\t\t\tc.handler.Disconnected(os.ErrInvalid.Error())\n\t\t\t}\n\t\t}()\n\t} else {\n\t\tc.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode)\n\t\tif len(modeStatus.ModeList) == 0 {\n\t\t\tc.handler.Disconnected(os.ErrInvalid.Error())\n\t\t\treturn\n\t\t}\n\t}\n\n\tif len(modeStatus.ModeList) == 0 {\n\t\treturn\n\t}\n\n\tstream, err := client.SubscribeClashMode(ctx, &emptypb.Empty{})\n\tif err != nil {\n\t\tc.handler.Disconnected(err.Error())\n\t\treturn\n\t}\n\n\tfor {\n\t\tmode, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tc.handler.Disconnected(err.Error())\n\t\t\treturn\n\t\t}\n\t\tc.handler.UpdateClashMode(mode.Mode)\n\t}\n}\n\nfunc (c *CommandClient) handleConnectionsStream() {\n\tclient, ctx := c.getStreamContext()\n\tinterval := c.options.StatusInterval\n\n\tstream, err := client.SubscribeConnections(ctx, &daemon.SubscribeConnectionsRequest{\n\t\tInterval: interval,\n\t})\n\tif err != nil {\n\t\tc.handler.Disconnected(err.Error())\n\t\treturn\n\t}\n\n\tfor {\n\t\tevents, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tc.handler.Disconnected(err.Error())\n\t\t\treturn\n\t\t}\n\t\tlibboxEvents := connectionEventsFromGRPC(events)\n\t\tc.handler.WriteConnectionEvents(libboxEvents)\n\t}\n}\n\nfunc (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error {\n\t_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {\n\t\treturn client.SelectOutbound(context.Background(), &daemon.SelectOutboundRequest{\n\t\t\tGroupTag:    groupTag,\n\t\t\tOutboundTag: outboundTag,\n\t\t})\n\t})\n\treturn err\n}\n\nfunc (c *CommandClient) URLTest(groupTag string) error {\n\t_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {\n\t\treturn client.URLTest(context.Background(), &daemon.URLTestRequest{\n\t\t\tOutboundTag: groupTag,\n\t\t})\n\t})\n\treturn err\n}\n\nfunc (c *CommandClient) SetClashMode(newMode string) error {\n\t_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {\n\t\treturn client.SetClashMode(context.Background(), &daemon.ClashMode{\n\t\t\tMode: newMode,\n\t\t})\n\t})\n\treturn err\n}\n\nfunc (c *CommandClient) CloseConnection(connId string) error {\n\t_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {\n\t\treturn client.CloseConnection(context.Background(), &daemon.CloseConnectionRequest{\n\t\t\tId: connId,\n\t\t})\n\t})\n\treturn err\n}\n\nfunc (c *CommandClient) CloseConnections() error {\n\t_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {\n\t\treturn client.CloseAllConnections(context.Background(), &emptypb.Empty{})\n\t})\n\treturn err\n}\n\nfunc (c *CommandClient) ServiceReload() error {\n\t_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {\n\t\treturn client.ReloadService(context.Background(), &emptypb.Empty{})\n\t})\n\treturn err\n}\n\nfunc (c *CommandClient) ServiceClose() error {\n\t_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {\n\t\treturn client.StopService(context.Background(), &emptypb.Empty{})\n\t})\n\treturn err\n}\n\nfunc (c *CommandClient) ClearLogs() error {\n\t_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {\n\t\treturn client.ClearLogs(context.Background(), &emptypb.Empty{})\n\t})\n\treturn err\n}\n\nfunc (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) {\n\treturn callWithResult(c, func(client daemon.StartedServiceClient) (*SystemProxyStatus, error) {\n\t\tstatus, err := client.GetSystemProxyStatus(context.Background(), &emptypb.Empty{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn systemProxyStatusFromGRPC(status), nil\n\t})\n}\n\nfunc (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error {\n\t_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {\n\t\treturn client.SetSystemProxyEnabled(context.Background(), &daemon.SetSystemProxyEnabledRequest{\n\t\t\tEnabled: isEnabled,\n\t\t})\n\t})\n\treturn err\n}\n\nfunc (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) {\n\treturn callWithResult(c, func(client daemon.StartedServiceClient) (DeprecatedNoteIterator, error) {\n\t\twarnings, err := client.GetDeprecatedWarnings(context.Background(), &emptypb.Empty{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar notes []*DeprecatedNote\n\t\tfor _, warning := range warnings.Warnings {\n\t\t\tnotes = append(notes, &DeprecatedNote{\n\t\t\t\tDescription:   warning.Message,\n\t\t\t\tMigrationLink: warning.MigrationLink,\n\t\t\t})\n\t\t}\n\t\treturn newIterator(notes), nil\n\t})\n}\n\nfunc (c *CommandClient) GetStartedAt() (int64, error) {\n\treturn callWithResult(c, func(client daemon.StartedServiceClient) (int64, error) {\n\t\tstartedAt, err := client.GetStartedAt(context.Background(), &emptypb.Empty{})\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\treturn startedAt.StartedAt, nil\n\t})\n}\n\nfunc (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {\n\t_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {\n\t\treturn client.SetGroupExpand(context.Background(), &daemon.SetGroupExpandRequest{\n\t\t\tGroupTag: groupTag,\n\t\t\tIsExpand: isExpand,\n\t\t})\n\t})\n\treturn err\n}\n"
  },
  {
    "path": "experimental/libbox/command_server.go",
    "content": "package libbox\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/daemon\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n)\n\ntype CommandServer struct {\n\t*daemon.StartedService\n\thandler           CommandServerHandler\n\tplatformInterface PlatformInterface\n\tplatformWrapper   *platformInterfaceWrapper\n\tgrpcServer        *grpc.Server\n\tlistener          net.Listener\n\tendPauseTimer     *time.Timer\n}\n\ntype CommandServerHandler interface {\n\tServiceStop() error\n\tServiceReload() error\n\tGetSystemProxyStatus() (*SystemProxyStatus, error)\n\tSetSystemProxyEnabled(enabled bool) error\n\tWriteDebugMessage(message string)\n}\n\nfunc NewCommandServer(handler CommandServerHandler, platformInterface PlatformInterface) (*CommandServer, error) {\n\tctx := baseContext(platformInterface)\n\tplatformWrapper := &platformInterfaceWrapper{\n\t\tiif:       platformInterface,\n\t\tuseProcFS: platformInterface.UseProcFS(),\n\t}\n\tservice.MustRegister[adapter.PlatformInterface](ctx, platformWrapper)\n\tserver := &CommandServer{\n\t\thandler:           handler,\n\t\tplatformInterface: platformInterface,\n\t\tplatformWrapper:   platformWrapper,\n\t}\n\tserver.StartedService = daemon.NewStartedService(daemon.ServiceOptions{\n\t\tContext: ctx,\n\t\t// Platform:         platformWrapper,\n\t\tHandler:     (*platformHandler)(server),\n\t\tDebug:       sDebug,\n\t\tLogMaxLines: sLogMaxLines,\n\t\tOOMKiller:   memoryLimitEnabled,\n\t\t// WorkingDirectory: sWorkingPath,\n\t\t// TempDirectory:    sTempPath,\n\t\t// UserID:           sUserID,\n\t\t// GroupID:          sGroupID,\n\t\t// SystemProxyEnabled: false,\n\t})\n\treturn server, nil\n}\n\nfunc unaryAuthInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {\n\tif sCommandServerSecret == \"\" {\n\t\treturn handler(ctx, req)\n\t}\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif !ok {\n\t\treturn nil, status.Error(codes.Unauthenticated, \"missing metadata\")\n\t}\n\tvalues := md.Get(\"x-command-secret\")\n\tif len(values) == 0 {\n\t\treturn nil, status.Error(codes.Unauthenticated, \"missing authentication secret\")\n\t}\n\tif values[0] != sCommandServerSecret {\n\t\treturn nil, status.Error(codes.Unauthenticated, \"invalid authentication secret\")\n\t}\n\treturn handler(ctx, req)\n}\n\nfunc streamAuthInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {\n\tif sCommandServerSecret == \"\" {\n\t\treturn handler(srv, ss)\n\t}\n\tmd, ok := metadata.FromIncomingContext(ss.Context())\n\tif !ok {\n\t\treturn status.Error(codes.Unauthenticated, \"missing metadata\")\n\t}\n\tvalues := md.Get(\"x-command-secret\")\n\tif len(values) == 0 {\n\t\treturn status.Error(codes.Unauthenticated, \"missing authentication secret\")\n\t}\n\tif values[0] != sCommandServerSecret {\n\t\treturn status.Error(codes.Unauthenticated, \"invalid authentication secret\")\n\t}\n\treturn handler(srv, ss)\n}\n\nfunc (s *CommandServer) Start() error {\n\tvar (\n\t\tlistener net.Listener\n\t\terr      error\n\t)\n\tif sCommandServerListenPort == 0 {\n\t\tsockPath := filepath.Join(sBasePath, \"command.sock\")\n\t\tos.Remove(sockPath)\n\t\tfor i := 0; i < 30; i++ {\n\t\t\tlistener, err = net.ListenUnix(\"unix\", &net.UnixAddr{\n\t\t\t\tName: sockPath,\n\t\t\t\tNet:  \"unix\",\n\t\t\t})\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif !errors.Is(err, syscall.EROFS) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ttime.Sleep(time.Second)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"listen command server\")\n\t\t}\n\t\tif sUserID != os.Getuid() {\n\t\t\terr = os.Chown(sockPath, sUserID, sGroupID)\n\t\t\tif err != nil {\n\t\t\t\tlistener.Close()\n\t\t\t\tos.Remove(sockPath)\n\t\t\t\treturn E.Cause(err, \"chown\")\n\t\t\t}\n\t\t}\n\t} else {\n\t\tlistener, err = net.Listen(\"tcp\", net.JoinHostPort(\"127.0.0.1\", strconv.Itoa(int(sCommandServerListenPort))))\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"listen command server\")\n\t\t}\n\t}\n\ts.listener = listener\n\tserverOptions := []grpc.ServerOption{\n\t\tgrpc.UnaryInterceptor(unaryAuthInterceptor),\n\t\tgrpc.StreamInterceptor(streamAuthInterceptor),\n\t}\n\ts.grpcServer = grpc.NewServer(serverOptions...)\n\tdaemon.RegisterStartedServiceServer(s.grpcServer, s.StartedService)\n\tgo s.grpcServer.Serve(listener)\n\treturn nil\n}\n\nfunc (s *CommandServer) Close() {\n\tif s.grpcServer != nil {\n\t\ts.grpcServer.Stop()\n\t}\n\tcommon.Close(s.listener)\n\ts.StartedService.Close()\n}\n\ntype OverrideOptions struct {\n\tAutoRedirect   bool\n\tIncludePackage StringIterator\n\tExcludePackage StringIterator\n}\n\nfunc (s *CommandServer) StartOrReloadService(configContent string, options *OverrideOptions) error {\n\treturn s.StartedService.StartOrReloadService(configContent, &daemon.OverrideOptions{\n\t\tAutoRedirect:   options.AutoRedirect,\n\t\tIncludePackage: iteratorToArray(options.IncludePackage),\n\t\tExcludePackage: iteratorToArray(options.ExcludePackage),\n\t})\n}\n\nfunc (s *CommandServer) CloseService() error {\n\treturn s.StartedService.CloseService()\n}\n\nfunc (s *CommandServer) WriteMessage(level int32, message string) {\n\ts.StartedService.WriteMessage(log.Level(level), message)\n}\n\nfunc (s *CommandServer) SetError(message string) {\n\ts.StartedService.SetError(E.New(message))\n}\n\nfunc (s *CommandServer) NeedWIFIState() bool {\n\tinstance := s.StartedService.Instance()\n\tif instance == nil || instance.Box() == nil {\n\t\treturn false\n\t}\n\treturn instance.Box().Network().NeedWIFIState()\n}\n\nfunc (s *CommandServer) NeedFindProcess() bool {\n\tinstance := s.StartedService.Instance()\n\tif instance == nil || instance.Box() == nil {\n\t\treturn false\n\t}\n\treturn instance.Box().Router().NeedFindProcess()\n}\n\nfunc (s *CommandServer) Pause() {\n\tinstance := s.StartedService.Instance()\n\tif instance == nil || instance.PauseManager() == nil {\n\t\treturn\n\t}\n\tinstance.PauseManager().DevicePause()\n\tif C.IsIos {\n\t\tif s.endPauseTimer == nil {\n\t\t\ts.endPauseTimer = time.AfterFunc(time.Minute, instance.PauseManager().DeviceWake)\n\t\t} else {\n\t\t\ts.endPauseTimer.Reset(time.Minute)\n\t\t}\n\t}\n}\n\nfunc (s *CommandServer) Wake() {\n\tinstance := s.StartedService.Instance()\n\tif instance == nil || instance.PauseManager() == nil {\n\t\treturn\n\t}\n\tif !C.IsIos {\n\t\tinstance.PauseManager().DeviceWake()\n\t}\n}\n\nfunc (s *CommandServer) ResetNetwork() {\n\tinstance := s.StartedService.Instance()\n\tif instance == nil || instance.Box() == nil {\n\t\treturn\n\t}\n\tinstance.Box().Router().ResetNetwork()\n}\n\nfunc (s *CommandServer) UpdateWIFIState() {\n\tinstance := s.StartedService.Instance()\n\tif instance == nil || instance.Box() == nil {\n\t\treturn\n\t}\n\tinstance.Box().Network().UpdateWIFIState()\n}\n\ntype platformHandler CommandServer\n\nfunc (h *platformHandler) ServiceStop() error {\n\treturn (*CommandServer)(h).handler.ServiceStop()\n}\n\nfunc (h *platformHandler) ServiceReload() error {\n\treturn (*CommandServer)(h).handler.ServiceReload()\n}\n\nfunc (h *platformHandler) SystemProxyStatus() (*daemon.SystemProxyStatus, error) {\n\tstatus, err := (*CommandServer)(h).handler.GetSystemProxyStatus()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &daemon.SystemProxyStatus{\n\t\tEnabled:   status.Enabled,\n\t\tAvailable: status.Available,\n\t}, nil\n}\n\nfunc (h *platformHandler) SetSystemProxyEnabled(enabled bool) error {\n\treturn (*CommandServer)(h).handler.SetSystemProxyEnabled(enabled)\n}\n\nfunc (h *platformHandler) WriteDebugMessage(message string) {\n\t(*CommandServer)(h).handler.WriteDebugMessage(message)\n}\n"
  },
  {
    "path": "experimental/libbox/command_types.go",
    "content": "package libbox\n\nimport (\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/daemon\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n)\n\ntype StatusMessage struct {\n\tMemory           int64\n\tGoroutines       int32\n\tConnectionsIn    int32\n\tConnectionsOut   int32\n\tTrafficAvailable bool\n\tUplink           int64\n\tDownlink         int64\n\tUplinkTotal      int64\n\tDownlinkTotal    int64\n}\n\ntype SystemProxyStatus struct {\n\tAvailable bool\n\tEnabled   bool\n}\n\ntype OutboundGroup struct {\n\tTag        string\n\tType       string\n\tSelectable bool\n\tSelected   string\n\tIsExpand   bool\n\titemList   []*OutboundGroupItem\n}\n\nfunc (g *OutboundGroup) GetItems() OutboundGroupItemIterator {\n\treturn newIterator(g.itemList)\n}\n\ntype OutboundGroupIterator interface {\n\tNext() *OutboundGroup\n\tHasNext() bool\n}\n\ntype OutboundGroupItem struct {\n\tTag          string\n\tType         string\n\tURLTestTime  int64\n\tURLTestDelay int32\n}\n\ntype OutboundGroupItemIterator interface {\n\tNext() *OutboundGroupItem\n\tHasNext() bool\n}\n\nconst (\n\tConnectionStateAll = iota\n\tConnectionStateActive\n\tConnectionStateClosed\n)\n\nconst (\n\tConnectionEventNew = iota\n\tConnectionEventUpdate\n\tConnectionEventClosed\n)\n\nconst (\n\tclosedConnectionMaxAge = int64((5 * time.Minute) / time.Millisecond)\n)\n\ntype ConnectionEvent struct {\n\tType          int32\n\tID            string\n\tConnection    *Connection\n\tUplinkDelta   int64\n\tDownlinkDelta int64\n\tClosedAt      int64\n}\n\ntype ConnectionEvents struct {\n\tReset  bool\n\tevents []*ConnectionEvent\n}\n\nfunc (c *ConnectionEvents) Iterator() ConnectionEventIterator {\n\treturn newIterator(c.events)\n}\n\ntype ConnectionEventIterator interface {\n\tNext() *ConnectionEvent\n\tHasNext() bool\n}\n\ntype Connections struct {\n\tconnectionMap map[string]*Connection\n\tinput         []Connection\n\tfiltered      []Connection\n\tfilterState   int32\n\tfilterApplied bool\n}\n\nfunc NewConnections() *Connections {\n\treturn &Connections{\n\t\tconnectionMap: make(map[string]*Connection),\n\t}\n}\n\nfunc (c *Connections) ApplyEvents(events *ConnectionEvents) {\n\tif events == nil {\n\t\treturn\n\t}\n\tif events.Reset {\n\t\tc.connectionMap = make(map[string]*Connection)\n\t}\n\n\tfor _, event := range events.events {\n\t\tswitch event.Type {\n\t\tcase ConnectionEventNew:\n\t\t\tif event.Connection != nil {\n\t\t\t\tconn := *event.Connection\n\t\t\t\tc.connectionMap[event.ID] = &conn\n\t\t\t}\n\t\tcase ConnectionEventUpdate:\n\t\t\tif conn, ok := c.connectionMap[event.ID]; ok {\n\t\t\t\tconn.Uplink = event.UplinkDelta\n\t\t\t\tconn.Downlink = event.DownlinkDelta\n\t\t\t\tconn.UplinkTotal += event.UplinkDelta\n\t\t\t\tconn.DownlinkTotal += event.DownlinkDelta\n\t\t\t}\n\t\tcase ConnectionEventClosed:\n\t\t\tif event.Connection != nil {\n\t\t\t\tconn := *event.Connection\n\t\t\t\tconn.ClosedAt = event.ClosedAt\n\t\t\t\tconn.Uplink = 0\n\t\t\t\tconn.Downlink = 0\n\t\t\t\tc.connectionMap[event.ID] = &conn\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif conn, ok := c.connectionMap[event.ID]; ok {\n\t\t\t\tconn.ClosedAt = event.ClosedAt\n\t\t\t\tconn.Uplink = 0\n\t\t\t\tconn.Downlink = 0\n\t\t\t}\n\t\t}\n\t}\n\n\tc.evictClosedConnections(time.Now().UnixMilli())\n\tc.input = c.input[:0]\n\tfor _, conn := range c.connectionMap {\n\t\tc.input = append(c.input, *conn)\n\t}\n\tif c.filterApplied {\n\t\tc.FilterState(c.filterState)\n\t} else {\n\t\tc.filtered = c.filtered[:0]\n\t\tc.filtered = append(c.filtered, c.input...)\n\t}\n}\n\nfunc (c *Connections) evictClosedConnections(nowMilliseconds int64) {\n\tfor id, conn := range c.connectionMap {\n\t\tif conn.ClosedAt == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif nowMilliseconds-conn.ClosedAt > closedConnectionMaxAge {\n\t\t\tdelete(c.connectionMap, id)\n\t\t}\n\t}\n}\n\nfunc (c *Connections) FilterState(state int32) {\n\tc.filterApplied = true\n\tc.filterState = state\n\tc.filtered = c.filtered[:0]\n\tswitch state {\n\tcase ConnectionStateAll:\n\t\tc.filtered = append(c.filtered, c.input...)\n\tcase ConnectionStateActive:\n\t\tfor _, connection := range c.input {\n\t\t\tif connection.ClosedAt == 0 {\n\t\t\t\tc.filtered = append(c.filtered, connection)\n\t\t\t}\n\t\t}\n\tcase ConnectionStateClosed:\n\t\tfor _, connection := range c.input {\n\t\t\tif connection.ClosedAt != 0 {\n\t\t\t\tc.filtered = append(c.filtered, connection)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (c *Connections) SortByDate() {\n\tslices.SortStableFunc(c.filtered, func(x, y Connection) int {\n\t\tif x.CreatedAt < y.CreatedAt {\n\t\t\treturn 1\n\t\t} else if x.CreatedAt > y.CreatedAt {\n\t\t\treturn -1\n\t\t} else {\n\t\t\treturn strings.Compare(y.ID, x.ID)\n\t\t}\n\t})\n}\n\nfunc (c *Connections) SortByTraffic() {\n\tslices.SortStableFunc(c.filtered, func(x, y Connection) int {\n\t\txTraffic := x.Uplink + x.Downlink\n\t\tyTraffic := y.Uplink + y.Downlink\n\t\tif xTraffic < yTraffic {\n\t\t\treturn 1\n\t\t} else if xTraffic > yTraffic {\n\t\t\treturn -1\n\t\t} else {\n\t\t\treturn strings.Compare(y.ID, x.ID)\n\t\t}\n\t})\n}\n\nfunc (c *Connections) SortByTrafficTotal() {\n\tslices.SortStableFunc(c.filtered, func(x, y Connection) int {\n\t\txTraffic := x.UplinkTotal + x.DownlinkTotal\n\t\tyTraffic := y.UplinkTotal + y.DownlinkTotal\n\t\tif xTraffic < yTraffic {\n\t\t\treturn 1\n\t\t} else if xTraffic > yTraffic {\n\t\t\treturn -1\n\t\t} else {\n\t\t\treturn strings.Compare(y.ID, x.ID)\n\t\t}\n\t})\n}\n\nfunc (c *Connections) Iterator() ConnectionIterator {\n\treturn newPtrIterator(c.filtered)\n}\n\ntype ProcessInfo struct {\n\tProcessID   int64\n\tUserID      int32\n\tUserName    string\n\tProcessPath string\n\tPackageName string\n}\n\ntype Connection struct {\n\tID            string\n\tInbound       string\n\tInboundType   string\n\tIPVersion     int32\n\tNetwork       string\n\tSource        string\n\tDestination   string\n\tDomain        string\n\tProtocol      string\n\tUser          string\n\tFromOutbound  string\n\tCreatedAt     int64\n\tClosedAt      int64\n\tUplink        int64\n\tDownlink      int64\n\tUplinkTotal   int64\n\tDownlinkTotal int64\n\tRule          string\n\tOutbound      string\n\tOutboundType  string\n\tchainList     []string\n\tProcessInfo   *ProcessInfo\n}\n\nfunc (c *Connection) Chain() StringIterator {\n\treturn newIterator(c.chainList)\n}\n\nfunc (c *Connection) DisplayDestination() string {\n\tdestination := M.ParseSocksaddr(c.Destination)\n\tif destination.IsIP() && c.Domain != \"\" {\n\t\tdestination = M.Socksaddr{\n\t\t\tFqdn: c.Domain,\n\t\t\tPort: destination.Port,\n\t\t}\n\t\treturn destination.String()\n\t}\n\treturn c.Destination\n}\n\ntype ConnectionIterator interface {\n\tNext() *Connection\n\tHasNext() bool\n}\n\nfunc statusMessageFromGRPC(status *daemon.Status) *StatusMessage {\n\tif status == nil {\n\t\treturn nil\n\t}\n\treturn &StatusMessage{\n\t\tMemory:           int64(status.Memory),\n\t\tGoroutines:       status.Goroutines,\n\t\tConnectionsIn:    status.ConnectionsIn,\n\t\tConnectionsOut:   status.ConnectionsOut,\n\t\tTrafficAvailable: status.TrafficAvailable,\n\t\tUplink:           status.Uplink,\n\t\tDownlink:         status.Downlink,\n\t\tUplinkTotal:      status.UplinkTotal,\n\t\tDownlinkTotal:    status.DownlinkTotal,\n\t}\n}\n\nfunc outboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator {\n\tif groups == nil || len(groups.Group) == 0 {\n\t\treturn newIterator([]*OutboundGroup{})\n\t}\n\tvar libboxGroups []*OutboundGroup\n\tfor _, g := range groups.Group {\n\t\tlibboxGroup := &OutboundGroup{\n\t\t\tTag:        g.Tag,\n\t\t\tType:       g.Type,\n\t\t\tSelectable: g.Selectable,\n\t\t\tSelected:   g.Selected,\n\t\t\tIsExpand:   g.IsExpand,\n\t\t}\n\t\tfor _, item := range g.Items {\n\t\t\tlibboxGroup.itemList = append(libboxGroup.itemList, &OutboundGroupItem{\n\t\t\t\tTag:          item.Tag,\n\t\t\t\tType:         item.Type,\n\t\t\t\tURLTestTime:  item.UrlTestTime,\n\t\t\t\tURLTestDelay: item.UrlTestDelay,\n\t\t\t})\n\t\t}\n\t\tlibboxGroups = append(libboxGroups, libboxGroup)\n\t}\n\treturn newIterator(libboxGroups)\n}\n\nfunc connectionFromGRPC(conn *daemon.Connection) Connection {\n\tvar processInfo *ProcessInfo\n\tif conn.ProcessInfo != nil {\n\t\tprocessInfo = &ProcessInfo{\n\t\t\tProcessID:   int64(conn.ProcessInfo.ProcessId),\n\t\t\tUserID:      conn.ProcessInfo.UserId,\n\t\t\tUserName:    conn.ProcessInfo.UserName,\n\t\t\tProcessPath: conn.ProcessInfo.ProcessPath,\n\t\t\tPackageName: conn.ProcessInfo.PackageName,\n\t\t}\n\t}\n\treturn Connection{\n\t\tID:            conn.Id,\n\t\tInbound:       conn.Inbound,\n\t\tInboundType:   conn.InboundType,\n\t\tIPVersion:     conn.IpVersion,\n\t\tNetwork:       conn.Network,\n\t\tSource:        conn.Source,\n\t\tDestination:   conn.Destination,\n\t\tDomain:        conn.Domain,\n\t\tProtocol:      conn.Protocol,\n\t\tUser:          conn.User,\n\t\tFromOutbound:  conn.FromOutbound,\n\t\tCreatedAt:     conn.CreatedAt,\n\t\tClosedAt:      conn.ClosedAt,\n\t\tUplink:        conn.Uplink,\n\t\tDownlink:      conn.Downlink,\n\t\tUplinkTotal:   conn.UplinkTotal,\n\t\tDownlinkTotal: conn.DownlinkTotal,\n\t\tRule:          conn.Rule,\n\t\tOutbound:      conn.Outbound,\n\t\tOutboundType:  conn.OutboundType,\n\t\tchainList:     conn.ChainList,\n\t\tProcessInfo:   processInfo,\n\t}\n}\n\nfunc connectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent {\n\tif event == nil {\n\t\treturn nil\n\t}\n\tlibboxEvent := &ConnectionEvent{\n\t\tType:          int32(event.Type),\n\t\tID:            event.Id,\n\t\tUplinkDelta:   event.UplinkDelta,\n\t\tDownlinkDelta: event.DownlinkDelta,\n\t\tClosedAt:      event.ClosedAt,\n\t}\n\tif event.Connection != nil {\n\t\tconn := connectionFromGRPC(event.Connection)\n\t\tlibboxEvent.Connection = &conn\n\t}\n\treturn libboxEvent\n}\n\nfunc connectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents {\n\tif events == nil {\n\t\treturn nil\n\t}\n\tlibboxEvents := &ConnectionEvents{\n\t\tReset: events.Reset_,\n\t}\n\tfor _, event := range events.Events {\n\t\tif libboxEvent := connectionEventFromGRPC(event); libboxEvent != nil {\n\t\t\tlibboxEvents.events = append(libboxEvents.events, libboxEvent)\n\t\t}\n\t}\n\treturn libboxEvents\n}\n\nfunc systemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus {\n\tif status == nil {\n\t\treturn nil\n\t}\n\treturn &SystemProxyStatus{\n\t\tAvailable: status.Available,\n\t\tEnabled:   status.Enabled,\n\t}\n}\n\nfunc systemProxyStatusToGRPC(status *SystemProxyStatus) *daemon.SystemProxyStatus {\n\tif status == nil {\n\t\treturn nil\n\t}\n\treturn &daemon.SystemProxyStatus{\n\t\tAvailable: status.Available,\n\t\tEnabled:   status.Enabled,\n\t}\n}\n"
  },
  {
    "path": "experimental/libbox/config.go",
    "content": "package libbox\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"os\"\n\n\tbox \"github.com/sagernet/sing-box\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/include\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\ttun \"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/filemanager\"\n)\n\nfunc baseContext(platformInterface PlatformInterface) context.Context {\n\tdnsRegistry := include.DNSTransportRegistry()\n\tif platformInterface != nil {\n\t\tif localTransport := platformInterface.LocalDNSTransport(); localTransport != nil {\n\t\t\tdns.RegisterTransport[option.LocalDNSServerOptions](dnsRegistry, C.DNSTypeLocal, func(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {\n\t\t\t\treturn newPlatformTransport(localTransport, tag, options), nil\n\t\t\t})\n\t\t}\n\t}\n\tctx := context.Background()\n\tctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)\n\treturn box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry())\n}\n\nfunc parseConfig(ctx context.Context, configContent string) (option.Options, error) {\n\toptions, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))\n\tif err != nil {\n\t\treturn option.Options{}, E.Cause(err, \"decode config\")\n\t}\n\treturn options, nil\n}\n\nfunc CheckConfig(configContent string) error {\n\tctx := baseContext(nil)\n\toptions, err := parseConfig(ctx, configContent)\n\tif err != nil {\n\t\treturn err\n\t}\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tctx = service.ContextWith[adapter.PlatformInterface](ctx, (*platformInterfaceStub)(nil))\n\tinstance, err := box.New(box.Options{\n\t\tContext: ctx,\n\t\tOptions: options,\n\t})\n\tif err == nil {\n\t\tinstance.Close()\n\t}\n\treturn err\n}\n\ntype platformInterfaceStub struct{}\n\nfunc (s *platformInterfaceStub) Initialize(networkManager adapter.NetworkManager) error {\n\treturn nil\n}\n\nfunc (s *platformInterfaceStub) UsePlatformAutoDetectInterfaceControl() bool {\n\treturn true\n}\n\nfunc (s *platformInterfaceStub) AutoDetectInterfaceControl(fd int) error {\n\treturn nil\n}\n\nfunc (s *platformInterfaceStub) UsePlatformInterface() bool {\n\treturn false\n}\n\nfunc (s *platformInterfaceStub) OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {\n\treturn nil, os.ErrInvalid\n}\n\nfunc (s *platformInterfaceStub) UsePlatformDefaultInterfaceMonitor() bool {\n\treturn true\n}\n\nfunc (s *platformInterfaceStub) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {\n\treturn (*interfaceMonitorStub)(nil)\n}\n\nfunc (s *platformInterfaceStub) UsePlatformNetworkInterfaces() bool {\n\treturn false\n}\n\nfunc (s *platformInterfaceStub) NetworkInterfaces() ([]adapter.NetworkInterface, error) {\n\treturn nil, os.ErrInvalid\n}\n\nfunc (s *platformInterfaceStub) UnderNetworkExtension() bool {\n\treturn false\n}\n\nfunc (s *platformInterfaceStub) NetworkExtensionIncludeAllNetworks() bool {\n\treturn false\n}\n\nfunc (s *platformInterfaceStub) ClearDNSCache() {\n}\n\nfunc (s *platformInterfaceStub) RequestPermissionForWIFIState() error {\n\treturn nil\n}\n\nfunc (s *platformInterfaceStub) UsePlatformWIFIMonitor() bool {\n\treturn false\n}\n\nfunc (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState {\n\treturn adapter.WIFIState{}\n}\n\nfunc (s *platformInterfaceStub) SystemCertificates() []string {\n\treturn nil\n}\n\nfunc (s *platformInterfaceStub) UsePlatformConnectionOwnerFinder() bool {\n\treturn false\n}\n\nfunc (s *platformInterfaceStub) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) {\n\treturn nil, os.ErrInvalid\n}\n\nfunc (s *platformInterfaceStub) UsePlatformNotification() bool {\n\treturn false\n}\n\nfunc (s *platformInterfaceStub) SendNotification(notification *adapter.Notification) error {\n\treturn nil\n}\n\nfunc (s *platformInterfaceStub) UsePlatformNeighborResolver() bool {\n\treturn false\n}\n\nfunc (s *platformInterfaceStub) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error {\n\treturn os.ErrInvalid\n}\n\nfunc (s *platformInterfaceStub) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error {\n\treturn nil\n}\n\nfunc (s *platformInterfaceStub) UsePlatformLocalDNSTransport() bool {\n\treturn false\n}\n\nfunc (s *platformInterfaceStub) LocalDNSTransport() dns.TransportConstructorFunc[option.LocalDNSServerOptions] {\n\treturn nil\n}\n\ntype interfaceMonitorStub struct{}\n\nfunc (s *interfaceMonitorStub) Start() error {\n\treturn os.ErrInvalid\n}\n\nfunc (s *interfaceMonitorStub) Close() error {\n\treturn os.ErrInvalid\n}\n\nfunc (s *interfaceMonitorStub) DefaultInterface() *control.Interface {\n\treturn nil\n}\n\nfunc (s *interfaceMonitorStub) OverrideAndroidVPN() bool {\n\treturn false\n}\n\nfunc (s *interfaceMonitorStub) AndroidVPNEnabled() bool {\n\treturn false\n}\n\nfunc (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpdateCallback) *list.Element[tun.DefaultInterfaceUpdateCallback] {\n\treturn nil\n}\n\nfunc (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {\n}\n\nfunc (s *interfaceMonitorStub) RegisterMyInterface(interfaceName string) {\n}\n\nfunc (s *interfaceMonitorStub) MyInterface() string {\n\treturn \"\"\n}\n\nfunc FormatConfig(configContent string) (*StringBox, error) {\n\toptions, err := parseConfig(baseContext(nil), configContent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar buffer bytes.Buffer\n\tencoder := json.NewEncoder(&buffer)\n\tencoder.SetIndent(\"\", \"  \")\n\terr = encoder.Encode(options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn wrapString(buffer.String()), nil\n}\n"
  },
  {
    "path": "experimental/libbox/deprecated.go",
    "content": "package libbox\n\nimport (\n\t\"github.com/sagernet/sing-box/experimental/deprecated\"\n)\n\nvar _ = deprecated.Note(DeprecatedNote{})\n\ntype DeprecatedNote struct {\n\tName              string\n\tDescription       string\n\tDeprecatedVersion string\n\tScheduledVersion  string\n\tEnvName           string\n\tMigrationLink     string\n}\n\nfunc (n DeprecatedNote) Impending() bool {\n\treturn deprecated.Note(n).Impending()\n}\n\nfunc (n DeprecatedNote) Message() string {\n\treturn deprecated.Note(n).Message()\n}\n\nfunc (n DeprecatedNote) MessageWithLink() string {\n\treturn deprecated.Note(n).MessageWithLink()\n}\n\ntype DeprecatedNoteIterator interface {\n\tHasNext() bool\n\tNext() *DeprecatedNote\n}\n"
  },
  {
    "path": "experimental/libbox/dns.go",
    "content": "package libbox\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/task\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\ntype LocalDNSTransport interface {\n\tRaw() bool\n\tLookup(ctx *ExchangeContext, network string, domain string) error\n\tExchange(ctx *ExchangeContext, message []byte) error\n}\n\nvar _ adapter.DNSTransport = (*platformTransport)(nil)\n\ntype platformTransport struct {\n\tdns.TransportAdapter\n\tiif LocalDNSTransport\n}\n\nfunc newPlatformTransport(iif LocalDNSTransport, tag string, options option.LocalDNSServerOptions) *platformTransport {\n\treturn &platformTransport{\n\t\tTransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),\n\t\tiif:              iif,\n\t}\n}\n\nfunc (p *platformTransport) Start(stage adapter.StartStage) error {\n\treturn nil\n}\n\nfunc (p *platformTransport) Close() error {\n\treturn nil\n}\n\nfunc (p *platformTransport) Reset() {\n}\n\nfunc (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tresponse := &ExchangeContext{\n\t\tcontext: ctx,\n\t}\n\tif p.iif.Raw() {\n\t\tmessageBytes, err := message.Pack()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar responseMessage *mDNS.Msg\n\t\tvar group task.Group\n\t\tgroup.Append0(func(ctx context.Context) error {\n\t\t\terr = p.iif.Exchange(response, messageBytes)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif response.error != nil {\n\t\t\t\treturn response.error\n\t\t\t}\n\t\t\tresponseMessage = &response.message\n\t\t\treturn nil\n\t\t})\n\t\terr = group.Run(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn responseMessage, nil\n\t} else {\n\t\tquestion := message.Question[0]\n\t\tvar network string\n\t\tswitch question.Qtype {\n\t\tcase mDNS.TypeA:\n\t\t\tnetwork = \"ip4\"\n\t\tcase mDNS.TypeAAAA:\n\t\t\tnetwork = \"ip6\"\n\t\tdefault:\n\t\t\treturn nil, E.New(\"only IP queries are supported by current version of Android\")\n\t\t}\n\t\tvar responseAddrs []netip.Addr\n\t\tvar group task.Group\n\t\tgroup.Append0(func(ctx context.Context) error {\n\t\t\terr := p.iif.Lookup(response, network, question.Name)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif response.error != nil {\n\t\t\t\treturn response.error\n\t\t\t}\n\t\t\tresponseAddrs = response.addresses\n\t\t\treturn nil\n\t\t})\n\t\terr := group.Run(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn dns.FixedResponse(message.Id, question, responseAddrs, C.DefaultDNSTTL), nil\n\t}\n}\n\ntype Func interface {\n\tInvoke() error\n}\n\ntype ExchangeContext struct {\n\tcontext   context.Context\n\tmessage   mDNS.Msg\n\taddresses []netip.Addr\n\terror     error\n}\n\nfunc (c *ExchangeContext) OnCancel(callback Func) {\n\tgo func() {\n\t\t<-c.context.Done()\n\t\tcallback.Invoke()\n\t}()\n}\n\nfunc (c *ExchangeContext) Success(result string) {\n\tc.addresses = common.Map(common.Filter(strings.Split(result, \"\\n\"), func(it string) bool {\n\t\treturn !common.IsEmpty(it)\n\t}), func(it string) netip.Addr {\n\t\treturn M.ParseSocksaddrHostPort(it, 0).Unwrap().Addr\n\t})\n}\n\nfunc (c *ExchangeContext) RawSuccess(result []byte) {\n\terr := c.message.Unpack(result)\n\tif err != nil {\n\t\tc.error = E.Cause(err, \"parse response\")\n\t}\n}\n\nfunc (c *ExchangeContext) ErrorCode(code int32) {\n\tc.error = dns.RcodeError(code)\n}\n\nfunc (c *ExchangeContext) ErrnoCode(code int32) {\n\tc.error = syscall.Errno(code)\n}\n"
  },
  {
    "path": "experimental/libbox/fdroid.go",
    "content": "package libbox\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nconst fdroidUserAgent = \"F-Droid 1.21.1\"\n\ntype FDroidUpdateInfo struct {\n\tVersionCode int32\n\tVersionName string\n\tDownloadURL string\n\tFileSize    int64\n\tFileSHA256  string\n}\n\ntype FDroidPingResult struct {\n\tURL       string\n\tLatencyMs int32\n\tError     string\n}\n\ntype FDroidPingResultIterator interface {\n\tLen() int32\n\tHasNext() bool\n\tNext() *FDroidPingResult\n}\n\ntype fdroidAPIResponse struct {\n\tPackageName          string             `json:\"packageName\"`\n\tSuggestedVersionCode int32              `json:\"suggestedVersionCode\"`\n\tPackages             []fdroidAPIPackage `json:\"packages\"`\n}\n\ntype fdroidAPIPackage struct {\n\tVersionName string `json:\"versionName\"`\n\tVersionCode int32  `json:\"versionCode\"`\n}\n\ntype fdroidEntry struct {\n\tTimestamp int64                      `json:\"timestamp\"`\n\tVersion   int                        `json:\"version\"`\n\tIndex     fdroidEntryFile            `json:\"index\"`\n\tDiffs     map[string]fdroidEntryFile `json:\"diffs\"`\n}\n\ntype fdroidEntryFile struct {\n\tName        string `json:\"name\"`\n\tSHA256      string `json:\"sha256\"`\n\tSize        int64  `json:\"size\"`\n\tNumPackages int    `json:\"numPackages\"`\n}\n\ntype fdroidIndexV2 struct {\n\tPackages map[string]fdroidV2Package `json:\"packages\"`\n}\n\ntype fdroidV2Package struct {\n\tVersions map[string]fdroidV2Version `json:\"versions\"`\n}\n\ntype fdroidV2Version struct {\n\tManifest fdroidV2Manifest `json:\"manifest\"`\n\tFile     fdroidV2File     `json:\"file\"`\n}\n\ntype fdroidV2Manifest struct {\n\tVersionCode int32  `json:\"versionCode\"`\n\tVersionName string `json:\"versionName\"`\n}\n\ntype fdroidV2File struct {\n\tName   string `json:\"name\"`\n\tSHA256 string `json:\"sha256\"`\n\tSize   int64  `json:\"size\"`\n}\n\ntype fdroidIndexV1 struct {\n\tPackages map[string][]fdroidV1Package `json:\"packages\"`\n}\n\ntype fdroidV1Package struct {\n\tVersionCode int32  `json:\"versionCode\"`\n\tVersionName string `json:\"versionName\"`\n\tApkName     string `json:\"apkName\"`\n\tSize        int64  `json:\"size\"`\n\tHash        string `json:\"hash\"`\n\tHashType    string `json:\"hashType\"`\n}\n\ntype fdroidCache struct {\n\tMirrorURL string `json:\"mirrorURL\"`\n\tTimestamp int64  `json:\"timestamp\"`\n\tETag      string `json:\"etag\"`\n\tIsV1      bool   `json:\"isV1,omitempty\"`\n}\n\nfunc CheckFDroidUpdate(mirrorURL, packageName string, currentVersionCode int32, cachePath string) (*FDroidUpdateInfo, error) {\n\tmirrorURL = strings.TrimRight(mirrorURL, \"/\")\n\tif strings.Contains(mirrorURL, \"f-droid.org\") {\n\t\treturn checkFDroidAPI(mirrorURL, packageName, currentVersionCode)\n\t}\n\tclient := newFDroidHTTPClient()\n\tdefer client.CloseIdleConnections()\n\tcache := loadFDroidCache(cachePath, mirrorURL)\n\tif cache != nil && cache.IsV1 {\n\t\treturn checkFDroidV1(client, mirrorURL, packageName, currentVersionCode, cachePath, cache)\n\t}\n\treturn checkFDroidV2(client, mirrorURL, packageName, currentVersionCode, cachePath, cache)\n}\n\nfunc PingFDroidMirrors(mirrorURLs string) (FDroidPingResultIterator, error) {\n\turls := strings.Split(mirrorURLs, \",\")\n\tresults := make([]*FDroidPingResult, len(urls))\n\tvar waitGroup sync.WaitGroup\n\tfor i, rawURL := range urls {\n\t\twaitGroup.Add(1)\n\t\tgo func(index int, target string) {\n\t\t\tdefer waitGroup.Done()\n\t\t\ttarget = strings.TrimSpace(target)\n\t\t\tresult := &FDroidPingResult{URL: target}\n\t\t\tlatency, err := pingTLS(target)\n\t\t\tif err != nil {\n\t\t\t\tresult.LatencyMs = -1\n\t\t\t\tresult.Error = err.Error()\n\t\t\t} else {\n\t\t\t\tresult.LatencyMs = int32(latency.Milliseconds())\n\t\t\t}\n\t\t\tresults[index] = result\n\t\t}(i, rawURL)\n\t}\n\twaitGroup.Wait()\n\tsort.Slice(results, func(i, j int) bool {\n\t\tif results[i].LatencyMs < 0 {\n\t\t\treturn false\n\t\t}\n\t\tif results[j].LatencyMs < 0 {\n\t\t\treturn true\n\t\t}\n\t\treturn results[i].LatencyMs < results[j].LatencyMs\n\t})\n\treturn newIterator(results), nil\n}\n\nfunc PingFDroidMirror(mirrorURL string) *FDroidPingResult {\n\tmirrorURL = strings.TrimSpace(mirrorURL)\n\tresult := &FDroidPingResult{URL: mirrorURL}\n\tlatency, err := pingTLS(mirrorURL)\n\tif err != nil {\n\t\tresult.LatencyMs = -1\n\t\tresult.Error = err.Error()\n\t} else {\n\t\tresult.LatencyMs = int32(latency.Milliseconds())\n\t}\n\treturn result\n}\n\nfunc newFDroidHTTPClient() *http.Client {\n\treturn &http.Client{\n\t\tTimeout: 30 * time.Second,\n\t}\n}\n\nfunc newFDroidRequest(requestURL string) (*http.Request, error) {\n\trequest, err := http.NewRequest(\"GET\", requestURL, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trequest.Header.Set(\"User-Agent\", fdroidUserAgent)\n\treturn request, nil\n}\n\nfunc checkFDroidAPI(mirrorURL, packageName string, currentVersionCode int32) (*FDroidUpdateInfo, error) {\n\tclient := newFDroidHTTPClient()\n\tdefer client.CloseIdleConnections()\n\n\tapiURL := \"https://f-droid.org/api/v1/packages/\" + packageName\n\trequest, err := newFDroidRequest(apiURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresponse, err := client.Do(request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer response.Body.Close()\n\n\tif response.StatusCode != http.StatusOK {\n\t\treturn nil, E.New(\"HTTP \", response.Status)\n\t}\n\n\tbody, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar apiResponse fdroidAPIResponse\n\terr = json.Unmarshal(body, &apiResponse)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar bestCode int32\n\tvar bestName string\n\tfor _, pkg := range apiResponse.Packages {\n\t\tif pkg.VersionCode > currentVersionCode && pkg.VersionCode > bestCode {\n\t\t\tbestCode = pkg.VersionCode\n\t\t\tbestName = pkg.VersionName\n\t\t}\n\t}\n\n\tif bestCode == 0 {\n\t\treturn nil, nil\n\t}\n\n\treturn &FDroidUpdateInfo{\n\t\tVersionCode: bestCode,\n\t\tVersionName: bestName,\n\t\tDownloadURL: \"https://f-droid.org/repo/\" + packageName + \"_\" + strconv.FormatInt(int64(bestCode), 10) + \".apk\",\n\t}, nil\n}\n\nfunc checkFDroidV2(client *http.Client, mirrorURL, packageName string, currentVersionCode int32, cachePath string, cache *fdroidCache) (*FDroidUpdateInfo, error) {\n\tentryURL := mirrorURL + \"/entry.jar\"\n\trequest, err := newFDroidRequest(entryURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif cache != nil && cache.ETag != \"\" {\n\t\trequest.Header.Set(\"If-None-Match\", cache.ETag)\n\t}\n\n\tresponse, err := client.Do(request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer response.Body.Close()\n\n\tif response.StatusCode == http.StatusNotModified {\n\t\treturn nil, nil\n\t}\n\tif response.StatusCode == http.StatusNotFound {\n\t\twriteFDroidCache(cachePath, mirrorURL, 0, \"\", true)\n\t\treturn checkFDroidV1(client, mirrorURL, packageName, currentVersionCode, cachePath, nil)\n\t}\n\tif response.StatusCode != http.StatusOK {\n\t\treturn nil, E.New(\"HTTP \", response.Status, \": \", entryURL)\n\t}\n\n\tjarData, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tetag := response.Header.Get(\"ETag\")\n\n\tvar entry fdroidEntry\n\terr = readJSONFromJar(jarData, \"entry.json\", &entry)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"read entry.jar\")\n\t}\n\n\tif entry.Timestamp == 0 {\n\t\treturn nil, E.New(\"entry.json not found in entry.jar\")\n\t}\n\n\tif cache != nil && cache.Timestamp == entry.Timestamp {\n\t\twriteFDroidCache(cachePath, mirrorURL, entry.Timestamp, etag, false)\n\t\treturn nil, nil\n\t}\n\n\tvar indexURL string\n\tif cache != nil {\n\t\tcachedTimestamp := strconv.FormatInt(cache.Timestamp, 10)\n\t\tif diff, ok := entry.Diffs[cachedTimestamp]; ok {\n\t\t\tindexURL = mirrorURL + \"/\" + diff.Name\n\t\t}\n\t}\n\tif indexURL == \"\" {\n\t\tindexURL = mirrorURL + \"/\" + entry.Index.Name\n\t}\n\n\tindexRequest, err := newFDroidRequest(indexURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tindexResponse, err := client.Do(indexRequest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer indexResponse.Body.Close()\n\n\tif indexResponse.StatusCode != http.StatusOK {\n\t\treturn nil, E.New(\"HTTP \", indexResponse.Status, \": \", indexURL)\n\t}\n\n\tindexData, err := io.ReadAll(indexResponse.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar index fdroidIndexV2\n\terr = json.Unmarshal(indexData, &index)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twriteFDroidCache(cachePath, mirrorURL, entry.Timestamp, etag, false)\n\n\tpkg, ok := index.Packages[packageName]\n\tif !ok {\n\t\treturn nil, nil\n\t}\n\n\tvar bestCode int32\n\tvar bestVersion fdroidV2Version\n\tfor _, version := range pkg.Versions {\n\t\tif version.Manifest.VersionCode > currentVersionCode && version.Manifest.VersionCode > bestCode {\n\t\t\tbestCode = version.Manifest.VersionCode\n\t\t\tbestVersion = version\n\t\t}\n\t}\n\n\tif bestCode == 0 {\n\t\treturn nil, nil\n\t}\n\n\treturn &FDroidUpdateInfo{\n\t\tVersionCode: bestCode,\n\t\tVersionName: bestVersion.Manifest.VersionName,\n\t\tDownloadURL: mirrorURL + \"/\" + bestVersion.File.Name,\n\t\tFileSize:    bestVersion.File.Size,\n\t\tFileSHA256:  bestVersion.File.SHA256,\n\t}, nil\n}\n\nfunc checkFDroidV1(client *http.Client, mirrorURL, packageName string, currentVersionCode int32, cachePath string, cache *fdroidCache) (*FDroidUpdateInfo, error) {\n\tindexURL := mirrorURL + \"/index-v1.jar\"\n\n\trequest, err := newFDroidRequest(indexURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif cache != nil && cache.ETag != \"\" {\n\t\trequest.Header.Set(\"If-None-Match\", cache.ETag)\n\t}\n\n\tresponse, err := client.Do(request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer response.Body.Close()\n\n\tif response.StatusCode == http.StatusNotModified {\n\t\treturn nil, nil\n\t}\n\tif response.StatusCode != http.StatusOK {\n\t\treturn nil, E.New(\"HTTP \", response.Status, \": \", indexURL)\n\t}\n\n\tjarData, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tetag := response.Header.Get(\"ETag\")\n\n\tvar index fdroidIndexV1\n\terr = readJSONFromJar(jarData, \"index-v1.json\", &index)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"read index-v1.jar\")\n\t}\n\n\twriteFDroidCache(cachePath, mirrorURL, 0, etag, true)\n\n\tpackages, ok := index.Packages[packageName]\n\tif !ok {\n\t\treturn nil, nil\n\t}\n\n\tvar bestCode int32\n\tvar bestPackage fdroidV1Package\n\tfor _, pkg := range packages {\n\t\tif pkg.VersionCode > currentVersionCode && pkg.VersionCode > bestCode {\n\t\t\tbestCode = pkg.VersionCode\n\t\t\tbestPackage = pkg\n\t\t}\n\t}\n\n\tif bestCode == 0 {\n\t\treturn nil, nil\n\t}\n\n\treturn &FDroidUpdateInfo{\n\t\tVersionCode: bestCode,\n\t\tVersionName: bestPackage.VersionName,\n\t\tDownloadURL: mirrorURL + \"/\" + bestPackage.ApkName,\n\t\tFileSize:    bestPackage.Size,\n\t\tFileSHA256:  bestPackage.Hash,\n\t}, nil\n}\n\nfunc readJSONFromJar(jarData []byte, fileName string, destination any) error {\n\tzipReader, err := zip.NewReader(bytes.NewReader(jarData), int64(len(jarData)))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, file := range zipReader.File {\n\t\tif file.Name != fileName {\n\t\t\tcontinue\n\t\t}\n\t\treader, err := file.Open()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdata, err := io.ReadAll(reader)\n\t\treader.Close()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn json.Unmarshal(data, destination)\n\t}\n\treturn nil\n}\n\nfunc pingTLS(mirrorURL string) (time.Duration, error) {\n\tparsed, err := url.Parse(mirrorURL)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\thost := parsed.Host\n\tif !strings.Contains(host, \":\") {\n\t\thost = host + \":443\"\n\t}\n\n\tdialer := &net.Dialer{Timeout: 5 * time.Second}\n\tstart := time.Now()\n\tconn, err := tls.DialWithDialer(dialer, \"tcp\", host, &tls.Config{})\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tlatency := time.Since(start)\n\tconn.Close()\n\treturn latency, nil\n}\n\nfunc loadFDroidCache(cachePath, mirrorURL string) *fdroidCache {\n\tcacheFile := filepath.Join(cachePath, \"fdroid_cache.json\")\n\tdata, err := os.ReadFile(cacheFile)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tvar cache fdroidCache\n\terr = json.Unmarshal(data, &cache)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tif cache.MirrorURL != mirrorURL {\n\t\treturn nil\n\t}\n\treturn &cache\n}\n\nfunc writeFDroidCache(cachePath, mirrorURL string, timestamp int64, etag string, isV1 bool) {\n\tcache := fdroidCache{\n\t\tMirrorURL: mirrorURL,\n\t\tTimestamp: timestamp,\n\t\tETag:      etag,\n\t\tIsV1:      isV1,\n\t}\n\tdata, err := json.Marshal(cache)\n\tif err != nil {\n\t\treturn\n\t}\n\tos.MkdirAll(cachePath, 0o755)\n\tos.WriteFile(filepath.Join(cachePath, \"fdroid_cache.json\"), data, 0o644)\n}\n"
  },
  {
    "path": "experimental/libbox/fdroid_mirrors.go",
    "content": "package libbox\n\ntype FDroidMirror struct {\n\tURL     string\n\tCountry string\n\tName    string\n}\n\ntype FDroidMirrorIterator interface {\n\tLen() int32\n\tHasNext() bool\n\tNext() *FDroidMirror\n}\n\nvar builtinFDroidMirrors = []FDroidMirror{\n\t// Official\n\t{URL: \"https://f-droid.org/repo\", Country: \"Official\", Name: \"f-droid.org\"},\n\t{URL: \"https://cloudflare.f-droid.org/repo\", Country: \"Official\", Name: \"Cloudflare CDN\"},\n\n\t// China\n\t{URL: \"https://mirrors.tuna.tsinghua.edu.cn/fdroid/repo\", Country: \"China\", Name: \"Tsinghua TUNA\"},\n\t{URL: \"https://mirrors.nju.edu.cn/fdroid/repo\", Country: \"China\", Name: \"Nanjing University\"},\n\t{URL: \"https://mirror.iscas.ac.cn/fdroid/repo\", Country: \"China\", Name: \"ISCAS\"},\n\t{URL: \"https://mirror.nyist.edu.cn/fdroid/repo\", Country: \"China\", Name: \"NYIST\"},\n\t{URL: \"https://mirrors.cqupt.edu.cn/fdroid/repo\", Country: \"China\", Name: \"CQUPT\"},\n\t{URL: \"https://mirrors.shanghaitech.edu.cn/fdroid/repo\", Country: \"China\", Name: \"ShanghaiTech\"},\n\n\t// India\n\t{URL: \"https://mirror.hyd.albony.in/fdroid/repo\", Country: \"India\", Name: \"Albony Hyderabad\"},\n\t{URL: \"https://mirror.del2.albony.in/fdroid/repo\", Country: \"India\", Name: \"Albony Delhi\"},\n\n\t// Taiwan\n\t{URL: \"https://mirror.ossplanet.net/fdroid/repo\", Country: \"Taiwan\", Name: \"OSSPlanet\"},\n\n\t// France\n\t{URL: \"https://fdroid.tetaneutral.net/fdroid/repo\", Country: \"France\", Name: \"tetaneutral.net\"},\n\t{URL: \"https://mirror.freedif.org/fdroid/repo\", Country: \"France\", Name: \"FreeDif\"},\n\n\t// Germany\n\t{URL: \"https://ftp.fau.de/fdroid/repo\", Country: \"Germany\", Name: \"FAU Erlangen\"},\n\t{URL: \"https://ftp.agdsn.de/fdroid/repo\", Country: \"Germany\", Name: \"AGDSN Dresden\"},\n\t{URL: \"https://ftp.gwdg.de/pub/android/fdroid/repo\", Country: \"Germany\", Name: \"GWDG\"},\n\t{URL: \"https://mirror.level66.network/fdroid/repo\", Country: \"Germany\", Name: \"Level66\"},\n\t{URL: \"https://mirror.mci-1.serverforge.org/fdroid/repo\", Country: \"Germany\", Name: \"ServerForge\"},\n\n\t// Netherlands\n\t{URL: \"https://ftp.snt.utwente.nl/pub/software/fdroid/repo\", Country: \"Netherlands\", Name: \"University of Twente\"},\n\n\t// Sweden\n\t{URL: \"https://ftp.lysator.liu.se/pub/fdroid/repo\", Country: \"Sweden\", Name: \"Lysator\"},\n\n\t// Denmark\n\t{URL: \"https://mirrors.dotsrc.org/fdroid/repo\", Country: \"Denmark\", Name: \"dotsrc.org\"},\n\n\t// Austria\n\t{URL: \"https://mirror.kumi.systems/fdroid/repo\", Country: \"Austria\", Name: \"Kumi Systems\"},\n\n\t// Switzerland\n\t{URL: \"https://mirror.init7.net/fdroid/repo\", Country: \"Switzerland\", Name: \"Init7\"},\n\n\t// Romania\n\t{URL: \"https://mirrors.hostico.ro/fdroid/repo\", Country: \"Romania\", Name: \"Hostico\"},\n\t{URL: \"https://mirrors.chroot.ro/fdroid/repo\", Country: \"Romania\", Name: \"Chroot\"},\n\t{URL: \"https://ftp.lug.ro/fdroid/repo\", Country: \"Romania\", Name: \"LUG Romania\"},\n\n\t// US\n\t{URL: \"https://plug-mirror.rcac.purdue.edu/fdroid/repo\", Country: \"US\", Name: \"Purdue\"},\n\t{URL: \"https://mirror.fcix.net/fdroid/repo\", Country: \"US\", Name: \"FCIX\"},\n\t{URL: \"https://opencolo.mm.fcix.net/fdroid/repo\", Country: \"US\", Name: \"OpenColo\"},\n\t{URL: \"https://forksystems.mm.fcix.net/fdroid/repo\", Country: \"US\", Name: \"Fork Systems\"},\n\t{URL: \"https://southfront.mm.fcix.net/fdroid/repo\", Country: \"US\", Name: \"South Front\"},\n\t{URL: \"https://ziply.mm.fcix.net/fdroid/repo\", Country: \"US\", Name: \"Ziply\"},\n\n\t// Canada\n\t{URL: \"https://mirror.quantum5.ca/fdroid/repo\", Country: \"Canada\", Name: \"Quantum5\"},\n\n\t// Australia\n\t{URL: \"https://mirror.aarnet.edu.au/fdroid/repo\", Country: \"Australia\", Name: \"AARNet\"},\n\n\t// Other\n\t{URL: \"https://mirror.cyberbits.eu/fdroid/repo\", Country: \"Europe\", Name: \"Cyberbits EU\"},\n\t{URL: \"https://mirror.eu.ossplanet.net/fdroid/repo\", Country: \"Europe\", Name: \"OSSPlanet EU\"},\n\t{URL: \"https://mirror.cyberbits.asia/fdroid/repo\", Country: \"Asia\", Name: \"Cyberbits Asia\"},\n\t{URL: \"https://mirrors.jevincanders.net/fdroid/repo\", Country: \"US\", Name: \"Jevincanders\"},\n\t{URL: \"https://mirrors.komogoto.com/fdroid/repo\", Country: \"US\", Name: \"Komogoto\"},\n\t{URL: \"https://fdroid.rasp.sh/fdroid/repo\", Country: \"Europe\", Name: \"rasp.sh\"},\n\t{URL: \"https://mirror.gofoss.xyz/fdroid/repo\", Country: \"Europe\", Name: \"GoFOSS\"},\n}\n\nfunc GetFDroidMirrors() FDroidMirrorIterator {\n\treturn newPtrIterator(builtinFDroidMirrors)\n}\n"
  },
  {
    "path": "experimental/libbox/ffi.json",
    "content": "{\n  \"version\": 1,\n  \"variables\": {\n    \"VERSION\": \"$(go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)\",\n    \"WORKSPACE_ROOT\": \"../../..\",\n    \"DEPLOY_ANDROID\": \"${WORKSPACE_ROOT}/sing-box-for-android/app/libs\",\n    \"DEPLOY_APPLE\": \"${WORKSPACE_ROOT}/sing-box-for-apple\",\n    \"DEPLOY_WINDOWS\": \"${WORKSPACE_ROOT}/sing-box-for-windows/local-packages\"\n  },\n  \"packages\": [\n    {\n      \"id\": \"libbox\",\n      \"path\": \".\",\n      \"java_package\": \"io.nekohasekai.libbox\",\n      \"csharp_namespace\": \"SagerNet\",\n      \"csharp_entrypoint\": \"Libbox\",\n      \"apple_prefix\": \"Libbox\"\n    }\n  ],\n  \"builds\": [\n    {\n      \"id\": \"android-main\",\n      \"packages\": [\"libbox\"],\n      \"default\": {\n        \"tags\": [\n          \"with_gvisor\",\n          \"with_quic\",\n          \"with_wireguard\",\n          \"with_utls\",\n          \"with_naive_outbound\",\n          \"with_clash_api\",\n          \"badlinkname\",\n          \"tfogo_checklinkname0\",\n          \"with_tailscale\",\n          \"ts_omit_logtail\",\n          \"ts_omit_ssh\",\n          \"ts_omit_drive\",\n          \"ts_omit_taildrop\",\n          \"ts_omit_webclient\",\n          \"ts_omit_doctor\",\n          \"ts_omit_capture\",\n          \"ts_omit_kube\",\n          \"ts_omit_aws\",\n          \"ts_omit_synology\",\n          \"ts_omit_bird\"\n        ],\n        \"ldflags\": \"-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0\",\n        \"trimpath\": true\n      }\n    },\n    {\n      \"id\": \"android-legacy\",\n      \"packages\": [\"libbox\"],\n      \"default\": {\n        \"tags\": [\n          \"with_gvisor\",\n          \"with_quic\",\n          \"with_wireguard\",\n          \"with_utls\",\n          \"with_clash_api\",\n          \"badlinkname\",\n          \"tfogo_checklinkname0\",\n          \"with_tailscale\",\n          \"ts_omit_logtail\",\n          \"ts_omit_ssh\",\n          \"ts_omit_drive\",\n          \"ts_omit_taildrop\",\n          \"ts_omit_webclient\",\n          \"ts_omit_doctor\",\n          \"ts_omit_capture\",\n          \"ts_omit_kube\",\n          \"ts_omit_aws\",\n          \"ts_omit_synology\",\n          \"ts_omit_bird\"\n        ],\n        \"ldflags\": \"-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0\",\n        \"trimpath\": true\n      }\n    },\n    {\n      \"id\": \"apple\",\n      \"packages\": [\"libbox\"],\n      \"default\": {\n        \"tags\": [\n          \"with_gvisor\",\n          \"with_quic\",\n          \"with_wireguard\",\n          \"with_utls\",\n          \"with_naive_outbound\",\n          \"with_clash_api\",\n          \"badlinkname\",\n          \"tfogo_checklinkname0\",\n          \"with_dhcp\",\n          \"grpcnotrace\",\n          \"with_tailscale\",\n          \"ts_omit_logtail\",\n          \"ts_omit_ssh\",\n          \"ts_omit_drive\",\n          \"ts_omit_taildrop\",\n          \"ts_omit_webclient\",\n          \"ts_omit_doctor\",\n          \"ts_omit_capture\",\n          \"ts_omit_kube\",\n          \"ts_omit_aws\",\n          \"ts_omit_synology\",\n          \"ts_omit_bird\"\n        ],\n        \"ldflags\": \"-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0\",\n        \"trimpath\": true\n      },\n      \"overrides\": [\n        {\n          \"match\": { \"os\": \"ios\" },\n          \"tags_append\": [\"with_low_memory\"]\n        },\n        {\n          \"match\": { \"os\": \"tvos\" },\n          \"tags_append\": [\"with_low_memory\"]\n        }\n      ]\n    },\n    {\n      \"id\": \"windows\",\n      \"packages\": [\"libbox\"],\n      \"default\": {\n        \"tags\": [\n          \"with_gvisor\",\n          \"with_quic\",\n          \"with_wireguard\",\n          \"with_utls\",\n          \"with_naive_outbound\",\n          \"with_purego\",\n          \"with_clash_api\",\n          \"badlinkname\",\n          \"tfogo_checklinkname0\",\n          \"with_tailscale\",\n          \"ts_omit_logtail\",\n          \"ts_omit_ssh\",\n          \"ts_omit_drive\",\n          \"ts_omit_taildrop\",\n          \"ts_omit_webclient\",\n          \"ts_omit_doctor\",\n          \"ts_omit_capture\",\n          \"ts_omit_kube\",\n          \"ts_omit_aws\",\n          \"ts_omit_synology\",\n          \"ts_omit_bird\"\n        ],\n        \"ldflags\": \"-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0\",\n        \"trimpath\": true\n      }\n    }\n  ],\n  \"platforms\": [\n    {\n      \"type\": \"android\",\n      \"build\": \"android-main\",\n      \"min_sdk\": 23,\n      \"ndk_version\": \"28.0.13004108\",\n      \"lib_name\": \"box\",\n      \"languages\": [{ \"type\": \"java\" }],\n      \"artifacts\": [\n        {\n          \"type\": \"aar\",\n          \"output_path\": \"libbox.aar\",\n          \"execute_after\": [\n            \"if [ -d \\\"${DEPLOY_ANDROID}\\\" ]; then\",\n            \"  rm -f \\\"${DEPLOY_ANDROID}/$$(basename \\\"${OUTPUT_PATH}\\\")\\\"\",\n            \"  mv \\\"${OUTPUT_PATH}\\\" \\\"${DEPLOY_ANDROID}/\\\"\",\n            \"fi\"\n          ]\n        }\n      ]\n    },\n    {\n      \"type\": \"android\",\n      \"build\": \"android-legacy\",\n      \"min_sdk\": 21,\n      \"ndk_version\": \"28.0.13004108\",\n      \"lib_name\": \"box\",\n      \"languages\": [{ \"type\": \"java\" }],\n      \"artifacts\": [\n        {\n          \"type\": \"aar\",\n          \"output_path\": \"libbox-legacy.aar\",\n          \"execute_after\": [\n            \"if [ -d \\\"${DEPLOY_ANDROID}\\\" ]; then\",\n            \"  rm -f \\\"${DEPLOY_ANDROID}/$$(basename \\\"${OUTPUT_PATH}\\\")\\\"\",\n            \"  mv \\\"${OUTPUT_PATH}\\\" \\\"${DEPLOY_ANDROID}/\\\"\",\n            \"fi\"\n          ]\n        }\n      ]\n    },\n    {\n      \"type\": \"apple\",\n      \"build\": \"apple\",\n      \"targets\": [\n        \"ios/arm64\",\n        \"ios/simulator/arm64\",\n        \"ios/simulator/amd64\",\n        \"tvos/arm64\",\n        \"tvos/simulator/arm64\",\n        \"tvos/simulator/amd64\",\n        \"macos/arm64\",\n        \"macos/amd64\"\n      ],\n      \"languages\": [{ \"type\": \"objc\" }],\n      \"artifacts\": [\n        {\n          \"type\": \"xcframework\",\n          \"module_name\": \"Libbox\",\n          \"execute_after\": [\n            \"if [ -d \\\"${DEPLOY_APPLE}\\\" ]; then\",\n            \"  rm -rf \\\"${DEPLOY_APPLE}/${MODULE_NAME}.xcframework\\\"\",\n            \"  mv \\\"${OUTPUT_PATH}\\\" \\\"${DEPLOY_APPLE}/\\\"\",\n            \"fi\"\n          ]\n        }\n      ]\n    },\n    {\n      \"type\": \"csharp\",\n      \"build\": \"windows\",\n      \"targets\": [\n        \"windows/amd64\"\n      ],\n      \"languages\": [{ \"type\": \"csharp\" }],\n      \"artifacts\": [\n        {\n          \"type\": \"nuget\",\n          \"package_id\": \"SagerNet.Libbox\",\n          \"package_version\": \"0.0.0-local\",\n          \"execute_after\": {\n            \"windows\": [\n              \"$$deployPath = '${DEPLOY_WINDOWS}'\",\n              \"if (Test-Path $$deployPath) {\",\n              \"  Remove-Item \\\"$$deployPath\\\\${PACKAGE_ID}.*.nupkg\\\" -ErrorAction SilentlyContinue\",\n              \"  Move-Item -Force '${OUTPUT_PATH}' \\\"$$deployPath\\\\\\\"\",\n              \"  $$cachePath = if ($$env:NUGET_PACKAGES) { $$env:NUGET_PACKAGES } else { \\\"$$env:USERPROFILE\\\\.nuget\\\\packages\\\" }\",\n              \"  Remove-Item -Recurse -Force \\\"$$cachePath\\\\sagernet.libbox\\\\${PACKAGE_VERSION}\\\" -ErrorAction SilentlyContinue\",\n              \"}\"\n            ],\n            \"default\": [\n              \"if [ -d \\\"${DEPLOY_WINDOWS}\\\" ]; then\",\n              \"  rm -f \\\"${DEPLOY_WINDOWS}/${PACKAGE_ID}.*.nupkg\\\"\",\n              \"  mv \\\"${OUTPUT_PATH}\\\" \\\"${DEPLOY_WINDOWS}/\\\"\",\n              \"  cache_path=\\\"$${NUGET_PACKAGES:-$${HOME}/.nuget/packages}\\\"\",\n              \"  rm -rf \\\"$${cache_path}/sagernet.libbox/${PACKAGE_VERSION}\\\"\",\n              \"fi\"\n            ]\n          }\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "experimental/libbox/http.go",
    "content": "package libbox\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"sync\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/protocol/socks\"\n\t\"github.com/sagernet/sing/protocol/socks/socks5\"\n)\n\ntype HTTPClient interface {\n\tRestrictedTLS()\n\tModernTLS()\n\tPinnedTLS12()\n\tPinnedSHA256(sumHex string)\n\tTrySocks5(port int32)\n\tKeepAlive()\n\tNewRequest() HTTPRequest\n\tClose()\n}\n\ntype HTTPRequest interface {\n\tSetURL(link string) error\n\tSetMethod(method string)\n\tSetHeader(key string, value string)\n\tSetContent(content []byte)\n\tSetContentString(content string)\n\tRandomUserAgent()\n\tSetUserAgent(userAgent string)\n\tExecute() (HTTPResponse, error)\n}\n\ntype HTTPResponse interface {\n\tGetContent() (*StringBox, error)\n\tWriteTo(path string) error\n}\n\nvar (\n\t_ HTTPClient   = (*httpClient)(nil)\n\t_ HTTPRequest  = (*httpRequest)(nil)\n\t_ HTTPResponse = (*httpResponse)(nil)\n)\n\ntype httpClient struct {\n\ttls       tls.Config\n\tclient    http.Client\n\ttransport http.Transport\n}\n\nfunc NewHTTPClient() HTTPClient {\n\tclient := new(httpClient)\n\tclient.client.Transport = &client.transport\n\tclient.transport.ForceAttemptHTTP2 = true\n\tclient.transport.TLSHandshakeTimeout = C.TCPTimeout\n\tclient.transport.TLSClientConfig = &client.tls\n\tclient.transport.DisableKeepAlives = true\n\treturn client\n}\n\nfunc (c *httpClient) ModernTLS() {\n\tc.setTLSVersion(tls.VersionTLS12, 0, func(suite *tls.CipherSuite) bool { return true })\n}\n\nfunc (c *httpClient) RestrictedTLS() {\n\tc.setTLSVersion(tls.VersionTLS13, 0, func(suite *tls.CipherSuite) bool {\n\t\treturn common.Contains(suite.SupportedVersions, uint16(tls.VersionTLS13))\n\t})\n}\n\nfunc (c *httpClient) setTLSVersion(minVersion, maxVersion uint16, filter func(*tls.CipherSuite) bool) {\n\tc.tls.MinVersion = minVersion\n\tif maxVersion != 0 {\n\t\tc.tls.MaxVersion = maxVersion\n\t}\n\tc.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), filter), func(it *tls.CipherSuite) uint16 {\n\t\treturn it.ID\n\t})\n}\n\nfunc (c *httpClient) PinnedTLS12() {\n\tc.setTLSVersion(tls.VersionTLS12, tls.VersionTLS12, func(suite *tls.CipherSuite) bool { return true })\n}\n\nfunc (c *httpClient) PinnedSHA256(sumHex string) {\n\tc.tls.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {\n\t\tfor _, rawCert := range rawCerts {\n\t\t\tcertSum := sha256.Sum256(rawCert)\n\t\t\tif sumHex == hex.EncodeToString(certSum[:]) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn E.New(\"pinned sha256 sum mismatch\")\n\t}\n}\n\nfunc (c *httpClient) TrySocks5(port int32) {\n\tdialer := new(net.Dialer)\n\tc.transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\tfor {\n\t\t\tsocksConn, err := dialer.DialContext(ctx, \"tcp\", \"127.0.0.1:\"+strconv.Itoa(int(port)))\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t_, err = socks.ClientHandshake5(socksConn, socks5.CommandConnect, M.ParseSocksaddr(addr), \"\", \"\")\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t//nolint:staticcheck\n\t\t\treturn socksConn, err\n\t\t}\n\t\treturn dialer.DialContext(ctx, network, addr)\n\t}\n}\n\nfunc (c *httpClient) KeepAlive() {\n\tc.transport.DisableKeepAlives = false\n}\n\nfunc (c *httpClient) NewRequest() HTTPRequest {\n\treq := &httpRequest{httpClient: c}\n\treq.request = http.Request{\n\t\tMethod: \"GET\",\n\t\tHeader: http.Header{},\n\t}\n\treturn req\n}\n\nfunc (c *httpClient) Close() {\n\tc.transport.CloseIdleConnections()\n}\n\ntype httpRequest struct {\n\t*httpClient\n\trequest http.Request\n}\n\nfunc (r *httpRequest) SetURL(link string) (err error) {\n\tr.request.URL, err = url.Parse(link)\n\tif err != nil {\n\t\treturn\n\t}\n\tif r.request.URL.User != nil {\n\t\tuser := r.request.URL.User.Username()\n\t\tpassword, _ := r.request.URL.User.Password()\n\t\tr.request.SetBasicAuth(user, password)\n\t}\n\treturn\n}\n\nfunc (r *httpRequest) SetMethod(method string) {\n\tr.request.Method = method\n}\n\nfunc (r *httpRequest) SetHeader(key string, value string) {\n\tr.request.Header.Set(key, value)\n}\n\nfunc (r *httpRequest) RandomUserAgent() {\n\tr.request.Header.Set(\"User-Agent\", fmt.Sprintf(\"curl/7.%d.%d\", rand.Int()%54, rand.Int()%2))\n}\n\nfunc (r *httpRequest) SetUserAgent(userAgent string) {\n\tr.request.Header.Set(\"User-Agent\", userAgent)\n}\n\nfunc (r *httpRequest) SetContent(content []byte) {\n\tr.request.Body = io.NopCloser(bytes.NewReader(content))\n\tr.request.ContentLength = int64(len(content))\n}\n\nfunc (r *httpRequest) SetContentString(content string) {\n\tr.SetContent([]byte(content))\n}\n\nfunc (r *httpRequest) Execute() (HTTPResponse, error) {\n\tresponse, err := r.client.Do(&r.request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thttpResp := &httpResponse{Response: response}\n\tif response.StatusCode != http.StatusOK {\n\t\treturn nil, errors.New(httpResp.errorString())\n\t}\n\treturn httpResp, nil\n}\n\ntype httpResponse struct {\n\t*http.Response\n\n\tgetContentOnce sync.Once\n\tcontent        []byte\n\tcontentError   error\n}\n\nfunc (h *httpResponse) errorString() string {\n\tcontent, err := h.GetContent()\n\tif err != nil {\n\t\treturn fmt.Sprint(\"HTTP \", h.Status)\n\t}\n\treturn fmt.Sprint(\"HTTP \", h.Status, \": \", content)\n}\n\nfunc (h *httpResponse) GetContent() (*StringBox, error) {\n\th.getContentOnce.Do(func() {\n\t\tdefer h.Body.Close()\n\t\th.content, h.contentError = io.ReadAll(h.Body)\n\t})\n\tif h.contentError != nil {\n\t\treturn nil, h.contentError\n\t}\n\treturn wrapString(string(h.content)), nil\n}\n\nfunc (h *httpResponse) WriteTo(path string) error {\n\tdefer h.Body.Close()\n\tfile, err := os.Create(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\treturn common.Error(bufio.Copy(file, h.Body))\n}\n"
  },
  {
    "path": "experimental/libbox/internal/procfs/procfs.go",
    "content": "package procfs\n\nimport (\n\t\"bufio\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unsafe\"\n\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nvar (\n\tnetIndexOfLocal = -1\n\tnetIndexOfUid   = -1\n\tnativeEndian    binary.ByteOrder\n)\n\nfunc init() {\n\tvar x uint32 = 0x01020304\n\tif *(*byte)(unsafe.Pointer(&x)) == 0x01 {\n\t\tnativeEndian = binary.BigEndian\n\t} else {\n\t\tnativeEndian = binary.LittleEndian\n\t}\n}\n\nfunc ResolveSocketByProcSearch(network string, source, _ netip.AddrPort) int32 {\n\tif netIndexOfLocal < 0 || netIndexOfUid < 0 {\n\t\treturn -1\n\t}\n\n\tpath := \"/proc/net/\"\n\n\tif network == N.NetworkTCP {\n\t\tpath += \"tcp\"\n\t} else {\n\t\tpath += \"udp\"\n\t}\n\n\tif source.Addr().Is6() {\n\t\tpath += \"6\"\n\t}\n\n\tsIP := source.Addr().AsSlice()\n\tif len(sIP) == 0 {\n\t\treturn -1\n\t}\n\n\tvar bytes [2]byte\n\tbinary.BigEndian.PutUint16(bytes[:], source.Port())\n\tlocal := fmt.Sprintf(\"%s:%s\", hex.EncodeToString(nativeEndianIP(sIP)), hex.EncodeToString(bytes[:]))\n\n\tfile, err := os.Open(path)\n\tif err != nil {\n\t\treturn -1\n\t}\n\n\tdefer file.Close()\n\n\treader := bufio.NewReader(file)\n\n\tfor {\n\t\trow, _, err := reader.ReadLine()\n\t\tif err != nil {\n\t\t\treturn -1\n\t\t}\n\n\t\tfields := strings.Fields(string(row))\n\n\t\tif len(fields) <= netIndexOfLocal || len(fields) <= netIndexOfUid {\n\t\t\tcontinue\n\t\t}\n\n\t\tif strings.EqualFold(local, fields[netIndexOfLocal]) {\n\t\t\tuid, err := strconv.Atoi(fields[netIndexOfUid])\n\t\t\tif err != nil {\n\t\t\t\treturn -1\n\t\t\t}\n\n\t\t\treturn int32(uid)\n\t\t}\n\t}\n}\n\nfunc nativeEndianIP(ip net.IP) []byte {\n\tresult := make([]byte, len(ip))\n\n\tfor i := 0; i < len(ip); i += 4 {\n\t\tvalue := binary.BigEndian.Uint32(ip[i:])\n\n\t\tnativeEndian.PutUint32(result[i:], value)\n\t}\n\n\treturn result\n}\n\nfunc init() {\n\tfile, err := os.Open(\"/proc/net/tcp\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefer file.Close()\n\n\treader := bufio.NewReader(file)\n\n\theader, _, err := reader.ReadLine()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcolumns := strings.Fields(string(header))\n\n\tvar txQueue, rxQueue, tr, tmWhen bool\n\n\tfor idx, col := range columns {\n\t\toffset := 0\n\n\t\tif txQueue && rxQueue {\n\t\t\toffset--\n\t\t}\n\n\t\tif tr && tmWhen {\n\t\t\toffset--\n\t\t}\n\n\t\tswitch col {\n\t\tcase \"tx_queue\":\n\t\t\ttxQueue = true\n\t\tcase \"rx_queue\":\n\t\t\trxQueue = true\n\t\tcase \"tr\":\n\t\t\ttr = true\n\t\tcase \"tm->when\":\n\t\t\ttmWhen = true\n\t\tcase \"local_address\":\n\t\t\tnetIndexOfLocal = idx + offset\n\t\tcase \"uid\":\n\t\t\tnetIndexOfUid = idx + offset\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "experimental/libbox/iterator.go",
    "content": "package libbox\n\nimport \"github.com/sagernet/sing/common\"\n\ntype StringIterator interface {\n\tLen() int32\n\tHasNext() bool\n\tNext() string\n}\n\ntype Int32Iterator interface {\n\tLen() int32\n\tHasNext() bool\n\tNext() int32\n}\n\nvar _ StringIterator = (*iterator[string])(nil)\n\ntype iterator[T any] struct {\n\tvalues []T\n}\n\nfunc newIterator[T any](values []T) *iterator[T] {\n\treturn &iterator[T]{values}\n}\n\n//go:noinline\nfunc newPtrIterator[T any](values []T) *iterator[*T] {\n\treturn &iterator[*T]{common.Map(values, func(value T) *T { return &value })}\n}\n\nfunc (i *iterator[T]) Len() int32 {\n\treturn int32(len(i.values))\n}\n\nfunc (i *iterator[T]) HasNext() bool {\n\treturn len(i.values) > 0\n}\n\nfunc (i *iterator[T]) Next() T {\n\tif len(i.values) == 0 {\n\t\treturn common.DefaultValue[T]()\n\t}\n\tnextValue := i.values[0]\n\ti.values = i.values[1:]\n\treturn nextValue\n}\n\ntype abstractIterator[T any] interface {\n\tNext() T\n\tHasNext() bool\n}\n\nfunc iteratorToArray[T any](iterator abstractIterator[T]) []T {\n\tif iterator == nil {\n\t\treturn nil\n\t}\n\tvar values []T\n\tfor iterator.HasNext() {\n\t\tvalues = append(values, iterator.Next())\n\t}\n\treturn values\n}\n"
  },
  {
    "path": "experimental/libbox/link_flags_stub.go",
    "content": "//go:build !unix\n\npackage libbox\n\nimport (\n\t\"net\"\n)\n\nfunc linkFlags(rawFlags uint32) net.Flags {\n\tpanic(\"stub!\")\n}\n"
  },
  {
    "path": "experimental/libbox/link_flags_unix.go",
    "content": "//go:build unix\n\npackage libbox\n\nimport (\n\t\"net\"\n\t\"syscall\"\n)\n\n// copied from net.linkFlags\nfunc linkFlags(rawFlags uint32) net.Flags {\n\tvar f net.Flags\n\tif rawFlags&syscall.IFF_UP != 0 {\n\t\tf |= net.FlagUp\n\t}\n\tif rawFlags&syscall.IFF_RUNNING != 0 {\n\t\tf |= net.FlagRunning\n\t}\n\tif rawFlags&syscall.IFF_BROADCAST != 0 {\n\t\tf |= net.FlagBroadcast\n\t}\n\tif rawFlags&syscall.IFF_LOOPBACK != 0 {\n\t\tf |= net.FlagLoopback\n\t}\n\tif rawFlags&syscall.IFF_POINTOPOINT != 0 {\n\t\tf |= net.FlagPointToPoint\n\t}\n\tif rawFlags&syscall.IFF_MULTICAST != 0 {\n\t\tf |= net.FlagMulticast\n\t}\n\treturn f\n}\n"
  },
  {
    "path": "experimental/libbox/log.go",
    "content": "//go:build darwin || linux\n\npackage libbox\n\nimport (\n\t\"os\"\n\t\"runtime\"\n\t\"runtime/debug\"\n)\n\nvar crashOutputFile *os.File\n\nfunc RedirectStderr(path string) error {\n\tif stats, err := os.Stat(path); err == nil && stats.Size() > 0 {\n\t\t_ = os.Rename(path, path+\".old\")\n\t}\n\toutputFile, err := os.Create(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif runtime.GOOS != \"android\" {\n\t\terr = outputFile.Chown(sUserID, sGroupID)\n\t\tif err != nil {\n\t\t\toutputFile.Close()\n\t\t\tos.Remove(outputFile.Name())\n\t\t\treturn err\n\t\t}\n\t}\n\terr = debug.SetCrashOutput(outputFile, debug.CrashOptions{})\n\tif err != nil {\n\t\toutputFile.Close()\n\t\tos.Remove(outputFile.Name())\n\t\treturn err\n\t}\n\tcrashOutputFile = outputFile\n\treturn nil\n}\n"
  },
  {
    "path": "experimental/libbox/memory.go",
    "content": "package libbox\n\nimport (\n\t\"math\"\n\truntimeDebug \"runtime/debug\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n)\n\nvar memoryLimitEnabled bool\n\nfunc SetMemoryLimit(enabled bool) {\n\tmemoryLimitEnabled = enabled\n\tconst memoryLimitGo = 45 * 1024 * 1024\n\tif enabled {\n\t\truntimeDebug.SetGCPercent(10)\n\t\tif C.IsIos {\n\t\t\truntimeDebug.SetMemoryLimit(memoryLimitGo)\n\t\t}\n\t} else {\n\t\truntimeDebug.SetGCPercent(100)\n\t\tif C.IsIos {\n\t\t\truntimeDebug.SetMemoryLimit(math.MaxInt64)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "experimental/libbox/monitor.go",
    "content": "package libbox\n\nimport (\n\ttun \"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/sing/common/x/list\"\n)\n\nvar (\n\t_ tun.DefaultInterfaceMonitor = (*platformDefaultInterfaceMonitor)(nil)\n\t_ InterfaceUpdateListener     = (*platformDefaultInterfaceMonitor)(nil)\n)\n\ntype platformDefaultInterfaceMonitor struct {\n\t*platformInterfaceWrapper\n\tlogger      logger.Logger\n\telement     *list.Element[tun.NetworkUpdateCallback]\n\tcallbacks   list.List[tun.DefaultInterfaceUpdateCallback]\n\tmyInterface string\n}\n\nfunc (m *platformDefaultInterfaceMonitor) Start() error {\n\treturn m.iif.StartDefaultInterfaceMonitor(m)\n}\n\nfunc (m *platformDefaultInterfaceMonitor) Close() error {\n\treturn m.iif.CloseDefaultInterfaceMonitor(m)\n}\n\nfunc (m *platformDefaultInterfaceMonitor) DefaultInterface() *control.Interface {\n\tm.defaultInterfaceAccess.Lock()\n\tdefer m.defaultInterfaceAccess.Unlock()\n\treturn m.defaultInterface\n}\n\nfunc (m *platformDefaultInterfaceMonitor) OverrideAndroidVPN() bool {\n\treturn false\n}\n\nfunc (m *platformDefaultInterfaceMonitor) AndroidVPNEnabled() bool {\n\treturn false\n}\n\nfunc (m *platformDefaultInterfaceMonitor) RegisterCallback(callback tun.DefaultInterfaceUpdateCallback) *list.Element[tun.DefaultInterfaceUpdateCallback] {\n\tm.defaultInterfaceAccess.Lock()\n\tdefer m.defaultInterfaceAccess.Unlock()\n\treturn m.callbacks.PushBack(callback)\n}\n\nfunc (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {\n\tm.defaultInterfaceAccess.Lock()\n\tdefer m.defaultInterfaceAccess.Unlock()\n\tm.callbacks.Remove(element)\n}\n\nfunc (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {\n\tif sFixAndroidStack {\n\t\tdone := make(chan struct{})\n\t\tgo func() {\n\t\t\tm.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)\n\t\t\tclose(done)\n\t\t}()\n\t\t<-done\n\t} else {\n\t\tm.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)\n\t}\n}\n\nfunc (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {\n\tm.isExpensive = isExpensive\n\tm.isConstrained = isConstrained\n\terr := m.networkManager.UpdateInterfaces()\n\tif err != nil {\n\t\tm.logger.Error(E.Cause(err, \"update interfaces\"))\n\t}\n\tm.defaultInterfaceAccess.Lock()\n\tif interfaceIndex32 == -1 {\n\t\tm.defaultInterface = nil\n\t\tcallbacks := m.callbacks.Array()\n\t\tm.defaultInterfaceAccess.Unlock()\n\t\tfor _, callback := range callbacks {\n\t\t\tcallback(nil, 0)\n\t\t}\n\t\treturn\n\t}\n\toldInterface := m.defaultInterface\n\tnewInterface, err := m.networkManager.InterfaceFinder().ByIndex(int(interfaceIndex32))\n\tif err != nil {\n\t\tm.defaultInterfaceAccess.Unlock()\n\t\tm.logger.Error(E.Cause(err, \"find updated interface: \", interfaceName))\n\t\treturn\n\t}\n\tm.defaultInterface = newInterface\n\tif oldInterface != nil && oldInterface.Name == m.defaultInterface.Name && oldInterface.Index == m.defaultInterface.Index {\n\t\tm.defaultInterfaceAccess.Unlock()\n\t\treturn\n\t}\n\tcallbacks := m.callbacks.Array()\n\tm.defaultInterfaceAccess.Unlock()\n\tfor _, callback := range callbacks {\n\t\tcallback(newInterface, 0)\n\t}\n}\n\nfunc (m *platformDefaultInterfaceMonitor) RegisterMyInterface(interfaceName string) {\n\tm.defaultInterfaceAccess.Lock()\n\tdefer m.defaultInterfaceAccess.Unlock()\n\tm.myInterface = interfaceName\n}\n\nfunc (m *platformDefaultInterfaceMonitor) MyInterface() string {\n\tm.defaultInterfaceAccess.Lock()\n\tdefer m.defaultInterfaceAccess.Unlock()\n\treturn m.myInterface\n}\n"
  },
  {
    "path": "experimental/libbox/neighbor.go",
    "content": "package libbox\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n)\n\ntype NeighborEntry struct {\n\tAddress    string\n\tMacAddress string\n\tHostname   string\n}\n\ntype NeighborEntryIterator interface {\n\tNext() *NeighborEntry\n\tHasNext() bool\n}\n\ntype NeighborSubscription struct {\n\tdone chan struct{}\n}\n\nfunc (s *NeighborSubscription) Close() {\n\tclose(s.done)\n}\n\nfunc tableToIterator(table map[netip.Addr]net.HardwareAddr) NeighborEntryIterator {\n\tentries := make([]*NeighborEntry, 0, len(table))\n\tfor address, mac := range table {\n\t\tentries = append(entries, &NeighborEntry{\n\t\t\tAddress:    address.String(),\n\t\t\tMacAddress: mac.String(),\n\t\t})\n\t}\n\treturn &neighborEntryIterator{entries}\n}\n\ntype neighborEntryIterator struct {\n\tentries []*NeighborEntry\n}\n\nfunc (i *neighborEntryIterator) HasNext() bool {\n\treturn len(i.entries) > 0\n}\n\nfunc (i *neighborEntryIterator) Next() *NeighborEntry {\n\tif len(i.entries) == 0 {\n\t\treturn nil\n\t}\n\tentry := i.entries[0]\n\ti.entries = i.entries[1:]\n\treturn entry\n}\n"
  },
  {
    "path": "experimental/libbox/neighbor_darwin.go",
    "content": "//go:build darwin\n\npackage libbox\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"slices\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/route\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\txroute \"golang.org/x/net/route\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {\n\tentries, err := route.ReadNeighborEntries()\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"initial neighbor dump\")\n\t}\n\ttable := make(map[netip.Addr]net.HardwareAddr)\n\tfor _, entry := range entries {\n\t\ttable[entry.Address] = entry.MACAddress\n\t}\n\tlistener.UpdateNeighborTable(tableToIterator(table))\n\trouteSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"open route socket\")\n\t}\n\terr = unix.SetNonblock(routeSocket, true)\n\tif err != nil {\n\t\tunix.Close(routeSocket)\n\t\treturn nil, E.Cause(err, \"set route socket nonblock\")\n\t}\n\tsubscription := &NeighborSubscription{\n\t\tdone: make(chan struct{}),\n\t}\n\tgo subscription.loop(listener, routeSocket, table)\n\treturn subscription, nil\n}\n\nfunc (s *NeighborSubscription) loop(listener NeighborUpdateListener, routeSocket int, table map[netip.Addr]net.HardwareAddr) {\n\trouteSocketFile := os.NewFile(uintptr(routeSocket), \"route\")\n\tdefer routeSocketFile.Close()\n\tbuffer := buf.NewPacket()\n\tdefer buffer.Release()\n\tfor {\n\t\tselect {\n\t\tcase <-s.done:\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\ttv := unix.NsecToTimeval(int64(3 * time.Second))\n\t\t_ = unix.SetsockoptTimeval(routeSocket, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv)\n\t\tn, err := routeSocketFile.Read(buffer.FreeBytes())\n\t\tif err != nil {\n\t\t\tif nerr, ok := err.(net.Error); ok && nerr.Timeout() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-s.done:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tmessages, err := xroute.ParseRIB(xroute.RIBTypeRoute, buffer.FreeBytes()[:n])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tchanged := false\n\t\tfor _, message := range messages {\n\t\t\trouteMessage, isRouteMessage := message.(*xroute.RouteMessage)\n\t\t\tif !isRouteMessage {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif routeMessage.Flags&unix.RTF_LLINFO == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\taddress, mac, isDelete, ok := route.ParseRouteNeighborMessage(routeMessage)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif isDelete {\n\t\t\t\tif _, exists := table[address]; exists {\n\t\t\t\t\tdelete(table, address)\n\t\t\t\t\tchanged = true\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\texisting, exists := table[address]\n\t\t\t\tif !exists || !slices.Equal(existing, mac) {\n\t\t\t\t\ttable[address] = mac\n\t\t\t\t\tchanged = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif changed {\n\t\t\tlistener.UpdateNeighborTable(tableToIterator(table))\n\t\t}\n\t}\n}\n\nfunc ReadBootpdLeases() NeighborEntryIterator {\n\tleaseIPToMAC, ipToHostname, macToHostname := route.ReloadLeaseFiles([]string{\"/var/db/dhcpd_leases\"})\n\tentries := make([]*NeighborEntry, 0, len(leaseIPToMAC))\n\tfor address, mac := range leaseIPToMAC {\n\t\tentry := &NeighborEntry{\n\t\t\tAddress:    address.String(),\n\t\t\tMacAddress: mac.String(),\n\t\t}\n\t\thostname, found := ipToHostname[address]\n\t\tif !found {\n\t\t\thostname = macToHostname[mac.String()]\n\t\t}\n\t\tentry.Hostname = hostname\n\t\tentries = append(entries, entry)\n\t}\n\treturn &neighborEntryIterator{entries}\n}\n"
  },
  {
    "path": "experimental/libbox/neighbor_linux.go",
    "content": "//go:build linux\n\npackage libbox\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/route\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"github.com/mdlayher/netlink\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {\n\tentries, err := route.ReadNeighborEntries()\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"initial neighbor dump\")\n\t}\n\ttable := make(map[netip.Addr]net.HardwareAddr)\n\tfor _, entry := range entries {\n\t\ttable[entry.Address] = entry.MACAddress\n\t}\n\tlistener.UpdateNeighborTable(tableToIterator(table))\n\tconnection, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{\n\t\tGroups: 1 << (unix.RTNLGRP_NEIGH - 1),\n\t})\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"subscribe neighbor updates\")\n\t}\n\tsubscription := &NeighborSubscription{\n\t\tdone: make(chan struct{}),\n\t}\n\tgo subscription.loop(listener, connection, table)\n\treturn subscription, nil\n}\n\nfunc (s *NeighborSubscription) loop(listener NeighborUpdateListener, connection *netlink.Conn, table map[netip.Addr]net.HardwareAddr) {\n\tdefer connection.Close()\n\tfor {\n\t\tselect {\n\t\tcase <-s.done:\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\terr := connection.SetReadDeadline(time.Now().Add(3 * time.Second))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tmessages, err := connection.Receive()\n\t\tif err != nil {\n\t\t\tif nerr, ok := err.(net.Error); ok && nerr.Timeout() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-s.done:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tchanged := false\n\t\tfor _, message := range messages {\n\t\t\taddress, mac, isDelete, ok := route.ParseNeighborMessage(message)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif isDelete {\n\t\t\t\tif _, exists := table[address]; exists {\n\t\t\t\t\tdelete(table, address)\n\t\t\t\t\tchanged = true\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\texisting, exists := table[address]\n\t\t\t\tif !exists || !slices.Equal(existing, mac) {\n\t\t\t\t\ttable[address] = mac\n\t\t\t\t\tchanged = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif changed {\n\t\t\tlistener.UpdateNeighborTable(tableToIterator(table))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "experimental/libbox/neighbor_stub.go",
    "content": "//go:build !linux && !darwin\n\npackage libbox\n\nimport \"os\"\n\nfunc SubscribeNeighborTable(_ NeighborUpdateListener) (*NeighborSubscription, error) {\n\treturn nil, os.ErrInvalid\n}\n"
  },
  {
    "path": "experimental/libbox/panic.go",
    "content": "package libbox\n\n// https://github.com/golang/go/issues/46893\n// TODO: remove after `bulkBarrierPreWrite: unaligned arguments` fixed\n\ntype StringBox struct {\n\tValue string\n}\n\nfunc wrapString(value string) *StringBox {\n\treturn &StringBox{Value: value}\n}\n"
  },
  {
    "path": "experimental/libbox/pidfd_android.go",
    "content": "package libbox\n\nimport (\n\t\"os\"\n\t_ \"unsafe\"\n)\n\n// https://github.com/SagerNet/sing-box/issues/3233\n// https://github.com/golang/go/issues/70508\n// https://github.com/tailscale/tailscale/issues/13452\n\n//go:linkname checkPidfdOnce os.checkPidfdOnce\nvar checkPidfdOnce func() error\n\nfunc init() {\n\tcheckPidfdOnce = func() error {\n\t\treturn os.ErrInvalid\n\t}\n}\n"
  },
  {
    "path": "experimental/libbox/platform.go",
    "content": "package libbox\n\nimport (\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n)\n\ntype PlatformInterface interface {\n\tLocalDNSTransport() LocalDNSTransport\n\tUsePlatformAutoDetectInterfaceControl() bool\n\tAutoDetectInterfaceControl(fd int32) error\n\tOpenTun(options TunOptions) (int32, error)\n\tUseProcFS() bool\n\tFindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (*ConnectionOwner, error)\n\tStartDefaultInterfaceMonitor(listener InterfaceUpdateListener) error\n\tCloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error\n\tGetInterfaces() (NetworkInterfaceIterator, error)\n\tUnderNetworkExtension() bool\n\tIncludeAllNetworks() bool\n\tReadWIFIState() *WIFIState\n\tSystemCertificates() StringIterator\n\tClearDNSCache()\n\tSendNotification(notification *Notification) error\n\tStartNeighborMonitor(listener NeighborUpdateListener) error\n\tCloseNeighborMonitor(listener NeighborUpdateListener) error\n\tRegisterMyInterface(name string)\n}\n\ntype NeighborUpdateListener interface {\n\tUpdateNeighborTable(entries NeighborEntryIterator)\n}\n\ntype ConnectionOwner struct {\n\tUserId             int32\n\tUserName           string\n\tProcessPath        string\n\tAndroidPackageName string\n}\n\ntype InterfaceUpdateListener interface {\n\tUpdateDefaultInterface(interfaceName string, interfaceIndex int32, isExpensive bool, isConstrained bool)\n}\n\nconst (\n\tInterfaceTypeWIFI     = int32(C.InterfaceTypeWIFI)\n\tInterfaceTypeCellular = int32(C.InterfaceTypeCellular)\n\tInterfaceTypeEthernet = int32(C.InterfaceTypeEthernet)\n\tInterfaceTypeOther    = int32(C.InterfaceTypeOther)\n)\n\ntype NetworkInterface struct {\n\tIndex     int32\n\tMTU       int32\n\tName      string\n\tAddresses StringIterator\n\tFlags     int32\n\n\tType      int32\n\tDNSServer StringIterator\n\tMetered   bool\n}\n\ntype WIFIState struct {\n\tSSID  string\n\tBSSID string\n}\n\nfunc NewWIFIState(wifiSSID string, wifiBSSID string) *WIFIState {\n\treturn &WIFIState{wifiSSID, wifiBSSID}\n}\n\ntype NetworkInterfaceIterator interface {\n\tNext() *NetworkInterface\n\tHasNext() bool\n}\n\ntype Notification struct {\n\tIdentifier string\n\tTypeName   string\n\tTypeID     int32\n\tTitle      string\n\tSubtitle   string\n\tBody       string\n\tOpenURL    string\n}\n\ntype OnDemandRule interface {\n\tTarget() int32\n\tDNSSearchDomainMatch() StringIterator\n\tDNSServerAddressMatch() StringIterator\n\tInterfaceTypeMatch() int32\n\tSSIDMatch() StringIterator\n\tProbeURL() string\n}\n\ntype OnDemandRuleIterator interface {\n\tNext() OnDemandRule\n\tHasNext() bool\n}\n\ntype onDemandRule struct {\n\toption.OnDemandRule\n}\n\nfunc (r *onDemandRule) Target() int32 {\n\tif r.OnDemandRule.Action == nil {\n\t\treturn -1\n\t}\n\treturn int32(*r.OnDemandRule.Action)\n}\n\nfunc (r *onDemandRule) DNSSearchDomainMatch() StringIterator {\n\treturn newIterator(r.OnDemandRule.DNSSearchDomainMatch)\n}\n\nfunc (r *onDemandRule) DNSServerAddressMatch() StringIterator {\n\treturn newIterator(r.OnDemandRule.DNSServerAddressMatch)\n}\n\nfunc (r *onDemandRule) InterfaceTypeMatch() int32 {\n\tif r.OnDemandRule.InterfaceTypeMatch == nil {\n\t\treturn -1\n\t}\n\treturn int32(*r.OnDemandRule.InterfaceTypeMatch)\n}\n\nfunc (r *onDemandRule) SSIDMatch() StringIterator {\n\treturn newIterator(r.OnDemandRule.SSIDMatch)\n}\n\nfunc (r *onDemandRule) ProbeURL() string {\n\treturn r.OnDemandRule.ProbeURL\n}\n"
  },
  {
    "path": "experimental/libbox/pprof.go",
    "content": "package libbox\n\nimport (\n\t\"net\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"strconv\"\n)\n\ntype PProfServer struct {\n\tserver *http.Server\n}\n\nfunc NewPProfServer(port int) *PProfServer {\n\treturn &PProfServer{\n\t\t&http.Server{\n\t\t\tAddr: \":\" + strconv.Itoa(port),\n\t\t},\n\t}\n}\n\nfunc (s *PProfServer) Start() error {\n\tln, err := net.Listen(\"tcp\", s.server.Addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tgo s.server.Serve(ln)\n\treturn nil\n}\n\nfunc (s *PProfServer) Close() error {\n\treturn s.server.Close()\n}\n"
  },
  {
    "path": "experimental/libbox/profile_import.go",
    "content": "package libbox\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"encoding/binary\"\n\t\"io\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/varbin\"\n)\n\nfunc EncodeChunkedMessage(data []byte) []byte {\n\tvar buffer bytes.Buffer\n\tbinary.Write(&buffer, binary.BigEndian, uint16(len(data)))\n\tbuffer.Write(data)\n\treturn buffer.Bytes()\n}\n\nfunc DecodeLengthChunk(data []byte) int32 {\n\treturn int32(binary.BigEndian.Uint16(data))\n}\n\nconst (\n\tMessageTypeError = iota\n\tMessageTypeProfileList\n\tMessageTypeProfileContentRequest\n\tMessageTypeProfileContent\n)\n\ntype ErrorMessage struct {\n\tMessage string\n}\n\nfunc (e *ErrorMessage) Encode() []byte {\n\tvar buffer bytes.Buffer\n\tbuffer.WriteByte(MessageTypeError)\n\twriteString(&buffer, e.Message)\n\treturn buffer.Bytes()\n}\n\nfunc DecodeErrorMessage(data []byte) (*ErrorMessage, error) {\n\treader := bytes.NewReader(data)\n\tmessageType, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif messageType != MessageTypeError {\n\t\treturn nil, E.New(\"invalid message\")\n\t}\n\tvar message ErrorMessage\n\tmessage.Message, err = readString(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &message, nil\n}\n\nconst (\n\tProfileTypeLocal int32 = iota\n\tProfileTypeiCloud\n\tProfileTypeRemote\n)\n\ntype ProfilePreview struct {\n\tProfileID int64\n\tName      string\n\tType      int32\n}\n\ntype ProfilePreviewIterator interface {\n\tNext() *ProfilePreview\n\tHasNext() bool\n}\n\ntype ProfileEncoder struct {\n\tprofiles []ProfilePreview\n}\n\nfunc (e *ProfileEncoder) Append(profile *ProfilePreview) {\n\te.profiles = append(e.profiles, *profile)\n}\n\nfunc (e *ProfileEncoder) Encode() []byte {\n\tvar buffer bytes.Buffer\n\tbuffer.WriteByte(MessageTypeProfileList)\n\tbinary.Write(&buffer, binary.BigEndian, uint16(len(e.profiles)))\n\tfor _, preview := range e.profiles {\n\t\tbinary.Write(&buffer, binary.BigEndian, preview.ProfileID)\n\t\twriteString(&buffer, preview.Name)\n\t\tbinary.Write(&buffer, binary.BigEndian, preview.Type)\n\t}\n\treturn buffer.Bytes()\n}\n\ntype ProfileDecoder struct {\n\tprofiles []*ProfilePreview\n}\n\nfunc (d *ProfileDecoder) Decode(data []byte) error {\n\treader := bytes.NewReader(data)\n\tmessageType, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif messageType != MessageTypeProfileList {\n\t\treturn E.New(\"invalid message\")\n\t}\n\tvar profileCount uint16\n\terr = binary.Read(reader, binary.BigEndian, &profileCount)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor i := 0; i < int(profileCount); i++ {\n\t\tvar profile ProfilePreview\n\t\terr = binary.Read(reader, binary.BigEndian, &profile.ProfileID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tprofile.Name, err = readString(reader)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = binary.Read(reader, binary.BigEndian, &profile.Type)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\td.profiles = append(d.profiles, &profile)\n\t}\n\treturn nil\n}\n\nfunc (d *ProfileDecoder) Iterator() ProfilePreviewIterator {\n\treturn newIterator(d.profiles)\n}\n\ntype ProfileContentRequest struct {\n\tProfileID int64\n}\n\nfunc (r *ProfileContentRequest) Encode() []byte {\n\tvar buffer bytes.Buffer\n\tbuffer.WriteByte(MessageTypeProfileContentRequest)\n\tbinary.Write(&buffer, binary.BigEndian, r.ProfileID)\n\treturn buffer.Bytes()\n}\n\nfunc DecodeProfileContentRequest(data []byte) (*ProfileContentRequest, error) {\n\treader := bytes.NewReader(data)\n\tmessageType, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif messageType != MessageTypeProfileContentRequest {\n\t\treturn nil, E.New(\"invalid message\")\n\t}\n\tvar request ProfileContentRequest\n\terr = binary.Read(reader, binary.BigEndian, &request.ProfileID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &request, nil\n}\n\ntype ProfileContent struct {\n\tName               string\n\tType               int32\n\tConfig             string\n\tRemotePath         string\n\tAutoUpdate         bool\n\tAutoUpdateInterval int32\n\tLastUpdated        int64\n}\n\nfunc (c *ProfileContent) Encode() []byte {\n\tbuffer := new(bytes.Buffer)\n\tbuffer.WriteByte(MessageTypeProfileContent)\n\tbuffer.WriteByte(1)\n\tgWriter := gzip.NewWriter(buffer)\n\twriter := bufio.NewWriter(gWriter)\n\twriteStringBuffered(writer, c.Name)\n\tbinary.Write(writer, binary.BigEndian, c.Type)\n\twriteStringBuffered(writer, c.Config)\n\tif c.Type != ProfileTypeLocal {\n\t\twriteStringBuffered(writer, c.RemotePath)\n\t}\n\tif c.Type == ProfileTypeRemote {\n\t\tbinary.Write(writer, binary.BigEndian, c.AutoUpdate)\n\t\tbinary.Write(writer, binary.BigEndian, c.AutoUpdateInterval)\n\t\tbinary.Write(writer, binary.BigEndian, c.LastUpdated)\n\t}\n\twriter.Flush()\n\tgWriter.Flush()\n\tgWriter.Close()\n\treturn buffer.Bytes()\n}\n\nfunc DecodeProfileContent(data []byte) (*ProfileContent, error) {\n\treader := bytes.NewReader(data)\n\tmessageType, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif messageType != MessageTypeProfileContent {\n\t\treturn nil, E.New(\"invalid message\")\n\t}\n\tversion, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgReader, err := gzip.NewReader(reader)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"unsupported profile\")\n\t}\n\tbReader := varbin.StubReader(gReader)\n\tvar content ProfileContent\n\tcontent.Name, err = readString(bReader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = binary.Read(bReader, binary.BigEndian, &content.Type)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcontent.Config, err = readString(bReader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif content.Type != ProfileTypeLocal {\n\t\tcontent.RemotePath, err = readString(bReader)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif content.Type == ProfileTypeRemote || (version == 0 && content.Type != ProfileTypeLocal) {\n\t\terr = binary.Read(bReader, binary.BigEndian, &content.AutoUpdate)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif version >= 1 {\n\t\t\terr = binary.Read(bReader, binary.BigEndian, &content.AutoUpdateInterval)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\terr = binary.Read(bReader, binary.BigEndian, &content.LastUpdated)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn &content, nil\n}\n\nfunc readString(reader io.ByteReader) (string, error) {\n\tlength, err := binary.ReadUvarint(reader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tbuf := make([]byte, length)\n\tfor i := range buf {\n\t\tbuf[i], err = reader.ReadByte()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\treturn string(buf), nil\n}\n\nfunc writeString(buffer *bytes.Buffer, value string) {\n\tvarbin.WriteUvarint(buffer, uint64(len(value)))\n\tbuffer.WriteString(value)\n}\n\nfunc writeStringBuffered(writer *bufio.Writer, value string) {\n\tvarbin.WriteUvarint(writer, uint64(len(value)))\n\twriter.WriteString(value)\n}\n"
  },
  {
    "path": "experimental/libbox/remote_profile.go",
    "content": "package libbox\n\nimport (\n\t\"net/url\"\n)\n\nfunc GenerateRemoteProfileImportLink(name string, remoteURL string) string {\n\timportLink := &url.URL{\n\t\tScheme:   \"sing-box\",\n\t\tHost:     \"import-remote-profile\",\n\t\tRawQuery: url.Values{\"url\": []string{remoteURL}}.Encode(),\n\t\tFragment: name,\n\t}\n\treturn importLink.String()\n}\n\ntype ImportRemoteProfile struct {\n\tName string\n\tURL  string\n\tHost string\n}\n\nfunc ParseRemoteProfileImportLink(importLink string) (*ImportRemoteProfile, error) {\n\timportURL, err := url.Parse(importLink)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tremoteURL, err := url.Parse(importURL.Query().Get(\"url\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tname := importURL.Fragment\n\tif name == \"\" {\n\t\tname = remoteURL.Host\n\t}\n\treturn &ImportRemoteProfile{\n\t\tName: name,\n\t\tURL:  remoteURL.String(),\n\t\tHost: remoteURL.Host,\n\t}, nil\n}\n"
  },
  {
    "path": "experimental/libbox/semver.go",
    "content": "package libbox\n\nimport (\n\t\"strings\"\n\n\t\"golang.org/x/mod/semver\"\n)\n\nfunc CompareSemver(left string, right string) bool {\n\tnormalizedLeft := normalizeSemver(left)\n\tif !semver.IsValid(normalizedLeft) {\n\t\treturn false\n\t}\n\tnormalizedRight := normalizeSemver(right)\n\tif !semver.IsValid(normalizedRight) {\n\t\treturn false\n\t}\n\treturn semver.Compare(normalizedLeft, normalizedRight) > 0\n}\n\nfunc normalizeSemver(version string) string {\n\ttrimmedVersion := strings.TrimSpace(version)\n\tif strings.HasPrefix(trimmedVersion, \"v\") {\n\t\treturn trimmedVersion\n\t}\n\treturn \"v\" + trimmedVersion\n}\n"
  },
  {
    "path": "experimental/libbox/semver_test.go",
    "content": "package libbox\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCompareSemver(t *testing.T) {\n\tt.Parallel()\n\n\trequire.False(t, CompareSemver(\"1.13.0-rc.4\", \"1.13.0\"))\n\trequire.True(t, CompareSemver(\"1.13.1\", \"1.13.0\"))\n\trequire.False(t, CompareSemver(\"v1.13.0\", \"1.13.0\"))\n\trequire.False(t, CompareSemver(\"1.13.0-\", \"1.13.0\"))\n}\n"
  },
  {
    "path": "experimental/libbox/service.go",
    "content": "package libbox\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"net\"\n\t\"net/netip\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"sync\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/experimental/libbox/internal/procfs\"\n\t\"github.com/sagernet/sing-box/option\"\n\ttun \"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\nvar _ adapter.PlatformInterface = (*platformInterfaceWrapper)(nil)\n\ntype platformInterfaceWrapper struct {\n\tiif                    PlatformInterface\n\tuseProcFS              bool\n\tnetworkManager         adapter.NetworkManager\n\tmyTunName              string\n\tdefaultInterfaceAccess sync.Mutex\n\tdefaultInterface       *control.Interface\n\tisExpensive            bool\n\tisConstrained          bool\n}\n\nfunc (w *platformInterfaceWrapper) Initialize(networkManager adapter.NetworkManager) error {\n\tw.networkManager = networkManager\n\treturn nil\n}\n\nfunc (w *platformInterfaceWrapper) UsePlatformAutoDetectInterfaceControl() bool {\n\treturn w.iif.UsePlatformAutoDetectInterfaceControl()\n}\n\nfunc (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error {\n\treturn w.iif.AutoDetectInterfaceControl(int32(fd))\n}\n\nfunc (w *platformInterfaceWrapper) UsePlatformInterface() bool {\n\treturn true\n}\n\nfunc (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {\n\tif len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {\n\t\treturn nil, E.New(\"platform: unsupported uid options\")\n\t}\n\tif len(options.IncludeAndroidUser) > 0 {\n\t\treturn nil, E.New(\"platform: unsupported android_user option\")\n\t}\n\trouteRanges, err := options.BuildAutoRouteRanges(true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttunFd, err := w.iif.OpenTun(&tunOptions{options, routeRanges, platformOptions})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toptions.Name, err = getTunnelName(tunFd)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"query tun name\")\n\t}\n\toptions.InterfaceMonitor.RegisterMyInterface(options.Name)\n\tdupFd, err := dup(int(tunFd))\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"dup tun file descriptor\")\n\t}\n\toptions.FileDescriptor = dupFd\n\tw.myTunName = options.Name\n\tw.iif.RegisterMyInterface(options.Name)\n\treturn tun.New(*options)\n}\n\nfunc (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool {\n\treturn true\n}\n\nfunc (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {\n\treturn &platformDefaultInterfaceMonitor{\n\t\tplatformInterfaceWrapper: w,\n\t\tlogger:                   logger,\n\t}\n}\n\nfunc (w *platformInterfaceWrapper) UsePlatformNetworkInterfaces() bool {\n\treturn true\n}\n\nfunc (w *platformInterfaceWrapper) NetworkInterfaces() ([]adapter.NetworkInterface, error) {\n\tinterfaceIterator, err := w.iif.GetInterfaces()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar interfaces []adapter.NetworkInterface\n\tfor _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) {\n\t\tif netInterface.Name == w.myTunName {\n\t\t\tcontinue\n\t\t}\n\t\tw.defaultInterfaceAccess.Lock()\n\t\t// (GOOS=windows) SA4006: this value of `isDefault` is never used\n\t\t// Why not used?\n\t\t//nolint:staticcheck\n\t\tisDefault := w.defaultInterface != nil && int(netInterface.Index) == w.defaultInterface.Index\n\t\tw.defaultInterfaceAccess.Unlock()\n\t\tinterfaces = append(interfaces, adapter.NetworkInterface{\n\t\t\tInterface: control.Interface{\n\t\t\t\tIndex:     int(netInterface.Index),\n\t\t\t\tMTU:       int(netInterface.MTU),\n\t\t\t\tName:      netInterface.Name,\n\t\t\t\tAddresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),\n\t\t\t\tFlags:     linkFlags(uint32(netInterface.Flags)),\n\t\t\t},\n\t\t\tType:        C.InterfaceType(netInterface.Type),\n\t\t\tDNSServers:  iteratorToArray[string](netInterface.DNSServer),\n\t\t\tExpensive:   netInterface.Metered || isDefault && w.isExpensive,\n\t\t\tConstrained: isDefault && w.isConstrained,\n\t\t})\n\t}\n\tinterfaces = common.UniqBy(interfaces, func(it adapter.NetworkInterface) string {\n\t\treturn it.Name\n\t})\n\treturn interfaces, nil\n}\n\nfunc (w *platformInterfaceWrapper) UnderNetworkExtension() bool {\n\treturn w.iif.UnderNetworkExtension()\n}\n\nfunc (w *platformInterfaceWrapper) NetworkExtensionIncludeAllNetworks() bool {\n\treturn w.iif.IncludeAllNetworks()\n}\n\nfunc (w *platformInterfaceWrapper) ClearDNSCache() {\n\tw.iif.ClearDNSCache()\n}\n\nfunc (w *platformInterfaceWrapper) RequestPermissionForWIFIState() error {\n\treturn nil\n}\n\nfunc (w *platformInterfaceWrapper) UsePlatformWIFIMonitor() bool {\n\treturn true\n}\n\nfunc (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {\n\twifiState := w.iif.ReadWIFIState()\n\tif wifiState == nil {\n\t\treturn adapter.WIFIState{}\n\t}\n\treturn (adapter.WIFIState)(*wifiState)\n}\n\nfunc (w *platformInterfaceWrapper) SystemCertificates() []string {\n\treturn iteratorToArray[string](w.iif.SystemCertificates())\n}\n\nfunc (w *platformInterfaceWrapper) UsePlatformConnectionOwnerFinder() bool {\n\treturn true\n}\n\nfunc (w *platformInterfaceWrapper) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) {\n\tif w.useProcFS {\n\t\tvar source netip.AddrPort\n\t\tvar destination netip.AddrPort\n\t\tsourceAddr, _ := netip.ParseAddr(request.SourceAddress)\n\t\tsource = netip.AddrPortFrom(sourceAddr, uint16(request.SourcePort))\n\t\tdestAddr, _ := netip.ParseAddr(request.DestinationAddress)\n\t\tdestination = netip.AddrPortFrom(destAddr, uint16(request.DestinationPort))\n\n\t\tvar network string\n\t\tswitch request.IpProtocol {\n\t\tcase int32(syscall.IPPROTO_TCP):\n\t\t\tnetwork = \"tcp\"\n\t\tcase int32(syscall.IPPROTO_UDP):\n\t\t\tnetwork = \"udp\"\n\t\tdefault:\n\t\t\treturn nil, E.New(\"unknown protocol: \", request.IpProtocol)\n\t\t}\n\n\t\tuid := procfs.ResolveSocketByProcSearch(network, source, destination)\n\t\tif uid == -1 {\n\t\t\treturn nil, E.New(\"procfs: not found\")\n\t\t}\n\t\treturn &adapter.ConnectionOwner{\n\t\t\tUserId: uid,\n\t\t}, nil\n\t}\n\n\tresult, err := w.iif.FindConnectionOwner(request.IpProtocol, request.SourceAddress, request.SourcePort, request.DestinationAddress, request.DestinationPort)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &adapter.ConnectionOwner{\n\t\tUserId:             result.UserId,\n\t\tUserName:           result.UserName,\n\t\tProcessPath:        result.ProcessPath,\n\t\tAndroidPackageName: result.AndroidPackageName,\n\t}, nil\n}\n\nfunc (w *platformInterfaceWrapper) DisableColors() bool {\n\treturn runtime.GOOS != \"android\"\n}\n\nfunc (w *platformInterfaceWrapper) UsePlatformNotification() bool {\n\treturn true\n}\n\nfunc (w *platformInterfaceWrapper) SendNotification(notification *adapter.Notification) error {\n\treturn w.iif.SendNotification((*Notification)(notification))\n}\n\nfunc (w *platformInterfaceWrapper) UsePlatformNeighborResolver() bool {\n\treturn true\n}\n\nfunc (w *platformInterfaceWrapper) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error {\n\treturn w.iif.StartNeighborMonitor(&neighborUpdateListenerWrapper{listener: listener})\n}\n\nfunc (w *platformInterfaceWrapper) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error {\n\treturn w.iif.CloseNeighborMonitor(nil)\n}\n\ntype neighborUpdateListenerWrapper struct {\n\tlistener adapter.NeighborUpdateListener\n}\n\nfunc (w *neighborUpdateListenerWrapper) UpdateNeighborTable(entries NeighborEntryIterator) {\n\tvar result []adapter.NeighborEntry\n\tfor entries.HasNext() {\n\t\tentry := entries.Next()\n\t\tif entry == nil {\n\t\t\tcontinue\n\t\t}\n\t\taddress, err := netip.ParseAddr(entry.Address)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tmacAddress, err := net.ParseMAC(entry.MacAddress)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tresult = append(result, adapter.NeighborEntry{\n\t\t\tAddress:    address,\n\t\t\tMACAddress: macAddress,\n\t\t\tHostname:   entry.Hostname,\n\t\t})\n\t}\n\tw.listener.UpdateNeighborTable(result)\n}\n\nfunc AvailablePort(startPort int32) (int32, error) {\n\tfor port := int(startPort); ; port++ {\n\t\tif port > 65535 {\n\t\t\treturn 0, E.New(\"no available port found\")\n\t\t}\n\t\tlistener, err := net.Listen(\"tcp\", net.JoinHostPort(\"127.0.0.1\", strconv.Itoa(int(port))))\n\t\tif err != nil {\n\t\t\tif errors.Is(err, syscall.EADDRINUSE) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn 0, E.Cause(err, \"find available port\")\n\t\t}\n\t\terr = listener.Close()\n\t\tif err != nil {\n\t\t\treturn 0, E.Cause(err, \"close listener\")\n\t\t}\n\t\treturn int32(port), nil\n\t}\n}\n\nfunc RandomHex(length int32) *StringBox {\n\tbytes := make([]byte, length)\n\tcommon.Must1(rand.Read(bytes))\n\treturn wrapString(hex.EncodeToString(bytes))\n}\n"
  },
  {
    "path": "experimental/libbox/service_other.go",
    "content": "//go:build !windows\n\npackage libbox\n\nimport \"syscall\"\n\nfunc dup(fd int) (nfd int, err error) {\n\treturn syscall.Dup(fd)\n}\n"
  },
  {
    "path": "experimental/libbox/service_windows.go",
    "content": "package libbox\n\nimport \"os\"\n\nfunc dup(fd int) (nfd int, err error) {\n\treturn 0, os.ErrInvalid\n}\n"
  },
  {
    "path": "experimental/libbox/setup.go",
    "content": "package libbox\n\nimport (\n\t\"os\"\n\t\"runtime/debug\"\n\t\"time\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/experimental/locale\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common/byteformats\"\n)\n\nvar (\n\tsBasePath                string\n\tsWorkingPath             string\n\tsTempPath                string\n\tsUserID                  int\n\tsGroupID                 int\n\tsFixAndroidStack         bool\n\tsCommandServerListenPort uint16\n\tsCommandServerSecret     string\n\tsLogMaxLines             int\n\tsDebug                   bool\n)\n\nfunc init() {\n\tdebug.SetPanicOnFault(true)\n\tdebug.SetTraceback(\"all\")\n}\n\ntype SetupOptions struct {\n\tBasePath                string\n\tWorkingPath             string\n\tTempPath                string\n\tFixAndroidStack         bool\n\tCommandServerListenPort int32\n\tCommandServerSecret     string\n\tLogMaxLines             int\n\tDebug                   bool\n}\n\nfunc Setup(options *SetupOptions) error {\n\tsBasePath = options.BasePath\n\tsWorkingPath = options.WorkingPath\n\tsTempPath = options.TempPath\n\n\tsUserID = os.Getuid()\n\tsGroupID = os.Getgid()\n\n\t// TODO: remove after fixed\n\t// https://github.com/golang/go/issues/68760\n\tsFixAndroidStack = options.FixAndroidStack\n\n\tsCommandServerListenPort = uint16(options.CommandServerListenPort)\n\tsCommandServerSecret = options.CommandServerSecret\n\tsLogMaxLines = options.LogMaxLines\n\tsDebug = options.Debug\n\n\tos.MkdirAll(sWorkingPath, 0o777)\n\tos.MkdirAll(sTempPath, 0o777)\n\treturn nil\n}\n\nfunc SetLocale(localeId string) {\n\tlocale.Set(localeId)\n}\n\nfunc Version() string {\n\treturn C.Version\n}\n\nfunc FormatBytes(length int64) string {\n\treturn byteformats.FormatKBytes(uint64(length))\n}\n\nfunc FormatMemoryBytes(length int64) string {\n\treturn byteformats.FormatMemoryKBytes(uint64(length))\n}\n\nfunc FormatDuration(duration int64) string {\n\treturn log.FormatDuration(time.Duration(duration) * time.Millisecond)\n}\n\nfunc ProxyDisplayType(proxyType string) string {\n\treturn C.ProxyDisplayName(proxyType)\n}\n"
  },
  {
    "path": "experimental/libbox/tun.go",
    "content": "package libbox\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\n\t\"github.com/sagernet/sing-box/option\"\n\ttun \"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype TunOptions interface {\n\tGetInet4Address() RoutePrefixIterator\n\tGetInet6Address() RoutePrefixIterator\n\tGetDNSServerAddress() (*StringBox, error)\n\tGetMTU() int32\n\tGetAutoRoute() bool\n\tGetStrictRoute() bool\n\tGetInet4RouteAddress() RoutePrefixIterator\n\tGetInet6RouteAddress() RoutePrefixIterator\n\tGetInet4RouteExcludeAddress() RoutePrefixIterator\n\tGetInet6RouteExcludeAddress() RoutePrefixIterator\n\tGetInet4RouteRange() RoutePrefixIterator\n\tGetInet6RouteRange() RoutePrefixIterator\n\tGetIncludePackage() StringIterator\n\tGetExcludePackage() StringIterator\n\tIsHTTPProxyEnabled() bool\n\tGetHTTPProxyServer() string\n\tGetHTTPProxyServerPort() int32\n\tGetHTTPProxyBypassDomain() StringIterator\n\tGetHTTPProxyMatchDomain() StringIterator\n}\n\ntype RoutePrefix struct {\n\taddress netip.Addr\n\tprefix  int\n}\n\nfunc (p *RoutePrefix) Address() string {\n\treturn p.address.String()\n}\n\nfunc (p *RoutePrefix) Prefix() int32 {\n\treturn int32(p.prefix)\n}\n\nfunc (p *RoutePrefix) Mask() string {\n\tvar bits int\n\tif p.address.Is6() {\n\t\tbits = 128\n\t} else {\n\t\tbits = 32\n\t}\n\treturn net.IP(net.CIDRMask(p.prefix, bits)).String()\n}\n\nfunc (p *RoutePrefix) String() string {\n\treturn netip.PrefixFrom(p.address, p.prefix).String()\n}\n\ntype RoutePrefixIterator interface {\n\tNext() *RoutePrefix\n\tHasNext() bool\n}\n\nfunc mapRoutePrefix(prefixes []netip.Prefix) RoutePrefixIterator {\n\treturn newIterator(common.Map(prefixes, func(prefix netip.Prefix) *RoutePrefix {\n\t\treturn &RoutePrefix{\n\t\t\taddress: prefix.Addr(),\n\t\t\tprefix:  prefix.Bits(),\n\t\t}\n\t}))\n}\n\nvar _ TunOptions = (*tunOptions)(nil)\n\ntype tunOptions struct {\n\t*tun.Options\n\trouteRanges []netip.Prefix\n\toption.TunPlatformOptions\n}\n\nfunc (o *tunOptions) GetInet4Address() RoutePrefixIterator {\n\treturn mapRoutePrefix(o.Inet4Address)\n}\n\nfunc (o *tunOptions) GetInet6Address() RoutePrefixIterator {\n\treturn mapRoutePrefix(o.Inet6Address)\n}\n\nfunc (o *tunOptions) GetDNSServerAddress() (*StringBox, error) {\n\tif len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 {\n\t\treturn nil, E.New(\"need one more IPv4 address for DNS hijacking\")\n\t}\n\treturn wrapString(o.Inet4Address[0].Addr().Next().String()), nil\n}\n\nfunc (o *tunOptions) GetMTU() int32 {\n\treturn int32(o.MTU)\n}\n\nfunc (o *tunOptions) GetAutoRoute() bool {\n\treturn o.AutoRoute\n}\n\nfunc (o *tunOptions) GetStrictRoute() bool {\n\treturn o.StrictRoute\n}\n\nfunc (o *tunOptions) GetInet4RouteAddress() RoutePrefixIterator {\n\treturn mapRoutePrefix(o.Inet4RouteAddress)\n}\n\nfunc (o *tunOptions) GetInet6RouteAddress() RoutePrefixIterator {\n\treturn mapRoutePrefix(o.Inet6RouteAddress)\n}\n\nfunc (o *tunOptions) GetInet4RouteExcludeAddress() RoutePrefixIterator {\n\treturn mapRoutePrefix(o.Inet4RouteExcludeAddress)\n}\n\nfunc (o *tunOptions) GetInet6RouteExcludeAddress() RoutePrefixIterator {\n\treturn mapRoutePrefix(o.Inet6RouteExcludeAddress)\n}\n\nfunc (o *tunOptions) GetInet4RouteRange() RoutePrefixIterator {\n\treturn mapRoutePrefix(common.Filter(o.routeRanges, func(it netip.Prefix) bool {\n\t\treturn it.Addr().Is4()\n\t}))\n}\n\nfunc (o *tunOptions) GetInet6RouteRange() RoutePrefixIterator {\n\treturn mapRoutePrefix(common.Filter(o.routeRanges, func(it netip.Prefix) bool {\n\t\treturn it.Addr().Is6()\n\t}))\n}\n\nfunc (o *tunOptions) GetIncludePackage() StringIterator {\n\treturn newIterator(o.IncludePackage)\n}\n\nfunc (o *tunOptions) GetExcludePackage() StringIterator {\n\treturn newIterator(o.ExcludePackage)\n}\n\nfunc (o *tunOptions) IsHTTPProxyEnabled() bool {\n\tif o.TunPlatformOptions.HTTPProxy == nil {\n\t\treturn false\n\t}\n\treturn o.TunPlatformOptions.HTTPProxy.Enabled\n}\n\nfunc (o *tunOptions) GetHTTPProxyServer() string {\n\treturn o.TunPlatformOptions.HTTPProxy.Server\n}\n\nfunc (o *tunOptions) GetHTTPProxyServerPort() int32 {\n\treturn int32(o.TunPlatformOptions.HTTPProxy.ServerPort)\n}\n\nfunc (o *tunOptions) GetHTTPProxyBypassDomain() StringIterator {\n\treturn newIterator(o.TunPlatformOptions.HTTPProxy.BypassDomain)\n}\n\nfunc (o *tunOptions) GetHTTPProxyMatchDomain() StringIterator {\n\treturn newIterator(o.TunPlatformOptions.HTTPProxy.MatchDomain)\n}\n"
  },
  {
    "path": "experimental/libbox/tun_darwin.go",
    "content": "package libbox\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\n// kanged from wireauard-apple\n\nconst utunControlName = \"com.apple.net.utun_control\"\n\nfunc GetTunnelFileDescriptor() int32 {\n\tctlInfo := &unix.CtlInfo{}\n\tcopy(ctlInfo.Name[:], utunControlName)\n\tfor fd := 0; fd < 1024; fd++ {\n\t\taddr, err := unix.Getpeername(fd)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\taddrCTL, loaded := addr.(*unix.SockaddrCtl)\n\t\tif !loaded {\n\t\t\tcontinue\n\t\t}\n\t\tif ctlInfo.Id == 0 {\n\t\t\terr = unix.IoctlCtlInfo(fd, ctlInfo)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif addrCTL.ID == ctlInfo.Id {\n\t\t\treturn int32(fd)\n\t\t}\n\t}\n\treturn -1\n}\n"
  },
  {
    "path": "experimental/libbox/tun_name_darwin.go",
    "content": "package libbox\n\nimport \"golang.org/x/sys/unix\"\n\nfunc getTunnelName(fd int32) (string, error) {\n\treturn unix.GetsockoptString(\n\t\tint(fd),\n\t\t2, /* #define SYSPROTO_CONTROL 2 */\n\t\t2, /* #define UTUN_OPT_IFNAME 2 */\n\t)\n}\n"
  },
  {
    "path": "experimental/libbox/tun_name_linux.go",
    "content": "package libbox\n\nimport (\n\t\"fmt\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nconst ifReqSize = unix.IFNAMSIZ + 64\n\nfunc getTunnelName(fd int32) (string, error) {\n\tvar ifr [ifReqSize]byte\n\tvar errno syscall.Errno\n\t_, _, errno = unix.Syscall(\n\t\tunix.SYS_IOCTL,\n\t\tuintptr(fd),\n\t\tuintptr(unix.TUNGETIFF),\n\t\tuintptr(unsafe.Pointer(&ifr[0])),\n\t)\n\tif errno != 0 {\n\t\treturn \"\", fmt.Errorf(\"failed to get name of TUN device: %w\", errno)\n\t}\n\treturn unix.ByteSliceToString(ifr[:]), nil\n}\n"
  },
  {
    "path": "experimental/libbox/tun_name_other.go",
    "content": "//go:build !(darwin || linux)\n\npackage libbox\n\nimport \"os\"\n\nfunc getTunnelName(fd int32) (string, error) {\n\treturn \"\", os.ErrInvalid\n}\n"
  },
  {
    "path": "experimental/locale/locale.go",
    "content": "package locale\n\nvar (\n\tlocaleRegistry = make(map[string]*Locale)\n\tcurrent        = defaultLocal\n)\n\ntype Locale struct {\n\t// deprecated messages for graphical clients\n\tLocale                  string\n\tDeprecatedMessage       string\n\tDeprecatedMessageNoLink string\n}\n\nvar defaultLocal = &Locale{\n\tLocale:                  \"en_US\",\n\tDeprecatedMessage:       \"%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.\",\n\tDeprecatedMessageNoLink: \"%s is deprecated in sing-box %s and will be removed in sing-box %s.\",\n}\n\nfunc Current() *Locale {\n\treturn current\n}\n\nfunc Set(localeId string) bool {\n\tlocale, loaded := localeRegistry[localeId]\n\tif !loaded {\n\t\treturn false\n\t}\n\tcurrent = locale\n\treturn true\n}\n"
  },
  {
    "path": "experimental/locale/locale_zh_CN.go",
    "content": "package locale\n\nvar warningMessageForEndUsers = \"\\n\\n如果您不明白此消息意味着什么：您的配置文件已过时，且将很快不可用。请联系您的配置提供者以更新配置。\"\n\nfunc init() {\n\tlocaleRegistry[\"zh_CN\"] = &Locale{\n\t\tLocale:                  \"zh_CN\",\n\t\tDeprecatedMessage:       \"%s 已在 sing-box %s 中被弃用，且将在 sing-box %s 中被移除，请参阅迁移指南。\" + warningMessageForEndUsers,\n\t\tDeprecatedMessageNoLink: \"%s 已在 sing-box %s 中被弃用，且将在 sing-box %s 中被移除。\" + warningMessageForEndUsers,\n\t}\n}\n"
  },
  {
    "path": "experimental/v2rayapi/server.go",
    "content": "package v2rayapi\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/experimental\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nfunc init() {\n\texperimental.RegisterV2RayServerConstructor(NewServer)\n}\n\nvar _ adapter.V2RayServer = (*Server)(nil)\n\ntype Server struct {\n\tlogger       log.Logger\n\tlisten       string\n\ttcpListener  net.Listener\n\tgrpcServer   *grpc.Server\n\tstatsService *StatsService\n}\n\nfunc NewServer(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) {\n\tgrpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))\n\tstatsService := NewStatsService(common.PtrValueOrDefault(options.Stats))\n\tif statsService != nil {\n\t\tRegisterStatsServiceServer(grpcServer, statsService)\n\t}\n\tserver := &Server{\n\t\tlogger:       logger,\n\t\tlisten:       options.Listen,\n\t\tgrpcServer:   grpcServer,\n\t\tstatsService: statsService,\n\t}\n\treturn server, nil\n}\n\nfunc (s *Server) Name() string {\n\treturn \"v2ray server\"\n}\n\nfunc (s *Server) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStatePostStart {\n\t\treturn nil\n\t}\n\tlistener, err := net.Listen(\"tcp\", s.listen)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.logger.Info(\"grpc server started at \", listener.Addr())\n\ts.tcpListener = listener\n\tgo func() {\n\t\terr = s.grpcServer.Serve(listener)\n\t\tif err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\t\ts.logger.Error(err)\n\t\t}\n\t}()\n\treturn nil\n}\n\nfunc (s *Server) Close() error {\n\tif s.grpcServer != nil {\n\t\ts.grpcServer.Stop()\n\t}\n\treturn common.Close(\n\t\tcommon.PtrOrNil(s.grpcServer),\n\t\ts.tcpListener,\n\t)\n}\n\nfunc (s *Server) StatsService() adapter.ConnectionTracker {\n\treturn s.statsService\n}\n"
  },
  {
    "path": "experimental/v2rayapi/stats.go",
    "content": "package v2rayapi\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc init() {\n\tStatsService_ServiceDesc.ServiceName = \"v2ray.core.app.stats.command.StatsService\"\n}\n\nvar (\n\t_ adapter.ConnectionTracker = (*StatsService)(nil)\n\t_ StatsServiceServer        = (*StatsService)(nil)\n)\n\ntype StatsService struct {\n\tcreatedAt time.Time\n\tinbounds  map[string]bool\n\toutbounds map[string]bool\n\tusers     map[string]bool\n\taccess    sync.Mutex\n\tcounters  map[string]*atomic.Int64\n}\n\nfunc NewStatsService(options option.V2RayStatsServiceOptions) *StatsService {\n\tif !options.Enabled {\n\t\treturn nil\n\t}\n\tinbounds := make(map[string]bool)\n\toutbounds := make(map[string]bool)\n\tusers := make(map[string]bool)\n\tfor _, inbound := range options.Inbounds {\n\t\tinbounds[inbound] = true\n\t}\n\tfor _, outbound := range options.Outbounds {\n\t\toutbounds[outbound] = true\n\t}\n\tfor _, user := range options.Users {\n\t\tusers[user] = true\n\t}\n\treturn &StatsService{\n\t\tcreatedAt: time.Now(),\n\t\tinbounds:  inbounds,\n\t\toutbounds: outbounds,\n\t\tusers:     users,\n\t\tcounters:  make(map[string]*atomic.Int64),\n\t}\n}\n\nfunc (s *StatsService) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {\n\tinbound := metadata.Inbound\n\tuser := metadata.User\n\toutbound := matchOutbound.Tag()\n\tvar readCounter []*atomic.Int64\n\tvar writeCounter []*atomic.Int64\n\tcountInbound := inbound != \"\" && s.inbounds[inbound]\n\tcountOutbound := outbound != \"\" && s.outbounds[outbound]\n\tcountUser := user != \"\" && s.users[user]\n\tif !countInbound && !countOutbound && !countUser {\n\t\treturn conn\n\t}\n\ts.access.Lock()\n\tif countInbound {\n\t\treadCounter = append(readCounter, s.loadOrCreateCounter(\"inbound>>>\"+inbound+\">>>traffic>>>uplink\"))\n\t\twriteCounter = append(writeCounter, s.loadOrCreateCounter(\"inbound>>>\"+inbound+\">>>traffic>>>downlink\"))\n\t}\n\tif countOutbound {\n\t\treadCounter = append(readCounter, s.loadOrCreateCounter(\"outbound>>>\"+outbound+\">>>traffic>>>uplink\"))\n\t\twriteCounter = append(writeCounter, s.loadOrCreateCounter(\"outbound>>>\"+outbound+\">>>traffic>>>downlink\"))\n\t}\n\tif countUser {\n\t\treadCounter = append(readCounter, s.loadOrCreateCounter(\"user>>>\"+user+\">>>traffic>>>uplink\"))\n\t\twriteCounter = append(writeCounter, s.loadOrCreateCounter(\"user>>>\"+user+\">>>traffic>>>downlink\"))\n\t}\n\ts.access.Unlock()\n\treturn bufio.NewInt64CounterConn(conn, readCounter, writeCounter)\n}\n\nfunc (s *StatsService) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn {\n\tinbound := metadata.Inbound\n\tuser := metadata.User\n\toutbound := matchOutbound.Tag()\n\tvar readCounter []*atomic.Int64\n\tvar writeCounter []*atomic.Int64\n\tcountInbound := inbound != \"\" && s.inbounds[inbound]\n\tcountOutbound := outbound != \"\" && s.outbounds[outbound]\n\tcountUser := user != \"\" && s.users[user]\n\tif !countInbound && !countOutbound && !countUser {\n\t\treturn conn\n\t}\n\ts.access.Lock()\n\tif countInbound {\n\t\treadCounter = append(readCounter, s.loadOrCreateCounter(\"inbound>>>\"+inbound+\">>>traffic>>>uplink\"))\n\t\twriteCounter = append(writeCounter, s.loadOrCreateCounter(\"inbound>>>\"+inbound+\">>>traffic>>>downlink\"))\n\t}\n\tif countOutbound {\n\t\treadCounter = append(readCounter, s.loadOrCreateCounter(\"outbound>>>\"+outbound+\">>>traffic>>>uplink\"))\n\t\twriteCounter = append(writeCounter, s.loadOrCreateCounter(\"outbound>>>\"+outbound+\">>>traffic>>>downlink\"))\n\t}\n\tif countUser {\n\t\treadCounter = append(readCounter, s.loadOrCreateCounter(\"user>>>\"+user+\">>>traffic>>>uplink\"))\n\t\twriteCounter = append(writeCounter, s.loadOrCreateCounter(\"user>>>\"+user+\">>>traffic>>>downlink\"))\n\t}\n\ts.access.Unlock()\n\treturn bufio.NewInt64CounterPacketConn(conn, readCounter, nil, writeCounter, nil)\n}\n\nfunc (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {\n\ts.access.Lock()\n\tcounter, loaded := s.counters[request.Name]\n\ts.access.Unlock()\n\tif !loaded {\n\t\treturn nil, E.New(request.Name, \" not found.\")\n\t}\n\tvar value int64\n\tif request.Reset_ {\n\t\tvalue = counter.Swap(0)\n\t} else {\n\t\tvalue = counter.Load()\n\t}\n\treturn &GetStatsResponse{Stat: &Stat{Name: request.Name, Value: value}}, nil\n}\n\nfunc (s *StatsService) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {\n\tvar response QueryStatsResponse\n\ts.access.Lock()\n\tdefer s.access.Unlock()\n\tif len(request.Patterns) == 0 {\n\t\tfor name, counter := range s.counters {\n\t\t\tvar value int64\n\t\t\tif request.Reset_ {\n\t\t\t\tvalue = counter.Swap(0)\n\t\t\t} else {\n\t\t\t\tvalue = counter.Load()\n\t\t\t}\n\t\t\tresponse.Stat = append(response.Stat, &Stat{Name: name, Value: value})\n\t\t}\n\t} else if request.Regexp {\n\t\tmatchers := make([]*regexp.Regexp, 0, len(request.Patterns))\n\t\tfor _, pattern := range request.Patterns {\n\t\t\tmatcher, err := regexp.Compile(pattern)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tmatchers = append(matchers, matcher)\n\t\t}\n\t\tfor name, counter := range s.counters {\n\t\t\tfor _, matcher := range matchers {\n\t\t\t\tif matcher.MatchString(name) {\n\t\t\t\t\tvar value int64\n\t\t\t\t\tif request.Reset_ {\n\t\t\t\t\t\tvalue = counter.Swap(0)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvalue = counter.Load()\n\t\t\t\t\t}\n\t\t\t\t\tresponse.Stat = append(response.Stat, &Stat{Name: name, Value: value})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor name, counter := range s.counters {\n\t\t\tfor _, matcher := range request.Patterns {\n\t\t\t\tif strings.Contains(name, matcher) {\n\t\t\t\t\tvar value int64\n\t\t\t\t\tif request.Reset_ {\n\t\t\t\t\t\tvalue = counter.Swap(0)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvalue = counter.Load()\n\t\t\t\t\t}\n\t\t\t\t\tresponse.Stat = append(response.Stat, &Stat{Name: name, Value: value})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn &response, nil\n}\n\nfunc (s *StatsService) GetSysStats(ctx context.Context, request *SysStatsRequest) (*SysStatsResponse, error) {\n\tvar rtm runtime.MemStats\n\truntime.ReadMemStats(&rtm)\n\tresponse := &SysStatsResponse{\n\t\tUptime:       uint32(time.Since(s.createdAt).Seconds()),\n\t\tNumGoroutine: uint32(runtime.NumGoroutine()),\n\t\tAlloc:        rtm.Alloc,\n\t\tTotalAlloc:   rtm.TotalAlloc,\n\t\tSys:          rtm.Sys,\n\t\tMallocs:      rtm.Mallocs,\n\t\tFrees:        rtm.Frees,\n\t\tLiveObjects:  rtm.Mallocs - rtm.Frees,\n\t\tNumGC:        rtm.NumGC,\n\t\tPauseTotalNs: rtm.PauseTotalNs,\n\t}\n\n\treturn response, nil\n}\n\nfunc (s *StatsService) mustEmbedUnimplementedStatsServiceServer() {\n}\n\n//nolint:staticcheck\nfunc (s *StatsService) loadOrCreateCounter(name string) *atomic.Int64 {\n\tcounter, loaded := s.counters[name]\n\tif loaded {\n\t\treturn counter\n\t}\n\tcounter = &atomic.Int64{}\n\ts.counters[name] = counter\n\treturn counter\n}\n"
  },
  {
    "path": "experimental/v2rayapi/stats.pb.go",
    "content": "package v2rayapi\n\nimport (\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype GetStatsRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Name of the stat counter.\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Whether or not to reset the counter to fetching its value.\n\tReset_        bool `protobuf:\"varint,2,opt,name=reset,proto3\" json:\"reset,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetStatsRequest) Reset() {\n\t*x = GetStatsRequest{}\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetStatsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetStatsRequest) ProtoMessage() {}\n\nfunc (x *GetStatsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetStatsRequest.ProtoReflect.Descriptor instead.\nfunc (*GetStatsRequest) Descriptor() ([]byte, []int) {\n\treturn file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *GetStatsRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *GetStatsRequest) GetReset_() bool {\n\tif x != nil {\n\t\treturn x.Reset_\n\t}\n\treturn false\n}\n\ntype Stat struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tValue         int64                  `protobuf:\"varint,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Stat) Reset() {\n\t*x = Stat{}\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Stat) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Stat) ProtoMessage() {}\n\nfunc (x *Stat) ProtoReflect() protoreflect.Message {\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Stat.ProtoReflect.Descriptor instead.\nfunc (*Stat) Descriptor() ([]byte, []int) {\n\treturn file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Stat) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Stat) GetValue() int64 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\ntype GetStatsResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tStat          *Stat                  `protobuf:\"bytes,1,opt,name=stat,proto3\" json:\"stat,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetStatsResponse) Reset() {\n\t*x = GetStatsResponse{}\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetStatsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetStatsResponse) ProtoMessage() {}\n\nfunc (x *GetStatsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetStatsResponse.ProtoReflect.Descriptor instead.\nfunc (*GetStatsResponse) Descriptor() ([]byte, []int) {\n\treturn file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *GetStatsResponse) GetStat() *Stat {\n\tif x != nil {\n\t\treturn x.Stat\n\t}\n\treturn nil\n}\n\ntype QueryStatsRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Deprecated, use Patterns instead\n\tPattern       string   `protobuf:\"bytes,1,opt,name=pattern,proto3\" json:\"pattern,omitempty\"`\n\tReset_        bool     `protobuf:\"varint,2,opt,name=reset,proto3\" json:\"reset,omitempty\"`\n\tPatterns      []string `protobuf:\"bytes,3,rep,name=patterns,proto3\" json:\"patterns,omitempty\"`\n\tRegexp        bool     `protobuf:\"varint,4,opt,name=regexp,proto3\" json:\"regexp,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *QueryStatsRequest) Reset() {\n\t*x = QueryStatsRequest{}\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *QueryStatsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*QueryStatsRequest) ProtoMessage() {}\n\nfunc (x *QueryStatsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use QueryStatsRequest.ProtoReflect.Descriptor instead.\nfunc (*QueryStatsRequest) Descriptor() ([]byte, []int) {\n\treturn file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *QueryStatsRequest) GetPattern() string {\n\tif x != nil {\n\t\treturn x.Pattern\n\t}\n\treturn \"\"\n}\n\nfunc (x *QueryStatsRequest) GetReset_() bool {\n\tif x != nil {\n\t\treturn x.Reset_\n\t}\n\treturn false\n}\n\nfunc (x *QueryStatsRequest) GetPatterns() []string {\n\tif x != nil {\n\t\treturn x.Patterns\n\t}\n\treturn nil\n}\n\nfunc (x *QueryStatsRequest) GetRegexp() bool {\n\tif x != nil {\n\t\treturn x.Regexp\n\t}\n\treturn false\n}\n\ntype QueryStatsResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tStat          []*Stat                `protobuf:\"bytes,1,rep,name=stat,proto3\" json:\"stat,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *QueryStatsResponse) Reset() {\n\t*x = QueryStatsResponse{}\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *QueryStatsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*QueryStatsResponse) ProtoMessage() {}\n\nfunc (x *QueryStatsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use QueryStatsResponse.ProtoReflect.Descriptor instead.\nfunc (*QueryStatsResponse) Descriptor() ([]byte, []int) {\n\treturn file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *QueryStatsResponse) GetStat() []*Stat {\n\tif x != nil {\n\t\treturn x.Stat\n\t}\n\treturn nil\n}\n\ntype SysStatsRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SysStatsRequest) Reset() {\n\t*x = SysStatsRequest{}\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SysStatsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SysStatsRequest) ProtoMessage() {}\n\nfunc (x *SysStatsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SysStatsRequest.ProtoReflect.Descriptor instead.\nfunc (*SysStatsRequest) Descriptor() ([]byte, []int) {\n\treturn file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{5}\n}\n\ntype SysStatsResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNumGoroutine  uint32                 `protobuf:\"varint,1,opt,name=NumGoroutine,proto3\" json:\"NumGoroutine,omitempty\"`\n\tNumGC         uint32                 `protobuf:\"varint,2,opt,name=NumGC,proto3\" json:\"NumGC,omitempty\"`\n\tAlloc         uint64                 `protobuf:\"varint,3,opt,name=Alloc,proto3\" json:\"Alloc,omitempty\"`\n\tTotalAlloc    uint64                 `protobuf:\"varint,4,opt,name=TotalAlloc,proto3\" json:\"TotalAlloc,omitempty\"`\n\tSys           uint64                 `protobuf:\"varint,5,opt,name=Sys,proto3\" json:\"Sys,omitempty\"`\n\tMallocs       uint64                 `protobuf:\"varint,6,opt,name=Mallocs,proto3\" json:\"Mallocs,omitempty\"`\n\tFrees         uint64                 `protobuf:\"varint,7,opt,name=Frees,proto3\" json:\"Frees,omitempty\"`\n\tLiveObjects   uint64                 `protobuf:\"varint,8,opt,name=LiveObjects,proto3\" json:\"LiveObjects,omitempty\"`\n\tPauseTotalNs  uint64                 `protobuf:\"varint,9,opt,name=PauseTotalNs,proto3\" json:\"PauseTotalNs,omitempty\"`\n\tUptime        uint32                 `protobuf:\"varint,10,opt,name=Uptime,proto3\" json:\"Uptime,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SysStatsResponse) Reset() {\n\t*x = SysStatsResponse{}\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SysStatsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SysStatsResponse) ProtoMessage() {}\n\nfunc (x *SysStatsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_experimental_v2rayapi_stats_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SysStatsResponse.ProtoReflect.Descriptor instead.\nfunc (*SysStatsResponse) Descriptor() ([]byte, []int) {\n\treturn file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *SysStatsResponse) GetNumGoroutine() uint32 {\n\tif x != nil {\n\t\treturn x.NumGoroutine\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetNumGC() uint32 {\n\tif x != nil {\n\t\treturn x.NumGC\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetAlloc() uint64 {\n\tif x != nil {\n\t\treturn x.Alloc\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetTotalAlloc() uint64 {\n\tif x != nil {\n\t\treturn x.TotalAlloc\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetSys() uint64 {\n\tif x != nil {\n\t\treturn x.Sys\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetMallocs() uint64 {\n\tif x != nil {\n\t\treturn x.Mallocs\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetFrees() uint64 {\n\tif x != nil {\n\t\treturn x.Frees\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetLiveObjects() uint64 {\n\tif x != nil {\n\t\treturn x.LiveObjects\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetPauseTotalNs() uint64 {\n\tif x != nil {\n\t\treturn x.PauseTotalNs\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetUptime() uint32 {\n\tif x != nil {\n\t\treturn x.Uptime\n\t}\n\treturn 0\n}\n\nvar File_experimental_v2rayapi_stats_proto protoreflect.FileDescriptor\n\nconst file_experimental_v2rayapi_stats_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"!experimental/v2rayapi/stats.proto\\x12\\x15experimental.v2rayapi\\\";\\n\" +\n\t\"\\x0fGetStatsRequest\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x14\\n\" +\n\t\"\\x05reset\\x18\\x02 \\x01(\\bR\\x05reset\\\"0\\n\" +\n\t\"\\x04Stat\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\x03R\\x05value\\\"C\\n\" +\n\t\"\\x10GetStatsResponse\\x12/\\n\" +\n\t\"\\x04stat\\x18\\x01 \\x01(\\v2\\x1b.experimental.v2rayapi.StatR\\x04stat\\\"w\\n\" +\n\t\"\\x11QueryStatsRequest\\x12\\x18\\n\" +\n\t\"\\apattern\\x18\\x01 \\x01(\\tR\\apattern\\x12\\x14\\n\" +\n\t\"\\x05reset\\x18\\x02 \\x01(\\bR\\x05reset\\x12\\x1a\\n\" +\n\t\"\\bpatterns\\x18\\x03 \\x03(\\tR\\bpatterns\\x12\\x16\\n\" +\n\t\"\\x06regexp\\x18\\x04 \\x01(\\bR\\x06regexp\\\"E\\n\" +\n\t\"\\x12QueryStatsResponse\\x12/\\n\" +\n\t\"\\x04stat\\x18\\x01 \\x03(\\v2\\x1b.experimental.v2rayapi.StatR\\x04stat\\\"\\x11\\n\" +\n\t\"\\x0fSysStatsRequest\\\"\\xa2\\x02\\n\" +\n\t\"\\x10SysStatsResponse\\x12\\\"\\n\" +\n\t\"\\fNumGoroutine\\x18\\x01 \\x01(\\rR\\fNumGoroutine\\x12\\x14\\n\" +\n\t\"\\x05NumGC\\x18\\x02 \\x01(\\rR\\x05NumGC\\x12\\x14\\n\" +\n\t\"\\x05Alloc\\x18\\x03 \\x01(\\x04R\\x05Alloc\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"TotalAlloc\\x18\\x04 \\x01(\\x04R\\n\" +\n\t\"TotalAlloc\\x12\\x10\\n\" +\n\t\"\\x03Sys\\x18\\x05 \\x01(\\x04R\\x03Sys\\x12\\x18\\n\" +\n\t\"\\aMallocs\\x18\\x06 \\x01(\\x04R\\aMallocs\\x12\\x14\\n\" +\n\t\"\\x05Frees\\x18\\a \\x01(\\x04R\\x05Frees\\x12 \\n\" +\n\t\"\\vLiveObjects\\x18\\b \\x01(\\x04R\\vLiveObjects\\x12\\\"\\n\" +\n\t\"\\fPauseTotalNs\\x18\\t \\x01(\\x04R\\fPauseTotalNs\\x12\\x16\\n\" +\n\t\"\\x06Uptime\\x18\\n\" +\n\t\" \\x01(\\rR\\x06Uptime2\\xb4\\x02\\n\" +\n\t\"\\fStatsService\\x12]\\n\" +\n\t\"\\bGetStats\\x12&.experimental.v2rayapi.GetStatsRequest\\x1a'.experimental.v2rayapi.GetStatsResponse\\\"\\x00\\x12c\\n\" +\n\t\"\\n\" +\n\t\"QueryStats\\x12(.experimental.v2rayapi.QueryStatsRequest\\x1a).experimental.v2rayapi.QueryStatsResponse\\\"\\x00\\x12`\\n\" +\n\t\"\\vGetSysStats\\x12&.experimental.v2rayapi.SysStatsRequest\\x1a'.experimental.v2rayapi.SysStatsResponse\\\"\\x00B4Z2github.com/sagernet/sing-box/experimental/v2rayapib\\x06proto3\"\n\nvar (\n\tfile_experimental_v2rayapi_stats_proto_rawDescOnce sync.Once\n\tfile_experimental_v2rayapi_stats_proto_rawDescData []byte\n)\n\nfunc file_experimental_v2rayapi_stats_proto_rawDescGZIP() []byte {\n\tfile_experimental_v2rayapi_stats_proto_rawDescOnce.Do(func() {\n\t\tfile_experimental_v2rayapi_stats_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_experimental_v2rayapi_stats_proto_rawDesc), len(file_experimental_v2rayapi_stats_proto_rawDesc)))\n\t})\n\treturn file_experimental_v2rayapi_stats_proto_rawDescData\n}\n\nvar (\n\tfile_experimental_v2rayapi_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 7)\n\tfile_experimental_v2rayapi_stats_proto_goTypes  = []any{\n\t\t(*GetStatsRequest)(nil),    // 0: experimental.v2rayapi.GetStatsRequest\n\t\t(*Stat)(nil),               // 1: experimental.v2rayapi.Stat\n\t\t(*GetStatsResponse)(nil),   // 2: experimental.v2rayapi.GetStatsResponse\n\t\t(*QueryStatsRequest)(nil),  // 3: experimental.v2rayapi.QueryStatsRequest\n\t\t(*QueryStatsResponse)(nil), // 4: experimental.v2rayapi.QueryStatsResponse\n\t\t(*SysStatsRequest)(nil),    // 5: experimental.v2rayapi.SysStatsRequest\n\t\t(*SysStatsResponse)(nil),   // 6: experimental.v2rayapi.SysStatsResponse\n\t}\n)\n\nvar file_experimental_v2rayapi_stats_proto_depIdxs = []int32{\n\t1, // 0: experimental.v2rayapi.GetStatsResponse.stat:type_name -> experimental.v2rayapi.Stat\n\t1, // 1: experimental.v2rayapi.QueryStatsResponse.stat:type_name -> experimental.v2rayapi.Stat\n\t0, // 2: experimental.v2rayapi.StatsService.GetStats:input_type -> experimental.v2rayapi.GetStatsRequest\n\t3, // 3: experimental.v2rayapi.StatsService.QueryStats:input_type -> experimental.v2rayapi.QueryStatsRequest\n\t5, // 4: experimental.v2rayapi.StatsService.GetSysStats:input_type -> experimental.v2rayapi.SysStatsRequest\n\t2, // 5: experimental.v2rayapi.StatsService.GetStats:output_type -> experimental.v2rayapi.GetStatsResponse\n\t4, // 6: experimental.v2rayapi.StatsService.QueryStats:output_type -> experimental.v2rayapi.QueryStatsResponse\n\t6, // 7: experimental.v2rayapi.StatsService.GetSysStats:output_type -> experimental.v2rayapi.SysStatsResponse\n\t5, // [5:8] is the sub-list for method output_type\n\t2, // [2:5] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_experimental_v2rayapi_stats_proto_init() }\nfunc file_experimental_v2rayapi_stats_proto_init() {\n\tif File_experimental_v2rayapi_stats_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_experimental_v2rayapi_stats_proto_rawDesc), len(file_experimental_v2rayapi_stats_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   7,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_experimental_v2rayapi_stats_proto_goTypes,\n\t\tDependencyIndexes: file_experimental_v2rayapi_stats_proto_depIdxs,\n\t\tMessageInfos:      file_experimental_v2rayapi_stats_proto_msgTypes,\n\t}.Build()\n\tFile_experimental_v2rayapi_stats_proto = out.File\n\tfile_experimental_v2rayapi_stats_proto_goTypes = nil\n\tfile_experimental_v2rayapi_stats_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "experimental/v2rayapi/stats.proto",
    "content": "syntax = \"proto3\";\n\npackage experimental.v2rayapi;\noption go_package = \"github.com/sagernet/sing-box/experimental/v2rayapi\";\n\nmessage GetStatsRequest {\n  // Name of the stat counter.\n  string name = 1;\n  // Whether or not to reset the counter to fetching its value.\n  bool reset = 2;\n}\n\nmessage Stat {\n  string name = 1;\n  int64 value = 2;\n}\n\nmessage GetStatsResponse {\n  Stat stat = 1;\n}\n\nmessage QueryStatsRequest {\n  // Deprecated, use Patterns instead\n  string pattern = 1;\n  bool reset = 2;\n  repeated string patterns = 3;\n  bool regexp = 4;\n}\n\nmessage QueryStatsResponse {\n  repeated Stat stat = 1;\n}\n\nmessage SysStatsRequest {}\n\nmessage SysStatsResponse {\n  uint32 NumGoroutine = 1;\n  uint32 NumGC = 2;\n  uint64 Alloc = 3;\n  uint64 TotalAlloc = 4;\n  uint64 Sys = 5;\n  uint64 Mallocs = 6;\n  uint64 Frees = 7;\n  uint64 LiveObjects = 8;\n  uint64 PauseTotalNs = 9;\n  uint32 Uptime = 10;\n}\n\nservice StatsService {\n  rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {}\n  rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {}\n  rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {}\n}"
  },
  {
    "path": "experimental/v2rayapi/stats_grpc.pb.go",
    "content": "package v2rayapi\n\nimport (\n\tcontext \"context\"\n\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tStatsService_GetStats_FullMethodName    = \"/experimental.v2rayapi.StatsService/GetStats\"\n\tStatsService_QueryStats_FullMethodName  = \"/experimental.v2rayapi.StatsService/QueryStats\"\n\tStatsService_GetSysStats_FullMethodName = \"/experimental.v2rayapi.StatsService/GetSysStats\"\n)\n\n// StatsServiceClient is the client API for StatsService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype StatsServiceClient interface {\n\tGetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error)\n\tQueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error)\n\tGetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error)\n}\n\ntype statsServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewStatsServiceClient(cc grpc.ClientConnInterface) StatsServiceClient {\n\treturn &statsServiceClient{cc}\n}\n\nfunc (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetStatsResponse)\n\terr := c.cc.Invoke(ctx, StatsService_GetStats_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(QueryStatsResponse)\n\terr := c.cc.Invoke(ctx, StatsService_QueryStats_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(SysStatsResponse)\n\terr := c.cc.Invoke(ctx, StatsService_GetSysStats_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// StatsServiceServer is the server API for StatsService service.\n// All implementations must embed UnimplementedStatsServiceServer\n// for forward compatibility.\ntype StatsServiceServer interface {\n\tGetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error)\n\tQueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error)\n\tGetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error)\n\tmustEmbedUnimplementedStatsServiceServer()\n}\n\n// UnimplementedStatsServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedStatsServiceServer struct{}\n\nfunc (UnimplementedStatsServiceServer) GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetStats not implemented\")\n}\n\nfunc (UnimplementedStatsServiceServer) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method QueryStats not implemented\")\n}\n\nfunc (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetSysStats not implemented\")\n}\nfunc (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {}\nfunc (UnimplementedStatsServiceServer) testEmbeddedByValue()                      {}\n\n// UnsafeStatsServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to StatsServiceServer will\n// result in compilation errors.\ntype UnsafeStatsServiceServer interface {\n\tmustEmbedUnimplementedStatsServiceServer()\n}\n\nfunc RegisterStatsServiceServer(s grpc.ServiceRegistrar, srv StatsServiceServer) {\n\t// If the following call panics, it indicates UnimplementedStatsServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&StatsService_ServiceDesc, srv)\n}\n\nfunc _StatsService_GetStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetStatsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StatsServiceServer).GetStats(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StatsService_GetStats_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StatsServiceServer).GetStats(ctx, req.(*GetStatsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StatsService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(QueryStatsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StatsServiceServer).QueryStats(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StatsService_QueryStats_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StatsServiceServer).QueryStats(ctx, req.(*QueryStatsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StatsService_GetSysStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SysStatsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StatsServiceServer).GetSysStats(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StatsService_GetSysStats_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StatsServiceServer).GetSysStats(ctx, req.(*SysStatsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// StatsService_ServiceDesc is the grpc.ServiceDesc for StatsService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar StatsService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"experimental.v2rayapi.StatsService\",\n\tHandlerType: (*StatsServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetStats\",\n\t\t\tHandler:    _StatsService_GetStats_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"QueryStats\",\n\t\t\tHandler:    _StatsService_QueryStats_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetSysStats\",\n\t\t\tHandler:    _StatsService_GetSysStats_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"experimental/v2rayapi/stats.proto\",\n}\n"
  },
  {
    "path": "experimental/v2rayapi.go",
    "content": "package experimental\n\nimport (\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n)\n\ntype V2RayServerConstructor = func(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error)\n\nvar v2rayServerConstructor V2RayServerConstructor\n\nfunc RegisterV2RayServerConstructor(constructor V2RayServerConstructor) {\n\tv2rayServerConstructor = constructor\n}\n\nfunc NewV2RayServer(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) {\n\tif v2rayServerConstructor == nil {\n\t\treturn nil, os.ErrInvalid\n\t}\n\treturn v2rayServerConstructor(logger, options)\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/sagernet/sing-box\n\ngo 1.24.7\n\nrequire (\n\tgithub.com/anthropics/anthropic-sdk-go v1.26.0\n\tgithub.com/anytls/sing-anytls v0.0.11\n\tgithub.com/caddyserver/certmagic v0.25.2\n\tgithub.com/coder/websocket v1.8.14\n\tgithub.com/cretz/bine v0.2.0\n\tgithub.com/database64128/tfo-go/v2 v2.3.2\n\tgithub.com/go-chi/chi/v5 v5.2.5\n\tgithub.com/go-chi/render v1.0.3\n\tgithub.com/godbus/dbus/v5 v5.2.2\n\tgithub.com/gofrs/uuid/v5 v5.4.0\n\tgithub.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91\n\tgithub.com/jsimonetti/rtnetlink v1.4.0\n\tgithub.com/keybase/go-keychain v0.0.1\n\tgithub.com/libdns/acmedns v0.5.0\n\tgithub.com/libdns/alidns v1.0.6\n\tgithub.com/libdns/cloudflare v0.2.2\n\tgithub.com/logrusorgru/aurora v2.0.3+incompatible\n\tgithub.com/mdlayher/netlink v1.9.0\n\tgithub.com/metacubex/utls v1.8.4\n\tgithub.com/mholt/acmez/v3 v3.1.6\n\tgithub.com/miekg/dns v1.1.72\n\tgithub.com/openai/openai-go/v3 v3.26.0\n\tgithub.com/oschwald/maxminddb-golang v1.13.1\n\tgithub.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1\n\tgithub.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a\n\tgithub.com/sagernet/cors v1.2.1\n\tgithub.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc\n\tgithub.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc\n\tgithub.com/sagernet/fswatch v0.1.1\n\tgithub.com/sagernet/gomobile v0.1.12\n\tgithub.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1\n\tgithub.com/sagernet/quic-go v0.59.0-sing-box-mod.4\n\tgithub.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde\n\tgithub.com/sagernet/sing-mux v0.3.4\n\tgithub.com/sagernet/sing-quic v0.6.0\n\tgithub.com/sagernet/sing-shadowsocks v0.2.8\n\tgithub.com/sagernet/sing-shadowsocks2 v0.2.1\n\tgithub.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11\n\tgithub.com/sagernet/sing-tun v0.8.4-0.20260315091454-bbe21100c226\n\tgithub.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1\n\tgithub.com/sagernet/smux v1.5.50-sing-box-mod.1\n\tgithub.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260311131347-f88b27eeb76e\n\tgithub.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c\n\tgithub.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854\n\tgithub.com/spf13/cobra v1.10.2\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/vishvananda/netns v0.0.5\n\tgo.uber.org/zap v1.27.1\n\tgo4.org/netipx v0.0.0-20231129151722-fdeea329fbba\n\tgolang.org/x/crypto v0.48.0\n\tgolang.org/x/exp v0.0.0-20251219203646-944ab1f22d93\n\tgolang.org/x/mod v0.33.0\n\tgolang.org/x/net v0.50.0\n\tgolang.org/x/sys v0.41.0\n\tgolang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10\n\tgoogle.golang.org/grpc v1.79.1\n\tgoogle.golang.org/protobuf v1.36.11\n\thowett.net/plist v1.0.1\n)\n\nrequire (\n\tfilippo.io/edwards25519 v1.1.0 // indirect\n\tgithub.com/ajg/form v1.5.1 // indirect\n\tgithub.com/akutz/memconn v0.1.0 // indirect\n\tgithub.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect\n\tgithub.com/andybalholm/brotli v1.1.0 // indirect\n\tgithub.com/caddyserver/zerossl v0.1.5 // indirect\n\tgithub.com/cenkalti/backoff/v4 v4.3.0 // indirect\n\tgithub.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect\n\tgithub.com/database64128/netx-go v0.1.1 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect\n\tgithub.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect\n\tgithub.com/ebitengine/purego v0.9.1 // indirect\n\tgithub.com/florianl/go-nfqueue/v2 v2.0.2 // indirect\n\tgithub.com/fsnotify/fsnotify v1.7.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.7.0 // indirect\n\tgithub.com/gaissmai/bart v0.18.0 // indirect\n\tgithub.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect\n\tgithub.com/go-ole/go-ole v1.3.0 // indirect\n\tgithub.com/gobwas/httphead v0.1.0 // indirect\n\tgithub.com/gobwas/pool v0.2.1 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/go-querystring v1.1.0 // indirect\n\tgithub.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/hashicorp/yamux v0.1.2 // indirect\n\tgithub.com/hdevalence/ed25519consensus v0.2.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/klauspost/compress v1.18.0 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.3.0 // indirect\n\tgithub.com/libdns/libdns v1.1.1 // indirect\n\tgithub.com/mdlayher/socket v0.5.1 // indirect\n\tgithub.com/mitchellh/go-ps v1.0.0 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.21 // indirect\n\tgithub.com/pires/go-proxyproto v0.8.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/prometheus-community/pro-bing v0.4.0 // indirect\n\tgithub.com/quic-go/qpack v0.6.0 // indirect\n\tgithub.com/safchain/ethtool v0.3.0 // indirect\n\tgithub.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect\n\tgithub.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect\n\tgithub.com/sagernet/nftables v0.3.0-beta.4 // indirect\n\tgithub.com/spf13/pflag v1.0.9 // indirect\n\tgithub.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect\n\tgithub.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect\n\tgithub.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect\n\tgithub.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect\n\tgithub.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect\n\tgithub.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect\n\tgithub.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect\n\tgithub.com/tidwall/gjson v1.18.0 // indirect\n\tgithub.com/tidwall/match v1.1.1 // indirect\n\tgithub.com/tidwall/pretty v1.2.1 // indirect\n\tgithub.com/tidwall/sjson v1.2.5 // indirect\n\tgithub.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgithub.com/zeebo/blake3 v0.2.4 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap/exp v0.3.0 // indirect\n\tgo4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect\n\tgolang.org/x/oauth2 v0.34.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/term v0.40.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/time v0.11.0 // indirect\n\tgolang.org/x/tools v0.42.0 // indirect\n\tgolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect\n\tgolang.zx2c4.com/wireguard/windows v0.5.3 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tlukechampine.com/blake3 v1.3.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=\ncode.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=\nfilippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngithub.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=\ngithub.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=\ngithub.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=\ngithub.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=\ngithub.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=\ngithub.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=\ngithub.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=\ngithub.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=\ngithub.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY=\ngithub.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=\ngithub.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=\ngithub.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=\ngithub.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc=\ngithub.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg=\ngithub.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=\ngithub.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=\ngithub.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=\ngithub.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=\ngithub.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=\ngithub.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=\ngithub.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=\ngithub.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=\ngithub.com/database64128/netx-go v0.1.1 h1:dT5LG7Gs7zFZBthFBbzWE6K8wAHjSNAaK7wCYZT7NzM=\ngithub.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc=\ngithub.com/database64128/tfo-go/v2 v2.3.2 h1:UhZMKiMq3swZGUiETkLBDzQnZBPSAeBMClpJGlnJ5Fw=\ngithub.com/database64128/tfo-go/v2 v2.3.2/go.mod h1:GC3uB5oa4beGpCUbRb2ZOWP73bJJFmMyAVgQSO7r724=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=\ngithub.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=\ngithub.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=\ngithub.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=\ngithub.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=\ngithub.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=\ngithub.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=\ngithub.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=\ngithub.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE=\ngithub.com/florianl/go-nfqueue/v2 v2.0.2/go.mod h1:VA09+iPOT43OMoCKNfXHyzujQUty2xmzyCRkBOlmabc=\ngithub.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=\ngithub.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=\ngithub.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=\ngithub.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=\ngithub.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo=\ngithub.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY=\ngithub.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=\ngithub.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=\ngithub.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=\ngithub.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=\ngithub.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=\ngithub.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=\ngithub.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=\ngithub.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I=\ngithub.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=\ngithub.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=\ngithub.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=\ngithub.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=\ngithub.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=\ngithub.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=\ngithub.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=\ngithub.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=\ngithub.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=\ngithub.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=\ngithub.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=\ngithub.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=\ngithub.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=\ngithub.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=\ngithub.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=\ngithub.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=\ngithub.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 h1:u9i04mGE3iliBh0EFuWaKsmcwrLacqGmq1G3XoaM7gY=\ngithub.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo=\ngithub.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=\ngithub.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=\ngithub.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=\ngithub.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=\ngithub.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=\ngithub.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=\ngithub.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=\ngithub.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ=\ngithub.com/libdns/acmedns v0.5.0 h1:5pRtmUj4Lb/QkNJSl1xgOGBUJTWW7RjpNaIhjpDXjPE=\ngithub.com/libdns/acmedns v0.5.0/go.mod h1:X7UAFP1Ep9NpTwWpVlrZzJLR7epynAy0wrIxSPFgKjQ=\ngithub.com/libdns/alidns v1.0.6 h1:/Ii428ty6WHFJmE24rZxq2taq++gh7rf9jhgLfp8PmM=\ngithub.com/libdns/alidns v1.0.6/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec=\ngithub.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI=\ngithub.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=\ngithub.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=\ngithub.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=\ngithub.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=\ngithub.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=\ngithub.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=\ngithub.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=\ngithub.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=\ngithub.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=\ngithub.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=\ngithub.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=\ngithub.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=\ngithub.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY=\ngithub.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=\ngithub.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=\ngithub.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=\ngithub.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=\ngithub.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=\ngithub.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=\ngithub.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xxJEnQE=\ngithub.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=\ngithub.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=\ngithub.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=\ngithub.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=\ngithub.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=\ngithub.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=\ngithub.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=\ngithub.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=\ngithub.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=\ngithub.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=\ngithub.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=\ngithub.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4=\ngithub.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU=\ngithub.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=\ngithub.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=\ngithub.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=\ngithub.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=\ngithub.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc h1:YK7PwJT0irRAEui9ASdXSxcE2BOVQipWMF/A1Ogt+7c=\ngithub.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=\ngithub.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc h1:EJPHOqk23IuBsTjXK9OXqkNxPbKOBWKRmviQoCcriAs=\ngithub.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc/go.mod h1:8aty0RW96DrJSMWXO6bRPMBJEjuqq5JWiOIi4bCRzFA=\ngithub.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9 h1:Qi0IKBpoPP3qZqIXuOKMsT2dv+l/MLWMyBHDMLRw2EA=\ngithub.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=\ngithub.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260309101654-0cbdcfddded9 h1:p+wCMjOhj46SpSD/AJeTGgkCcbyA76FyH631XZatyU8=\ngithub.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=\ngithub.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260309101654-0cbdcfddded9 h1:Y7lWrZwEhC/HX8Pb5C92CrQihuaE7hrHmWB2ykst3iQ=\ngithub.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=\ngithub.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:3Ggy5wiyjA6t+aVVPnXlSEIVj9zkxd4ybH3NsvsNefs=\ngithub.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=\ngithub.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260309101654-0cbdcfddded9 h1:DuFTCnZloblY+7olXiZoRdueWfxi34EV5UheTFKM2rA=\ngithub.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=\ngithub.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:x/6T2gjpLw9yNdCVR6xBlzMUzED9fxNFNt6U6A6SOh8=\ngithub.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=\ngithub.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9 h1:Lx9PExM70rg8aNxPm0JPeSr5SWC3yFiCz4wIq86ugx8=\ngithub.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=\ngithub.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:BTEpw7/vKR9BNBsHebfpiGHDCPpjVJ3vLIbHNU3VUfM=\ngithub.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=\ngithub.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9 h1:hdEph9nQXRnKwc/lIDwo15rmzbC6znXF5jJWHPN1Fiw=\ngithub.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=\ngithub.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260309101654-0cbdcfddded9 h1:Iq++oYV7dtRJHTpu8yclHJdn+1oj2t1e84/YpdXYWW8=\ngithub.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=\ngithub.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260309101654-0cbdcfddded9 h1:Y43fuLL8cgwRHpEKwxh0O3vYp7g/SZGvbkJj3cQ6USA=\ngithub.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=\ngithub.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260309101654-0cbdcfddded9 h1:bX2GJmF0VCC+tBrVAa49YEsmJ4A9dLmwoA6DJUxRtCY=\ngithub.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=\ngithub.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260309101654-0cbdcfddded9 h1:gQTR/2azUCInE0r3kmesZT9xu+x801+BmtDY0d0Tw9Y=\ngithub.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=\ngithub.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260309101654-0cbdcfddded9 h1:X4mP3jlYvxgrKpZLOKMmc/O8T5/zP83/23pgfQOc3tY=\ngithub.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=\ngithub.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:c6xj2nXr/65EDiRFddUKQIBQ/b/lAPoH8WFYlgadaPc=\ngithub.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=\ngithub.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260309101654-0cbdcfddded9 h1:ahbl7yjOvGVVNUwk9TcQk+xejVfoYAYFRlhWnby0/YM=\ngithub.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=\ngithub.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260309101654-0cbdcfddded9 h1:JC5Zv5+J85da6g5G56VhdaK53fmo6Os2q/wWi5QlxOw=\ngithub.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=\ngithub.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260309101654-0cbdcfddded9 h1:4bt7Go588BoM4VjNYMxx0MrvbwlFQn3DdRDCM7BmkRo=\ngithub.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk=\ngithub.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260309101654-0cbdcfddded9 h1:E1z0BeLUh8EZfCjIyS9BrfCocZrt+0KPS0bzop3Sxf4=\ngithub.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E=\ngithub.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260309101654-0cbdcfddded9 h1:d8ejxRHO7Vi9JqR/6DxR7RyI/swA2JfDWATR4T7otBw=\ngithub.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8=\ngithub.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260309101654-0cbdcfddded9 h1:iUDVEVu3RxL5ArPIY72BesbuX5zQ1la/ZFwKpQcGc5c=\ngithub.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w=\ngithub.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260309101654-0cbdcfddded9 h1:xB6ikOC/R3n3hjy68EJ0sbZhH4vwEhd6JM9jZ1U2SVY=\ngithub.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0=\ngithub.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260309101654-0cbdcfddded9 h1:mBOuLCPOOMMq8N1+dUM5FqZclqga1+u6fAbPqQcbIhc=\ngithub.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs=\ngithub.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260309101654-0cbdcfddded9 h1:cwPyDfj+ZNFE7kvcWbayQJyeC/KQA16HTXOxgHphL0w=\ngithub.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc=\ngithub.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9 h1:Zk9zG8kt3mXAboclUXQlvvxKQuhnI8u5NdDEl8uotNY=\ngithub.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=\ngithub.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:Lu05srGqddQRMnl1MZtGAReln2yJljeGx9b1IadlMJ8=\ngithub.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=\ngithub.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9 h1:Tk9bDywUmOtc0iMjjCVIwMlAQNsxCy+bK+bTNA0OaBE=\ngithub.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=\ngithub.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260309101654-0cbdcfddded9 h1:tQqDQw3tEHdQpt7NTdAwF3UvZ3CjNIj/IJKMRFmm388=\ngithub.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=\ngithub.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:biUIbI2YxUrcQikEfS/bwPA8NsHp/WO+VZUG4morUmE=\ngithub.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=\ngithub.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=\ngithub.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=\ngithub.com/sagernet/gomobile v0.1.12 h1:XwzjZaclFF96deLqwAgK8gU3w0M2A8qxgDmhV+A0wjg=\ngithub.com/sagernet/gomobile v0.1.12/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=\ngithub.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o=\ngithub.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4=\ngithub.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=\ngithub.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=\ngithub.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=\ngithub.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=\ngithub.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=\ngithub.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=\ngithub.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde h1:RNQzlpnsXIuu1HGts/fIzJ1PR7RhrzaNlU52MDyiX1c=\ngithub.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=\ngithub.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=\ngithub.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=\ngithub.com/sagernet/sing-quic v0.6.0 h1:dhrFnP45wgVKEOT1EvtsToxdzRnHIDIAgj6WHV9pLyM=\ngithub.com/sagernet/sing-quic v0.6.0/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=\ngithub.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=\ngithub.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=\ngithub.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=\ngithub.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=\ngithub.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=\ngithub.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=\ngithub.com/sagernet/sing-tun v0.8.4-0.20260315091454-bbe21100c226 h1:Shy/fsm+pqVq6OkBAWPaOmOiPT/AwoRxQLiV1357Y0Y=\ngithub.com/sagernet/sing-tun v0.8.4-0.20260315091454-bbe21100c226/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=\ngithub.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=\ngithub.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=\ngithub.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=\ngithub.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=\ngithub.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260311131347-f88b27eeb76e h1:Sv1qUhJIidjSTc24XEknovDZnbmVSlAXj8wNVgIfgGo=\ngithub.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260311131347-f88b27eeb76e/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc=\ngithub.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c h1:f9cXNB+IOOPnR8DOLMTpr42jf7naxh5Un5Y09BBf5Cg=\ngithub.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=\ngithub.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=\ngithub.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=\ngithub.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=\ngithub.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=\ngithub.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=\ngithub.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=\ngithub.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=\ngithub.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=\ngithub.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=\ngithub.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=\ngithub.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=\ngithub.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=\ngithub.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=\ngithub.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14=\ngithub.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=\ngithub.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=\ngithub.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=\ngithub.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=\ngithub.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=\ngithub.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=\ngithub.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=\ngithub.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=\ngithub.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=\ngithub.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=\ngithub.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=\ngithub.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngithub.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=\ngithub.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=\ngithub.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=\ngithub.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=\ngithub.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=\ngithub.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=\ngo.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=\ngo.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngo4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=\ngo4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=\ngo4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=\ngo4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=\ngolang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=\ngolang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=\ngolang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=\ngolang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=\ngolang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=\ngolang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=\ngolang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=\ngolang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=\ngolang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=\ngolang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=\ngolang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=\ngolang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=\ngolang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU=\ngolang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=\ngolang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=\ngolang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=\ngoogle.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=\ngopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhowett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=\nhowett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=\nlukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=\nlukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=\nsoftware.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=\nsoftware.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=\n"
  },
  {
    "path": "include/ccm.go",
    "content": "//go:build with_ccm && (!darwin || cgo)\n\npackage include\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter/service\"\n\t\"github.com/sagernet/sing-box/service/ccm\"\n)\n\nfunc registerCCMService(registry *service.Registry) {\n\tccm.RegisterService(registry)\n}\n"
  },
  {
    "path": "include/ccm_stub.go",
    "content": "//go:build !with_ccm\n\npackage include\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/service\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc registerCCMService(registry *service.Registry) {\n\tservice.Register[option.CCMServiceOptions](registry, C.TypeCCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) {\n\t\treturn nil, E.New(`CCM is not included in this build, rebuild with -tags with_CCM`)\n\t})\n}\n"
  },
  {
    "path": "include/ccm_stub_darwin.go",
    "content": "//go:build with_ccm && darwin && !cgo\n\npackage include\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/service\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc registerCCMService(registry *service.Registry) {\n\tservice.Register[option.CCMServiceOptions](registry, C.TypeCCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) {\n\t\treturn nil, E.New(`CCM requires CGO on darwin, rebuild with CGO_ENABLED=1`)\n\t})\n}\n"
  },
  {
    "path": "include/clashapi.go",
    "content": "//go:build with_clash_api\n\npackage include\n\nimport _ \"github.com/sagernet/sing-box/experimental/clashapi\"\n"
  },
  {
    "path": "include/clashapi_stub.go",
    "content": "//go:build !with_clash_api\n\npackage include\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/experimental\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc init() {\n\texperimental.RegisterClashServerConstructor(func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {\n\t\treturn nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`)\n\t})\n}\n"
  },
  {
    "path": "include/dhcp.go",
    "content": "//go:build with_dhcp\n\npackage include\n\nimport (\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport/dhcp\"\n)\n\nfunc registerDHCPTransport(registry *dns.TransportRegistry) {\n\tdhcp.RegisterTransport(registry)\n}\n"
  },
  {
    "path": "include/dhcp_stub.go",
    "content": "//go:build !with_dhcp\n\npackage include\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc registerDHCPTransport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.DHCPDNSServerOptions](registry, C.DNSTypeDHCP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) {\n\t\treturn nil, E.New(`DHCP is not included in this build, rebuild with -tags with_dhcp`)\n\t})\n}\n"
  },
  {
    "path": "include/naive_outbound.go",
    "content": "//go:build with_naive_outbound\n\npackage include\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/protocol/naive\"\n)\n\nfunc registerNaiveOutbound(registry *outbound.Registry) {\n\tnaive.RegisterOutbound(registry)\n}\n"
  },
  {
    "path": "include/naive_outbound_stub.go",
    "content": "//go:build !with_naive_outbound\n\npackage include\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc registerNaiveOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.NaiveOutboundOptions](registry, C.TypeNaive, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveOutboundOptions) (adapter.Outbound, error) {\n\t\treturn nil, E.New(`naive outbound is not included in this build, rebuild with -tags with_naive_outbound`)\n\t})\n}\n"
  },
  {
    "path": "include/ocm.go",
    "content": "//go:build with_ocm\n\npackage include\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter/service\"\n\t\"github.com/sagernet/sing-box/service/ocm\"\n)\n\nfunc registerOCMService(registry *service.Registry) {\n\tocm.RegisterService(registry)\n}\n"
  },
  {
    "path": "include/ocm_stub.go",
    "content": "//go:build !with_ocm\n\npackage include\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/service\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc registerOCMService(registry *service.Registry) {\n\tservice.Register[option.OCMServiceOptions](registry, C.TypeOCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) {\n\t\treturn nil, E.New(`OCM is not included in this build, rebuild with -tags with_ocm`)\n\t})\n}\n"
  },
  {
    "path": "include/oom_killer.go",
    "content": "package include\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter/service\"\n\t\"github.com/sagernet/sing-box/service/oomkiller\"\n)\n\nfunc registerOOMKillerService(registry *service.Registry) {\n\toomkiller.RegisterService(registry)\n}\n"
  },
  {
    "path": "include/quic.go",
    "content": "//go:build with_quic\n\npackage include\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport/quic\"\n\t\"github.com/sagernet/sing-box/protocol/hysteria\"\n\t\"github.com/sagernet/sing-box/protocol/hysteria2\"\n\t_ \"github.com/sagernet/sing-box/protocol/naive/quic\"\n\t\"github.com/sagernet/sing-box/protocol/tuic\"\n\t_ \"github.com/sagernet/sing-box/transport/v2rayquic\"\n)\n\nfunc registerQUICInbounds(registry *inbound.Registry) {\n\thysteria.RegisterInbound(registry)\n\ttuic.RegisterInbound(registry)\n\thysteria2.RegisterInbound(registry)\n}\n\nfunc registerQUICOutbounds(registry *outbound.Registry) {\n\thysteria.RegisterOutbound(registry)\n\ttuic.RegisterOutbound(registry)\n\thysteria2.RegisterOutbound(registry)\n}\n\nfunc registerQUICTransports(registry *dns.TransportRegistry) {\n\tquic.RegisterTransport(registry)\n\tquic.RegisterHTTP3Transport(registry)\n}\n"
  },
  {
    "path": "include/quic_stub.go",
    "content": "//go:build !with_quic\n\npackage include\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/protocol/naive\"\n\t\"github.com/sagernet/sing-box/transport/v2ray\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc init() {\n\tv2ray.RegisterQUICConstructor(\n\t\tfunc(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {\n\t\t\treturn nil, C.ErrQUICNotIncluded\n\t\t},\n\t\tfunc(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {\n\t\t\treturn nil, C.ErrQUICNotIncluded\n\t\t},\n\t)\n}\n\nfunc registerQUICInbounds(registry *inbound.Registry) {\n\tinbound.Register[option.HysteriaInboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) {\n\t\treturn nil, C.ErrQUICNotIncluded\n\t})\n\tinbound.Register[option.TUICInboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) {\n\t\treturn nil, C.ErrQUICNotIncluded\n\t})\n\tinbound.Register[option.Hysteria2InboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) {\n\t\treturn nil, C.ErrQUICNotIncluded\n\t})\n\tnaive.ConfigureHTTP3ListenerFunc = func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error) {\n\t\treturn nil, C.ErrQUICNotIncluded\n\t}\n}\n\nfunc registerQUICOutbounds(registry *outbound.Registry) {\n\toutbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) {\n\t\treturn nil, C.ErrQUICNotIncluded\n\t})\n\toutbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) {\n\t\treturn nil, C.ErrQUICNotIncluded\n\t})\n\toutbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) {\n\t\treturn nil, C.ErrQUICNotIncluded\n\t})\n}\n\nfunc registerQUICTransports(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.RemoteTLSDNSServerOptions](registry, C.DNSTypeQUIC, func(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) {\n\t\treturn nil, C.ErrQUICNotIncluded\n\t})\n\tdns.RegisterTransport[option.RemoteHTTPSDNSServerOptions](registry, C.DNSTypeHTTP3, func(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {\n\t\treturn nil, C.ErrQUICNotIncluded\n\t})\n}\n"
  },
  {
    "path": "include/registry.go",
    "content": "package include\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/endpoint\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/adapter/service\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport\"\n\t\"github.com/sagernet/sing-box/dns/transport/fakeip\"\n\t\"github.com/sagernet/sing-box/dns/transport/hosts\"\n\t\"github.com/sagernet/sing-box/dns/transport/local\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/protocol/anytls\"\n\t\"github.com/sagernet/sing-box/protocol/block\"\n\t\"github.com/sagernet/sing-box/protocol/direct\"\n\t\"github.com/sagernet/sing-box/protocol/group\"\n\t\"github.com/sagernet/sing-box/protocol/http\"\n\t\"github.com/sagernet/sing-box/protocol/mixed\"\n\t\"github.com/sagernet/sing-box/protocol/naive\"\n\t\"github.com/sagernet/sing-box/protocol/redirect\"\n\t\"github.com/sagernet/sing-box/protocol/shadowsocks\"\n\t\"github.com/sagernet/sing-box/protocol/shadowtls\"\n\t\"github.com/sagernet/sing-box/protocol/socks\"\n\t\"github.com/sagernet/sing-box/protocol/ssh\"\n\t\"github.com/sagernet/sing-box/protocol/tor\"\n\t\"github.com/sagernet/sing-box/protocol/trojan\"\n\t\"github.com/sagernet/sing-box/protocol/tun\"\n\t\"github.com/sagernet/sing-box/protocol/vless\"\n\t\"github.com/sagernet/sing-box/protocol/vmess\"\n\t\"github.com/sagernet/sing-box/service/resolved\"\n\t\"github.com/sagernet/sing-box/service/ssmapi\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc Context(ctx context.Context) context.Context {\n\treturn box.Context(ctx, InboundRegistry(), OutboundRegistry(), EndpointRegistry(), DNSTransportRegistry(), ServiceRegistry())\n}\n\nfunc InboundRegistry() *inbound.Registry {\n\tregistry := inbound.NewRegistry()\n\n\ttun.RegisterInbound(registry)\n\tredirect.RegisterRedirect(registry)\n\tredirect.RegisterTProxy(registry)\n\tdirect.RegisterInbound(registry)\n\n\tsocks.RegisterInbound(registry)\n\thttp.RegisterInbound(registry)\n\tmixed.RegisterInbound(registry)\n\n\tshadowsocks.RegisterInbound(registry)\n\tvmess.RegisterInbound(registry)\n\ttrojan.RegisterInbound(registry)\n\tnaive.RegisterInbound(registry)\n\tshadowtls.RegisterInbound(registry)\n\tvless.RegisterInbound(registry)\n\tanytls.RegisterInbound(registry)\n\n\tregisterQUICInbounds(registry)\n\tregisterStubForRemovedInbounds(registry)\n\n\treturn registry\n}\n\nfunc OutboundRegistry() *outbound.Registry {\n\tregistry := outbound.NewRegistry()\n\n\tdirect.RegisterOutbound(registry)\n\n\tblock.RegisterOutbound(registry)\n\n\tgroup.RegisterSelector(registry)\n\tgroup.RegisterURLTest(registry)\n\n\tsocks.RegisterOutbound(registry)\n\thttp.RegisterOutbound(registry)\n\tshadowsocks.RegisterOutbound(registry)\n\tvmess.RegisterOutbound(registry)\n\ttrojan.RegisterOutbound(registry)\n\tregisterNaiveOutbound(registry)\n\ttor.RegisterOutbound(registry)\n\tssh.RegisterOutbound(registry)\n\tshadowtls.RegisterOutbound(registry)\n\tvless.RegisterOutbound(registry)\n\tanytls.RegisterOutbound(registry)\n\n\tregisterQUICOutbounds(registry)\n\tregisterStubForRemovedOutbounds(registry)\n\n\treturn registry\n}\n\nfunc EndpointRegistry() *endpoint.Registry {\n\tregistry := endpoint.NewRegistry()\n\n\tregisterWireGuardEndpoint(registry)\n\tregisterTailscaleEndpoint(registry)\n\n\treturn registry\n}\n\nfunc DNSTransportRegistry() *dns.TransportRegistry {\n\tregistry := dns.NewTransportRegistry()\n\n\ttransport.RegisterTCP(registry)\n\ttransport.RegisterUDP(registry)\n\ttransport.RegisterTLS(registry)\n\ttransport.RegisterHTTPS(registry)\n\thosts.RegisterTransport(registry)\n\tlocal.RegisterTransport(registry)\n\tfakeip.RegisterTransport(registry)\n\tresolved.RegisterTransport(registry)\n\n\tregisterQUICTransports(registry)\n\tregisterDHCPTransport(registry)\n\tregisterTailscaleTransport(registry)\n\n\treturn registry\n}\n\nfunc ServiceRegistry() *service.Registry {\n\tregistry := service.NewRegistry()\n\n\tresolved.RegisterService(registry)\n\tssmapi.RegisterService(registry)\n\n\tregisterDERPService(registry)\n\tregisterCCMService(registry)\n\tregisterOCMService(registry)\n\tregisterOOMKillerService(registry)\n\n\treturn registry\n}\n\nfunc registerStubForRemovedInbounds(registry *inbound.Registry) {\n\tinbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {\n\t\treturn nil, E.New(\"ShadowsocksR is deprecated and removed in sing-box 1.6.0\")\n\t})\n}\n\nfunc registerStubForRemovedOutbounds(registry *outbound.Registry) {\n\toutbound.Register[option.ShadowsocksROutboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) {\n\t\treturn nil, E.New(\"ShadowsocksR is deprecated and removed in sing-box 1.6.0\")\n\t})\n\toutbound.Register[option.StubOptions](registry, C.TypeWireGuard, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.StubOptions) (adapter.Outbound, error) {\n\t\treturn nil, E.New(\"WireGuard outbound is deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use WireGuard endpoint instead\")\n\t})\n}\n"
  },
  {
    "path": "include/tailscale.go",
    "content": "//go:build with_tailscale\n\npackage include\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter/endpoint\"\n\t\"github.com/sagernet/sing-box/adapter/service\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/protocol/tailscale\"\n\t\"github.com/sagernet/sing-box/service/derp\"\n)\n\nfunc registerTailscaleEndpoint(registry *endpoint.Registry) {\n\ttailscale.RegisterEndpoint(registry)\n}\n\nfunc registerTailscaleTransport(registry *dns.TransportRegistry) {\n\ttailscale.RegistryTransport(registry)\n}\n\nfunc registerDERPService(registry *service.Registry) {\n\tderp.Register(registry)\n}\n"
  },
  {
    "path": "include/tailscale_stub.go",
    "content": "//go:build !with_tailscale\n\npackage include\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/endpoint\"\n\t\"github.com/sagernet/sing-box/adapter/service\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc registerTailscaleEndpoint(registry *endpoint.Registry) {\n\tendpoint.Register[option.TailscaleEndpointOptions](registry, C.TypeTailscale, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TailscaleEndpointOptions) (adapter.Endpoint, error) {\n\t\treturn nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`)\n\t})\n}\n\nfunc registerTailscaleTransport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.TailscaleDNSServerOptions](registry, C.DNSTypeTailscale, func(ctx context.Context, logger log.ContextLogger, tag string, options option.TailscaleDNSServerOptions) (adapter.DNSTransport, error) {\n\t\treturn nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`)\n\t})\n}\n\nfunc registerDERPService(registry *service.Registry) {\n\tservice.Register[option.DERPServiceOptions](registry, C.TypeDERP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPServiceOptions) (adapter.Service, error) {\n\t\treturn nil, E.New(`DERP is not included in this build, rebuild with -tags with_tailscale`)\n\t})\n}\n"
  },
  {
    "path": "include/tz_android.go",
    "content": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// kanged from https://github.com/golang/mobile/blob/c713f31d574bb632a93f169b2cc99c9e753fef0e/app/android.go#L89\n\npackage include\n\n// #include <time.h>\nimport \"C\"\nimport \"time\"\n\nfunc init() {\n\tvar currentT C.time_t\n\tvar currentTM C.struct_tm\n\tC.time(&currentT)\n\tC.localtime_r(&currentT, &currentTM)\n\ttzOffset := int(currentTM.tm_gmtoff)\n\ttz := C.GoString(currentTM.tm_zone)\n\ttime.Local = time.FixedZone(tz, tzOffset)\n}\n"
  },
  {
    "path": "include/tz_ios.go",
    "content": "package include\n\n/*\n#cgo CFLAGS: -x objective-c\n#cgo LDFLAGS: -framework Foundation\n#import <Foundation/Foundation.h>\nconst char* getSystemTimeZone() {\n    NSTimeZone *timeZone = [NSTimeZone systemTimeZone];\n    NSString *timeZoneName = [timeZone description];\n    return [timeZoneName UTF8String];\n}\n*/\nimport \"C\"\n\nimport (\n\t\"strings\"\n\t\"time\"\n)\n\nfunc init() {\n\ttzDescription := C.GoString(C.getSystemTimeZone())\n\tif len(tzDescription) == 0 {\n\t\treturn\n\t}\n\tlocation, err := time.LoadLocation(strings.Split(tzDescription, \" \")[0])\n\tif err != nil {\n\t\treturn\n\t}\n\ttime.Local = location\n}\n"
  },
  {
    "path": "include/v2rayapi.go",
    "content": "//go:build with_v2ray_api\n\npackage include\n\nimport _ \"github.com/sagernet/sing-box/experimental/v2rayapi\"\n"
  },
  {
    "path": "include/v2rayapi_stub.go",
    "content": "//go:build !with_v2ray_api\n\npackage include\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/experimental\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc init() {\n\texperimental.RegisterV2RayServerConstructor(func(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) {\n\t\treturn nil, E.New(`v2ray api is not included in this build, rebuild with -tags with_v2ray_api`)\n\t})\n}\n"
  },
  {
    "path": "include/wireguard.go",
    "content": "//go:build with_wireguard\n\npackage include\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter/endpoint\"\n\t\"github.com/sagernet/sing-box/protocol/wireguard\"\n)\n\nfunc registerWireGuardEndpoint(registry *endpoint.Registry) {\n\twireguard.RegisterEndpoint(registry)\n}\n"
  },
  {
    "path": "include/wireguard_stub.go",
    "content": "//go:build !with_wireguard\n\npackage include\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/endpoint\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc registerWireGuardEndpoint(registry *endpoint.Registry) {\n\tendpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) {\n\t\treturn nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`)\n\t})\n}\n"
  },
  {
    "path": "log/export.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"time\"\n)\n\nvar std ContextLogger\n\nfunc init() {\n\tstd = NewDefaultFactory(\n\t\tcontext.Background(),\n\t\tFormatter{BaseTime: time.Now()},\n\t\tos.Stderr,\n\t\t\"\",\n\t\tnil,\n\t\tfalse,\n\t).Logger()\n}\n\nfunc StdLogger() ContextLogger {\n\treturn std\n}\n\nfunc SetStdLogger(logger ContextLogger) {\n\tstd = logger\n}\n\nfunc Trace(args ...any) {\n\tstd.Trace(args...)\n}\n\nfunc Debug(args ...any) {\n\tstd.Debug(args...)\n}\n\nfunc Info(args ...any) {\n\tstd.Info(args...)\n}\n\nfunc Warn(args ...any) {\n\tstd.Warn(args...)\n}\n\nfunc Error(args ...any) {\n\tstd.Error(args...)\n}\n\nfunc Fatal(args ...any) {\n\tstd.Fatal(args...)\n}\n\nfunc Panic(args ...any) {\n\tstd.Panic(args...)\n}\n\nfunc TraceContext(ctx context.Context, args ...any) {\n\tstd.TraceContext(ctx, args...)\n}\n\nfunc DebugContext(ctx context.Context, args ...any) {\n\tstd.DebugContext(ctx, args...)\n}\n\nfunc InfoContext(ctx context.Context, args ...any) {\n\tstd.InfoContext(ctx, args...)\n}\n\nfunc WarnContext(ctx context.Context, args ...any) {\n\tstd.WarnContext(ctx, args...)\n}\n\nfunc ErrorContext(ctx context.Context, args ...any) {\n\tstd.ErrorContext(ctx, args...)\n}\n\nfunc FatalContext(ctx context.Context, args ...any) {\n\tstd.FatalContext(ctx, args...)\n}\n\nfunc PanicContext(ctx context.Context, args ...any) {\n\tstd.PanicContext(ctx, args...)\n}\n"
  },
  {
    "path": "log/factory.go",
    "content": "package log\n\nimport (\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/sing/common/observable\"\n)\n\ntype (\n\tLogger        logger.Logger\n\tContextLogger logger.ContextLogger\n)\n\ntype Factory interface {\n\tStart() error\n\tClose() error\n\tLevel() Level\n\tSetLevel(level Level)\n\tLogger() ContextLogger\n\tNewLogger(tag string) ContextLogger\n}\n\ntype ObservableFactory interface {\n\tFactory\n\tobservable.Observable[Entry]\n}\n\ntype Entry struct {\n\tLevel   Level\n\tMessage string\n}\n"
  },
  {
    "path": "log/format.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tF \"github.com/sagernet/sing/common/format\"\n\n\t\"github.com/logrusorgru/aurora\"\n)\n\ntype Formatter struct {\n\tBaseTime         time.Time\n\tDisableColors    bool\n\tDisableTimestamp bool\n\tFullTimestamp    bool\n\tTimestampFormat  string\n\tDisableLineBreak bool\n}\n\nfunc (f Formatter) Format(ctx context.Context, level Level, tag string, message string, timestamp time.Time) string {\n\tlevelString := strings.ToUpper(FormatLevel(level))\n\tif !f.DisableColors {\n\t\tswitch level {\n\t\tcase LevelDebug, LevelTrace:\n\t\t\tlevelString = aurora.White(levelString).String()\n\t\tcase LevelInfo:\n\t\t\tlevelString = aurora.Cyan(levelString).String()\n\t\tcase LevelWarn:\n\t\t\tlevelString = aurora.Yellow(levelString).String()\n\t\tcase LevelError, LevelFatal, LevelPanic:\n\t\t\tlevelString = aurora.Red(levelString).String()\n\t\t}\n\t}\n\tif tag != \"\" {\n\t\tmessage = tag + \": \" + message\n\t}\n\tvar id ID\n\tvar hasId bool\n\tif ctx != nil {\n\t\tid, hasId = IDFromContext(ctx)\n\t}\n\tif hasId {\n\t\tactiveDuration := FormatDuration(time.Since(id.CreatedAt))\n\t\tif !f.DisableColors {\n\t\t\tvar color aurora.Color\n\t\t\tcolor = aurora.Color(uint8(id.ID))\n\t\t\tcolor %= 215\n\t\t\trow := uint(color / 36)\n\t\t\tcolumn := uint(color % 36)\n\n\t\t\tvar r, g, b float32\n\t\t\tr = float32(row * 51)\n\t\t\tg = float32(column / 6 * 51)\n\t\t\tb = float32((column % 6) * 51)\n\t\t\tluma := 0.2126*r + 0.7152*g + 0.0722*b\n\t\t\tif luma < 60 {\n\t\t\t\trow = 5 - row\n\t\t\t\tcolumn = 35 - column\n\t\t\t\tcolor = aurora.Color(row*36 + column)\n\t\t\t}\n\t\t\tcolor += 16\n\t\t\tcolor = color << 16\n\t\t\tcolor |= 1 << 14\n\t\t\tmessage = F.ToString(\"[\", aurora.Colorize(id.ID, color).String(), \" \", activeDuration, \"] \", message)\n\t\t} else {\n\t\t\tmessage = F.ToString(\"[\", id.ID, \" \", activeDuration, \"] \", message)\n\t\t}\n\t}\n\tswitch {\n\tcase f.DisableTimestamp:\n\t\tmessage = levelString + \" \" + message\n\tcase f.FullTimestamp:\n\t\tmessage = timestamp.Format(f.TimestampFormat) + \" \" + levelString + \" \" + message\n\tdefault:\n\t\tmessage = levelString + \"[\" + xd(int(timestamp.Sub(f.BaseTime)/time.Second), 4) + \"] \" + message\n\t}\n\tif f.DisableLineBreak {\n\t\tif message[len(message)-1] == '\\n' {\n\t\t\tmessage = message[:len(message)-1]\n\t\t}\n\t} else {\n\t\tif message[len(message)-1] != '\\n' {\n\t\t\tmessage += \"\\n\"\n\t\t}\n\t}\n\treturn message\n}\n\nfunc (f Formatter) FormatWithSimple(ctx context.Context, level Level, tag string, message string, timestamp time.Time) (string, string) {\n\tlevelString := strings.ToUpper(FormatLevel(level))\n\tif !f.DisableColors {\n\t\tswitch level {\n\t\tcase LevelDebug, LevelTrace:\n\t\t\tlevelString = aurora.White(levelString).String()\n\t\tcase LevelInfo:\n\t\t\tlevelString = aurora.Cyan(levelString).String()\n\t\tcase LevelWarn:\n\t\t\tlevelString = aurora.Yellow(levelString).String()\n\t\tcase LevelError, LevelFatal, LevelPanic:\n\t\t\tlevelString = aurora.Red(levelString).String()\n\t\t}\n\t}\n\tif tag != \"\" {\n\t\tmessage = tag + \": \" + message\n\t}\n\tmessageSimple := message\n\tvar id ID\n\tvar hasId bool\n\tif ctx != nil {\n\t\tid, hasId = IDFromContext(ctx)\n\t}\n\tif hasId {\n\t\tactiveDuration := FormatDuration(time.Since(id.CreatedAt))\n\t\tif !f.DisableColors {\n\t\t\tvar color aurora.Color\n\t\t\tcolor = aurora.Color(uint8(id.ID))\n\t\t\tcolor %= 215\n\t\t\trow := uint(color / 36)\n\t\t\tcolumn := uint(color % 36)\n\n\t\t\tvar r, g, b float32\n\t\t\tr = float32(row * 51)\n\t\t\tg = float32(column / 6 * 51)\n\t\t\tb = float32((column % 6) * 51)\n\t\t\tluma := 0.2126*r + 0.7152*g + 0.0722*b\n\t\t\tif luma < 60 {\n\t\t\t\trow = 5 - row\n\t\t\t\tcolumn = 35 - column\n\t\t\t\tcolor = aurora.Color(row*36 + column)\n\t\t\t}\n\t\t\tcolor += 16\n\t\t\tcolor = color << 16\n\t\t\tcolor |= 1 << 14\n\t\t\tmessage = F.ToString(\"[\", aurora.Colorize(id.ID, color).String(), \" \", activeDuration, \"] \", message)\n\t\t} else {\n\t\t\tmessage = F.ToString(\"[\", id.ID, \" \", activeDuration, \"] \", message)\n\t\t}\n\t\tmessageSimple = F.ToString(\"[\", id.ID, \" \", activeDuration, \"] \", messageSimple)\n\n\t}\n\tswitch {\n\tcase f.DisableTimestamp:\n\t\tmessage = levelString + \" \" + message\n\tcase f.FullTimestamp:\n\t\tmessage = timestamp.Format(f.TimestampFormat) + \" \" + levelString + \" \" + message\n\tdefault:\n\t\tmessage = levelString + \"[\" + xd(int(timestamp.Sub(f.BaseTime)/time.Second), 4) + \"] \" + message\n\t}\n\tif message[len(message)-1] != '\\n' {\n\t\tmessage += \"\\n\"\n\t}\n\treturn message, messageSimple\n}\n\nfunc xd(value int, x int) string {\n\tmessage := strconv.Itoa(value)\n\tfor len(message) < x {\n\t\tmessage = \"0\" + message\n\t}\n\treturn message\n}\n\nfunc FormatDuration(duration time.Duration) string {\n\tif duration < time.Second {\n\t\treturn F.ToString(duration.Milliseconds(), \"ms\")\n\t} else if duration < time.Minute {\n\t\treturn F.ToString(int64(duration.Seconds()), \".\", int64(duration.Seconds()*100)%100, \"s\")\n\t} else {\n\t\treturn F.ToString(int64(duration.Minutes()), \"m\", int64(duration.Seconds())%60, \"s\")\n\t}\n}\n"
  },
  {
    "path": "log/id.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common/random\"\n)\n\nfunc init() {\n\trandom.InitializeSeed()\n}\n\ntype idKey struct{}\n\ntype ID struct {\n\tID        uint32\n\tCreatedAt time.Time\n}\n\nfunc ContextWithNewID(ctx context.Context) context.Context {\n\treturn ContextWithID(ctx, ID{\n\t\tID:        rand.Uint32(),\n\t\tCreatedAt: time.Now(),\n\t})\n}\n\nfunc ContextWithID(ctx context.Context, id ID) context.Context {\n\treturn context.WithValue(ctx, (*idKey)(nil), id)\n}\n\nfunc IDFromContext(ctx context.Context) (ID, bool) {\n\tid, loaded := ctx.Value((*idKey)(nil)).(ID)\n\treturn id, loaded\n}\n"
  },
  {
    "path": "log/level.go",
    "content": "package log\n\nimport (\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype Level = uint8\n\nconst (\n\tLevelPanic Level = iota\n\tLevelFatal\n\tLevelError\n\tLevelWarn\n\tLevelInfo\n\tLevelDebug\n\tLevelTrace\n)\n\nfunc FormatLevel(level Level) string {\n\tswitch level {\n\tcase LevelTrace:\n\t\treturn \"trace\"\n\tcase LevelDebug:\n\t\treturn \"debug\"\n\tcase LevelInfo:\n\t\treturn \"info\"\n\tcase LevelWarn:\n\t\treturn \"warn\"\n\tcase LevelError:\n\t\treturn \"error\"\n\tcase LevelFatal:\n\t\treturn \"fatal\"\n\tcase LevelPanic:\n\t\treturn \"panic\"\n\tdefault:\n\t\treturn \"unknown\"\n\t}\n}\n\nfunc ParseLevel(level string) (Level, error) {\n\tswitch level {\n\tcase \"trace\":\n\t\treturn LevelTrace, nil\n\tcase \"debug\":\n\t\treturn LevelDebug, nil\n\tcase \"info\":\n\t\treturn LevelInfo, nil\n\tcase \"warn\", \"warning\":\n\t\treturn LevelWarn, nil\n\tcase \"error\":\n\t\treturn LevelError, nil\n\tcase \"fatal\":\n\t\treturn LevelFatal, nil\n\tcase \"panic\":\n\t\treturn LevelPanic, nil\n\tdefault:\n\t\treturn LevelTrace, E.New(\"unknown log level: \", level)\n\t}\n}\n"
  },
  {
    "path": "log/log.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype Options struct {\n\tContext        context.Context\n\tOptions        option.LogOptions\n\tObservable     bool\n\tDefaultWriter  io.Writer\n\tBaseTime       time.Time\n\tPlatformWriter PlatformWriter\n}\n\nfunc New(options Options) (Factory, error) {\n\tlogOptions := options.Options\n\n\tif logOptions.Disabled {\n\t\treturn NewNOPFactory(), nil\n\t}\n\n\tvar logWriter io.Writer\n\tvar logFilePath string\n\n\tswitch logOptions.Output {\n\tcase \"\":\n\t\tlogWriter = options.DefaultWriter\n\t\tif logWriter == nil {\n\t\t\tlogWriter = os.Stderr\n\t\t}\n\tcase \"stderr\":\n\t\tlogWriter = os.Stderr\n\tcase \"stdout\":\n\t\tlogWriter = os.Stdout\n\tdefault:\n\t\tlogWriter = io.Discard\n\t\tlogFilePath = logOptions.Output\n\t}\n\tlogFormatter := Formatter{\n\t\tBaseTime:         options.BaseTime,\n\t\tDisableColors:    logOptions.DisableColor || logFilePath != \"\",\n\t\tDisableTimestamp: !logOptions.Timestamp && logFilePath != \"\",\n\t\tFullTimestamp:    logOptions.Timestamp,\n\t\tTimestampFormat:  \"-0700 2006-01-02 15:04:05\",\n\t}\n\tfactory := NewDefaultFactory(\n\t\toptions.Context,\n\t\tlogFormatter,\n\t\tlogWriter,\n\t\tlogFilePath,\n\t\toptions.PlatformWriter,\n\t\toptions.Observable,\n\t)\n\tif logOptions.Level != \"\" {\n\t\tlogLevel, err := ParseLevel(logOptions.Level)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse log level\")\n\t\t}\n\t\tfactory.SetLevel(logLevel)\n\t} else {\n\t\tfactory.SetLevel(LevelTrace)\n\t}\n\treturn factory, nil\n}\n"
  },
  {
    "path": "log/nop.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing/common/observable\"\n)\n\nvar _ ObservableFactory = (*nopFactory)(nil)\n\ntype nopFactory struct{}\n\nfunc NewNOPFactory() ObservableFactory {\n\treturn (*nopFactory)(nil)\n}\n\nfunc (f *nopFactory) Start() error {\n\treturn nil\n}\n\nfunc (f *nopFactory) Close() error {\n\treturn nil\n}\n\nfunc (f *nopFactory) Level() Level {\n\treturn LevelTrace\n}\n\nfunc (f *nopFactory) SetLevel(level Level) {\n}\n\nfunc (f *nopFactory) Logger() ContextLogger {\n\treturn f\n}\n\nfunc (f *nopFactory) NewLogger(tag string) ContextLogger {\n\treturn f\n}\n\nfunc (f *nopFactory) Trace(args ...any) {\n}\n\nfunc (f *nopFactory) Debug(args ...any) {\n}\n\nfunc (f *nopFactory) Info(args ...any) {\n}\n\nfunc (f *nopFactory) Warn(args ...any) {\n}\n\nfunc (f *nopFactory) Error(args ...any) {\n}\n\nfunc (f *nopFactory) Fatal(args ...any) {\n}\n\nfunc (f *nopFactory) Panic(args ...any) {\n}\n\nfunc (f *nopFactory) TraceContext(ctx context.Context, args ...any) {\n}\n\nfunc (f *nopFactory) DebugContext(ctx context.Context, args ...any) {\n}\n\nfunc (f *nopFactory) InfoContext(ctx context.Context, args ...any) {\n}\n\nfunc (f *nopFactory) WarnContext(ctx context.Context, args ...any) {\n}\n\nfunc (f *nopFactory) ErrorContext(ctx context.Context, args ...any) {\n}\n\nfunc (f *nopFactory) FatalContext(ctx context.Context, args ...any) {\n}\n\nfunc (f *nopFactory) PanicContext(ctx context.Context, args ...any) {\n}\n\nfunc (f *nopFactory) Subscribe() (subscription observable.Subscription[Entry], done <-chan struct{}, err error) {\n\treturn nil, nil, os.ErrInvalid\n}\n\nfunc (f *nopFactory) UnSubscribe(subscription observable.Subscription[Entry]) {\n}\n"
  },
  {
    "path": "log/observable.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/observable\"\n\t\"github.com/sagernet/sing/service/filemanager\"\n)\n\nvar _ Factory = (*defaultFactory)(nil)\n\ntype defaultFactory struct {\n\tctx               context.Context\n\tformatter         Formatter\n\tplatformFormatter Formatter\n\twriter            io.Writer\n\tfile              *os.File\n\tfilePath          string\n\tplatformWriter    PlatformWriter\n\tneedObservable    bool\n\tlevel             Level\n\tsubscriber        *observable.Subscriber[Entry]\n\tobserver          *observable.Observer[Entry]\n}\n\nfunc NewDefaultFactory(\n\tctx context.Context,\n\tformatter Formatter,\n\twriter io.Writer,\n\tfilePath string,\n\tplatformWriter PlatformWriter,\n\tneedObservable bool,\n) ObservableFactory {\n\tfactory := &defaultFactory{\n\t\tctx:       ctx,\n\t\tformatter: formatter,\n\t\tplatformFormatter: Formatter{\n\t\t\tBaseTime:         formatter.BaseTime,\n\t\t\tDisableLineBreak: true,\n\t\t},\n\t\twriter:         writer,\n\t\tfilePath:       filePath,\n\t\tplatformWriter: platformWriter,\n\t\tneedObservable: needObservable,\n\t\tlevel:          LevelTrace,\n\t\tsubscriber:     observable.NewSubscriber[Entry](128),\n\t}\n\t/*if platformWriter != nil {\n\t\tfactory.platformFormatter.DisableColors = platformWriter.DisableColors()\n\t}*/\n\tif needObservable {\n\t\tfactory.observer = observable.NewObserver[Entry](factory.subscriber, 64)\n\t}\n\treturn factory\n}\n\nfunc (f *defaultFactory) Start() error {\n\tif f.filePath != \"\" {\n\t\tlogFile, err := filemanager.OpenFile(f.ctx, f.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tf.writer = logFile\n\t\tf.file = logFile\n\t}\n\treturn nil\n}\n\nfunc (f *defaultFactory) Close() error {\n\treturn common.Close(\n\t\tcommon.PtrOrNil(f.file),\n\t\tf.subscriber,\n\t)\n}\n\nfunc (f *defaultFactory) Level() Level {\n\treturn f.level\n}\n\nfunc (f *defaultFactory) SetLevel(level Level) {\n\tf.level = level\n}\n\nfunc (f *defaultFactory) Logger() ContextLogger {\n\treturn f.NewLogger(\"\")\n}\n\nfunc (f *defaultFactory) NewLogger(tag string) ContextLogger {\n\treturn &observableLogger{f, tag}\n}\n\nfunc (f *defaultFactory) Subscribe() (subscription observable.Subscription[Entry], done <-chan struct{}, err error) {\n\treturn f.observer.Subscribe()\n}\n\nfunc (f *defaultFactory) UnSubscribe(sub observable.Subscription[Entry]) {\n\tf.observer.UnSubscribe(sub)\n}\n\nvar _ ContextLogger = (*observableLogger)(nil)\n\ntype observableLogger struct {\n\t*defaultFactory\n\ttag string\n}\n\nfunc (l *observableLogger) Log(ctx context.Context, level Level, args []any) {\n\tlevel = OverrideLevelFromContext(level, ctx)\n\tif level > l.level && l.platformWriter == nil {\n\t\treturn\n\t}\n\tnowTime := time.Now()\n\tif level <= l.level {\n\t\tif l.needObservable {\n\t\t\tmessage, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), nowTime)\n\t\t\tif level == LevelPanic {\n\t\t\t\tpanic(message)\n\t\t\t}\n\t\t\tl.writer.Write([]byte(message))\n\t\t\tif level == LevelFatal {\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tl.subscriber.Emit(Entry{level, messageSimple})\n\t\t} else {\n\t\t\tmessage := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime)\n\t\t\tif level == LevelPanic {\n\t\t\t\tpanic(message)\n\t\t\t}\n\t\t\tl.writer.Write([]byte(message))\n\t\t\tif level == LevelFatal {\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}\n\t}\n\tif l.platformWriter != nil {\n\t\tl.platformWriter.WriteMessage(level, l.platformFormatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime))\n\t}\n}\n\nfunc (l *observableLogger) Trace(args ...any) {\n\tl.TraceContext(context.Background(), args...)\n}\n\nfunc (l *observableLogger) Debug(args ...any) {\n\tl.DebugContext(context.Background(), args...)\n}\n\nfunc (l *observableLogger) Info(args ...any) {\n\tl.InfoContext(context.Background(), args...)\n}\n\nfunc (l *observableLogger) Warn(args ...any) {\n\tl.WarnContext(context.Background(), args...)\n}\n\nfunc (l *observableLogger) Error(args ...any) {\n\tl.ErrorContext(context.Background(), args...)\n}\n\nfunc (l *observableLogger) Fatal(args ...any) {\n\tl.FatalContext(context.Background(), args...)\n}\n\nfunc (l *observableLogger) Panic(args ...any) {\n\tl.PanicContext(context.Background(), args...)\n}\n\nfunc (l *observableLogger) TraceContext(ctx context.Context, args ...any) {\n\tl.Log(ctx, LevelTrace, args)\n}\n\nfunc (l *observableLogger) DebugContext(ctx context.Context, args ...any) {\n\tl.Log(ctx, LevelDebug, args)\n}\n\nfunc (l *observableLogger) InfoContext(ctx context.Context, args ...any) {\n\tl.Log(ctx, LevelInfo, args)\n}\n\nfunc (l *observableLogger) WarnContext(ctx context.Context, args ...any) {\n\tl.Log(ctx, LevelWarn, args)\n}\n\nfunc (l *observableLogger) ErrorContext(ctx context.Context, args ...any) {\n\tl.Log(ctx, LevelError, args)\n}\n\nfunc (l *observableLogger) FatalContext(ctx context.Context, args ...any) {\n\tl.Log(ctx, LevelFatal, args)\n}\n\nfunc (l *observableLogger) PanicContext(ctx context.Context, args ...any) {\n\tl.Log(ctx, LevelPanic, args)\n}\n"
  },
  {
    "path": "log/override.go",
    "content": "package log\n\nimport (\n\t\"context\"\n)\n\ntype overrideLevelKey struct{}\n\nfunc ContextWithOverrideLevel(ctx context.Context, level Level) context.Context {\n\treturn context.WithValue(ctx, (*overrideLevelKey)(nil), level)\n}\n\nfunc OverrideLevelFromContext(origin Level, ctx context.Context) Level {\n\tlevel, loaded := ctx.Value((*overrideLevelKey)(nil)).(Level)\n\tif !loaded || origin > level {\n\t\treturn origin\n\t}\n\treturn level\n}\n"
  },
  {
    "path": "log/platform.go",
    "content": "package log\n\ntype PlatformWriter interface {\n\tWriteMessage(level Level, message string)\n}\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: sing-box\nsite_url: https://sing-box.sagernet.org/\nsite_author: nekohasekai\nrepo_url: https://github.com/SagerNet/sing-box\nrepo_name: SagerNet/sing-box\ncopyright: Copyright &copy; 2022 nekohasekai\nsite_description: The universal proxy platform.\nremote_branch: docs\nedit_uri: \"\"\ntheme:\n  name: material\n  logo: assets/icon.svg\n  favicon: assets/icon.svg\n  palette:\n    - media: \"(prefers-color-scheme)\"\n      toggle:\n        icon: material/link\n        name: Switch to light mode\n    - media: \"(prefers-color-scheme: light)\"\n      scheme: default\n      primary: white\n      toggle:\n        icon: material/toggle-switch\n        name: Switch to dark mode\n    - media: \"(prefers-color-scheme: dark)\"\n      scheme: slate\n      primary: black\n      toggle:\n        icon: material/toggle-switch-off\n        name: Switch to system preference\n  features:\n    #    - navigation.instant\n    - navigation.tracking\n    - navigation.tabs\n    - navigation.indexes\n    - navigation.expand\n    - navigation.sections\n    - header.autohide\n    - content.code.copy\n    - content.code.select\n    - content.code.annotate\n  icon:\n    admonition:\n      question: material/new-box\nnav:\n  - Home:\n      - index.md\n      - Change Log: changelog.md\n      - Migration: migration.md\n      - Deprecated: deprecated.md\n      - Support: support.md\n      - Sponsors: sponsors.md\n  - Installation:\n      - Package Manager: installation/package-manager.md\n      - Docker: installation/docker.md\n      - Build from source: installation/build-from-source.md\n  - Graphical Clients:\n      - clients/index.md\n      - Android:\n          - clients/android/index.md\n          - Features: clients/android/features.md\n      - Apple platforms:\n          - clients/apple/index.md\n          - Features: clients/apple/features.md\n      - General: clients/general.md\n      - Privacy policy: clients/privacy.md\n  - Manual:\n      - Proxy:\n          - Server: manual/proxy/server.md\n          - Client: manual/proxy/client.md\n      #          - TUN: manual/proxy/tun.md\n      - Proxy Protocol:\n          - Shadowsocks: manual/proxy-protocol/shadowsocks.md\n          - Trojan: manual/proxy-protocol/trojan.md\n          - Hysteria 2: manual/proxy-protocol/hysteria2.md\n      - Misc:\n          - TunnelVision: manual/misc/tunnelvision.md\n  - Configuration:\n      - configuration/index.md\n      - Log:\n          - configuration/log/index.md\n      - DNS:\n          - configuration/dns/index.md\n          - DNS Server:\n              - configuration/dns/server/index.md\n              - Legacy: configuration/dns/server/legacy.md\n              - Local: configuration/dns/server/local.md\n              - Hosts: configuration/dns/server/hosts.md\n              - TCP: configuration/dns/server/tcp.md\n              - UDP: configuration/dns/server/udp.md\n              - TLS: configuration/dns/server/tls.md\n              - QUIC: configuration/dns/server/quic.md\n              - HTTPS: configuration/dns/server/https.md\n              - HTTP3: configuration/dns/server/http3.md\n              - DHCP: configuration/dns/server/dhcp.md\n              - FakeIP: configuration/dns/server/fakeip.md\n              - Tailscale: configuration/dns/server/tailscale.md\n              - Resolved: configuration/dns/server/resolved.md\n          - DNS Rule: configuration/dns/rule.md\n          - DNS Rule Action: configuration/dns/rule_action.md\n          - FakeIP: configuration/dns/fakeip.md\n      - NTP: configuration/ntp/index.md\n      - Certificate: configuration/certificate/index.md\n      - Route:\n          - configuration/route/index.md\n          - GeoIP: configuration/route/geoip.md\n          - Geosite: configuration/route/geosite.md\n          - Route Rule: configuration/route/rule.md\n          - Rule Action: configuration/route/rule_action.md\n          - Protocol Sniff: configuration/route/sniff.md\n      - Rule Set:\n          - configuration/rule-set/index.md\n          - Source Format: configuration/rule-set/source-format.md\n          - Headless Rule: configuration/rule-set/headless-rule.md\n          - AdGuard DNS Filer: configuration/rule-set/adguard.md\n      - Experimental:\n          - configuration/experimental/index.md\n          - Cache File: configuration/experimental/cache-file.md\n          - Clash API: configuration/experimental/clash-api.md\n          - V2Ray API: configuration/experimental/v2ray-api.md\n      - Shared:\n          - Listen Fields: configuration/shared/listen.md\n          - Dial Fields: configuration/shared/dial.md\n          - TLS: configuration/shared/tls.md\n          - DNS01 Challenge Fields: configuration/shared/dns01_challenge.md\n          - Pre-match: configuration/shared/pre-match.md\n          - Multiplex: configuration/shared/multiplex.md\n          - V2Ray Transport: configuration/shared/v2ray-transport.md\n          - UDP over TCP: configuration/shared/udp-over-tcp.md\n          - TCP Brutal: configuration/shared/tcp-brutal.md\n          - Wi-Fi State: configuration/shared/wifi-state.md\n          - Neighbor Resolution: configuration/shared/neighbor.md\n      - Endpoint:\n          - configuration/endpoint/index.md\n          - WireGuard: configuration/endpoint/wireguard.md\n          - Tailscale: configuration/endpoint/tailscale.md\n      - Inbound:\n          - configuration/inbound/index.md\n          - Direct: configuration/inbound/direct.md\n          - Mixed: configuration/inbound/mixed.md\n          - SOCKS: configuration/inbound/socks.md\n          - HTTP: configuration/inbound/http.md\n          - Shadowsocks: configuration/inbound/shadowsocks.md\n          - VMess: configuration/inbound/vmess.md\n          - Trojan: configuration/inbound/trojan.md\n          - Naive: configuration/inbound/naive.md\n          - Hysteria: configuration/inbound/hysteria.md\n          - ShadowTLS: configuration/inbound/shadowtls.md\n          - VLESS: configuration/inbound/vless.md\n          - TUIC: configuration/inbound/tuic.md\n          - Hysteria2: configuration/inbound/hysteria2.md\n          - AnyTLS: configuration/inbound/anytls.md\n          - Tun: configuration/inbound/tun.md\n          - Redirect: configuration/inbound/redirect.md\n          - TProxy: configuration/inbound/tproxy.md\n      - Outbound:\n          - configuration/outbound/index.md\n          - Direct: configuration/outbound/direct.md\n          - Block: configuration/outbound/block.md\n          - SOCKS: configuration/outbound/socks.md\n          - HTTP: configuration/outbound/http.md\n          - Shadowsocks: configuration/outbound/shadowsocks.md\n          - VMess: configuration/outbound/vmess.md\n          - Trojan: configuration/outbound/trojan.md\n          - Naive: configuration/outbound/naive.md\n          - WireGuard: configuration/outbound/wireguard.md\n          - Hysteria: configuration/outbound/hysteria.md\n          - ShadowTLS: configuration/outbound/shadowtls.md\n          - VLESS: configuration/outbound/vless.md\n          - TUIC: configuration/outbound/tuic.md\n          - Hysteria2: configuration/outbound/hysteria2.md\n          - AnyTLS: configuration/outbound/anytls.md\n          - Tor: configuration/outbound/tor.md\n          - SSH: configuration/outbound/ssh.md\n          - DNS: configuration/outbound/dns.md\n          - Selector: configuration/outbound/selector.md\n          - URLTest: configuration/outbound/urltest.md\n      - Service:\n          - configuration/service/index.md\n          - DERP: configuration/service/derp.md\n          - Resolved: configuration/service/resolved.md\n          - SSM API: configuration/service/ssm-api.md\n          - CCM: configuration/service/ccm.md\n          - OCM: configuration/service/ocm.md\nmarkdown_extensions:\n  - toc:\n      slugify: !!python/object/apply:pymdownx.slugs.slugify\n        kwds:\n          case: lower\n  - pymdownx.inlinehilite\n  - pymdownx.snippets\n  - pymdownx.superfences\n  - pymdownx.details\n  - pymdownx.critic\n  - pymdownx.caret\n  - pymdownx.keys\n  - pymdownx.mark\n  - pymdownx.tilde\n  - pymdownx.magiclink\n  - admonition\n  - attr_list\n  - md_in_html\n  - footnotes\n  - def_list\n  - pymdownx.highlight:\n      anchor_linenums: true\n  - pymdownx.tabbed:\n      alternate_style: true\n  - pymdownx.tasklist:\n      custom_checkbox: true\n  - pymdownx.emoji:\n      emoji_index: !!python/name:material.extensions.emoji.twemoji\n      emoji_generator: !!python/name:material.extensions.emoji.to_svg\n  - pymdownx.superfences:\n      custom_fences:\n        - name: mermaid\n          class: mermaid\n          format: !!python/name:pymdownx.superfences.fence_code_format\nextra:\n  social:\n    - icon: fontawesome/brands/github\n      link: https://github.com/SagerNet/sing-box\n  generator: false\nplugins:\n  - search\n  - i18n:\n      docs_structure: suffix\n      fallback_to_default: true\n      languages:\n        - build: true\n          default: true\n          locale: en\n          name: English\n        - build: true\n          default: false\n          locale: zh\n          name: 简体中文\n          nav_translations:\n            Home: 开始\n            Change Log: 更新日志\n            Migration: 迁移指南\n            Deprecated: 废弃功能列表\n            Support: 支持\n\n            Installation: 安装\n            Package Manager: 包管理器\n            Build from source: 从源代码构建\n\n            Graphical Clients: 图形界面客户端\n            Features: 特性\n            Apple platforms: Apple 平台\n            General: 通用\n            Privacy policy: 隐私政策\n\n            Configuration: 配置\n            Log: 日志\n            DNS Server: DNS 服务器\n            DNS Rule: DNS 规则\n            DNS Rule Action: DNS 规则动作\n\n            Route: 路由\n            Route Rule: 路由规则\n            Rule Action: 规则动作\n            Protocol Sniff: 协议探测\n\n            Rule Set: 规则集\n            Source Format: 源文件格式\n            Headless Rule: 无头规则\n\n            Experimental: 实验性\n            Cache File: 缓存文件\n\n            Shared: 通用\n            Listen Fields: 监听字段\n            Dial Fields: 拨号字段\n            DNS01 Challenge Fields: DNS01 验证字段\n            Multiplex: 多路复用\n            V2Ray Transport: V2Ray 传输层\n            Wi-Fi State: Wi-Fi 状态\n\n            Endpoint: 端点\n            Inbound: 入站\n            Outbound: 出站\n\n            Manual: 手册\n      reconfigure_material: true\n      reconfigure_search: true\n"
  },
  {
    "path": "option/anytls.go",
    "content": "package option\n\nimport \"github.com/sagernet/sing/common/json/badoption\"\n\ntype AnyTLSInboundOptions struct {\n\tListenOptions\n\tInboundTLSOptionsContainer\n\tUsers         []AnyTLSUser               `json:\"users,omitempty\"`\n\tPaddingScheme badoption.Listable[string] `json:\"padding_scheme,omitempty\"`\n}\n\ntype AnyTLSUser struct {\n\tName     string `json:\"name,omitempty\"`\n\tPassword string `json:\"password,omitempty\"`\n}\n\ntype AnyTLSOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tOutboundTLSOptionsContainer\n\tPassword                 string             `json:\"password,omitempty\"`\n\tIdleSessionCheckInterval badoption.Duration `json:\"idle_session_check_interval,omitempty\"`\n\tIdleSessionTimeout       badoption.Duration `json:\"idle_session_timeout,omitempty\"`\n\tMinIdleSession           int                `json:\"min_idle_session,omitempty\"`\n}\n"
  },
  {
    "path": "option/ccm.go",
    "content": "package option\n\nimport (\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype CCMServiceOptions struct {\n\tListenOptions\n\tInboundTLSOptionsContainer\n\tCredentialPath string               `json:\"credential_path,omitempty\"`\n\tUsers          []CCMUser            `json:\"users,omitempty\"`\n\tHeaders        badoption.HTTPHeader `json:\"headers,omitempty\"`\n\tDetour         string               `json:\"detour,omitempty\"`\n\tUsagesPath     string               `json:\"usages_path,omitempty\"`\n}\n\ntype CCMUser struct {\n\tName  string `json:\"name,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n"
  },
  {
    "path": "option/certificate.go",
    "content": "package option\n\nimport (\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype _CertificateOptions struct {\n\tStore                    string                     `json:\"store,omitempty\"`\n\tCertificate              badoption.Listable[string] `json:\"certificate,omitempty\"`\n\tCertificatePath          badoption.Listable[string] `json:\"certificate_path,omitempty\"`\n\tCertificateDirectoryPath badoption.Listable[string] `json:\"certificate_directory_path,omitempty\"`\n}\n\ntype CertificateOptions _CertificateOptions\n\nfunc (o CertificateOptions) MarshalJSON() ([]byte, error) {\n\tswitch o.Store {\n\tcase C.CertificateStoreSystem:\n\t\to.Store = \"\"\n\t}\n\treturn json.Marshal((*_CertificateOptions)(&o))\n}\n\nfunc (o *CertificateOptions) UnmarshalJSON(data []byte) error {\n\terr := json.Unmarshal(data, (*_CertificateOptions)(o))\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch o.Store {\n\tcase C.CertificateStoreSystem, \"\":\n\t\to.Store = C.CertificateStoreSystem\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "option/debug.go",
    "content": "package option\n\nimport \"github.com/sagernet/sing/common/byteformats\"\n\ntype DebugOptions struct {\n\tListen       string                   `json:\"listen,omitempty\"`\n\tGCPercent    *int                     `json:\"gc_percent,omitempty\"`\n\tMaxStack     *int                     `json:\"max_stack,omitempty\"`\n\tMaxThreads   *int                     `json:\"max_threads,omitempty\"`\n\tPanicOnFault *bool                    `json:\"panic_on_fault,omitempty\"`\n\tTraceBack    string                   `json:\"trace_back,omitempty\"`\n\tMemoryLimit  *byteformats.MemoryBytes `json:\"memory_limit,omitempty\"`\n\tOOMKiller    *bool                    `json:\"oom_killer,omitempty\"`\n}\n"
  },
  {
    "path": "option/direct.go",
    "content": "package option\n\nimport (\n\t\"context\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n)\n\ntype DirectInboundOptions struct {\n\tListenOptions\n\tNetwork         NetworkList `json:\"network,omitempty\"`\n\tOverrideAddress string      `json:\"override_address,omitempty\"`\n\tOverridePort    uint16      `json:\"override_port,omitempty\"`\n}\n\ntype _DirectOutboundOptions struct {\n\tDialerOptions\n\t// Deprecated: Use Route Action instead\n\tOverrideAddress string `json:\"override_address,omitempty\"`\n\t// Deprecated: Use Route Action instead\n\tOverridePort uint16 `json:\"override_port,omitempty\"`\n\t// Deprecated: removed\n\tProxyProtocol uint8 `json:\"proxy_protocol,omitempty\"`\n}\n\ntype DirectOutboundOptions _DirectOutboundOptions\n\nfunc (d *DirectOutboundOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {\n\terr := json.UnmarshalDisallowUnknownFields(content, (*_DirectOutboundOptions)(d))\n\tif err != nil {\n\t\treturn err\n\t}\n\t//nolint:staticcheck\n\tif d.OverrideAddress != \"\" || d.OverridePort != 0 {\n\t\treturn E.New(\"destination override fields in direct outbound are deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use route options instead\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "option/dns.go",
    "content": "package option\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"net/url\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/experimental/deprecated\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"github.com/miekg/dns\"\n)\n\ntype RawDNSOptions struct {\n\tServers        []DNSServerOptions `json:\"servers,omitempty\"`\n\tRules          []DNSRule          `json:\"rules,omitempty\"`\n\tFinal          string             `json:\"final,omitempty\"`\n\tReverseMapping bool               `json:\"reverse_mapping,omitempty\"`\n\tDNSClientOptions\n}\n\ntype LegacyDNSOptions struct {\n\tFakeIP *LegacyDNSFakeIPOptions `json:\"fakeip,omitempty\"`\n}\n\ntype DNSOptions struct {\n\tRawDNSOptions\n\tLegacyDNSOptions\n}\n\ntype contextKeyDontUpgrade struct{}\n\nfunc ContextWithDontUpgrade(ctx context.Context) context.Context {\n\treturn context.WithValue(ctx, (*contextKeyDontUpgrade)(nil), true)\n}\n\nfunc dontUpgradeFromContext(ctx context.Context) bool {\n\treturn ctx.Value((*contextKeyDontUpgrade)(nil)) == true\n}\n\nfunc (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {\n\terr := json.UnmarshalContext(ctx, content, &o.LegacyDNSOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdontUpgrade := dontUpgradeFromContext(ctx)\n\tlegacyOptions := o.LegacyDNSOptions\n\tif !dontUpgrade {\n\t\tif o.FakeIP != nil && o.FakeIP.Enabled {\n\t\t\tdeprecated.Report(ctx, deprecated.OptionLegacyDNSFakeIPOptions)\n\t\t\tctx = context.WithValue(ctx, (*LegacyDNSFakeIPOptions)(nil), o.FakeIP)\n\t\t}\n\t\to.LegacyDNSOptions = LegacyDNSOptions{}\n\t}\n\terr = badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !dontUpgrade {\n\t\trcodeMap := make(map[string]int)\n\t\to.Servers = common.Filter(o.Servers, func(it DNSServerOptions) bool {\n\t\t\tif it.Type == C.DNSTypeLegacyRcode {\n\t\t\t\trcodeMap[it.Tag] = it.Options.(int)\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tif len(rcodeMap) > 0 {\n\t\t\tfor i := 0; i < len(o.Rules); i++ {\n\t\t\t\trewriteRcode(rcodeMap, &o.Rules[i])\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc rewriteRcode(rcodeMap map[string]int, rule *DNSRule) {\n\tswitch rule.Type {\n\tcase C.RuleTypeDefault:\n\t\trewriteRcodeAction(rcodeMap, &rule.DefaultOptions.DNSRuleAction)\n\tcase C.RuleTypeLogical:\n\t\trewriteRcodeAction(rcodeMap, &rule.LogicalOptions.DNSRuleAction)\n\t}\n}\n\nfunc rewriteRcodeAction(rcodeMap map[string]int, ruleAction *DNSRuleAction) {\n\tif ruleAction.Action != C.RuleActionTypeRoute {\n\t\treturn\n\t}\n\trcode, loaded := rcodeMap[ruleAction.RouteOptions.Server]\n\tif !loaded {\n\t\treturn\n\t}\n\truleAction.Action = C.RuleActionTypePredefined\n\truleAction.PredefinedOptions.Rcode = common.Ptr(DNSRCode(rcode))\n}\n\ntype DNSClientOptions struct {\n\tStrategy         DomainStrategy        `json:\"strategy,omitempty\"`\n\tDisableCache     bool                  `json:\"disable_cache,omitempty\"`\n\tDisableExpire    bool                  `json:\"disable_expire,omitempty\"`\n\tIndependentCache bool                  `json:\"independent_cache,omitempty\"`\n\tCacheCapacity    uint32                `json:\"cache_capacity,omitempty\"`\n\tClientSubnet     *badoption.Prefixable `json:\"client_subnet,omitempty\"`\n}\n\ntype LegacyDNSFakeIPOptions struct {\n\tEnabled    bool              `json:\"enabled,omitempty\"`\n\tInet4Range *badoption.Prefix `json:\"inet4_range,omitempty\"`\n\tInet6Range *badoption.Prefix `json:\"inet6_range,omitempty\"`\n}\n\ntype DNSTransportOptionsRegistry interface {\n\tCreateOptions(transportType string) (any, bool)\n}\ntype _DNSServerOptions struct {\n\tType    string `json:\"type,omitempty\"`\n\tTag     string `json:\"tag,omitempty\"`\n\tOptions any    `json:\"-\"`\n}\n\ntype DNSServerOptions _DNSServerOptions\n\nfunc (o *DNSServerOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) {\n\tswitch o.Type {\n\tcase C.DNSTypeLegacy:\n\t\to.Type = \"\"\n\t}\n\treturn badjson.MarshallObjectsContext(ctx, (*_DNSServerOptions)(o), o.Options)\n}\n\nfunc (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {\n\terr := json.UnmarshalContext(ctx, content, (*_DNSServerOptions)(o))\n\tif err != nil {\n\t\treturn err\n\t}\n\tregistry := service.FromContext[DNSTransportOptionsRegistry](ctx)\n\tif registry == nil {\n\t\treturn E.New(\"missing DNS transport options registry in context\")\n\t}\n\tvar options any\n\tswitch o.Type {\n\tcase \"\", C.DNSTypeLegacy:\n\t\to.Type = C.DNSTypeLegacy\n\t\toptions = new(LegacyDNSServerOptions)\n\t\tdeprecated.Report(ctx, deprecated.OptionLegacyDNSTransport)\n\tdefault:\n\t\tvar loaded bool\n\t\toptions, loaded = registry.CreateOptions(o.Type)\n\t\tif !loaded {\n\t\t\treturn E.New(\"unknown transport type: \", o.Type)\n\t\t}\n\t}\n\terr = badjson.UnmarshallExcludedContext(ctx, content, (*_DNSServerOptions)(o), options)\n\tif err != nil {\n\t\treturn err\n\t}\n\to.Options = options\n\tif o.Type == C.DNSTypeLegacy && !dontUpgradeFromContext(ctx) {\n\t\terr = o.Upgrade(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (o *DNSServerOptions) Upgrade(ctx context.Context) error {\n\tif o.Type != C.DNSTypeLegacy {\n\t\treturn nil\n\t}\n\toptions := o.Options.(*LegacyDNSServerOptions)\n\tserverURL, _ := url.Parse(options.Address)\n\tvar serverType string\n\tif serverURL != nil && serverURL.Scheme != \"\" {\n\t\tserverType = serverURL.Scheme\n\t} else {\n\t\tswitch options.Address {\n\t\tcase \"local\", \"fakeip\":\n\t\t\tserverType = options.Address\n\t\tdefault:\n\t\t\tserverType = C.DNSTypeUDP\n\t\t}\n\t}\n\tremoteOptions := RemoteDNSServerOptions{\n\t\tRawLocalDNSServerOptions: RawLocalDNSServerOptions{\n\t\t\tDialerOptions: DialerOptions{\n\t\t\t\tDetour: options.Detour,\n\t\t\t\tDomainResolver: &DomainResolveOptions{\n\t\t\t\t\tServer:   options.AddressResolver,\n\t\t\t\t\tStrategy: options.AddressStrategy,\n\t\t\t\t},\n\t\t\t\tFallbackDelay: options.AddressFallbackDelay,\n\t\t\t},\n\t\t\tLegacy:              true,\n\t\t\tLegacyStrategy:      options.Strategy,\n\t\t\tLegacyDefaultDialer: options.Detour == \"\",\n\t\t\tLegacyClientSubnet:  options.ClientSubnet.Build(netip.Prefix{}),\n\t\t},\n\t\tLegacyAddressResolver:      options.AddressResolver,\n\t\tLegacyAddressStrategy:      options.AddressStrategy,\n\t\tLegacyAddressFallbackDelay: options.AddressFallbackDelay,\n\t}\n\tswitch serverType {\n\tcase C.DNSTypeLocal:\n\t\to.Type = C.DNSTypeLocal\n\t\to.Options = &LocalDNSServerOptions{\n\t\t\tRawLocalDNSServerOptions: remoteOptions.RawLocalDNSServerOptions,\n\t\t}\n\tcase C.DNSTypeUDP:\n\t\to.Type = C.DNSTypeUDP\n\t\to.Options = &remoteOptions\n\t\tvar serverAddr M.Socksaddr\n\t\tif serverURL == nil || serverURL.Scheme == \"\" {\n\t\t\tserverAddr = M.ParseSocksaddr(options.Address)\n\t\t} else {\n\t\t\tserverAddr = M.ParseSocksaddr(serverURL.Host)\n\t\t}\n\t\tif !serverAddr.IsValid() {\n\t\t\treturn E.New(\"invalid server address\")\n\t\t}\n\t\tremoteOptions.Server = serverAddr.AddrString()\n\t\tif serverAddr.Port != 0 && serverAddr.Port != 53 {\n\t\t\tremoteOptions.ServerPort = serverAddr.Port\n\t\t}\n\tcase C.DNSTypeTCP:\n\t\to.Type = C.DNSTypeTCP\n\t\to.Options = &remoteOptions\n\t\tif serverURL == nil {\n\t\t\treturn E.New(\"invalid server address\")\n\t\t}\n\t\tserverAddr := M.ParseSocksaddr(serverURL.Host)\n\t\tif !serverAddr.IsValid() {\n\t\t\treturn E.New(\"invalid server address\")\n\t\t}\n\t\tremoteOptions.Server = serverAddr.AddrString()\n\t\tif serverAddr.Port != 0 && serverAddr.Port != 53 {\n\t\t\tremoteOptions.ServerPort = serverAddr.Port\n\t\t}\n\tcase C.DNSTypeTLS, C.DNSTypeQUIC:\n\t\to.Type = serverType\n\t\tif serverURL == nil {\n\t\t\treturn E.New(\"invalid server address\")\n\t\t}\n\t\tserverAddr := M.ParseSocksaddr(serverURL.Host)\n\t\tif !serverAddr.IsValid() {\n\t\t\treturn E.New(\"invalid server address\")\n\t\t}\n\t\tremoteOptions.Server = serverAddr.AddrString()\n\t\tif serverAddr.Port != 0 && serverAddr.Port != 853 {\n\t\t\tremoteOptions.ServerPort = serverAddr.Port\n\t\t}\n\t\to.Options = &RemoteTLSDNSServerOptions{\n\t\t\tRemoteDNSServerOptions: remoteOptions,\n\t\t}\n\tcase C.DNSTypeHTTPS, C.DNSTypeHTTP3:\n\t\to.Type = serverType\n\t\thttpsOptions := RemoteHTTPSDNSServerOptions{\n\t\t\tRemoteTLSDNSServerOptions: RemoteTLSDNSServerOptions{\n\t\t\t\tRemoteDNSServerOptions: remoteOptions,\n\t\t\t},\n\t\t}\n\t\to.Options = &httpsOptions\n\t\tif serverURL == nil {\n\t\t\treturn E.New(\"invalid server address\")\n\t\t}\n\t\tserverAddr := M.ParseSocksaddr(serverURL.Host)\n\t\tif !serverAddr.IsValid() {\n\t\t\treturn E.New(\"invalid server address\")\n\t\t}\n\t\thttpsOptions.Server = serverAddr.AddrString()\n\t\tif serverAddr.Port != 0 && serverAddr.Port != 443 {\n\t\t\thttpsOptions.ServerPort = serverAddr.Port\n\t\t}\n\t\tif serverURL.Path != \"/dns-query\" {\n\t\t\thttpsOptions.Path = serverURL.Path\n\t\t}\n\tcase \"rcode\":\n\t\tvar rcode int\n\t\tif serverURL == nil {\n\t\t\treturn E.New(\"invalid server address\")\n\t\t}\n\t\tswitch serverURL.Host {\n\t\tcase \"success\":\n\t\t\trcode = dns.RcodeSuccess\n\t\tcase \"format_error\":\n\t\t\trcode = dns.RcodeFormatError\n\t\tcase \"server_failure\":\n\t\t\trcode = dns.RcodeServerFailure\n\t\tcase \"name_error\":\n\t\t\trcode = dns.RcodeNameError\n\t\tcase \"not_implemented\":\n\t\t\trcode = dns.RcodeNotImplemented\n\t\tcase \"refused\":\n\t\t\trcode = dns.RcodeRefused\n\t\tdefault:\n\t\t\treturn E.New(\"unknown rcode: \", serverURL.Host)\n\t\t}\n\t\to.Type = C.DNSTypeLegacyRcode\n\t\to.Options = rcode\n\tcase C.DNSTypeDHCP:\n\t\to.Type = C.DNSTypeDHCP\n\t\tdhcpOptions := DHCPDNSServerOptions{}\n\t\tif serverURL == nil {\n\t\t\treturn E.New(\"invalid server address\")\n\t\t}\n\t\tif serverURL.Host != \"\" && serverURL.Host != \"auto\" {\n\t\t\tdhcpOptions.Interface = serverURL.Host\n\t\t}\n\t\to.Options = &dhcpOptions\n\tcase C.DNSTypeFakeIP:\n\t\to.Type = C.DNSTypeFakeIP\n\t\tfakeipOptions := FakeIPDNSServerOptions{}\n\t\tif legacyOptions, loaded := ctx.Value((*LegacyDNSFakeIPOptions)(nil)).(*LegacyDNSFakeIPOptions); loaded {\n\t\t\tfakeipOptions.Inet4Range = legacyOptions.Inet4Range\n\t\t\tfakeipOptions.Inet6Range = legacyOptions.Inet6Range\n\t\t}\n\t\to.Options = &fakeipOptions\n\tdefault:\n\t\treturn E.New(\"unsupported DNS server scheme: \", serverType)\n\t}\n\treturn nil\n}\n\ntype DNSServerAddressOptions struct {\n\tServer     string `json:\"server\"`\n\tServerPort uint16 `json:\"server_port,omitempty\"`\n}\n\nfunc (o DNSServerAddressOptions) Build() M.Socksaddr {\n\treturn M.ParseSocksaddrHostPort(o.Server, o.ServerPort)\n}\n\nfunc (o DNSServerAddressOptions) ServerIsDomain() bool {\n\treturn o.Build().IsDomain()\n}\n\nfunc (o *DNSServerAddressOptions) TakeServerOptions() ServerOptions {\n\treturn ServerOptions(*o)\n}\n\nfunc (o *DNSServerAddressOptions) ReplaceServerOptions(options ServerOptions) {\n\t*o = DNSServerAddressOptions(options)\n}\n\ntype LegacyDNSServerOptions struct {\n\tAddress              string                `json:\"address\"`\n\tAddressResolver      string                `json:\"address_resolver,omitempty\"`\n\tAddressStrategy      DomainStrategy        `json:\"address_strategy,omitempty\"`\n\tAddressFallbackDelay badoption.Duration    `json:\"address_fallback_delay,omitempty\"`\n\tStrategy             DomainStrategy        `json:\"strategy,omitempty\"`\n\tDetour               string                `json:\"detour,omitempty\"`\n\tClientSubnet         *badoption.Prefixable `json:\"client_subnet,omitempty\"`\n}\n\ntype HostsDNSServerOptions struct {\n\tPath       badoption.Listable[string]                                `json:\"path,omitempty\"`\n\tPredefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:\"predefined,omitempty\"`\n}\n\ntype RawLocalDNSServerOptions struct {\n\tDialerOptions\n\tLegacy              bool           `json:\"-\"`\n\tLegacyStrategy      DomainStrategy `json:\"-\"`\n\tLegacyDefaultDialer bool           `json:\"-\"`\n\tLegacyClientSubnet  netip.Prefix   `json:\"-\"`\n}\n\ntype LocalDNSServerOptions struct {\n\tRawLocalDNSServerOptions\n\tPreferGo bool `json:\"prefer_go,omitempty\"`\n}\n\ntype RemoteDNSServerOptions struct {\n\tRawLocalDNSServerOptions\n\tDNSServerAddressOptions\n\tLegacyAddressResolver      string             `json:\"-\"`\n\tLegacyAddressStrategy      DomainStrategy     `json:\"-\"`\n\tLegacyAddressFallbackDelay badoption.Duration `json:\"-\"`\n}\n\ntype RemoteTLSDNSServerOptions struct {\n\tRemoteDNSServerOptions\n\tOutboundTLSOptionsContainer\n}\n\ntype RemoteHTTPSDNSServerOptions struct {\n\tRemoteTLSDNSServerOptions\n\tPath    string               `json:\"path,omitempty\"`\n\tMethod  string               `json:\"method,omitempty\"`\n\tHeaders badoption.HTTPHeader `json:\"headers,omitempty\"`\n}\n\ntype FakeIPDNSServerOptions struct {\n\tInet4Range *badoption.Prefix `json:\"inet4_range,omitempty\"`\n\tInet6Range *badoption.Prefix `json:\"inet6_range,omitempty\"`\n}\n\ntype DHCPDNSServerOptions struct {\n\tLocalDNSServerOptions\n\tInterface string `json:\"interface,omitempty\"`\n}\n"
  },
  {
    "path": "option/dns_record.go",
    "content": "package option\n\nimport (\n\t\"encoding/base64\"\n\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\n\t\"github.com/miekg/dns\"\n)\n\ntype DNSRCode int\n\nfunc (r DNSRCode) MarshalJSON() ([]byte, error) {\n\trCodeValue, loaded := dns.RcodeToString[int(r)]\n\tif loaded {\n\t\treturn json.Marshal(rCodeValue)\n\t}\n\treturn json.Marshal(int(r))\n}\n\nfunc (r *DNSRCode) UnmarshalJSON(bytes []byte) error {\n\tvar intValue int\n\terr := json.Unmarshal(bytes, &intValue)\n\tif err == nil {\n\t\t*r = DNSRCode(intValue)\n\t\treturn nil\n\t}\n\tvar stringValue string\n\terr = json.Unmarshal(bytes, &stringValue)\n\tif err != nil {\n\t\treturn err\n\t}\n\trCodeValue, loaded := dns.StringToRcode[stringValue]\n\tif !loaded {\n\t\treturn E.New(\"unknown rcode: \" + stringValue)\n\t}\n\t*r = DNSRCode(rCodeValue)\n\treturn nil\n}\n\nfunc (r *DNSRCode) Build() int {\n\tif r == nil {\n\t\treturn dns.RcodeSuccess\n\t}\n\treturn int(*r)\n}\n\ntype DNSRecordOptions struct {\n\tdns.RR\n\tfromBase64 bool\n}\n\nfunc (o DNSRecordOptions) MarshalJSON() ([]byte, error) {\n\tif o.fromBase64 {\n\t\tbuffer := buf.Get(dns.Len(o.RR))\n\t\tdefer buf.Put(buffer)\n\t\toffset, err := dns.PackRR(o.RR, buffer, 0, nil, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn json.Marshal(base64.StdEncoding.EncodeToString(buffer[:offset]))\n\t}\n\treturn json.Marshal(o.RR.String())\n}\n\nfunc (o *DNSRecordOptions) UnmarshalJSON(data []byte) error {\n\tvar stringValue string\n\terr := json.Unmarshal(data, &stringValue)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbinary, err := base64.StdEncoding.DecodeString(stringValue)\n\tif err == nil {\n\t\treturn o.unmarshalBase64(binary)\n\t}\n\trecord, err := dns.NewRR(stringValue)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif a, isA := record.(*dns.A); isA {\n\t\ta.A = M.AddrFromIP(a.A).Unmap().AsSlice()\n\t}\n\to.RR = record\n\treturn nil\n}\n\nfunc (o *DNSRecordOptions) unmarshalBase64(binary []byte) error {\n\trecord, _, err := dns.UnpackRR(binary, 0)\n\tif err != nil {\n\t\treturn E.New(\"parse binary DNS record\")\n\t}\n\to.RR = record\n\to.fromBase64 = true\n\treturn nil\n}\n\nfunc (o DNSRecordOptions) Build() dns.RR {\n\treturn o.RR\n}\n"
  },
  {
    "path": "option/endpoint.go",
    "content": "package option\n\nimport (\n\t\"context\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/service\"\n)\n\ntype EndpointOptionsRegistry interface {\n\tCreateOptions(endpointType string) (any, bool)\n}\n\ntype _Endpoint struct {\n\tType    string `json:\"type\"`\n\tTag     string `json:\"tag,omitempty\"`\n\tOptions any    `json:\"-\"`\n}\n\ntype Endpoint _Endpoint\n\nfunc (h *Endpoint) MarshalJSONContext(ctx context.Context) ([]byte, error) {\n\treturn badjson.MarshallObjectsContext(ctx, (*_Endpoint)(h), h.Options)\n}\n\nfunc (h *Endpoint) UnmarshalJSONContext(ctx context.Context, content []byte) error {\n\terr := json.UnmarshalContext(ctx, content, (*_Endpoint)(h))\n\tif err != nil {\n\t\treturn err\n\t}\n\tregistry := service.FromContext[EndpointOptionsRegistry](ctx)\n\tif registry == nil {\n\t\treturn E.New(\"missing endpoint fields registry in context\")\n\t}\n\toptions, loaded := registry.CreateOptions(h.Type)\n\tif !loaded {\n\t\treturn E.New(\"unknown endpoint type: \", h.Type)\n\t}\n\terr = badjson.UnmarshallExcludedContext(ctx, content, (*_Endpoint)(h), options)\n\tif err != nil {\n\t\treturn err\n\t}\n\th.Options = options\n\treturn nil\n}\n"
  },
  {
    "path": "option/experimental.go",
    "content": "package option\n\nimport \"github.com/sagernet/sing/common/json/badoption\"\n\ntype ExperimentalOptions struct {\n\tCacheFile *CacheFileOptions `json:\"cache_file,omitempty\"`\n\tClashAPI  *ClashAPIOptions  `json:\"clash_api,omitempty\"`\n\tV2RayAPI  *V2RayAPIOptions  `json:\"v2ray_api,omitempty\"`\n\tDebug     *DebugOptions     `json:\"debug,omitempty\"`\n}\n\ntype CacheFileOptions struct {\n\tEnabled     bool               `json:\"enabled,omitempty\"`\n\tPath        string             `json:\"path,omitempty\"`\n\tCacheID     string             `json:\"cache_id,omitempty\"`\n\tStoreFakeIP bool               `json:\"store_fakeip,omitempty\"`\n\tStoreRDRC   bool               `json:\"store_rdrc,omitempty\"`\n\tRDRCTimeout badoption.Duration `json:\"rdrc_timeout,omitempty\"`\n}\n\ntype ClashAPIOptions struct {\n\tExternalController               string                     `json:\"external_controller,omitempty\"`\n\tExternalUI                       string                     `json:\"external_ui,omitempty\"`\n\tExternalUIDownloadURL            string                     `json:\"external_ui_download_url,omitempty\"`\n\tExternalUIDownloadDetour         string                     `json:\"external_ui_download_detour,omitempty\"`\n\tSecret                           string                     `json:\"secret,omitempty\"`\n\tDefaultMode                      string                     `json:\"default_mode,omitempty\"`\n\tModeList                         []string                   `json:\"-\"`\n\tAccessControlAllowOrigin         badoption.Listable[string] `json:\"access_control_allow_origin,omitempty\"`\n\tAccessControlAllowPrivateNetwork bool                       `json:\"access_control_allow_private_network,omitempty\"`\n\n\t// Deprecated: migrated to global cache file\n\tCacheFile string `json:\"cache_file,omitempty\"`\n\t// Deprecated: migrated to global cache file\n\tCacheID string `json:\"cache_id,omitempty\"`\n\t// Deprecated: migrated to global cache file\n\tStoreMode bool `json:\"store_mode,omitempty\"`\n\t// Deprecated: migrated to global cache file\n\tStoreSelected bool `json:\"store_selected,omitempty\"`\n\t// Deprecated: migrated to global cache file\n\tStoreFakeIP bool `json:\"store_fakeip,omitempty\"`\n}\n\ntype V2RayAPIOptions struct {\n\tListen string                    `json:\"listen,omitempty\"`\n\tStats  *V2RayStatsServiceOptions `json:\"stats,omitempty\"`\n}\n\ntype V2RayStatsServiceOptions struct {\n\tEnabled   bool     `json:\"enabled,omitempty\"`\n\tInbounds  []string `json:\"inbounds,omitempty\"`\n\tOutbounds []string `json:\"outbounds,omitempty\"`\n\tUsers     []string `json:\"users,omitempty\"`\n}\n"
  },
  {
    "path": "option/group.go",
    "content": "package option\n\nimport \"github.com/sagernet/sing/common/json/badoption\"\n\ntype SelectorOutboundOptions struct {\n\tOutbounds                 []string `json:\"outbounds\"`\n\tDefault                   string   `json:\"default,omitempty\"`\n\tInterruptExistConnections bool     `json:\"interrupt_exist_connections,omitempty\"`\n}\n\ntype URLTestOutboundOptions struct {\n\tOutbounds                 []string           `json:\"outbounds\"`\n\tURL                       string             `json:\"url,omitempty\"`\n\tInterval                  badoption.Duration `json:\"interval,omitempty\"`\n\tTolerance                 uint16             `json:\"tolerance,omitempty\"`\n\tIdleTimeout               badoption.Duration `json:\"idle_timeout,omitempty\"`\n\tInterruptExistConnections bool               `json:\"interrupt_exist_connections,omitempty\"`\n}\n"
  },
  {
    "path": "option/hysteria.go",
    "content": "package option\n\nimport (\n\t\"github.com/sagernet/sing/common/byteformats\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype HysteriaInboundOptions struct {\n\tListenOptions\n\tUp                  *byteformats.NetworkBytesCompat `json:\"up,omitempty\"`\n\tUpMbps              int                             `json:\"up_mbps,omitempty\"`\n\tDown                *byteformats.NetworkBytesCompat `json:\"down,omitempty\"`\n\tDownMbps            int                             `json:\"down_mbps,omitempty\"`\n\tObfs                string                          `json:\"obfs,omitempty\"`\n\tUsers               []HysteriaUser                  `json:\"users,omitempty\"`\n\tReceiveWindowConn   uint64                          `json:\"recv_window_conn,omitempty\"`\n\tReceiveWindowClient uint64                          `json:\"recv_window_client,omitempty\"`\n\tMaxConnClient       int                             `json:\"max_conn_client,omitempty\"`\n\tDisableMTUDiscovery bool                            `json:\"disable_mtu_discovery,omitempty\"`\n\tInboundTLSOptionsContainer\n}\n\ntype HysteriaUser struct {\n\tName       string `json:\"name,omitempty\"`\n\tAuth       []byte `json:\"auth,omitempty\"`\n\tAuthString string `json:\"auth_str,omitempty\"`\n}\n\ntype HysteriaOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tServerPorts         badoption.Listable[string]      `json:\"server_ports,omitempty\"`\n\tHopInterval         badoption.Duration              `json:\"hop_interval,omitempty\"`\n\tUp                  *byteformats.NetworkBytesCompat `json:\"up,omitempty\"`\n\tUpMbps              int                             `json:\"up_mbps,omitempty\"`\n\tDown                *byteformats.NetworkBytesCompat `json:\"down,omitempty\"`\n\tDownMbps            int                             `json:\"down_mbps,omitempty\"`\n\tObfs                string                          `json:\"obfs,omitempty\"`\n\tAuth                []byte                          `json:\"auth,omitempty\"`\n\tAuthString          string                          `json:\"auth_str,omitempty\"`\n\tReceiveWindowConn   uint64                          `json:\"recv_window_conn,omitempty\"`\n\tReceiveWindow       uint64                          `json:\"recv_window,omitempty\"`\n\tDisableMTUDiscovery bool                            `json:\"disable_mtu_discovery,omitempty\"`\n\tNetwork             NetworkList                     `json:\"network,omitempty\"`\n\tOutboundTLSOptionsContainer\n}\n"
  },
  {
    "path": "option/hysteria2.go",
    "content": "package option\n\nimport (\n\t\"net/url\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype Hysteria2InboundOptions struct {\n\tListenOptions\n\tUpMbps                int             `json:\"up_mbps,omitempty\"`\n\tDownMbps              int             `json:\"down_mbps,omitempty\"`\n\tObfs                  *Hysteria2Obfs  `json:\"obfs,omitempty\"`\n\tUsers                 []Hysteria2User `json:\"users,omitempty\"`\n\tIgnoreClientBandwidth bool            `json:\"ignore_client_bandwidth,omitempty\"`\n\tInboundTLSOptionsContainer\n\tMasquerade  *Hysteria2Masquerade `json:\"masquerade,omitempty\"`\n\tBrutalDebug bool                 `json:\"brutal_debug,omitempty\"`\n}\n\ntype Hysteria2Obfs struct {\n\tType     string `json:\"type,omitempty\"`\n\tPassword string `json:\"password,omitempty\"`\n}\n\ntype Hysteria2User struct {\n\tName     string `json:\"name,omitempty\"`\n\tPassword string `json:\"password,omitempty\"`\n}\n\ntype _Hysteria2Masquerade struct {\n\tType          string                    `json:\"type,omitempty\"`\n\tFileOptions   Hysteria2MasqueradeFile   `json:\"-\"`\n\tProxyOptions  Hysteria2MasqueradeProxy  `json:\"-\"`\n\tStringOptions Hysteria2MasqueradeString `json:\"-\"`\n}\n\ntype Hysteria2Masquerade _Hysteria2Masquerade\n\nfunc (m Hysteria2Masquerade) MarshalJSON() ([]byte, error) {\n\tvar v any\n\tswitch m.Type {\n\tcase C.Hysterai2MasqueradeTypeFile:\n\t\tv = m.FileOptions\n\tcase C.Hysterai2MasqueradeTypeProxy:\n\t\tv = m.ProxyOptions\n\tcase C.Hysterai2MasqueradeTypeString:\n\t\tv = m.StringOptions\n\tdefault:\n\t\treturn nil, E.New(\"unknown masquerade type: \", m.Type)\n\t}\n\treturn badjson.MarshallObjects((_Hysteria2Masquerade)(m), v)\n}\n\nfunc (m *Hysteria2Masquerade) UnmarshalJSON(bytes []byte) error {\n\tvar urlString string\n\terr := json.Unmarshal(bytes, &urlString)\n\tif err == nil {\n\t\tmasqueradeURL, err := url.Parse(urlString)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"invalid masquerade URL\")\n\t\t}\n\t\tswitch masqueradeURL.Scheme {\n\t\tcase \"file\":\n\t\t\tm.Type = C.Hysterai2MasqueradeTypeFile\n\t\t\tm.FileOptions.Directory = masqueradeURL.Path\n\t\tcase \"http\", \"https\":\n\t\t\tm.Type = C.Hysterai2MasqueradeTypeProxy\n\t\t\tm.ProxyOptions.URL = urlString\n\t\tdefault:\n\t\t\treturn E.New(\"unknown masquerade URL scheme: \", masqueradeURL.Scheme)\n\t\t}\n\t\treturn nil\n\t}\n\terr = json.Unmarshal(bytes, (*_Hysteria2Masquerade)(m))\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar v any\n\tswitch m.Type {\n\tcase C.Hysterai2MasqueradeTypeFile:\n\t\tv = &m.FileOptions\n\tcase C.Hysterai2MasqueradeTypeProxy:\n\t\tv = &m.ProxyOptions\n\tcase C.Hysterai2MasqueradeTypeString:\n\t\tv = &m.StringOptions\n\tdefault:\n\t\treturn E.New(\"unknown masquerade type: \", m.Type)\n\t}\n\treturn badjson.UnmarshallExcluded(bytes, (*_Hysteria2Masquerade)(m), v)\n}\n\ntype Hysteria2MasqueradeFile struct {\n\tDirectory string `json:\"directory\"`\n}\n\ntype Hysteria2MasqueradeProxy struct {\n\tURL         string `json:\"url\"`\n\tRewriteHost bool   `json:\"rewrite_host,omitempty\"`\n}\n\ntype Hysteria2MasqueradeString struct {\n\tStatusCode int                  `json:\"status_code,omitempty\"`\n\tHeaders    badoption.HTTPHeader `json:\"headers,omitempty\"`\n\tContent    string               `json:\"content\"`\n}\n\ntype Hysteria2OutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tServerPorts badoption.Listable[string] `json:\"server_ports,omitempty\"`\n\tHopInterval badoption.Duration         `json:\"hop_interval,omitempty\"`\n\tUpMbps      int                        `json:\"up_mbps,omitempty\"`\n\tDownMbps    int                        `json:\"down_mbps,omitempty\"`\n\tObfs        *Hysteria2Obfs             `json:\"obfs,omitempty\"`\n\tPassword    string                     `json:\"password,omitempty\"`\n\tNetwork     NetworkList                `json:\"network,omitempty\"`\n\tOutboundTLSOptionsContainer\n\tBrutalDebug bool `json:\"brutal_debug,omitempty\"`\n}\n"
  },
  {
    "path": "option/inbound.go",
    "content": "package option\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\t\"github.com/sagernet/sing/service\"\n)\n\ntype InboundOptionsRegistry interface {\n\tCreateOptions(outboundType string) (any, bool)\n}\n\ntype _Inbound struct {\n\tType    string `json:\"type\"`\n\tTag     string `json:\"tag,omitempty\"`\n\tOptions any    `json:\"-\"`\n}\n\ntype Inbound _Inbound\n\nfunc (h *Inbound) MarshalJSONContext(ctx context.Context) ([]byte, error) {\n\treturn badjson.MarshallObjectsContext(ctx, (*_Inbound)(h), h.Options)\n}\n\nfunc (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) error {\n\terr := json.UnmarshalContext(ctx, content, (*_Inbound)(h))\n\tif err != nil {\n\t\treturn err\n\t}\n\tregistry := service.FromContext[InboundOptionsRegistry](ctx)\n\tif registry == nil {\n\t\treturn E.New(\"missing inbound fields registry in context\")\n\t}\n\toptions, loaded := registry.CreateOptions(h.Type)\n\tif !loaded {\n\t\treturn E.New(\"unknown inbound type: \", h.Type)\n\t}\n\terr = badjson.UnmarshallExcludedContext(ctx, content, (*_Inbound)(h), options)\n\tif err != nil {\n\t\treturn err\n\t}\n\th.Options = options\n\treturn nil\n}\n\n// Deprecated: Use rule action instead\ntype InboundOptions struct {\n\tSniffEnabled              bool               `json:\"sniff,omitempty\"`\n\tSniffOverrideDestination  bool               `json:\"sniff_override_destination,omitempty\"`\n\tSniffTimeout              badoption.Duration `json:\"sniff_timeout,omitempty\"`\n\tDomainStrategy            DomainStrategy     `json:\"domain_strategy,omitempty\"`\n\tUDPDisableDomainUnmapping bool               `json:\"udp_disable_domain_unmapping,omitempty\"`\n}\n\ntype ListenOptions struct {\n\tListen               *badoption.Addr    `json:\"listen,omitempty\"`\n\tListenPort           uint16             `json:\"listen_port,omitempty\"`\n\tBindInterface        string             `json:\"bind_interface,omitempty\"`\n\tRoutingMark          FwMark             `json:\"routing_mark,omitempty\"`\n\tReuseAddr            bool               `json:\"reuse_addr,omitempty\"`\n\tNetNs                string             `json:\"netns,omitempty\"`\n\tDisableTCPKeepAlive  bool               `json:\"disable_tcp_keep_alive,omitempty\"`\n\tTCPKeepAlive         badoption.Duration `json:\"tcp_keep_alive,omitempty\"`\n\tTCPKeepAliveInterval badoption.Duration `json:\"tcp_keep_alive_interval,omitempty\"`\n\tTCPFastOpen          bool               `json:\"tcp_fast_open,omitempty\"`\n\tTCPMultiPath         bool               `json:\"tcp_multi_path,omitempty\"`\n\tUDPFragment          *bool              `json:\"udp_fragment,omitempty\"`\n\tUDPFragmentDefault   bool               `json:\"-\"`\n\tUDPTimeout           UDPTimeoutCompat   `json:\"udp_timeout,omitempty\"`\n\tDetour               string             `json:\"detour,omitempty\"`\n\n\t// Deprecated: removed\n\tProxyProtocol bool `json:\"proxy_protocol,omitempty\"`\n\t// Deprecated: removed\n\tProxyProtocolAcceptNoHeader bool `json:\"proxy_protocol_accept_no_header,omitempty\"`\n\tInboundOptions\n}\n\ntype UDPTimeoutCompat badoption.Duration\n\nfunc (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal((time.Duration)(c).String())\n}\n\nfunc (c *UDPTimeoutCompat) UnmarshalJSON(data []byte) error {\n\tvar valueNumber int64\n\terr := json.Unmarshal(data, &valueNumber)\n\tif err == nil {\n\t\t*c = UDPTimeoutCompat(time.Second * time.Duration(valueNumber))\n\t\treturn nil\n\t}\n\treturn json.Unmarshal(data, (*badoption.Duration)(c))\n}\n\ntype ListenOptionsWrapper interface {\n\tTakeListenOptions() ListenOptions\n\tReplaceListenOptions(options ListenOptions)\n}\n\nfunc (o *ListenOptions) TakeListenOptions() ListenOptions {\n\treturn *o\n}\n\nfunc (o *ListenOptions) ReplaceListenOptions(options ListenOptions) {\n\t*o = options\n}\n"
  },
  {
    "path": "option/multiplex.go",
    "content": "package option\n\ntype InboundMultiplexOptions struct {\n\tEnabled bool           `json:\"enabled,omitempty\"`\n\tPadding bool           `json:\"padding,omitempty\"`\n\tBrutal  *BrutalOptions `json:\"brutal,omitempty\"`\n}\n\ntype OutboundMultiplexOptions struct {\n\tEnabled        bool           `json:\"enabled,omitempty\"`\n\tProtocol       string         `json:\"protocol,omitempty\"`\n\tMaxConnections int            `json:\"max_connections,omitempty\"`\n\tMinStreams     int            `json:\"min_streams,omitempty\"`\n\tMaxStreams     int            `json:\"max_streams,omitempty\"`\n\tPadding        bool           `json:\"padding,omitempty\"`\n\tBrutal         *BrutalOptions `json:\"brutal,omitempty\"`\n}\n\ntype BrutalOptions struct {\n\tEnabled  bool `json:\"enabled,omitempty\"`\n\tUpMbps   int  `json:\"up_mbps,omitempty\"`\n\tDownMbps int  `json:\"down_mbps,omitempty\"`\n}\n"
  },
  {
    "path": "option/naive.go",
    "content": "package option\n\nimport (\n\t\"github.com/sagernet/sing/common/auth\"\n\t\"github.com/sagernet/sing/common/byteformats\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype QuicheCongestionControl string\n\nconst (\n\tQuicheCongestionControlDefault QuicheCongestionControl = \"\"\n\tQuicheCongestionControlBBR     QuicheCongestionControl = \"TBBR\"\n\tQuicheCongestionControlBBRv2   QuicheCongestionControl = \"B2ON\"\n\tQuicheCongestionControlCubic   QuicheCongestionControl = \"QBIC\"\n\tQuicheCongestionControlReno    QuicheCongestionControl = \"RENO\"\n)\n\ntype NaiveInboundOptions struct {\n\tListenOptions\n\tUsers                 []auth.User `json:\"users,omitempty\"`\n\tNetwork               NetworkList `json:\"network,omitempty\"`\n\tQUICCongestionControl string      `json:\"quic_congestion_control,omitempty\"`\n\tInboundTLSOptionsContainer\n}\n\ntype NaiveOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tUsername                 string                   `json:\"username,omitempty\"`\n\tPassword                 string                   `json:\"password,omitempty\"`\n\tInsecureConcurrency      int                      `json:\"insecure_concurrency,omitempty\"`\n\tExtraHeaders             badoption.HTTPHeader     `json:\"extra_headers,omitempty\"`\n\tReceiveWindow            *byteformats.MemoryBytes `json:\"stream_receive_window,omitempty\"`\n\tUDPOverTCP               *UDPOverTCPOptions       `json:\"udp_over_tcp,omitempty\"`\n\tQUIC                     bool                     `json:\"quic,omitempty\"`\n\tQUICCongestionControl    string                   `json:\"quic_congestion_control,omitempty\"`\n\tQUICSessionReceiveWindow *byteformats.MemoryBytes `json:\"quic_session_receive_window,omitempty\"`\n\tOutboundTLSOptionsContainer\n}\n"
  },
  {
    "path": "option/ntp.go",
    "content": "package option\n\nimport \"github.com/sagernet/sing/common/json/badoption\"\n\ntype NTPOptions struct {\n\tEnabled       bool               `json:\"enabled,omitempty\"`\n\tInterval      badoption.Duration `json:\"interval,omitempty\"`\n\tWriteToSystem bool               `json:\"write_to_system,omitempty\"`\n\tServerOptions\n\tDialerOptions\n}\n"
  },
  {
    "path": "option/ocm.go",
    "content": "package option\n\nimport (\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype OCMServiceOptions struct {\n\tListenOptions\n\tInboundTLSOptionsContainer\n\tCredentialPath string               `json:\"credential_path,omitempty\"`\n\tUsers          []OCMUser            `json:\"users,omitempty\"`\n\tHeaders        badoption.HTTPHeader `json:\"headers,omitempty\"`\n\tDetour         string               `json:\"detour,omitempty\"`\n\tUsagesPath     string               `json:\"usages_path,omitempty\"`\n}\n\ntype OCMUser struct {\n\tName  string `json:\"name,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n"
  },
  {
    "path": "option/oom_killer.go",
    "content": "package option\n\nimport (\n\t\"github.com/sagernet/sing/common/byteformats\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype OOMKillerServiceOptions struct {\n\tMemoryLimit       *byteformats.MemoryBytes `json:\"memory_limit,omitempty\"`\n\tSafetyMargin      *byteformats.MemoryBytes `json:\"safety_margin,omitempty\"`\n\tMinInterval       badoption.Duration       `json:\"min_interval,omitempty\"`\n\tMaxInterval       badoption.Duration       `json:\"max_interval,omitempty\"`\n\tChecksBeforeLimit int                      `json:\"checks_before_limit,omitempty\"`\n}\n"
  },
  {
    "path": "option/options.go",
    "content": "package option\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json\"\n)\n\ntype _Options struct {\n\tRawMessage   json.RawMessage      `json:\"-\"`\n\tSchema       string               `json:\"$schema,omitempty\"`\n\tLog          *LogOptions          `json:\"log,omitempty\"`\n\tDNS          *DNSOptions          `json:\"dns,omitempty\"`\n\tNTP          *NTPOptions          `json:\"ntp,omitempty\"`\n\tCertificate  *CertificateOptions  `json:\"certificate,omitempty\"`\n\tEndpoints    []Endpoint           `json:\"endpoints,omitempty\"`\n\tInbounds     []Inbound            `json:\"inbounds,omitempty\"`\n\tOutbounds    []Outbound           `json:\"outbounds,omitempty\"`\n\tRoute        *RouteOptions        `json:\"route,omitempty\"`\n\tServices     []Service            `json:\"services,omitempty\"`\n\tExperimental *ExperimentalOptions `json:\"experimental,omitempty\"`\n}\n\ntype Options _Options\n\nfunc (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) error {\n\tdecoder := json.NewDecoderContext(ctx, bytes.NewReader(content))\n\tdecoder.DisallowUnknownFields()\n\terr := decoder.Decode((*_Options)(o))\n\tif err != nil {\n\t\treturn err\n\t}\n\to.RawMessage = content\n\treturn checkOptions(o)\n}\n\ntype LogOptions struct {\n\tDisabled     bool   `json:\"disabled,omitempty\"`\n\tLevel        string `json:\"level,omitempty\"`\n\tOutput       string `json:\"output,omitempty\"`\n\tTimestamp    bool   `json:\"timestamp,omitempty\"`\n\tDisableColor bool   `json:\"-\"`\n}\n\ntype StubOptions struct{}\n\nfunc checkOptions(options *Options) error {\n\terr := checkInbounds(options.Inbounds)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = checkOutbounds(options.Outbounds, options.Endpoints)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc checkInbounds(inbounds []Inbound) error {\n\tseen := make(map[string]bool)\n\tfor i, inbound := range inbounds {\n\t\ttag := inbound.Tag\n\t\tif tag == \"\" {\n\t\t\ttag = F.ToString(i)\n\t\t}\n\t\tif seen[tag] {\n\t\t\treturn E.New(\"duplicate inbound tag: \", tag)\n\t\t}\n\t\tseen[tag] = true\n\t}\n\treturn nil\n}\n\nfunc checkOutbounds(outbounds []Outbound, endpoints []Endpoint) error {\n\tseen := make(map[string]bool)\n\tfor i, outbound := range outbounds {\n\t\ttag := outbound.Tag\n\t\tif tag == \"\" {\n\t\t\ttag = F.ToString(i)\n\t\t}\n\t\tif seen[tag] {\n\t\t\treturn E.New(\"duplicate outbound/endpoint tag: \", tag)\n\t\t}\n\t\tseen[tag] = true\n\t}\n\tfor i, endpoint := range endpoints {\n\t\ttag := endpoint.Tag\n\t\tif tag == \"\" {\n\t\t\ttag = F.ToString(i)\n\t\t}\n\t\tif seen[tag] {\n\t\t\treturn E.New(\"duplicate outbound/endpoint tag: \", tag)\n\t\t}\n\t\tseen[tag] = true\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "option/outbound.go",
    "content": "package option\n\nimport (\n\t\"context\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/service\"\n)\n\ntype OutboundOptionsRegistry interface {\n\tCreateOptions(outboundType string) (any, bool)\n}\n\ntype _Outbound struct {\n\tType    string `json:\"type\"`\n\tTag     string `json:\"tag,omitempty\"`\n\tOptions any    `json:\"-\"`\n}\n\ntype Outbound _Outbound\n\nfunc (h *Outbound) MarshalJSONContext(ctx context.Context) ([]byte, error) {\n\treturn badjson.MarshallObjectsContext(ctx, (*_Outbound)(h), h.Options)\n}\n\nfunc (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) error {\n\terr := json.UnmarshalContext(ctx, content, (*_Outbound)(h))\n\tif err != nil {\n\t\treturn err\n\t}\n\tregistry := service.FromContext[OutboundOptionsRegistry](ctx)\n\tif registry == nil {\n\t\treturn E.New(\"missing outbound options registry in context\")\n\t}\n\tswitch h.Type {\n\tcase C.TypeDNS:\n\t\treturn E.New(\"dns outbound is deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use rule actions instead\")\n\t}\n\toptions, loaded := registry.CreateOptions(h.Type)\n\tif !loaded {\n\t\treturn E.New(\"unknown outbound type: \", h.Type)\n\t}\n\terr = badjson.UnmarshallExcludedContext(ctx, content, (*_Outbound)(h), options)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif listenWrapper, isListen := options.(ListenOptionsWrapper); isListen {\n\t\t//nolint:staticcheck\n\t\tif listenWrapper.TakeListenOptions().InboundOptions != (InboundOptions{}) {\n\t\t\treturn E.New(\"legacy inbound fields are deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use rule actions instead\")\n\t\t}\n\t}\n\th.Options = options\n\treturn nil\n}\n\ntype DialerOptionsWrapper interface {\n\tTakeDialerOptions() DialerOptions\n\tReplaceDialerOptions(options DialerOptions)\n}\n\ntype DialerOptions struct {\n\tDetour               string                            `json:\"detour,omitempty\"`\n\tBindInterface        string                            `json:\"bind_interface,omitempty\"`\n\tInet4BindAddress     *badoption.Addr                   `json:\"inet4_bind_address,omitempty\"`\n\tInet6BindAddress     *badoption.Addr                   `json:\"inet6_bind_address,omitempty\"`\n\tBindAddressNoPort    bool                              `json:\"bind_address_no_port,omitempty\"`\n\tProtectPath          string                            `json:\"protect_path,omitempty\"`\n\tRoutingMark          FwMark                            `json:\"routing_mark,omitempty\"`\n\tReuseAddr            bool                              `json:\"reuse_addr,omitempty\"`\n\tNetNs                string                            `json:\"netns,omitempty\"`\n\tConnectTimeout       badoption.Duration                `json:\"connect_timeout,omitempty\"`\n\tTCPFastOpen          bool                              `json:\"tcp_fast_open,omitempty\"`\n\tTCPMultiPath         bool                              `json:\"tcp_multi_path,omitempty\"`\n\tDisableTCPKeepAlive  bool                              `json:\"disable_tcp_keep_alive,omitempty\"`\n\tTCPKeepAlive         badoption.Duration                `json:\"tcp_keep_alive,omitempty\"`\n\tTCPKeepAliveInterval badoption.Duration                `json:\"tcp_keep_alive_interval,omitempty\"`\n\tUDPFragment          *bool                             `json:\"udp_fragment,omitempty\"`\n\tUDPFragmentDefault   bool                              `json:\"-\"`\n\tDomainResolver       *DomainResolveOptions             `json:\"domain_resolver,omitempty\"`\n\tNetworkStrategy      *NetworkStrategy                  `json:\"network_strategy,omitempty\"`\n\tNetworkType          badoption.Listable[InterfaceType] `json:\"network_type,omitempty\"`\n\tFallbackNetworkType  badoption.Listable[InterfaceType] `json:\"fallback_network_type,omitempty\"`\n\tFallbackDelay        badoption.Duration                `json:\"fallback_delay,omitempty\"`\n\n\t// Deprecated: migrated to domain resolver\n\tDomainStrategy DomainStrategy `json:\"domain_strategy,omitempty\"`\n}\n\ntype _DomainResolveOptions struct {\n\tServer       string                `json:\"server\"`\n\tStrategy     DomainStrategy        `json:\"strategy,omitempty\"`\n\tDisableCache bool                  `json:\"disable_cache,omitempty\"`\n\tRewriteTTL   *uint32               `json:\"rewrite_ttl,omitempty\"`\n\tClientSubnet *badoption.Prefixable `json:\"client_subnet,omitempty\"`\n}\n\ntype DomainResolveOptions _DomainResolveOptions\n\nfunc (o DomainResolveOptions) MarshalJSON() ([]byte, error) {\n\tif o.Server == \"\" {\n\t\treturn []byte(\"{}\"), nil\n\t} else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) &&\n\t\t!o.DisableCache &&\n\t\to.RewriteTTL == nil &&\n\t\to.ClientSubnet == nil {\n\t\treturn json.Marshal(o.Server)\n\t} else {\n\t\treturn json.Marshal((_DomainResolveOptions)(o))\n\t}\n}\n\nfunc (o *DomainResolveOptions) UnmarshalJSON(bytes []byte) error {\n\tvar stringValue string\n\terr := json.Unmarshal(bytes, &stringValue)\n\tif err == nil {\n\t\to.Server = stringValue\n\t\treturn nil\n\t}\n\terr = json.Unmarshal(bytes, (*_DomainResolveOptions)(o))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif o.Server == \"\" {\n\t\treturn E.New(\"empty domain_resolver.server\")\n\t}\n\treturn nil\n}\n\nfunc (o *DialerOptions) TakeDialerOptions() DialerOptions {\n\treturn *o\n}\n\nfunc (o *DialerOptions) ReplaceDialerOptions(options DialerOptions) {\n\t*o = options\n}\n\ntype ServerOptionsWrapper interface {\n\tTakeServerOptions() ServerOptions\n\tReplaceServerOptions(options ServerOptions)\n}\n\ntype ServerOptions struct {\n\tServer     string `json:\"server\"`\n\tServerPort uint16 `json:\"server_port\"`\n}\n\nfunc (o ServerOptions) Build() M.Socksaddr {\n\treturn M.ParseSocksaddrHostPort(o.Server, o.ServerPort)\n}\n\nfunc (o ServerOptions) ServerIsDomain() bool {\n\treturn o.Build().IsDomain()\n}\n\nfunc (o *ServerOptions) TakeServerOptions() ServerOptions {\n\treturn *o\n}\n\nfunc (o *ServerOptions) ReplaceServerOptions(options ServerOptions) {\n\t*o = options\n}\n"
  },
  {
    "path": "option/platform.go",
    "content": "package option\n\nimport (\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype OnDemandOptions struct {\n\tEnabled bool           `json:\"enabled,omitempty\"`\n\tRules   []OnDemandRule `json:\"rules,omitempty\"`\n}\n\ntype OnDemandRule struct {\n\tAction                *OnDemandRuleAction        `json:\"action,omitempty\"`\n\tDNSSearchDomainMatch  badoption.Listable[string] `json:\"dns_search_domain_match,omitempty\"`\n\tDNSServerAddressMatch badoption.Listable[string] `json:\"dns_server_address_match,omitempty\"`\n\tInterfaceTypeMatch    *OnDemandRuleInterfaceType `json:\"interface_type_match,omitempty\"`\n\tSSIDMatch             badoption.Listable[string] `json:\"ssid_match,omitempty\"`\n\tProbeURL              string                     `json:\"probe_url,omitempty\"`\n}\n\ntype OnDemandRuleAction int\n\nfunc (r *OnDemandRuleAction) MarshalJSON() ([]byte, error) {\n\tif r == nil {\n\t\treturn nil, nil\n\t}\n\tvalue := *r\n\tvar actionName string\n\tswitch value {\n\tcase 1:\n\t\tactionName = \"connect\"\n\tcase 2:\n\t\tactionName = \"disconnect\"\n\tcase 3:\n\t\tactionName = \"evaluate_connection\"\n\tdefault:\n\t\treturn nil, E.New(\"unknown action: \", value)\n\t}\n\treturn json.Marshal(actionName)\n}\n\nfunc (r *OnDemandRuleAction) UnmarshalJSON(bytes []byte) error {\n\tvar actionName string\n\tif err := json.Unmarshal(bytes, &actionName); err != nil {\n\t\treturn err\n\t}\n\tvar actionValue int\n\tswitch actionName {\n\tcase \"connect\":\n\t\tactionValue = 1\n\tcase \"disconnect\":\n\t\tactionValue = 2\n\tcase \"evaluate_connection\":\n\t\tactionValue = 3\n\tcase \"ignore\":\n\t\tactionValue = 4\n\tdefault:\n\t\treturn E.New(\"unknown action name: \", actionName)\n\t}\n\t*r = OnDemandRuleAction(actionValue)\n\treturn nil\n}\n\ntype OnDemandRuleInterfaceType int\n\nfunc (r *OnDemandRuleInterfaceType) MarshalJSON() ([]byte, error) {\n\tif r == nil {\n\t\treturn nil, nil\n\t}\n\tvalue := *r\n\tvar interfaceTypeName string\n\tswitch value {\n\tcase 1:\n\t\tinterfaceTypeName = \"any\"\n\tcase 2:\n\t\tinterfaceTypeName = \"wifi\"\n\tcase 3:\n\t\tinterfaceTypeName = \"cellular\"\n\tdefault:\n\t\treturn nil, E.New(\"unknown interface type: \", value)\n\t}\n\treturn json.Marshal(interfaceTypeName)\n}\n\nfunc (r *OnDemandRuleInterfaceType) UnmarshalJSON(bytes []byte) error {\n\tvar interfaceTypeName string\n\tif err := json.Unmarshal(bytes, &interfaceTypeName); err != nil {\n\t\treturn err\n\t}\n\tvar interfaceTypeValue int\n\tswitch interfaceTypeName {\n\tcase \"any\":\n\t\tinterfaceTypeValue = 1\n\tcase \"wifi\":\n\t\tinterfaceTypeValue = 2\n\tcase \"cellular\":\n\t\tinterfaceTypeValue = 3\n\tdefault:\n\t\treturn E.New(\"unknown interface type name: \", interfaceTypeName)\n\t}\n\t*r = OnDemandRuleInterfaceType(interfaceTypeValue)\n\treturn nil\n}\n"
  },
  {
    "path": "option/redir.go",
    "content": "package option\n\ntype RedirectInboundOptions struct {\n\tListenOptions\n}\n\ntype TProxyInboundOptions struct {\n\tListenOptions\n\tNetwork NetworkList `json:\"network,omitempty\"`\n}\n"
  },
  {
    "path": "option/resolved.go",
    "content": "package option\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype _ResolvedServiceOptions struct {\n\tListenOptions\n}\n\ntype ResolvedServiceOptions _ResolvedServiceOptions\n\nfunc (r ResolvedServiceOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) {\n\tif r.Listen != nil && netip.Addr(*r.Listen) == (netip.AddrFrom4([4]byte{127, 0, 0, 53})) {\n\t\tr.Listen = nil\n\t}\n\tif r.ListenPort == 53 {\n\t\tr.ListenPort = 0\n\t}\n\treturn json.MarshalContext(ctx, (*_ResolvedServiceOptions)(&r))\n}\n\nfunc (r *ResolvedServiceOptions) UnmarshalJSONContext(ctx context.Context, bytes []byte) error {\n\terr := json.UnmarshalContextDisallowUnknownFields(ctx, bytes, (*_ResolvedServiceOptions)(r))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif r.Listen == nil {\n\t\tr.Listen = (*badoption.Addr)(common.Ptr(netip.AddrFrom4([4]byte{127, 0, 0, 53})))\n\t}\n\tif r.ListenPort == 0 {\n\t\tr.ListenPort = 53\n\t}\n\treturn nil\n}\n\ntype ResolvedDNSServerOptions struct {\n\tService                string `json:\"service\"`\n\tAcceptDefaultResolvers bool   `json:\"accept_default_resolvers,omitempty\"`\n\t// NDots                  int                `json:\"ndots,omitempty\"`\n\t// Timeout                badoption.Duration `json:\"timeout,omitempty\"`\n\t// Attempts               int                `json:\"attempts,omitempty\"`\n\t// Rotate                 bool               `json:\"rotate,omitempty\"`\n}\n"
  },
  {
    "path": "option/route.go",
    "content": "package option\n\nimport \"github.com/sagernet/sing/common/json/badoption\"\n\ntype RouteOptions struct {\n\tGeoIP                      *GeoIPOptions                     `json:\"geoip,omitempty\"`\n\tGeosite                    *GeositeOptions                   `json:\"geosite,omitempty\"`\n\tRules                      []Rule                            `json:\"rules,omitempty\"`\n\tRuleSet                    []RuleSet                         `json:\"rule_set,omitempty\"`\n\tFinal                      string                            `json:\"final,omitempty\"`\n\tFindProcess                bool                              `json:\"find_process,omitempty\"`\n\tFindNeighbor               bool                              `json:\"find_neighbor,omitempty\"`\n\tDHCPLeaseFiles             badoption.Listable[string]        `json:\"dhcp_lease_files,omitempty\"`\n\tAutoDetectInterface        bool                              `json:\"auto_detect_interface,omitempty\"`\n\tOverrideAndroidVPN         bool                              `json:\"override_android_vpn,omitempty\"`\n\tDefaultInterface           string                            `json:\"default_interface,omitempty\"`\n\tDefaultMark                FwMark                            `json:\"default_mark,omitempty\"`\n\tDefaultDomainResolver      *DomainResolveOptions             `json:\"default_domain_resolver,omitempty\"`\n\tDefaultNetworkStrategy     *NetworkStrategy                  `json:\"default_network_strategy,omitempty\"`\n\tDefaultNetworkType         badoption.Listable[InterfaceType] `json:\"default_network_type,omitempty\"`\n\tDefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:\"default_fallback_network_type,omitempty\"`\n\tDefaultFallbackDelay       badoption.Duration                `json:\"default_fallback_delay,omitempty\"`\n}\n\ntype GeoIPOptions struct {\n\tPath           string `json:\"path,omitempty\"`\n\tDownloadURL    string `json:\"download_url,omitempty\"`\n\tDownloadDetour string `json:\"download_detour,omitempty\"`\n}\n\ntype GeositeOptions struct {\n\tPath           string `json:\"path,omitempty\"`\n\tDownloadURL    string `json:\"download_url,omitempty\"`\n\tDownloadDetour string `json:\"download_detour,omitempty\"`\n}\n"
  },
  {
    "path": "option/rule.go",
    "content": "package option\n\nimport (\n\t\"reflect\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype _Rule struct {\n\tType           string      `json:\"type,omitempty\"`\n\tDefaultOptions DefaultRule `json:\"-\"`\n\tLogicalOptions LogicalRule `json:\"-\"`\n}\n\ntype Rule _Rule\n\nfunc (r Rule) MarshalJSON() ([]byte, error) {\n\tvar v any\n\tswitch r.Type {\n\tcase C.RuleTypeDefault:\n\t\tr.Type = \"\"\n\t\tv = r.DefaultOptions\n\tcase C.RuleTypeLogical:\n\t\tv = r.LogicalOptions\n\tdefault:\n\t\treturn nil, E.New(\"unknown rule type: \" + r.Type)\n\t}\n\treturn badjson.MarshallObjects((_Rule)(r), v)\n}\n\nfunc (r *Rule) UnmarshalJSON(bytes []byte) error {\n\terr := json.Unmarshal(bytes, (*_Rule)(r))\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar v any\n\tswitch r.Type {\n\tcase \"\", C.RuleTypeDefault:\n\t\tr.Type = C.RuleTypeDefault\n\t\tv = &r.DefaultOptions\n\tcase C.RuleTypeLogical:\n\t\tv = &r.LogicalOptions\n\tdefault:\n\t\treturn E.New(\"unknown rule type: \" + r.Type)\n\t}\n\terr = badjson.UnmarshallExcluded(bytes, (*_Rule)(r), v)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (r Rule) IsValid() bool {\n\tswitch r.Type {\n\tcase C.RuleTypeDefault:\n\t\treturn r.DefaultOptions.IsValid()\n\tcase C.RuleTypeLogical:\n\t\treturn r.LogicalOptions.IsValid()\n\tdefault:\n\t\tpanic(\"unknown rule type: \" + r.Type)\n\t}\n}\n\ntype RawDefaultRule struct {\n\tInbound                  badoption.Listable[string]                                                  `json:\"inbound,omitempty\"`\n\tIPVersion                int                                                                         `json:\"ip_version,omitempty\"`\n\tNetwork                  badoption.Listable[string]                                                  `json:\"network,omitempty\"`\n\tAuthUser                 badoption.Listable[string]                                                  `json:\"auth_user,omitempty\"`\n\tProtocol                 badoption.Listable[string]                                                  `json:\"protocol,omitempty\"`\n\tClient                   badoption.Listable[string]                                                  `json:\"client,omitempty\"`\n\tDomain                   badoption.Listable[string]                                                  `json:\"domain,omitempty\"`\n\tDomainSuffix             badoption.Listable[string]                                                  `json:\"domain_suffix,omitempty\"`\n\tDomainKeyword            badoption.Listable[string]                                                  `json:\"domain_keyword,omitempty\"`\n\tDomainRegex              badoption.Listable[string]                                                  `json:\"domain_regex,omitempty\"`\n\tGeosite                  badoption.Listable[string]                                                  `json:\"geosite,omitempty\"`\n\tSourceGeoIP              badoption.Listable[string]                                                  `json:\"source_geoip,omitempty\"`\n\tGeoIP                    badoption.Listable[string]                                                  `json:\"geoip,omitempty\"`\n\tSourceIPCIDR             badoption.Listable[string]                                                  `json:\"source_ip_cidr,omitempty\"`\n\tSourceIPIsPrivate        bool                                                                        `json:\"source_ip_is_private,omitempty\"`\n\tIPCIDR                   badoption.Listable[string]                                                  `json:\"ip_cidr,omitempty\"`\n\tIPIsPrivate              bool                                                                        `json:\"ip_is_private,omitempty\"`\n\tSourcePort               badoption.Listable[uint16]                                                  `json:\"source_port,omitempty\"`\n\tSourcePortRange          badoption.Listable[string]                                                  `json:\"source_port_range,omitempty\"`\n\tPort                     badoption.Listable[uint16]                                                  `json:\"port,omitempty\"`\n\tPortRange                badoption.Listable[string]                                                  `json:\"port_range,omitempty\"`\n\tProcessName              badoption.Listable[string]                                                  `json:\"process_name,omitempty\"`\n\tProcessPath              badoption.Listable[string]                                                  `json:\"process_path,omitempty\"`\n\tProcessPathRegex         badoption.Listable[string]                                                  `json:\"process_path_regex,omitempty\"`\n\tPackageName              badoption.Listable[string]                                                  `json:\"package_name,omitempty\"`\n\tUser                     badoption.Listable[string]                                                  `json:\"user,omitempty\"`\n\tUserID                   badoption.Listable[int32]                                                   `json:\"user_id,omitempty\"`\n\tClashMode                string                                                                      `json:\"clash_mode,omitempty\"`\n\tNetworkType              badoption.Listable[InterfaceType]                                           `json:\"network_type,omitempty\"`\n\tNetworkIsExpensive       bool                                                                        `json:\"network_is_expensive,omitempty\"`\n\tNetworkIsConstrained     bool                                                                        `json:\"network_is_constrained,omitempty\"`\n\tWIFISSID                 badoption.Listable[string]                                                  `json:\"wifi_ssid,omitempty\"`\n\tWIFIBSSID                badoption.Listable[string]                                                  `json:\"wifi_bssid,omitempty\"`\n\tInterfaceAddress         *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]]        `json:\"interface_address,omitempty\"`\n\tNetworkInterfaceAddress  *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:\"network_interface_address,omitempty\"`\n\tDefaultInterfaceAddress  badoption.Listable[*badoption.Prefixable]                                   `json:\"default_interface_address,omitempty\"`\n\tSourceMACAddress         badoption.Listable[string]                                                  `json:\"source_mac_address,omitempty\"`\n\tSourceHostname           badoption.Listable[string]                                                  `json:\"source_hostname,omitempty\"`\n\tPreferredBy              badoption.Listable[string]                                                  `json:\"preferred_by,omitempty\"`\n\tRuleSet                  badoption.Listable[string]                                                  `json:\"rule_set,omitempty\"`\n\tRuleSetIPCIDRMatchSource bool                                                                        `json:\"rule_set_ip_cidr_match_source,omitempty\"`\n\tInvert                   bool                                                                        `json:\"invert,omitempty\"`\n\n\t// Deprecated: renamed to rule_set_ip_cidr_match_source\n\tDeprecated_RulesetIPCIDRMatchSource bool `json:\"rule_set_ipcidr_match_source,omitempty\"`\n}\n\ntype DefaultRule struct {\n\tRawDefaultRule\n\tRuleAction\n}\n\nfunc (r DefaultRule) MarshalJSON() ([]byte, error) {\n\treturn badjson.MarshallObjects(r.RawDefaultRule, r.RuleAction)\n}\n\nfunc (r *DefaultRule) UnmarshalJSON(data []byte) error {\n\terr := json.Unmarshal(data, &r.RawDefaultRule)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn badjson.UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction)\n}\n\nfunc (r DefaultRule) IsValid() bool {\n\tvar defaultValue DefaultRule\n\tdefaultValue.Invert = r.Invert\n\treturn !reflect.DeepEqual(r, defaultValue)\n}\n\ntype RawLogicalRule struct {\n\tMode   string `json:\"mode\"`\n\tRules  []Rule `json:\"rules,omitempty\"`\n\tInvert bool   `json:\"invert,omitempty\"`\n}\n\ntype LogicalRule struct {\n\tRawLogicalRule\n\tRuleAction\n}\n\nfunc (r LogicalRule) MarshalJSON() ([]byte, error) {\n\treturn badjson.MarshallObjects(r.RawLogicalRule, r.RuleAction)\n}\n\nfunc (r *LogicalRule) UnmarshalJSON(data []byte) error {\n\terr := json.Unmarshal(data, &r.RawLogicalRule)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn badjson.UnmarshallExcluded(data, &r.RawLogicalRule, &r.RuleAction)\n}\n\nfunc (r *LogicalRule) IsValid() bool {\n\treturn len(r.Rules) > 0 && common.All(r.Rules, Rule.IsValid)\n}\n"
  },
  {
    "path": "option/rule_action.go",
    "content": "package option\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"time\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype _RuleAction struct {\n\tAction              string                    `json:\"action,omitempty\"`\n\tRouteOptions        RouteActionOptions        `json:\"-\"`\n\tRouteOptionsOptions RouteOptionsActionOptions `json:\"-\"`\n\tDirectOptions       DirectActionOptions       `json:\"-\"`\n\tBypassOptions       RouteActionOptions        `json:\"-\"`\n\tRejectOptions       RejectActionOptions       `json:\"-\"`\n\tSniffOptions        RouteActionSniff          `json:\"-\"`\n\tResolveOptions      RouteActionResolve        `json:\"-\"`\n}\n\ntype RuleAction _RuleAction\n\nfunc (r RuleAction) MarshalJSON() ([]byte, error) {\n\tif r.Action == \"\" {\n\t\treturn json.Marshal(struct{}{})\n\t}\n\tvar v any\n\tswitch r.Action {\n\tcase C.RuleActionTypeRoute:\n\t\tr.Action = \"\"\n\t\tv = r.RouteOptions\n\tcase C.RuleActionTypeRouteOptions:\n\t\tv = r.RouteOptionsOptions\n\tcase C.RuleActionTypeDirect:\n\t\tv = r.DirectOptions\n\tcase C.RuleActionTypeBypass:\n\t\tv = r.BypassOptions\n\tcase C.RuleActionTypeReject:\n\t\tv = r.RejectOptions\n\tcase C.RuleActionTypeHijackDNS:\n\t\tv = nil\n\tcase C.RuleActionTypeSniff:\n\t\tv = r.SniffOptions\n\tcase C.RuleActionTypeResolve:\n\t\tv = r.ResolveOptions\n\tdefault:\n\t\treturn nil, E.New(\"unknown rule action: \" + r.Action)\n\t}\n\tif v == nil {\n\t\treturn badjson.MarshallObjects((_RuleAction)(r))\n\t}\n\treturn badjson.MarshallObjects((_RuleAction)(r), v)\n}\n\nfunc (r *RuleAction) UnmarshalJSON(data []byte) error {\n\terr := json.Unmarshal(data, (*_RuleAction)(r))\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar v any\n\tswitch r.Action {\n\tcase \"\", C.RuleActionTypeRoute:\n\t\tr.Action = C.RuleActionTypeRoute\n\t\tv = &r.RouteOptions\n\tcase C.RuleActionTypeRouteOptions:\n\t\tv = &r.RouteOptionsOptions\n\tcase C.RuleActionTypeDirect:\n\t\tv = &r.DirectOptions\n\tcase C.RuleActionTypeBypass:\n\t\tv = &r.BypassOptions\n\tcase C.RuleActionTypeReject:\n\t\tv = &r.RejectOptions\n\tcase C.RuleActionTypeHijackDNS:\n\t\tv = nil\n\tcase C.RuleActionTypeSniff:\n\t\tv = &r.SniffOptions\n\tcase C.RuleActionTypeResolve:\n\t\tv = &r.ResolveOptions\n\tdefault:\n\t\treturn E.New(\"unknown rule action: \" + r.Action)\n\t}\n\tif v == nil {\n\t\t// check unknown fields\n\t\treturn json.UnmarshalDisallowUnknownFields(data, &_RuleAction{})\n\t}\n\terr = badjson.UnmarshallExcluded(data, (*_RuleAction)(r), v)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype _DNSRuleAction struct {\n\tAction              string                       `json:\"action,omitempty\"`\n\tRouteOptions        DNSRouteActionOptions        `json:\"-\"`\n\tRouteOptionsOptions DNSRouteOptionsActionOptions `json:\"-\"`\n\tRejectOptions       RejectActionOptions          `json:\"-\"`\n\tPredefinedOptions   DNSRouteActionPredefined     `json:\"-\"`\n}\n\ntype DNSRuleAction _DNSRuleAction\n\nfunc (r DNSRuleAction) MarshalJSON() ([]byte, error) {\n\tif r.Action == \"\" {\n\t\treturn json.Marshal(struct{}{})\n\t}\n\tvar v any\n\tswitch r.Action {\n\tcase C.RuleActionTypeRoute:\n\t\tr.Action = \"\"\n\t\tv = r.RouteOptions\n\tcase C.RuleActionTypeRouteOptions:\n\t\tv = r.RouteOptionsOptions\n\tcase C.RuleActionTypeReject:\n\t\tv = r.RejectOptions\n\tcase C.RuleActionTypePredefined:\n\t\tv = r.PredefinedOptions\n\tdefault:\n\t\treturn nil, E.New(\"unknown DNS rule action: \" + r.Action)\n\t}\n\treturn badjson.MarshallObjects((_DNSRuleAction)(r), v)\n}\n\nfunc (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) error {\n\terr := json.Unmarshal(data, (*_DNSRuleAction)(r))\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar v any\n\tswitch r.Action {\n\tcase \"\", C.RuleActionTypeRoute:\n\t\tr.Action = C.RuleActionTypeRoute\n\t\tv = &r.RouteOptions\n\tcase C.RuleActionTypeRouteOptions:\n\t\tv = &r.RouteOptionsOptions\n\tcase C.RuleActionTypeReject:\n\t\tv = &r.RejectOptions\n\tcase C.RuleActionTypePredefined:\n\t\tv = &r.PredefinedOptions\n\tdefault:\n\t\treturn E.New(\"unknown DNS rule action: \" + r.Action)\n\t}\n\treturn badjson.UnmarshallExcludedContext(ctx, data, (*_DNSRuleAction)(r), v)\n}\n\ntype RouteActionOptions struct {\n\tOutbound string `json:\"outbound,omitempty\"`\n\tRawRouteOptionsActionOptions\n}\n\ntype RawRouteOptionsActionOptions struct {\n\tOverrideAddress string `json:\"override_address,omitempty\"`\n\tOverridePort    uint16 `json:\"override_port,omitempty\"`\n\n\tNetworkStrategy *NetworkStrategy `json:\"network_strategy,omitempty\"`\n\tFallbackDelay   uint32           `json:\"fallback_delay,omitempty\"`\n\n\tUDPDisableDomainUnmapping bool               `json:\"udp_disable_domain_unmapping,omitempty\"`\n\tUDPConnect                bool               `json:\"udp_connect,omitempty\"`\n\tUDPTimeout                badoption.Duration `json:\"udp_timeout,omitempty\"`\n\n\tTLSFragment              bool               `json:\"tls_fragment,omitempty\"`\n\tTLSFragmentFallbackDelay badoption.Duration `json:\"tls_fragment_fallback_delay,omitempty\"`\n\tTLSRecordFragment        bool               `json:\"tls_record_fragment,omitempty\"`\n}\n\ntype RouteOptionsActionOptions RawRouteOptionsActionOptions\n\nfunc (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error {\n\terr := json.Unmarshal(data, (*RawRouteOptionsActionOptions)(r))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif *r == (RouteOptionsActionOptions{}) {\n\t\treturn E.New(\"empty route option action\")\n\t}\n\tif r.TLSFragment && r.TLSRecordFragment {\n\t\treturn E.New(\"`tls_fragment` and `tls_record_fragment` are mutually exclusive\")\n\t}\n\treturn nil\n}\n\ntype DNSRouteActionOptions struct {\n\tServer       string                `json:\"server,omitempty\"`\n\tStrategy     DomainStrategy        `json:\"strategy,omitempty\"`\n\tDisableCache bool                  `json:\"disable_cache,omitempty\"`\n\tRewriteTTL   *uint32               `json:\"rewrite_ttl,omitempty\"`\n\tClientSubnet *badoption.Prefixable `json:\"client_subnet,omitempty\"`\n}\n\ntype _DNSRouteOptionsActionOptions struct {\n\tStrategy     DomainStrategy        `json:\"strategy,omitempty\"`\n\tDisableCache bool                  `json:\"disable_cache,omitempty\"`\n\tRewriteTTL   *uint32               `json:\"rewrite_ttl,omitempty\"`\n\tClientSubnet *badoption.Prefixable `json:\"client_subnet,omitempty\"`\n}\n\ntype DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions\n\nfunc (r *DNSRouteOptionsActionOptions) UnmarshalJSON(data []byte) error {\n\terr := json.Unmarshal(data, (*_DNSRouteOptionsActionOptions)(r))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif *r == (DNSRouteOptionsActionOptions{}) {\n\t\treturn E.New(\"empty DNS route option action\")\n\t}\n\treturn nil\n}\n\ntype _DirectActionOptions DialerOptions\n\ntype DirectActionOptions _DirectActionOptions\n\nfunc (d DirectActionOptions) Descriptions() []string {\n\tvar descriptions []string\n\tif d.BindInterface != \"\" {\n\t\tdescriptions = append(descriptions, \"bind_interface=\"+d.BindInterface)\n\t}\n\tif d.Inet4BindAddress != nil {\n\t\tdescriptions = append(descriptions, \"inet4_bind_address=\"+d.Inet4BindAddress.Build(netip.IPv4Unspecified()).String())\n\t}\n\tif d.Inet6BindAddress != nil {\n\t\tdescriptions = append(descriptions, \"inet6_bind_address=\"+d.Inet6BindAddress.Build(netip.IPv6Unspecified()).String())\n\t}\n\tif d.RoutingMark != 0 {\n\t\tdescriptions = append(descriptions, \"routing_mark=\"+fmt.Sprintf(\"0x%x\", d.RoutingMark))\n\t}\n\tif d.ReuseAddr {\n\t\tdescriptions = append(descriptions, \"reuse_addr\")\n\t}\n\tif d.ConnectTimeout != 0 {\n\t\tdescriptions = append(descriptions, \"connect_timeout=\"+time.Duration(d.ConnectTimeout).String())\n\t}\n\tif d.TCPFastOpen {\n\t\tdescriptions = append(descriptions, \"tcp_fast_open\")\n\t}\n\tif d.TCPMultiPath {\n\t\tdescriptions = append(descriptions, \"tcp_multi_path\")\n\t}\n\tif d.UDPFragment != nil {\n\t\tdescriptions = append(descriptions, \"udp_fragment=\"+fmt.Sprint(*d.UDPFragment))\n\t}\n\tif d.DomainStrategy != DomainStrategy(C.DomainStrategyAsIS) {\n\t\tdescriptions = append(descriptions, \"domain_strategy=\"+d.DomainStrategy.String())\n\t}\n\tif d.FallbackDelay != 0 {\n\t\tdescriptions = append(descriptions, \"fallback_delay=\"+time.Duration(d.FallbackDelay).String())\n\t}\n\treturn descriptions\n}\n\nfunc (d *DirectActionOptions) UnmarshalJSON(data []byte) error {\n\terr := json.Unmarshal(data, (*_DirectActionOptions)(d))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif d.Detour != \"\" {\n\t\treturn E.New(\"detour is not available in the current context\")\n\t}\n\treturn nil\n}\n\ntype _RejectActionOptions struct {\n\tMethod string `json:\"method,omitempty\"`\n\tNoDrop bool   `json:\"no_drop,omitempty\"`\n}\n\ntype RejectActionOptions _RejectActionOptions\n\nfunc (r RejectActionOptions) MarshalJSON() ([]byte, error) {\n\tswitch r.Method {\n\tcase C.RuleActionRejectMethodDefault:\n\t\tr.Method = \"\"\n\t}\n\treturn json.Marshal((_RejectActionOptions)(r))\n}\n\nfunc (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error {\n\terr := json.Unmarshal(bytes, (*_RejectActionOptions)(r))\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch r.Method {\n\tcase \"\", C.RuleActionRejectMethodDefault:\n\t\tr.Method = C.RuleActionRejectMethodDefault\n\tcase C.RuleActionRejectMethodDrop:\n\tcase C.RuleActionRejectMethodReply:\n\tdefault:\n\t\treturn E.New(\"unknown reject method: \" + r.Method)\n\t}\n\tif r.Method == C.RuleActionRejectMethodDrop && r.NoDrop {\n\t\treturn E.New(\"no_drop is not available in current context\")\n\t}\n\treturn nil\n}\n\ntype RouteActionSniff struct {\n\tSniffer badoption.Listable[string] `json:\"sniffer,omitempty\"`\n\tTimeout badoption.Duration         `json:\"timeout,omitempty\"`\n}\n\ntype RouteActionResolve struct {\n\tServer       string                `json:\"server,omitempty\"`\n\tStrategy     DomainStrategy        `json:\"strategy,omitempty\"`\n\tDisableCache bool                  `json:\"disable_cache,omitempty\"`\n\tRewriteTTL   *uint32               `json:\"rewrite_ttl,omitempty\"`\n\tClientSubnet *badoption.Prefixable `json:\"client_subnet,omitempty\"`\n}\n\ntype DNSRouteActionPredefined struct {\n\tRcode  *DNSRCode                            `json:\"rcode,omitempty\"`\n\tAnswer badoption.Listable[DNSRecordOptions] `json:\"answer,omitempty\"`\n\tNs     badoption.Listable[DNSRecordOptions] `json:\"ns,omitempty\"`\n\tExtra  badoption.Listable[DNSRecordOptions] `json:\"extra,omitempty\"`\n}\n"
  },
  {
    "path": "option/rule_dns.go",
    "content": "package option\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype _DNSRule struct {\n\tType           string         `json:\"type,omitempty\"`\n\tDefaultOptions DefaultDNSRule `json:\"-\"`\n\tLogicalOptions LogicalDNSRule `json:\"-\"`\n}\n\ntype DNSRule _DNSRule\n\nfunc (r DNSRule) MarshalJSON() ([]byte, error) {\n\tvar v any\n\tswitch r.Type {\n\tcase C.RuleTypeDefault:\n\t\tr.Type = \"\"\n\t\tv = r.DefaultOptions\n\tcase C.RuleTypeLogical:\n\t\tv = r.LogicalOptions\n\tdefault:\n\t\treturn nil, E.New(\"unknown rule type: \" + r.Type)\n\t}\n\treturn badjson.MarshallObjects((_DNSRule)(r), v)\n}\n\nfunc (r *DNSRule) UnmarshalJSONContext(ctx context.Context, bytes []byte) error {\n\terr := json.Unmarshal(bytes, (*_DNSRule)(r))\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar v any\n\tswitch r.Type {\n\tcase \"\", C.RuleTypeDefault:\n\t\tr.Type = C.RuleTypeDefault\n\t\tv = &r.DefaultOptions\n\tcase C.RuleTypeLogical:\n\t\tv = &r.LogicalOptions\n\tdefault:\n\t\treturn E.New(\"unknown rule type: \" + r.Type)\n\t}\n\terr = badjson.UnmarshallExcludedContext(ctx, bytes, (*_DNSRule)(r), v)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (r DNSRule) IsValid() bool {\n\tswitch r.Type {\n\tcase C.RuleTypeDefault:\n\t\treturn r.DefaultOptions.IsValid()\n\tcase C.RuleTypeLogical:\n\t\treturn r.LogicalOptions.IsValid()\n\tdefault:\n\t\tpanic(\"unknown DNS rule type: \" + r.Type)\n\t}\n}\n\ntype RawDefaultDNSRule struct {\n\tInbound                  badoption.Listable[string]                                                  `json:\"inbound,omitempty\"`\n\tIPVersion                int                                                                         `json:\"ip_version,omitempty\"`\n\tQueryType                badoption.Listable[DNSQueryType]                                            `json:\"query_type,omitempty\"`\n\tNetwork                  badoption.Listable[string]                                                  `json:\"network,omitempty\"`\n\tAuthUser                 badoption.Listable[string]                                                  `json:\"auth_user,omitempty\"`\n\tProtocol                 badoption.Listable[string]                                                  `json:\"protocol,omitempty\"`\n\tDomain                   badoption.Listable[string]                                                  `json:\"domain,omitempty\"`\n\tDomainSuffix             badoption.Listable[string]                                                  `json:\"domain_suffix,omitempty\"`\n\tDomainKeyword            badoption.Listable[string]                                                  `json:\"domain_keyword,omitempty\"`\n\tDomainRegex              badoption.Listable[string]                                                  `json:\"domain_regex,omitempty\"`\n\tGeosite                  badoption.Listable[string]                                                  `json:\"geosite,omitempty\"`\n\tSourceGeoIP              badoption.Listable[string]                                                  `json:\"source_geoip,omitempty\"`\n\tGeoIP                    badoption.Listable[string]                                                  `json:\"geoip,omitempty\"`\n\tIPCIDR                   badoption.Listable[string]                                                  `json:\"ip_cidr,omitempty\"`\n\tIPIsPrivate              bool                                                                        `json:\"ip_is_private,omitempty\"`\n\tIPAcceptAny              bool                                                                        `json:\"ip_accept_any,omitempty\"`\n\tSourceIPCIDR             badoption.Listable[string]                                                  `json:\"source_ip_cidr,omitempty\"`\n\tSourceIPIsPrivate        bool                                                                        `json:\"source_ip_is_private,omitempty\"`\n\tSourcePort               badoption.Listable[uint16]                                                  `json:\"source_port,omitempty\"`\n\tSourcePortRange          badoption.Listable[string]                                                  `json:\"source_port_range,omitempty\"`\n\tPort                     badoption.Listable[uint16]                                                  `json:\"port,omitempty\"`\n\tPortRange                badoption.Listable[string]                                                  `json:\"port_range,omitempty\"`\n\tProcessName              badoption.Listable[string]                                                  `json:\"process_name,omitempty\"`\n\tProcessPath              badoption.Listable[string]                                                  `json:\"process_path,omitempty\"`\n\tProcessPathRegex         badoption.Listable[string]                                                  `json:\"process_path_regex,omitempty\"`\n\tPackageName              badoption.Listable[string]                                                  `json:\"package_name,omitempty\"`\n\tUser                     badoption.Listable[string]                                                  `json:\"user,omitempty\"`\n\tUserID                   badoption.Listable[int32]                                                   `json:\"user_id,omitempty\"`\n\tOutbound                 badoption.Listable[string]                                                  `json:\"outbound,omitempty\"`\n\tClashMode                string                                                                      `json:\"clash_mode,omitempty\"`\n\tNetworkType              badoption.Listable[InterfaceType]                                           `json:\"network_type,omitempty\"`\n\tNetworkIsExpensive       bool                                                                        `json:\"network_is_expensive,omitempty\"`\n\tNetworkIsConstrained     bool                                                                        `json:\"network_is_constrained,omitempty\"`\n\tWIFISSID                 badoption.Listable[string]                                                  `json:\"wifi_ssid,omitempty\"`\n\tWIFIBSSID                badoption.Listable[string]                                                  `json:\"wifi_bssid,omitempty\"`\n\tInterfaceAddress         *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]]        `json:\"interface_address,omitempty\"`\n\tNetworkInterfaceAddress  *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:\"network_interface_address,omitempty\"`\n\tDefaultInterfaceAddress  badoption.Listable[*badoption.Prefixable]                                   `json:\"default_interface_address,omitempty\"`\n\tSourceMACAddress         badoption.Listable[string]                                                  `json:\"source_mac_address,omitempty\"`\n\tSourceHostname           badoption.Listable[string]                                                  `json:\"source_hostname,omitempty\"`\n\tRuleSet                  badoption.Listable[string]                                                  `json:\"rule_set,omitempty\"`\n\tRuleSetIPCIDRMatchSource bool                                                                        `json:\"rule_set_ip_cidr_match_source,omitempty\"`\n\tRuleSetIPCIDRAcceptEmpty bool                                                                        `json:\"rule_set_ip_cidr_accept_empty,omitempty\"`\n\tInvert                   bool                                                                        `json:\"invert,omitempty\"`\n\n\t// Deprecated: renamed to rule_set_ip_cidr_match_source\n\tDeprecated_RulesetIPCIDRMatchSource bool `json:\"rule_set_ipcidr_match_source,omitempty\"`\n}\n\ntype DefaultDNSRule struct {\n\tRawDefaultDNSRule\n\tDNSRuleAction\n}\n\nfunc (r DefaultDNSRule) MarshalJSON() ([]byte, error) {\n\treturn badjson.MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction)\n}\n\nfunc (r *DefaultDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error {\n\terr := json.UnmarshalContext(ctx, data, &r.RawDefaultDNSRule)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn badjson.UnmarshallExcludedContext(ctx, data, &r.RawDefaultDNSRule, &r.DNSRuleAction)\n}\n\nfunc (r DefaultDNSRule) IsValid() bool {\n\tvar defaultValue DefaultDNSRule\n\tdefaultValue.Invert = r.Invert\n\treturn !reflect.DeepEqual(r, defaultValue)\n}\n\ntype RawLogicalDNSRule struct {\n\tMode   string    `json:\"mode\"`\n\tRules  []DNSRule `json:\"rules,omitempty\"`\n\tInvert bool      `json:\"invert,omitempty\"`\n}\n\ntype LogicalDNSRule struct {\n\tRawLogicalDNSRule\n\tDNSRuleAction\n}\n\nfunc (r LogicalDNSRule) MarshalJSON() ([]byte, error) {\n\treturn badjson.MarshallObjects(r.RawLogicalDNSRule, r.DNSRuleAction)\n}\n\nfunc (r *LogicalDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error {\n\terr := json.Unmarshal(data, &r.RawLogicalDNSRule)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn badjson.UnmarshallExcludedContext(ctx, data, &r.RawLogicalDNSRule, &r.DNSRuleAction)\n}\n\nfunc (r *LogicalDNSRule) IsValid() bool {\n\treturn len(r.Rules) > 0 && common.All(r.Rules, DNSRule.IsValid)\n}\n"
  },
  {
    "path": "option/rule_set.go",
    "content": "package option\n\nimport (\n\t\"net/url\"\n\t\"path/filepath\"\n\t\"reflect\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/domain\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"go4.org/netipx\"\n)\n\ntype _RuleSet struct {\n\tType          string        `json:\"type,omitempty\"`\n\tTag           string        `json:\"tag\"`\n\tFormat        string        `json:\"format,omitempty\"`\n\tInlineOptions PlainRuleSet  `json:\"-\"`\n\tLocalOptions  LocalRuleSet  `json:\"-\"`\n\tRemoteOptions RemoteRuleSet `json:\"-\"`\n}\n\ntype RuleSet _RuleSet\n\nfunc (r RuleSet) MarshalJSON() ([]byte, error) {\n\tif r.Type != C.RuleSetTypeInline {\n\t\tvar defaultFormat string\n\t\tswitch r.Type {\n\t\tcase C.RuleSetTypeLocal:\n\t\t\tdefaultFormat = ruleSetDefaultFormat(r.LocalOptions.Path)\n\t\tcase C.RuleSetTypeRemote:\n\t\t\tdefaultFormat = ruleSetDefaultFormat(r.RemoteOptions.URL)\n\t\t}\n\t\tif r.Format == defaultFormat {\n\t\t\tr.Format = \"\"\n\t\t}\n\t}\n\tvar v any\n\tswitch r.Type {\n\tcase \"\", C.RuleSetTypeInline:\n\t\tr.Type = \"\"\n\t\tv = r.InlineOptions\n\tcase C.RuleSetTypeLocal:\n\t\tv = r.LocalOptions\n\tcase C.RuleSetTypeRemote:\n\t\tv = r.RemoteOptions\n\tdefault:\n\t\treturn nil, E.New(\"unknown rule-set type: \" + r.Type)\n\t}\n\treturn badjson.MarshallObjects((_RuleSet)(r), v)\n}\n\nfunc (r *RuleSet) UnmarshalJSON(bytes []byte) error {\n\terr := json.Unmarshal(bytes, (*_RuleSet)(r))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif r.Tag == \"\" {\n\t\treturn E.New(\"missing tag\")\n\t}\n\tvar v any\n\tswitch r.Type {\n\tcase \"\", C.RuleSetTypeInline:\n\t\tr.Type = C.RuleSetTypeInline\n\t\tv = &r.InlineOptions\n\tcase C.RuleSetTypeLocal:\n\t\tv = &r.LocalOptions\n\tcase C.RuleSetTypeRemote:\n\t\tv = &r.RemoteOptions\n\tdefault:\n\t\treturn E.New(\"unknown rule-set type: \" + r.Type)\n\t}\n\terr = badjson.UnmarshallExcluded(bytes, (*_RuleSet)(r), v)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif r.Type != C.RuleSetTypeInline {\n\t\tif r.Format == \"\" {\n\t\t\tswitch r.Type {\n\t\t\tcase C.RuleSetTypeLocal:\n\t\t\t\tr.Format = ruleSetDefaultFormat(r.LocalOptions.Path)\n\t\t\tcase C.RuleSetTypeRemote:\n\t\t\t\tr.Format = ruleSetDefaultFormat(r.RemoteOptions.URL)\n\t\t\t}\n\t\t}\n\t\tswitch r.Format {\n\t\tcase \"\":\n\t\t\treturn E.New(\"missing format\")\n\t\tcase C.RuleSetFormatSource, C.RuleSetFormatBinary:\n\t\tdefault:\n\t\t\treturn E.New(\"unknown rule-set format: \" + r.Format)\n\t\t}\n\t} else {\n\t\tr.Format = \"\"\n\t}\n\treturn nil\n}\n\nfunc ruleSetDefaultFormat(path string) string {\n\tif pathURL, err := url.Parse(path); err == nil {\n\t\tpath = pathURL.Path\n\t}\n\tswitch filepath.Ext(path) {\n\tcase \".json\":\n\t\treturn C.RuleSetFormatSource\n\tcase \".srs\":\n\t\treturn C.RuleSetFormatBinary\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\ntype LocalRuleSet struct {\n\tPath string `json:\"path,omitempty\"`\n}\n\ntype RemoteRuleSet struct {\n\tURL            string             `json:\"url\"`\n\tDownloadDetour string             `json:\"download_detour,omitempty\"`\n\tUpdateInterval badoption.Duration `json:\"update_interval,omitempty\"`\n}\n\ntype _HeadlessRule struct {\n\tType           string              `json:\"type,omitempty\"`\n\tDefaultOptions DefaultHeadlessRule `json:\"-\"`\n\tLogicalOptions LogicalHeadlessRule `json:\"-\"`\n}\n\ntype HeadlessRule _HeadlessRule\n\nfunc (r HeadlessRule) MarshalJSON() ([]byte, error) {\n\tvar v any\n\tswitch r.Type {\n\tcase C.RuleTypeDefault:\n\t\tr.Type = \"\"\n\t\tv = r.DefaultOptions\n\tcase C.RuleTypeLogical:\n\t\tv = r.LogicalOptions\n\tdefault:\n\t\treturn nil, E.New(\"unknown rule type: \" + r.Type)\n\t}\n\treturn badjson.MarshallObjects((_HeadlessRule)(r), v)\n}\n\nfunc (r *HeadlessRule) UnmarshalJSON(bytes []byte) error {\n\terr := json.Unmarshal(bytes, (*_HeadlessRule)(r))\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar v any\n\tswitch r.Type {\n\tcase \"\", C.RuleTypeDefault:\n\t\tr.Type = C.RuleTypeDefault\n\t\tv = &r.DefaultOptions\n\tcase C.RuleTypeLogical:\n\t\tv = &r.LogicalOptions\n\tdefault:\n\t\treturn E.New(\"unknown rule type: \" + r.Type)\n\t}\n\terr = badjson.UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (r HeadlessRule) IsValid() bool {\n\tswitch r.Type {\n\tcase C.RuleTypeDefault, \"\":\n\t\treturn r.DefaultOptions.IsValid()\n\tcase C.RuleTypeLogical:\n\t\treturn r.LogicalOptions.IsValid()\n\tdefault:\n\t\tpanic(\"unknown rule type: \" + r.Type)\n\t}\n}\n\ntype DefaultHeadlessRule struct {\n\tQueryType               badoption.Listable[DNSQueryType]                                            `json:\"query_type,omitempty\"`\n\tNetwork                 badoption.Listable[string]                                                  `json:\"network,omitempty\"`\n\tDomain                  badoption.Listable[string]                                                  `json:\"domain,omitempty\"`\n\tDomainSuffix            badoption.Listable[string]                                                  `json:\"domain_suffix,omitempty\"`\n\tDomainKeyword           badoption.Listable[string]                                                  `json:\"domain_keyword,omitempty\"`\n\tDomainRegex             badoption.Listable[string]                                                  `json:\"domain_regex,omitempty\"`\n\tSourceIPCIDR            badoption.Listable[string]                                                  `json:\"source_ip_cidr,omitempty\"`\n\tIPCIDR                  badoption.Listable[string]                                                  `json:\"ip_cidr,omitempty\"`\n\tSourcePort              badoption.Listable[uint16]                                                  `json:\"source_port,omitempty\"`\n\tSourcePortRange         badoption.Listable[string]                                                  `json:\"source_port_range,omitempty\"`\n\tPort                    badoption.Listable[uint16]                                                  `json:\"port,omitempty\"`\n\tPortRange               badoption.Listable[string]                                                  `json:\"port_range,omitempty\"`\n\tProcessName             badoption.Listable[string]                                                  `json:\"process_name,omitempty\"`\n\tProcessPath             badoption.Listable[string]                                                  `json:\"process_path,omitempty\"`\n\tProcessPathRegex        badoption.Listable[string]                                                  `json:\"process_path_regex,omitempty\"`\n\tPackageName             badoption.Listable[string]                                                  `json:\"package_name,omitempty\"`\n\tNetworkType             badoption.Listable[InterfaceType]                                           `json:\"network_type,omitempty\"`\n\tNetworkIsExpensive      bool                                                                        `json:\"network_is_expensive,omitempty\"`\n\tNetworkIsConstrained    bool                                                                        `json:\"network_is_constrained,omitempty\"`\n\tWIFISSID                badoption.Listable[string]                                                  `json:\"wifi_ssid,omitempty\"`\n\tWIFIBSSID               badoption.Listable[string]                                                  `json:\"wifi_bssid,omitempty\"`\n\tNetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:\"network_interface_address,omitempty\"`\n\tDefaultInterfaceAddress badoption.Listable[*badoption.Prefixable]                                   `json:\"default_interface_address,omitempty\"`\n\n\tInvert bool `json:\"invert,omitempty\"`\n\n\tDomainMatcher *domain.Matcher `json:\"-\"`\n\tSourceIPSet   *netipx.IPSet   `json:\"-\"`\n\tIPSet         *netipx.IPSet   `json:\"-\"`\n\n\tAdGuardDomain        badoption.Listable[string] `json:\"-\"`\n\tAdGuardDomainMatcher *domain.AdGuardMatcher     `json:\"-\"`\n}\n\nfunc (r DefaultHeadlessRule) IsValid() bool {\n\tvar defaultValue DefaultHeadlessRule\n\tdefaultValue.Invert = r.Invert\n\treturn !reflect.DeepEqual(r, defaultValue)\n}\n\ntype LogicalHeadlessRule struct {\n\tMode   string         `json:\"mode\"`\n\tRules  []HeadlessRule `json:\"rules,omitempty\"`\n\tInvert bool           `json:\"invert,omitempty\"`\n}\n\nfunc (r LogicalHeadlessRule) IsValid() bool {\n\treturn len(r.Rules) > 0 && common.All(r.Rules, HeadlessRule.IsValid)\n}\n\ntype _PlainRuleSetCompat struct {\n\tVersion    uint8           `json:\"version\"`\n\tOptions    PlainRuleSet    `json:\"-\"`\n\tRawMessage json.RawMessage `json:\"-\"`\n}\n\ntype PlainRuleSetCompat _PlainRuleSetCompat\n\nfunc (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {\n\tvar v any\n\tswitch r.Version {\n\tcase C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:\n\t\tv = r.Options\n\tdefault:\n\t\treturn nil, E.New(\"unknown rule-set version: \", r.Version)\n\t}\n\treturn badjson.MarshallObjects((_PlainRuleSetCompat)(r), v)\n}\n\nfunc (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {\n\terr := json.Unmarshal(bytes, (*_PlainRuleSetCompat)(r))\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar v any\n\tswitch r.Version {\n\tcase C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:\n\t\tv = &r.Options\n\tcase 0:\n\t\treturn E.New(\"missing rule-set version\")\n\tdefault:\n\t\treturn E.New(\"unknown rule-set version: \", r.Version)\n\t}\n\terr = badjson.UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v)\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.RawMessage = bytes\n\treturn nil\n}\n\nfunc (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {\n\tswitch r.Version {\n\tcase C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:\n\tdefault:\n\t\treturn PlainRuleSet{}, E.New(\"unknown rule-set version: \" + F.ToString(r.Version))\n\t}\n\treturn r.Options, nil\n}\n\ntype PlainRuleSet struct {\n\tRules []HeadlessRule `json:\"rules,omitempty\"`\n}\n"
  },
  {
    "path": "option/service.go",
    "content": "package option\n\nimport (\n\t\"context\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/service\"\n)\n\ntype ServiceOptionsRegistry interface {\n\tCreateOptions(serviceType string) (any, bool)\n}\n\ntype _Service struct {\n\tType    string `json:\"type\"`\n\tTag     string `json:\"tag,omitempty\"`\n\tOptions any    `json:\"-\"`\n}\n\ntype Service _Service\n\nfunc (h *Service) MarshalJSONContext(ctx context.Context) ([]byte, error) {\n\treturn badjson.MarshallObjectsContext(ctx, (*_Service)(h), h.Options)\n}\n\nfunc (h *Service) UnmarshalJSONContext(ctx context.Context, content []byte) error {\n\terr := json.UnmarshalContext(ctx, content, (*_Service)(h))\n\tif err != nil {\n\t\treturn err\n\t}\n\tregistry := service.FromContext[ServiceOptionsRegistry](ctx)\n\tif registry == nil {\n\t\treturn E.New(\"missing service fields registry in context\")\n\t}\n\toptions, loaded := registry.CreateOptions(h.Type)\n\tif !loaded {\n\t\treturn E.New(\"unknown inbound type: \", h.Type)\n\t}\n\terr = badjson.UnmarshallExcludedContext(ctx, content, (*_Service)(h), options)\n\tif err != nil {\n\t\treturn err\n\t}\n\th.Options = options\n\treturn nil\n}\n"
  },
  {
    "path": "option/shadowsocks.go",
    "content": "package option\n\ntype ShadowsocksInboundOptions struct {\n\tListenOptions\n\tNetwork      NetworkList              `json:\"network,omitempty\"`\n\tMethod       string                   `json:\"method\"`\n\tPassword     string                   `json:\"password,omitempty\"`\n\tUsers        []ShadowsocksUser        `json:\"users,omitempty\"`\n\tDestinations []ShadowsocksDestination `json:\"destinations,omitempty\"`\n\tMultiplex    *InboundMultiplexOptions `json:\"multiplex,omitempty\"`\n\tManaged      bool                     `json:\"managed,omitempty\"`\n}\n\ntype ShadowsocksUser struct {\n\tName     string `json:\"name\"`\n\tPassword string `json:\"password\"`\n}\n\ntype ShadowsocksDestination struct {\n\tName     string `json:\"name\"`\n\tPassword string `json:\"password\"`\n\tServerOptions\n}\n\ntype ShadowsocksOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tMethod        string                    `json:\"method\"`\n\tPassword      string                    `json:\"password\"`\n\tPlugin        string                    `json:\"plugin,omitempty\"`\n\tPluginOptions string                    `json:\"plugin_opts,omitempty\"`\n\tNetwork       NetworkList               `json:\"network,omitempty\"`\n\tUDPOverTCP    *UDPOverTCPOptions        `json:\"udp_over_tcp,omitempty\"`\n\tMultiplex     *OutboundMultiplexOptions `json:\"multiplex,omitempty\"`\n}\n"
  },
  {
    "path": "option/shadowsocksr.go",
    "content": "package option\n\ntype ShadowsocksROutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tMethod        string      `json:\"method\"`\n\tPassword      string      `json:\"password\"`\n\tObfs          string      `json:\"obfs,omitempty\"`\n\tObfsParam     string      `json:\"obfs_param,omitempty\"`\n\tProtocol      string      `json:\"protocol,omitempty\"`\n\tProtocolParam string      `json:\"protocol_param,omitempty\"`\n\tNetwork       NetworkList `json:\"network,omitempty\"`\n}\n"
  },
  {
    "path": "option/shadowtls.go",
    "content": "package option\n\nimport (\n\t\"encoding/json\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n)\n\ntype ShadowTLSInboundOptions struct {\n\tListenOptions\n\tVersion                int                                                  `json:\"version,omitempty\"`\n\tPassword               string                                               `json:\"password,omitempty\"`\n\tUsers                  []ShadowTLSUser                                      `json:\"users,omitempty\"`\n\tHandshake              ShadowTLSHandshakeOptions                            `json:\"handshake,omitempty\"`\n\tHandshakeForServerName *badjson.TypedMap[string, ShadowTLSHandshakeOptions] `json:\"handshake_for_server_name,omitempty\"`\n\tStrictMode             bool                                                 `json:\"strict_mode,omitempty\"`\n\tWildcardSNI            WildcardSNI                                          `json:\"wildcard_sni,omitempty\"`\n}\n\ntype WildcardSNI int\n\nconst (\n\tShadowTLSWildcardSNIOff WildcardSNI = iota\n\tShadowTLSWildcardSNIAuthed\n\tShadowTLSWildcardSNIAll\n)\n\nfunc (w WildcardSNI) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(w.String())\n}\n\nfunc (w WildcardSNI) String() string {\n\tswitch w {\n\tcase ShadowTLSWildcardSNIOff:\n\t\treturn \"off\"\n\tcase ShadowTLSWildcardSNIAuthed:\n\t\treturn \"authed\"\n\tcase ShadowTLSWildcardSNIAll:\n\t\treturn \"all\"\n\tdefault:\n\t\tpanic(\"unknown wildcard SNI value\")\n\t}\n}\n\nfunc (w *WildcardSNI) UnmarshalJSON(bytes []byte) error {\n\tvar valueString string\n\terr := json.Unmarshal(bytes, &valueString)\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch valueString {\n\tcase \"off\", \"\":\n\t\t*w = ShadowTLSWildcardSNIOff\n\tcase \"authed\":\n\t\t*w = ShadowTLSWildcardSNIAuthed\n\tcase \"all\":\n\t\t*w = ShadowTLSWildcardSNIAll\n\tdefault:\n\t\treturn E.New(\"unknown wildcard SNI value: \", valueString)\n\t}\n\treturn nil\n}\n\ntype ShadowTLSUser struct {\n\tName     string `json:\"name,omitempty\"`\n\tPassword string `json:\"password,omitempty\"`\n}\n\ntype ShadowTLSHandshakeOptions struct {\n\tServerOptions\n\tDialerOptions\n}\n\ntype ShadowTLSOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tVersion  int    `json:\"version,omitempty\"`\n\tPassword string `json:\"password,omitempty\"`\n\tOutboundTLSOptionsContainer\n}\n"
  },
  {
    "path": "option/simple.go",
    "content": "package option\n\nimport (\n\t\"github.com/sagernet/sing/common/auth\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype SocksInboundOptions struct {\n\tListenOptions\n\tUsers          []auth.User           `json:\"users,omitempty\"`\n\tDomainResolver *DomainResolveOptions `json:\"domain_resolver,omitempty\"`\n}\n\ntype HTTPMixedInboundOptions struct {\n\tListenOptions\n\tUsers          []auth.User           `json:\"users,omitempty\"`\n\tDomainResolver *DomainResolveOptions `json:\"domain_resolver,omitempty\"`\n\tSetSystemProxy bool                  `json:\"set_system_proxy,omitempty\"`\n\tInboundTLSOptionsContainer\n}\n\ntype SOCKSOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tVersion    string             `json:\"version,omitempty\"`\n\tUsername   string             `json:\"username,omitempty\"`\n\tPassword   string             `json:\"password,omitempty\"`\n\tNetwork    NetworkList        `json:\"network,omitempty\"`\n\tUDPOverTCP *UDPOverTCPOptions `json:\"udp_over_tcp,omitempty\"`\n}\n\ntype HTTPOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tUsername string `json:\"username,omitempty\"`\n\tPassword string `json:\"password,omitempty\"`\n\tOutboundTLSOptionsContainer\n\tPath    string               `json:\"path,omitempty\"`\n\tHeaders badoption.HTTPHeader `json:\"headers,omitempty\"`\n}\n"
  },
  {
    "path": "option/ssh.go",
    "content": "package option\n\nimport \"github.com/sagernet/sing/common/json/badoption\"\n\ntype SSHOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tUser                 string                     `json:\"user,omitempty\"`\n\tPassword             string                     `json:\"password,omitempty\"`\n\tPrivateKey           badoption.Listable[string] `json:\"private_key,omitempty\"`\n\tPrivateKeyPath       string                     `json:\"private_key_path,omitempty\"`\n\tPrivateKeyPassphrase string                     `json:\"private_key_passphrase,omitempty\"`\n\tHostKey              badoption.Listable[string] `json:\"host_key,omitempty\"`\n\tHostKeyAlgorithms    badoption.Listable[string] `json:\"host_key_algorithms,omitempty\"`\n\tClientVersion        string                     `json:\"client_version,omitempty\"`\n}\n"
  },
  {
    "path": "option/ssmapi.go",
    "content": "package option\n\nimport (\n\t\"github.com/sagernet/sing/common/json/badjson\"\n)\n\ntype SSMAPIServiceOptions struct {\n\tListenOptions\n\tServers   *badjson.TypedMap[string, string] `json:\"servers\"`\n\tCachePath string                            `json:\"cache_path,omitempty\"`\n\tInboundTLSOptionsContainer\n}\n"
  },
  {
    "path": "option/tailscale.go",
    "content": "package option\n\nimport (\n\t\"net/netip\"\n\t\"net/url\"\n\t\"reflect\"\n\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n)\n\ntype TailscaleEndpointOptions struct {\n\tDialerOptions\n\tStateDirectory             string                     `json:\"state_directory,omitempty\"`\n\tAuthKey                    string                     `json:\"auth_key,omitempty\"`\n\tControlURL                 string                     `json:\"control_url,omitempty\"`\n\tEphemeral                  bool                       `json:\"ephemeral,omitempty\"`\n\tHostname                   string                     `json:\"hostname,omitempty\"`\n\tAcceptRoutes               bool                       `json:\"accept_routes,omitempty\"`\n\tExitNode                   string                     `json:\"exit_node,omitempty\"`\n\tExitNodeAllowLANAccess     bool                       `json:\"exit_node_allow_lan_access,omitempty\"`\n\tAdvertiseRoutes            []netip.Prefix             `json:\"advertise_routes,omitempty\"`\n\tAdvertiseExitNode          bool                       `json:\"advertise_exit_node,omitempty\"`\n\tAdvertiseTags              badoption.Listable[string] `json:\"advertise_tags,omitempty\"`\n\tRelayServerPort            *uint16                    `json:\"relay_server_port,omitempty\"`\n\tRelayServerStaticEndpoints []netip.AddrPort           `json:\"relay_server_static_endpoints,omitempty\"`\n\tSystemInterface            bool                       `json:\"system_interface,omitempty\"`\n\tSystemInterfaceName        string                     `json:\"system_interface_name,omitempty\"`\n\tSystemInterfaceMTU         uint32                     `json:\"system_interface_mtu,omitempty\"`\n\tUDPTimeout                 UDPTimeoutCompat           `json:\"udp_timeout,omitempty\"`\n}\n\ntype TailscaleDNSServerOptions struct {\n\tEndpoint               string `json:\"endpoint,omitempty\"`\n\tAcceptDefaultResolvers bool   `json:\"accept_default_resolvers,omitempty\"`\n}\n\ntype DERPServiceOptions struct {\n\tListenOptions\n\tInboundTLSOptionsContainer\n\tConfigPath           string                                          `json:\"config_path,omitempty\"`\n\tVerifyClientEndpoint badoption.Listable[string]                      `json:\"verify_client_endpoint,omitempty\"`\n\tVerifyClientURL      badoption.Listable[*DERPVerifyClientURLOptions] `json:\"verify_client_url,omitempty\"`\n\tHome                 string                                          `json:\"home,omitempty\"`\n\tMeshWith             badoption.Listable[*DERPMeshOptions]            `json:\"mesh_with,omitempty\"`\n\tMeshPSK              string                                          `json:\"mesh_psk,omitempty\"`\n\tMeshPSKFile          string                                          `json:\"mesh_psk_file,omitempty\"`\n\tSTUN                 *DERPSTUNListenOptions                          `json:\"stun,omitempty\"`\n}\n\ntype _DERPVerifyClientURLOptions struct {\n\tURL string `json:\"url,omitempty\"`\n\tDialerOptions\n}\n\ntype DERPVerifyClientURLOptions _DERPVerifyClientURLOptions\n\nfunc (d DERPVerifyClientURLOptions) ServerIsDomain() bool {\n\tverifyURL, err := url.Parse(d.URL)\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn M.ParseSocksaddr(verifyURL.Hostname()).IsDomain()\n}\n\nfunc (d DERPVerifyClientURLOptions) MarshalJSON() ([]byte, error) {\n\tif reflect.DeepEqual(d, _DERPVerifyClientURLOptions{}) {\n\t\treturn json.Marshal(d.URL)\n\t} else {\n\t\treturn json.Marshal(_DERPVerifyClientURLOptions(d))\n\t}\n}\n\nfunc (d *DERPVerifyClientURLOptions) UnmarshalJSON(bytes []byte) error {\n\tvar stringValue string\n\terr := json.Unmarshal(bytes, &stringValue)\n\tif err == nil {\n\t\td.URL = stringValue\n\t\treturn nil\n\t}\n\treturn json.Unmarshal(bytes, (*_DERPVerifyClientURLOptions)(d))\n}\n\ntype DERPMeshOptions struct {\n\tServerOptions\n\tHost string `json:\"host,omitempty\"`\n\tOutboundTLSOptionsContainer\n\tDialerOptions\n}\n\ntype _DERPSTUNListenOptions struct {\n\tEnabled bool\n\tListenOptions\n}\n\ntype DERPSTUNListenOptions _DERPSTUNListenOptions\n\nfunc (d DERPSTUNListenOptions) MarshalJSON() ([]byte, error) {\n\tportOptions := _DERPSTUNListenOptions{\n\t\tEnabled: d.Enabled,\n\t\tListenOptions: ListenOptions{\n\t\t\tListenPort: d.ListenPort,\n\t\t},\n\t}\n\tif _DERPSTUNListenOptions(d) == portOptions {\n\t\treturn json.Marshal(d.Enabled)\n\t} else {\n\t\treturn json.Marshal(_DERPSTUNListenOptions(d))\n\t}\n}\n\nfunc (d *DERPSTUNListenOptions) UnmarshalJSON(bytes []byte) error {\n\tvar portValue uint16\n\terr := json.Unmarshal(bytes, &portValue)\n\tif err == nil {\n\t\td.Enabled = true\n\t\td.ListenPort = portValue\n\t\treturn nil\n\t}\n\treturn json.Unmarshal(bytes, (*_DERPSTUNListenOptions)(d))\n}\n"
  },
  {
    "path": "option/tls.go",
    "content": "package option\n\nimport (\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"strings\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype InboundTLSOptions struct {\n\tEnabled                          bool                                `json:\"enabled,omitempty\"`\n\tServerName                       string                              `json:\"server_name,omitempty\"`\n\tInsecure                         bool                                `json:\"insecure,omitempty\"`\n\tALPN                             badoption.Listable[string]          `json:\"alpn,omitempty\"`\n\tMinVersion                       string                              `json:\"min_version,omitempty\"`\n\tMaxVersion                       string                              `json:\"max_version,omitempty\"`\n\tCipherSuites                     badoption.Listable[string]          `json:\"cipher_suites,omitempty\"`\n\tCurvePreferences                 badoption.Listable[CurvePreference] `json:\"curve_preferences,omitempty\"`\n\tCertificate                      badoption.Listable[string]          `json:\"certificate,omitempty\"`\n\tCertificatePath                  string                              `json:\"certificate_path,omitempty\"`\n\tClientAuthentication             ClientAuthType                      `json:\"client_authentication,omitempty\"`\n\tClientCertificate                badoption.Listable[string]          `json:\"client_certificate,omitempty\"`\n\tClientCertificatePath            badoption.Listable[string]          `json:\"client_certificate_path,omitempty\"`\n\tClientCertificatePublicKeySHA256 badoption.Listable[[]byte]          `json:\"client_certificate_public_key_sha256,omitempty\"`\n\tKey                              badoption.Listable[string]          `json:\"key,omitempty\"`\n\tKeyPath                          string                              `json:\"key_path,omitempty\"`\n\tKernelTx                         bool                                `json:\"kernel_tx,omitempty\"`\n\tKernelRx                         bool                                `json:\"kernel_rx,omitempty\"`\n\tACME                             *InboundACMEOptions                 `json:\"acme,omitempty\"`\n\tECH                              *InboundECHOptions                  `json:\"ech,omitempty\"`\n\tReality                          *InboundRealityOptions              `json:\"reality,omitempty\"`\n}\n\ntype ClientAuthType tls.ClientAuthType\n\nfunc (t ClientAuthType) MarshalJSON() ([]byte, error) {\n\tvar stringValue string\n\tswitch t {\n\tcase ClientAuthType(tls.NoClientCert):\n\t\tstringValue = \"no\"\n\tcase ClientAuthType(tls.RequestClientCert):\n\t\tstringValue = \"request\"\n\tcase ClientAuthType(tls.RequireAnyClientCert):\n\t\tstringValue = \"require-any\"\n\tcase ClientAuthType(tls.VerifyClientCertIfGiven):\n\t\tstringValue = \"verify-if-given\"\n\tcase ClientAuthType(tls.RequireAndVerifyClientCert):\n\t\tstringValue = \"require-and-verify\"\n\tdefault:\n\t\treturn nil, E.New(\"unknown client authentication type: \", int(t))\n\t}\n\treturn json.Marshal(stringValue)\n}\n\nfunc (t *ClientAuthType) UnmarshalJSON(data []byte) error {\n\tvar stringValue string\n\terr := json.Unmarshal(data, &stringValue)\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch stringValue {\n\tcase \"no\":\n\t\t*t = ClientAuthType(tls.NoClientCert)\n\tcase \"request\":\n\t\t*t = ClientAuthType(tls.RequestClientCert)\n\tcase \"require-any\":\n\t\t*t = ClientAuthType(tls.RequireAnyClientCert)\n\tcase \"verify-if-given\":\n\t\t*t = ClientAuthType(tls.VerifyClientCertIfGiven)\n\tcase \"require-and-verify\":\n\t\t*t = ClientAuthType(tls.RequireAndVerifyClientCert)\n\tdefault:\n\t\treturn E.New(\"unknown client authentication type: \", stringValue)\n\t}\n\treturn nil\n}\n\ntype InboundTLSOptionsContainer struct {\n\tTLS *InboundTLSOptions `json:\"tls,omitempty\"`\n}\n\ntype InboundTLSOptionsWrapper interface {\n\tTakeInboundTLSOptions() *InboundTLSOptions\n\tReplaceInboundTLSOptions(options *InboundTLSOptions)\n}\n\nfunc (o *InboundTLSOptionsContainer) TakeInboundTLSOptions() *InboundTLSOptions {\n\treturn o.TLS\n}\n\nfunc (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTLSOptions) {\n\to.TLS = options\n}\n\ntype OutboundTLSOptions struct {\n\tEnabled                    bool                                `json:\"enabled,omitempty\"`\n\tDisableSNI                 bool                                `json:\"disable_sni,omitempty\"`\n\tServerName                 string                              `json:\"server_name,omitempty\"`\n\tInsecure                   bool                                `json:\"insecure,omitempty\"`\n\tALPN                       badoption.Listable[string]          `json:\"alpn,omitempty\"`\n\tMinVersion                 string                              `json:\"min_version,omitempty\"`\n\tMaxVersion                 string                              `json:\"max_version,omitempty\"`\n\tCipherSuites               badoption.Listable[string]          `json:\"cipher_suites,omitempty\"`\n\tCurvePreferences           badoption.Listable[CurvePreference] `json:\"curve_preferences,omitempty\"`\n\tCertificate                badoption.Listable[string]          `json:\"certificate,omitempty\"`\n\tCertificatePath            string                              `json:\"certificate_path,omitempty\"`\n\tCertificatePublicKeySHA256 badoption.Listable[[]byte]          `json:\"certificate_public_key_sha256,omitempty\"`\n\tClientCertificate          badoption.Listable[string]          `json:\"client_certificate,omitempty\"`\n\tClientCertificatePath      string                              `json:\"client_certificate_path,omitempty\"`\n\tClientKey                  badoption.Listable[string]          `json:\"client_key,omitempty\"`\n\tClientKeyPath              string                              `json:\"client_key_path,omitempty\"`\n\tFragment                   bool                                `json:\"fragment,omitempty\"`\n\tFragmentFallbackDelay      badoption.Duration                  `json:\"fragment_fallback_delay,omitempty\"`\n\tRecordFragment             bool                                `json:\"record_fragment,omitempty\"`\n\tKernelTx                   bool                                `json:\"kernel_tx,omitempty\"`\n\tKernelRx                   bool                                `json:\"kernel_rx,omitempty\"`\n\tECH                        *OutboundECHOptions                 `json:\"ech,omitempty\"`\n\tUTLS                       *OutboundUTLSOptions                `json:\"utls,omitempty\"`\n\tReality                    *OutboundRealityOptions             `json:\"reality,omitempty\"`\n}\n\ntype OutboundTLSOptionsContainer struct {\n\tTLS *OutboundTLSOptions `json:\"tls,omitempty\"`\n}\n\ntype OutboundTLSOptionsWrapper interface {\n\tTakeOutboundTLSOptions() *OutboundTLSOptions\n\tReplaceOutboundTLSOptions(options *OutboundTLSOptions)\n}\n\nfunc (o *OutboundTLSOptionsContainer) TakeOutboundTLSOptions() *OutboundTLSOptions {\n\treturn o.TLS\n}\n\nfunc (o *OutboundTLSOptionsContainer) ReplaceOutboundTLSOptions(options *OutboundTLSOptions) {\n\to.TLS = options\n}\n\ntype CurvePreference tls.CurveID\n\nconst (\n\tCurveP256      = 23\n\tCurveP384      = 24\n\tCurveP521      = 25\n\tX25519         = 29\n\tX25519MLKEM768 = 4588\n)\n\nfunc (c CurvePreference) MarshalJSON() ([]byte, error) {\n\tvar stringValue string\n\tswitch c {\n\tcase CurvePreference(CurveP256):\n\t\tstringValue = \"P256\"\n\tcase CurvePreference(CurveP384):\n\t\tstringValue = \"P384\"\n\tcase CurvePreference(CurveP521):\n\t\tstringValue = \"P521\"\n\tcase CurvePreference(X25519):\n\t\tstringValue = \"X25519\"\n\tcase CurvePreference(X25519MLKEM768):\n\t\tstringValue = \"X25519MLKEM768\"\n\tdefault:\n\t\treturn nil, E.New(\"unknown curve id: \", int(c))\n\t}\n\treturn json.Marshal(stringValue)\n}\n\nfunc (c *CurvePreference) UnmarshalJSON(data []byte) error {\n\tvar stringValue string\n\terr := json.Unmarshal(data, &stringValue)\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch strings.ToUpper(stringValue) {\n\tcase \"P256\":\n\t\t*c = CurvePreference(CurveP256)\n\tcase \"P384\":\n\t\t*c = CurvePreference(CurveP384)\n\tcase \"P521\":\n\t\t*c = CurvePreference(CurveP521)\n\tcase \"X25519\":\n\t\t*c = CurvePreference(X25519)\n\tcase \"X25519MLKEM768\":\n\t\t*c = CurvePreference(X25519MLKEM768)\n\tdefault:\n\t\treturn E.New(\"unknown curve name: \", stringValue)\n\t}\n\treturn nil\n}\n\ntype InboundRealityOptions struct {\n\tEnabled           bool                           `json:\"enabled,omitempty\"`\n\tHandshake         InboundRealityHandshakeOptions `json:\"handshake,omitempty\"`\n\tPrivateKey        string                         `json:\"private_key,omitempty\"`\n\tShortID           badoption.Listable[string]     `json:\"short_id,omitempty\"`\n\tMaxTimeDifference badoption.Duration             `json:\"max_time_difference,omitempty\"`\n}\n\ntype InboundRealityHandshakeOptions struct {\n\tServerOptions\n\tDialerOptions\n}\n\ntype InboundECHOptions struct {\n\tEnabled bool                       `json:\"enabled,omitempty\"`\n\tKey     badoption.Listable[string] `json:\"key,omitempty\"`\n\tKeyPath string                     `json:\"key_path,omitempty\"`\n\n\t// Deprecated: not supported by stdlib\n\tPQSignatureSchemesEnabled bool `json:\"pq_signature_schemes_enabled,omitempty\"`\n\t// Deprecated: added by fault\n\tDynamicRecordSizingDisabled bool `json:\"dynamic_record_sizing_disabled,omitempty\"`\n}\n\ntype OutboundECHOptions struct {\n\tEnabled         bool                       `json:\"enabled,omitempty\"`\n\tConfig          badoption.Listable[string] `json:\"config,omitempty\"`\n\tConfigPath      string                     `json:\"config_path,omitempty\"`\n\tQueryServerName string                     `json:\"query_server_name,omitempty\"`\n\n\t// Deprecated: not supported by stdlib\n\tPQSignatureSchemesEnabled bool `json:\"pq_signature_schemes_enabled,omitempty\"`\n\t// Deprecated: added by fault\n\tDynamicRecordSizingDisabled bool `json:\"dynamic_record_sizing_disabled,omitempty\"`\n}\n\ntype OutboundUTLSOptions struct {\n\tEnabled     bool   `json:\"enabled,omitempty\"`\n\tFingerprint string `json:\"fingerprint,omitempty\"`\n}\n\ntype OutboundRealityOptions struct {\n\tEnabled   bool   `json:\"enabled,omitempty\"`\n\tPublicKey string `json:\"public_key,omitempty\"`\n\tShortID   string `json:\"short_id,omitempty\"`\n}\n"
  },
  {
    "path": "option/tls_acme.go",
    "content": "package option\n\nimport (\n\tC \"github.com/sagernet/sing-box/constant\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype InboundACMEOptions struct {\n\tDomain                  badoption.Listable[string]  `json:\"domain,omitempty\"`\n\tDataDirectory           string                      `json:\"data_directory,omitempty\"`\n\tDefaultServerName       string                      `json:\"default_server_name,omitempty\"`\n\tEmail                   string                      `json:\"email,omitempty\"`\n\tProvider                string                      `json:\"provider,omitempty\"`\n\tDisableHTTPChallenge    bool                        `json:\"disable_http_challenge,omitempty\"`\n\tDisableTLSALPNChallenge bool                        `json:\"disable_tls_alpn_challenge,omitempty\"`\n\tAlternativeHTTPPort     uint16                      `json:\"alternative_http_port,omitempty\"`\n\tAlternativeTLSPort      uint16                      `json:\"alternative_tls_port,omitempty\"`\n\tExternalAccount         *ACMEExternalAccountOptions `json:\"external_account,omitempty\"`\n\tDNS01Challenge          *ACMEDNS01ChallengeOptions  `json:\"dns01_challenge,omitempty\"`\n}\n\ntype ACMEExternalAccountOptions struct {\n\tKeyID  string `json:\"key_id,omitempty\"`\n\tMACKey string `json:\"mac_key,omitempty\"`\n}\n\ntype _ACMEDNS01ChallengeOptions struct {\n\tProvider          string                     `json:\"provider,omitempty\"`\n\tAliDNSOptions     ACMEDNS01AliDNSOptions     `json:\"-\"`\n\tCloudflareOptions ACMEDNS01CloudflareOptions `json:\"-\"`\n\tACMEDNSOptions    ACMEDNS01ACMEDNSOptions    `json:\"-\"`\n}\n\ntype ACMEDNS01ChallengeOptions _ACMEDNS01ChallengeOptions\n\nfunc (o ACMEDNS01ChallengeOptions) MarshalJSON() ([]byte, error) {\n\tvar v any\n\tswitch o.Provider {\n\tcase C.DNSProviderAliDNS:\n\t\tv = o.AliDNSOptions\n\tcase C.DNSProviderCloudflare:\n\t\tv = o.CloudflareOptions\n\tcase C.DNSProviderACMEDNS:\n\t\tv = o.ACMEDNSOptions\n\tcase \"\":\n\t\treturn nil, E.New(\"missing provider type\")\n\tdefault:\n\t\treturn nil, E.New(\"unknown provider type: \" + o.Provider)\n\t}\n\treturn badjson.MarshallObjects((_ACMEDNS01ChallengeOptions)(o), v)\n}\n\nfunc (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {\n\terr := json.Unmarshal(bytes, (*_ACMEDNS01ChallengeOptions)(o))\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar v any\n\tswitch o.Provider {\n\tcase C.DNSProviderAliDNS:\n\t\tv = &o.AliDNSOptions\n\tcase C.DNSProviderCloudflare:\n\t\tv = &o.CloudflareOptions\n\tcase C.DNSProviderACMEDNS:\n\t\tv = &o.ACMEDNSOptions\n\tdefault:\n\t\treturn E.New(\"unknown provider type: \" + o.Provider)\n\t}\n\terr = badjson.UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype ACMEDNS01AliDNSOptions struct {\n\tAccessKeyID     string `json:\"access_key_id,omitempty\"`\n\tAccessKeySecret string `json:\"access_key_secret,omitempty\"`\n\tRegionID        string `json:\"region_id,omitempty\"`\n\tSecurityToken   string `json:\"security_token,omitempty\"`\n}\n\ntype ACMEDNS01CloudflareOptions struct {\n\tAPIToken  string `json:\"api_token,omitempty\"`\n\tZoneToken string `json:\"zone_token,omitempty\"`\n}\n\ntype ACMEDNS01ACMEDNSOptions struct {\n\tUsername  string `json:\"username,omitempty\"`\n\tPassword  string `json:\"password,omitempty\"`\n\tSubdomain string `json:\"subdomain,omitempty\"`\n\tServerURL string `json:\"server_url,omitempty\"`\n}\n"
  },
  {
    "path": "option/tor.go",
    "content": "package option\n\ntype TorOutboundOptions struct {\n\tDialerOptions\n\tExecutablePath string            `json:\"executable_path,omitempty\"`\n\tExtraArgs      []string          `json:\"extra_args,omitempty\"`\n\tDataDirectory  string            `json:\"data_directory,omitempty\"`\n\tOptions        map[string]string `json:\"torrc,omitempty\"`\n}\n"
  },
  {
    "path": "option/trojan.go",
    "content": "package option\n\ntype TrojanInboundOptions struct {\n\tListenOptions\n\tUsers []TrojanUser `json:\"users,omitempty\"`\n\tInboundTLSOptionsContainer\n\tFallback        *ServerOptions            `json:\"fallback,omitempty\"`\n\tFallbackForALPN map[string]*ServerOptions `json:\"fallback_for_alpn,omitempty\"`\n\tMultiplex       *InboundMultiplexOptions  `json:\"multiplex,omitempty\"`\n\tTransport       *V2RayTransportOptions    `json:\"transport,omitempty\"`\n}\n\ntype TrojanUser struct {\n\tName     string `json:\"name\"`\n\tPassword string `json:\"password\"`\n}\n\ntype TrojanOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tPassword string      `json:\"password\"`\n\tNetwork  NetworkList `json:\"network,omitempty\"`\n\tOutboundTLSOptionsContainer\n\tMultiplex *OutboundMultiplexOptions `json:\"multiplex,omitempty\"`\n\tTransport *V2RayTransportOptions    `json:\"transport,omitempty\"`\n}\n"
  },
  {
    "path": "option/tuic.go",
    "content": "package option\n\nimport \"github.com/sagernet/sing/common/json/badoption\"\n\ntype TUICInboundOptions struct {\n\tListenOptions\n\tUsers             []TUICUser         `json:\"users,omitempty\"`\n\tCongestionControl string             `json:\"congestion_control,omitempty\"`\n\tAuthTimeout       badoption.Duration `json:\"auth_timeout,omitempty\"`\n\tZeroRTTHandshake  bool               `json:\"zero_rtt_handshake,omitempty\"`\n\tHeartbeat         badoption.Duration `json:\"heartbeat,omitempty\"`\n\tInboundTLSOptionsContainer\n}\n\ntype TUICUser struct {\n\tName     string `json:\"name,omitempty\"`\n\tUUID     string `json:\"uuid,omitempty\"`\n\tPassword string `json:\"password,omitempty\"`\n}\n\ntype TUICOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tUUID              string             `json:\"uuid,omitempty\"`\n\tPassword          string             `json:\"password,omitempty\"`\n\tCongestionControl string             `json:\"congestion_control,omitempty\"`\n\tUDPRelayMode      string             `json:\"udp_relay_mode,omitempty\"`\n\tUDPOverStream     bool               `json:\"udp_over_stream,omitempty\"`\n\tZeroRTTHandshake  bool               `json:\"zero_rtt_handshake,omitempty\"`\n\tHeartbeat         badoption.Duration `json:\"heartbeat,omitempty\"`\n\tNetwork           NetworkList        `json:\"network,omitempty\"`\n\tOutboundTLSOptionsContainer\n}\n"
  },
  {
    "path": "option/tun.go",
    "content": "package option\n\nimport (\n\t\"net/netip\"\n\t\"strconv\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype TunInboundOptions struct {\n\tInterfaceName                 string                           `json:\"interface_name,omitempty\"`\n\tMTU                           uint32                           `json:\"mtu,omitempty\"`\n\tAddress                       badoption.Listable[netip.Prefix] `json:\"address,omitempty\"`\n\tAutoRoute                     bool                             `json:\"auto_route,omitempty\"`\n\tIPRoute2TableIndex            int                              `json:\"iproute2_table_index,omitempty\"`\n\tIPRoute2RuleIndex             int                              `json:\"iproute2_rule_index,omitempty\"`\n\tAutoRedirect                  bool                             `json:\"auto_redirect,omitempty\"`\n\tAutoRedirectInputMark         FwMark                           `json:\"auto_redirect_input_mark,omitempty\"`\n\tAutoRedirectOutputMark        FwMark                           `json:\"auto_redirect_output_mark,omitempty\"`\n\tAutoRedirectResetMark         FwMark                           `json:\"auto_redirect_reset_mark,omitempty\"`\n\tAutoRedirectNFQueue           uint16                           `json:\"auto_redirect_nfqueue,omitempty\"`\n\tAutoRedirectFallbackRuleIndex int                              `json:\"auto_redirect_iproute2_fallback_rule_index,omitempty\"`\n\tExcludeMPTCP                  bool                             `json:\"exclude_mptcp,omitempty\"`\n\tLoopbackAddress               badoption.Listable[netip.Addr]   `json:\"loopback_address,omitempty\"`\n\tStrictRoute                   bool                             `json:\"strict_route,omitempty\"`\n\tRouteAddress                  badoption.Listable[netip.Prefix] `json:\"route_address,omitempty\"`\n\tRouteAddressSet               badoption.Listable[string]       `json:\"route_address_set,omitempty\"`\n\tRouteExcludeAddress           badoption.Listable[netip.Prefix] `json:\"route_exclude_address,omitempty\"`\n\tRouteExcludeAddressSet        badoption.Listable[string]       `json:\"route_exclude_address_set,omitempty\"`\n\tIncludeInterface              badoption.Listable[string]       `json:\"include_interface,omitempty\"`\n\tExcludeInterface              badoption.Listable[string]       `json:\"exclude_interface,omitempty\"`\n\tIncludeUID                    badoption.Listable[uint32]       `json:\"include_uid,omitempty\"`\n\tIncludeUIDRange               badoption.Listable[string]       `json:\"include_uid_range,omitempty\"`\n\tExcludeUID                    badoption.Listable[uint32]       `json:\"exclude_uid,omitempty\"`\n\tExcludeUIDRange               badoption.Listable[string]       `json:\"exclude_uid_range,omitempty\"`\n\tIncludeAndroidUser            badoption.Listable[int]          `json:\"include_android_user,omitempty\"`\n\tIncludePackage                badoption.Listable[string]       `json:\"include_package,omitempty\"`\n\tExcludePackage                badoption.Listable[string]       `json:\"exclude_package,omitempty\"`\n\tIncludeMACAddress             badoption.Listable[string]       `json:\"include_mac_address,omitempty\"`\n\tExcludeMACAddress             badoption.Listable[string]       `json:\"exclude_mac_address,omitempty\"`\n\tUDPTimeout                    UDPTimeoutCompat                 `json:\"udp_timeout,omitempty\"`\n\tStack                         string                           `json:\"stack,omitempty\"`\n\tPlatform                      *TunPlatformOptions              `json:\"platform,omitempty\"`\n\tInboundOptions\n\n\t// Deprecated: removed\n\tGSO bool `json:\"gso,omitempty\"`\n\t// Deprecated: merged to Address\n\tInet4Address badoption.Listable[netip.Prefix] `json:\"inet4_address,omitempty\"`\n\t// Deprecated: merged to Address\n\tInet6Address badoption.Listable[netip.Prefix] `json:\"inet6_address,omitempty\"`\n\t// Deprecated: merged to RouteAddress\n\tInet4RouteAddress badoption.Listable[netip.Prefix] `json:\"inet4_route_address,omitempty\"`\n\t// Deprecated: merged to RouteAddress\n\tInet6RouteAddress badoption.Listable[netip.Prefix] `json:\"inet6_route_address,omitempty\"`\n\t// Deprecated: merged to RouteExcludeAddress\n\tInet4RouteExcludeAddress badoption.Listable[netip.Prefix] `json:\"inet4_route_exclude_address,omitempty\"`\n\t// Deprecated: merged to RouteExcludeAddress\n\tInet6RouteExcludeAddress badoption.Listable[netip.Prefix] `json:\"inet6_route_exclude_address,omitempty\"`\n\t// Deprecated: removed\n\tEndpointIndependentNat bool `json:\"endpoint_independent_nat,omitempty\"`\n}\n\ntype FwMark uint32\n\nfunc (f FwMark) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(F.ToString(\"0x\", strconv.FormatUint(uint64(f), 16)))\n}\n\nfunc (f *FwMark) UnmarshalJSON(bytes []byte) error {\n\tvar stringValue string\n\terr := json.Unmarshal(bytes, &stringValue)\n\tif err != nil {\n\t\tif rawErr := json.Unmarshal(bytes, (*uint32)(f)); rawErr == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn E.Cause(err, \"invalid number or string mark\")\n\t}\n\tintValue, err := strconv.ParseUint(stringValue, 0, 32)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*f = FwMark(intValue)\n\treturn nil\n}\n"
  },
  {
    "path": "option/tun_platform.go",
    "content": "package option\n\nimport \"github.com/sagernet/sing/common/json/badoption\"\n\ntype TunPlatformOptions struct {\n\tHTTPProxy *HTTPProxyOptions `json:\"http_proxy,omitempty\"`\n}\n\ntype HTTPProxyOptions struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tServerOptions\n\tBypassDomain badoption.Listable[string] `json:\"bypass_domain,omitempty\"`\n\tMatchDomain  badoption.Listable[string] `json:\"match_domain,omitempty\"`\n}\n"
  },
  {
    "path": "option/types.go",
    "content": "package option\n\nimport (\n\t\"strings\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\ntype NetworkList string\n\nfunc (v *NetworkList) UnmarshalJSON(content []byte) error {\n\tvar networkList []string\n\terr := json.Unmarshal(content, &networkList)\n\tif err != nil {\n\t\tvar networkItem string\n\t\terr = json.Unmarshal(content, &networkItem)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnetworkList = []string{networkItem}\n\t}\n\tfor _, networkName := range networkList {\n\t\tswitch networkName {\n\t\tcase N.NetworkTCP, N.NetworkUDP:\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn E.New(\"unknown network: \" + networkName)\n\t\t}\n\t}\n\t*v = NetworkList(strings.Join(networkList, \"\\n\"))\n\treturn nil\n}\n\nfunc (v NetworkList) Build() []string {\n\tif v == \"\" {\n\t\treturn []string{N.NetworkTCP, N.NetworkUDP}\n\t}\n\treturn strings.Split(string(v), \"\\n\")\n}\n\ntype DomainStrategy C.DomainStrategy\n\nfunc (s DomainStrategy) String() string {\n\tswitch C.DomainStrategy(s) {\n\tcase C.DomainStrategyAsIS:\n\t\treturn \"\"\n\tcase C.DomainStrategyPreferIPv4:\n\t\treturn \"prefer_ipv4\"\n\tcase C.DomainStrategyPreferIPv6:\n\t\treturn \"prefer_ipv6\"\n\tcase C.DomainStrategyIPv4Only:\n\t\treturn \"ipv4_only\"\n\tcase C.DomainStrategyIPv6Only:\n\t\treturn \"ipv6_only\"\n\tdefault:\n\t\tpanic(E.New(\"unknown domain strategy: \", s))\n\t}\n}\n\nfunc (s DomainStrategy) MarshalJSON() ([]byte, error) {\n\tvar value string\n\tswitch C.DomainStrategy(s) {\n\tcase C.DomainStrategyAsIS:\n\t\tvalue = \"\"\n\t\t// value = \"as_is\"\n\tcase C.DomainStrategyPreferIPv4:\n\t\tvalue = \"prefer_ipv4\"\n\tcase C.DomainStrategyPreferIPv6:\n\t\tvalue = \"prefer_ipv6\"\n\tcase C.DomainStrategyIPv4Only:\n\t\tvalue = \"ipv4_only\"\n\tcase C.DomainStrategyIPv6Only:\n\t\tvalue = \"ipv6_only\"\n\tdefault:\n\t\treturn nil, E.New(\"unknown domain strategy: \", s)\n\t}\n\treturn json.Marshal(value)\n}\n\nfunc (s *DomainStrategy) UnmarshalJSON(bytes []byte) error {\n\tvar value string\n\terr := json.Unmarshal(bytes, &value)\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch value {\n\tcase \"\", \"as_is\":\n\t\t*s = DomainStrategy(C.DomainStrategyAsIS)\n\tcase \"prefer_ipv4\":\n\t\t*s = DomainStrategy(C.DomainStrategyPreferIPv4)\n\tcase \"prefer_ipv6\":\n\t\t*s = DomainStrategy(C.DomainStrategyPreferIPv6)\n\tcase \"ipv4_only\":\n\t\t*s = DomainStrategy(C.DomainStrategyIPv4Only)\n\tcase \"ipv6_only\":\n\t\t*s = DomainStrategy(C.DomainStrategyIPv6Only)\n\tdefault:\n\t\treturn E.New(\"unknown domain strategy: \", value)\n\t}\n\treturn nil\n}\n\ntype DNSQueryType uint16\n\nfunc (t DNSQueryType) String() string {\n\ttypeName, loaded := mDNS.TypeToString[uint16(t)]\n\tif loaded {\n\t\treturn typeName\n\t}\n\treturn F.ToString(uint16(t))\n}\n\nfunc (t DNSQueryType) MarshalJSON() ([]byte, error) {\n\ttypeName, loaded := mDNS.TypeToString[uint16(t)]\n\tif loaded {\n\t\treturn json.Marshal(typeName)\n\t}\n\treturn json.Marshal(uint16(t))\n}\n\nfunc (t *DNSQueryType) UnmarshalJSON(bytes []byte) error {\n\tvar valueNumber uint16\n\terr := json.Unmarshal(bytes, &valueNumber)\n\tif err == nil {\n\t\t*t = DNSQueryType(valueNumber)\n\t\treturn nil\n\t}\n\tvar valueString string\n\terr = json.Unmarshal(bytes, &valueString)\n\tif err == nil {\n\t\tqueryType, loaded := mDNS.StringToType[valueString]\n\t\tif loaded {\n\t\t\t*t = DNSQueryType(queryType)\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn E.New(\"unknown DNS query type: \", string(bytes))\n}\n\nfunc DNSQueryTypeToString(queryType uint16) string {\n\ttypeName, loaded := mDNS.TypeToString[queryType]\n\tif loaded {\n\t\treturn typeName\n\t}\n\treturn F.ToString(queryType)\n}\n\ntype NetworkStrategy C.NetworkStrategy\n\nfunc (n NetworkStrategy) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(C.NetworkStrategy(n).String())\n}\n\nfunc (n *NetworkStrategy) UnmarshalJSON(content []byte) error {\n\tvar value string\n\terr := json.Unmarshal(content, &value)\n\tif err != nil {\n\t\treturn err\n\t}\n\tstrategy, loaded := C.StringToNetworkStrategy[value]\n\tif !loaded {\n\t\treturn E.New(\"unknown network strategy: \", value)\n\t}\n\t*n = NetworkStrategy(strategy)\n\treturn nil\n}\n\ntype InterfaceType C.InterfaceType\n\nfunc (t InterfaceType) Build() C.InterfaceType {\n\treturn C.InterfaceType(t)\n}\n\nfunc (t InterfaceType) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(C.InterfaceType(t).String())\n}\n\nfunc (t *InterfaceType) UnmarshalJSON(content []byte) error {\n\tvar value string\n\terr := json.Unmarshal(content, &value)\n\tif err != nil {\n\t\treturn err\n\t}\n\tinterfaceType, loaded := C.StringToInterfaceType[value]\n\tif !loaded {\n\t\treturn E.New(\"unknown interface type: \", value)\n\t}\n\t*t = InterfaceType(interfaceType)\n\treturn nil\n}\n"
  },
  {
    "path": "option/udp_over_tcp.go",
    "content": "package option\n\nimport (\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/uot\"\n)\n\ntype _UDPOverTCPOptions struct {\n\tEnabled bool  `json:\"enabled,omitempty\"`\n\tVersion uint8 `json:\"version,omitempty\"`\n}\n\ntype UDPOverTCPOptions _UDPOverTCPOptions\n\nfunc (o UDPOverTCPOptions) MarshalJSON() ([]byte, error) {\n\tswitch o.Version {\n\tcase 0, uot.Version:\n\t\treturn json.Marshal(o.Enabled)\n\tdefault:\n\t\treturn json.Marshal(_UDPOverTCPOptions(o))\n\t}\n}\n\nfunc (o *UDPOverTCPOptions) UnmarshalJSON(bytes []byte) error {\n\terr := json.Unmarshal(bytes, &o.Enabled)\n\tif err == nil {\n\t\treturn nil\n\t}\n\treturn json.UnmarshalDisallowUnknownFields(bytes, (*_UDPOverTCPOptions)(o))\n}\n"
  },
  {
    "path": "option/v2ray.go",
    "content": "package option\n"
  },
  {
    "path": "option/v2ray_transport.go",
    "content": "package option\n\nimport (\n\tC \"github.com/sagernet/sing-box/constant\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype _V2RayTransportOptions struct {\n\tType               string                  `json:\"type\"`\n\tHTTPOptions        V2RayHTTPOptions        `json:\"-\"`\n\tWebsocketOptions   V2RayWebsocketOptions   `json:\"-\"`\n\tQUICOptions        V2RayQUICOptions        `json:\"-\"`\n\tGRPCOptions        V2RayGRPCOptions        `json:\"-\"`\n\tHTTPUpgradeOptions V2RayHTTPUpgradeOptions `json:\"-\"`\n}\n\ntype V2RayTransportOptions _V2RayTransportOptions\n\nfunc (o V2RayTransportOptions) MarshalJSON() ([]byte, error) {\n\tvar v any\n\tswitch o.Type {\n\tcase C.V2RayTransportTypeHTTP:\n\t\tv = o.HTTPOptions\n\tcase C.V2RayTransportTypeWebsocket:\n\t\tv = o.WebsocketOptions\n\tcase C.V2RayTransportTypeQUIC:\n\t\tv = o.QUICOptions\n\tcase C.V2RayTransportTypeGRPC:\n\t\tv = o.GRPCOptions\n\tcase C.V2RayTransportTypeHTTPUpgrade:\n\t\tv = o.HTTPUpgradeOptions\n\tcase \"\":\n\t\treturn nil, E.New(\"missing transport type\")\n\tdefault:\n\t\treturn nil, E.New(\"unknown transport type: \" + o.Type)\n\t}\n\treturn badjson.MarshallObjects((_V2RayTransportOptions)(o), v)\n}\n\nfunc (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {\n\terr := json.Unmarshal(bytes, (*_V2RayTransportOptions)(o))\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar v any\n\tswitch o.Type {\n\tcase C.V2RayTransportTypeHTTP:\n\t\tv = &o.HTTPOptions\n\tcase C.V2RayTransportTypeWebsocket:\n\t\tv = &o.WebsocketOptions\n\tcase C.V2RayTransportTypeQUIC:\n\t\tv = &o.QUICOptions\n\tcase C.V2RayTransportTypeGRPC:\n\t\tv = &o.GRPCOptions\n\tcase C.V2RayTransportTypeHTTPUpgrade:\n\t\tv = &o.HTTPUpgradeOptions\n\tdefault:\n\t\treturn E.New(\"unknown transport type: \" + o.Type)\n\t}\n\terr = badjson.UnmarshallExcluded(bytes, (*_V2RayTransportOptions)(o), v)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype V2RayHTTPOptions struct {\n\tHost        badoption.Listable[string] `json:\"host,omitempty\"`\n\tPath        string                     `json:\"path,omitempty\"`\n\tMethod      string                     `json:\"method,omitempty\"`\n\tHeaders     badoption.HTTPHeader       `json:\"headers,omitempty\"`\n\tIdleTimeout badoption.Duration         `json:\"idle_timeout,omitempty\"`\n\tPingTimeout badoption.Duration         `json:\"ping_timeout,omitempty\"`\n}\n\ntype V2RayWebsocketOptions struct {\n\tPath                string               `json:\"path,omitempty\"`\n\tHeaders             badoption.HTTPHeader `json:\"headers,omitempty\"`\n\tMaxEarlyData        uint32               `json:\"max_early_data,omitempty\"`\n\tEarlyDataHeaderName string               `json:\"early_data_header_name,omitempty\"`\n}\n\ntype V2RayQUICOptions struct{}\n\ntype V2RayGRPCOptions struct {\n\tServiceName         string             `json:\"service_name,omitempty\"`\n\tIdleTimeout         badoption.Duration `json:\"idle_timeout,omitempty\"`\n\tPingTimeout         badoption.Duration `json:\"ping_timeout,omitempty\"`\n\tPermitWithoutStream bool               `json:\"permit_without_stream,omitempty\"`\n\tForceLite           bool               `json:\"-\"` // for test\n}\n\ntype V2RayHTTPUpgradeOptions struct {\n\tHost    string               `json:\"host,omitempty\"`\n\tPath    string               `json:\"path,omitempty\"`\n\tHeaders badoption.HTTPHeader `json:\"headers,omitempty\"`\n}\n"
  },
  {
    "path": "option/vless.go",
    "content": "package option\n\ntype VLESSInboundOptions struct {\n\tListenOptions\n\tUsers []VLESSUser `json:\"users,omitempty\"`\n\tInboundTLSOptionsContainer\n\tMultiplex *InboundMultiplexOptions `json:\"multiplex,omitempty\"`\n\tTransport *V2RayTransportOptions   `json:\"transport,omitempty\"`\n}\n\ntype VLESSUser struct {\n\tName string `json:\"name\"`\n\tUUID string `json:\"uuid\"`\n\tFlow string `json:\"flow,omitempty\"`\n}\n\ntype VLESSOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tUUID    string      `json:\"uuid\"`\n\tFlow    string      `json:\"flow,omitempty\"`\n\tNetwork NetworkList `json:\"network,omitempty\"`\n\tOutboundTLSOptionsContainer\n\tMultiplex      *OutboundMultiplexOptions `json:\"multiplex,omitempty\"`\n\tTransport      *V2RayTransportOptions    `json:\"transport,omitempty\"`\n\tPacketEncoding *string                   `json:\"packet_encoding,omitempty\"`\n}\n"
  },
  {
    "path": "option/vmess.go",
    "content": "package option\n\ntype VMessInboundOptions struct {\n\tListenOptions\n\tUsers []VMessUser `json:\"users,omitempty\"`\n\tInboundTLSOptionsContainer\n\tMultiplex *InboundMultiplexOptions `json:\"multiplex,omitempty\"`\n\tTransport *V2RayTransportOptions   `json:\"transport,omitempty\"`\n}\n\ntype VMessUser struct {\n\tName    string `json:\"name\"`\n\tUUID    string `json:\"uuid\"`\n\tAlterId int    `json:\"alterId,omitempty\"`\n}\n\ntype VMessOutboundOptions struct {\n\tDialerOptions\n\tServerOptions\n\tUUID                string      `json:\"uuid\"`\n\tSecurity            string      `json:\"security\"`\n\tAlterId             int         `json:\"alter_id,omitempty\"`\n\tGlobalPadding       bool        `json:\"global_padding,omitempty\"`\n\tAuthenticatedLength bool        `json:\"authenticated_length,omitempty\"`\n\tNetwork             NetworkList `json:\"network,omitempty\"`\n\tOutboundTLSOptionsContainer\n\tPacketEncoding string                    `json:\"packet_encoding,omitempty\"`\n\tMultiplex      *OutboundMultiplexOptions `json:\"multiplex,omitempty\"`\n\tTransport      *V2RayTransportOptions    `json:\"transport,omitempty\"`\n}\n"
  },
  {
    "path": "option/wireguard.go",
    "content": "package option\n\nimport (\n\t\"net/netip\"\n\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\ntype WireGuardEndpointOptions struct {\n\tSystem     bool                             `json:\"system,omitempty\"`\n\tName       string                           `json:\"name,omitempty\"`\n\tMTU        uint32                           `json:\"mtu,omitempty\"`\n\tAddress    badoption.Listable[netip.Prefix] `json:\"address\"`\n\tPrivateKey string                           `json:\"private_key\"`\n\tListenPort uint16                           `json:\"listen_port,omitempty\"`\n\tPeers      []WireGuardPeer                  `json:\"peers,omitempty\"`\n\tUDPTimeout badoption.Duration               `json:\"udp_timeout,omitempty\"`\n\tWorkers    int                              `json:\"workers,omitempty\"`\n\tDialerOptions\n}\n\ntype WireGuardPeer struct {\n\tAddress                     string                           `json:\"address,omitempty\"`\n\tPort                        uint16                           `json:\"port,omitempty\"`\n\tPublicKey                   string                           `json:\"public_key,omitempty\"`\n\tPreSharedKey                string                           `json:\"pre_shared_key,omitempty\"`\n\tAllowedIPs                  badoption.Listable[netip.Prefix] `json:\"allowed_ips,omitempty\"`\n\tPersistentKeepaliveInterval uint16                           `json:\"persistent_keepalive_interval,omitempty\"`\n\tReserved                    []uint8                          `json:\"reserved,omitempty\"`\n}\n"
  },
  {
    "path": "protocol/anytls/inbound.go",
    "content": "package anytls\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/common/uot\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\tanytls \"github.com/anytls/sing-anytls\"\n\t\"github.com/anytls/sing-anytls/padding\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.AnyTLSInboundOptions](registry, C.TypeAnyTLS, NewInbound)\n}\n\ntype Inbound struct {\n\tinbound.Adapter\n\ttlsConfig tls.ServerConfig\n\trouter    adapter.ConnectionRouterEx\n\tlogger    logger.ContextLogger\n\tlistener  *listener.Listener\n\tservice   *anytls.Service\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSInboundOptions) (adapter.Inbound, error) {\n\tinbound := &Inbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeAnyTLS, tag),\n\t\trouter:  uot.NewRouter(router, logger),\n\t\tlogger:  logger,\n\t}\n\n\tif options.TLS != nil && options.TLS.Enabled {\n\t\ttlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tinbound.tlsConfig = tlsConfig\n\t}\n\n\tpaddingScheme := padding.DefaultPaddingScheme\n\tif len(options.PaddingScheme) > 0 {\n\t\tpaddingScheme = []byte(strings.Join(options.PaddingScheme, \"\\n\"))\n\t}\n\n\tservice, err := anytls.NewService(anytls.ServiceConfig{\n\t\tUsers: common.Map(options.Users, func(it option.AnyTLSUser) anytls.User {\n\t\t\treturn (anytls.User)(it)\n\t\t}),\n\t\tPaddingScheme: paddingScheme,\n\t\tHandler:       (*inboundHandler)(inbound),\n\t\tLogger:        logger,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinbound.service = service\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tNetwork:           []string{N.NetworkTCP},\n\t\tListen:            options.ListenOptions,\n\t\tConnectionHandler: inbound,\n\t})\n\treturn inbound, nil\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif h.tlsConfig != nil {\n\t\terr := h.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn h.listener.Start()\n}\n\nfunc (h *Inbound) Close() error {\n\treturn common.Close(h.listener, h.tlsConfig)\n}\n\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tif h.tlsConfig != nil {\n\t\ttlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)\n\t\tif err != nil {\n\t\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source, \": TLS handshake\"))\n\t\t\treturn\n\t\t}\n\t\tconn = tlsConn\n\t}\n\terr := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)\n\tif err != nil {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source))\n\t}\n}\n\ntype inboundHandler Inbound\n\nfunc (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tmetadata.Source = source\n\tmetadata.Destination = destination.Unwrap()\n\tif userName, _ := auth.UserFromContext[string](ctx); userName != \"\" {\n\t\tmetadata.User = userName\n\t\th.logger.InfoContext(ctx, \"[\", userName, \"] inbound connection to \", metadata.Destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\t}\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "protocol/anytls/outbound.go",
    "content": "package anytls\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/uot\"\n\n\tanytls \"github.com/anytls/sing-anytls\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.AnyTLSOutboundOptions](registry, C.TypeAnyTLS, NewOutbound)\n}\n\ntype Outbound struct {\n\toutbound.Adapter\n\tdialer    tls.Dialer\n\tserver    M.Socksaddr\n\ttlsConfig tls.Config\n\tclient    *anytls.Client\n\tuotClient *uot.Client\n\tlogger    log.ContextLogger\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSOutboundOptions) (adapter.Outbound, error) {\n\toutbound := &Outbound{\n\t\tAdapter: outbound.NewAdapterWithDialerOptions(C.TypeAnyTLS, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),\n\t\tserver:  options.ServerOptions.Build(),\n\t\tlogger:  logger,\n\t}\n\tif options.TLS == nil || !options.TLS.Enabled {\n\t\treturn nil, C.ErrTLSRequired\n\t}\n\t// TCP Fast Open is incompatible with anytls because TFO creates a lazy connection\n\t// that only establishes on first write. The lazy connection returns an empty address\n\t// before establishment, but anytls SOCKS wrapper tries to access the remote address\n\t// during handshake, causing a null pointer dereference crash.\n\tif options.DialerOptions.TCPFastOpen {\n\t\treturn nil, E.New(\"tcp_fast_open is not supported with anytls outbound\")\n\t}\n\n\ttlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound.tlsConfig = tlsConfig\n\n\toutboundDialer, err := dialer.NewWithOptions(dialer.Options{\n\t\tContext:        ctx,\n\t\tOptions:        options.DialerOptions,\n\t\tRemoteIsDomain: options.ServerIsDomain(),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\toutbound.dialer = tls.NewDialer(outboundDialer, tlsConfig)\n\n\tclient, err := anytls.NewClient(ctx, anytls.ClientConfig{\n\t\tPassword:                 options.Password,\n\t\tIdleSessionCheckInterval: options.IdleSessionCheckInterval.Build(),\n\t\tIdleSessionTimeout:       options.IdleSessionTimeout.Build(),\n\t\tMinIdleSession:           options.MinIdleSession,\n\t\tDialOut:                  outbound.dialOut,\n\t\tLogger:                   logger,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound.client = client\n\n\toutbound.uotClient = &uot.Client{\n\t\tDialer:  (anytlsDialer)(client.CreateProxy),\n\t\tVersion: uot.Version,\n\t}\n\treturn outbound, nil\n}\n\ntype anytlsDialer func(ctx context.Context, destination M.Socksaddr) (net.Conn, error)\n\nfunc (d anytlsDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\treturn d(ctx, destination)\n}\n\nfunc (d anytlsDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn nil, os.ErrInvalid\n}\n\nfunc (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {\n\treturn h.dialer.DialTLSContext(ctx, h.server)\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\t\treturn h.client.CreateProxy(ctx, destination)\n\tcase N.NetworkUDP:\n\t\th.logger.InfoContext(ctx, \"outbound UoT packet connection to \", destination)\n\t\treturn h.uotClient.DialContext(ctx, network, destination)\n\t}\n\treturn nil, os.ErrInvalid\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\th.logger.InfoContext(ctx, \"outbound UoT packet connection to \", destination)\n\treturn h.uotClient.ListenPacket(ctx, destination)\n}\n\nfunc (h *Outbound) Close() error {\n\treturn common.Close(h.client)\n}\n"
  },
  {
    "path": "protocol/block/outbound.go",
    "content": "package block\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.StubOptions](registry, C.TypeBlock, New)\n}\n\ntype Outbound struct {\n\toutbound.Adapter\n\tlogger logger.ContextLogger\n}\n\nfunc New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, _ option.StubOptions) (adapter.Outbound, error) {\n\treturn &Outbound{\n\t\tAdapter: outbound.NewAdapter(C.TypeBlock, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil),\n\t\tlogger:  logger,\n\t}, nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\th.logger.InfoContext(ctx, \"blocked connection to \", destination)\n\treturn nil, syscall.EPERM\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\th.logger.InfoContext(ctx, \"blocked packet connection to \", destination)\n\treturn nil, syscall.EPERM\n}\n"
  },
  {
    "path": "protocol/direct/inbound.go",
    "content": "package direct\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/udpnat2\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.DirectInboundOptions](registry, C.TypeDirect, NewInbound)\n}\n\ntype Inbound struct {\n\tinbound.Adapter\n\tctx                 context.Context\n\trouter              adapter.ConnectionRouterEx\n\tlogger              log.ContextLogger\n\tlistener            *listener.Listener\n\tudpNat              *udpnat.Service\n\toverrideOption      int\n\toverrideDestination M.Socksaddr\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectInboundOptions) (adapter.Inbound, error) {\n\toptions.UDPFragmentDefault = true\n\tinbound := &Inbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeDirect, tag),\n\t\tctx:     ctx,\n\t\trouter:  router,\n\t\tlogger:  logger,\n\t}\n\tif options.OverrideAddress != \"\" && options.OverridePort != 0 {\n\t\tinbound.overrideOption = 1\n\t\tinbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort)\n\t} else if options.OverrideAddress != \"\" {\n\t\tinbound.overrideOption = 2\n\t\tinbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort)\n\t} else if options.OverridePort != 0 {\n\t\tinbound.overrideOption = 3\n\t\tinbound.overrideDestination = M.Socksaddr{Port: options.OverridePort}\n\t}\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\tinbound.udpNat = udpnat.New(inbound, inbound.preparePacketConnection, udpTimeout, false)\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tNetwork:           options.Network.Build(),\n\t\tListen:            options.ListenOptions,\n\t\tConnectionHandler: inbound,\n\t\tPacketHandler:     inbound,\n\t})\n\treturn inbound, nil\n}\n\nfunc (i *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn i.listener.Start()\n}\n\nfunc (i *Inbound) Close() error {\n\treturn i.listener.Close()\n}\n\nfunc (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {\n\ti.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, i.listener.UDPAddr(), nil)\n}\n\nfunc (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = i.Tag()\n\tmetadata.InboundType = i.Type()\n\tdestination := metadata.OriginDestination\n\tswitch i.overrideOption {\n\tcase 1:\n\t\tdestination = i.overrideDestination\n\tcase 2:\n\t\tdestination.Addr = i.overrideDestination.Addr\n\tcase 3:\n\t\tdestination.Port = i.overrideDestination.Port\n\t}\n\tmetadata.Destination = destination\n\tif i.overrideOption != 0 {\n\t\ti.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\t}\n\ti.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\ti.logger.InfoContext(ctx, \"inbound packet connection from \", source)\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = i.Tag()\n\tmetadata.InboundType = i.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = i.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tmetadata.Source = source\n\tdestination = i.listener.UDPAddr()\n\tswitch i.overrideOption {\n\tcase 1:\n\t\tdestination = i.overrideDestination\n\tcase 2:\n\t\tdestination.Addr = i.overrideDestination.Addr\n\tcase 3:\n\t\tdestination.Port = i.overrideDestination.Port\n\tdefault:\n\t}\n\ti.logger.InfoContext(ctx, \"inbound packet connection to \", destination)\n\tmetadata.Destination = destination\n\tif i.overrideOption != 0 {\n\t\tconn = bufio.NewDestinationNATPacketConn(bufio.NewNetPacketConn(conn), i.listener.UDPAddr(), destination)\n\t}\n\ti.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (i *Inbound) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) {\n\treturn true, log.ContextWithNewID(i.ctx), &directPacketWriter{i.listener.PacketWriter(), source}, nil\n}\n\ntype directPacketWriter struct {\n\twriter N.PacketWriter\n\tsource M.Socksaddr\n}\n\nfunc (w *directPacketWriter) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error {\n\treturn w.writer.WritePacket(buffer, w.source)\n}\n"
  },
  {
    "path": "protocol/direct/loopback_detect.go",
    "content": "package direct\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype loopBackDetector struct {\n\tnetworkManager   adapter.NetworkManager\n\tconnAccess       sync.RWMutex\n\tpacketConnAccess sync.RWMutex\n\tconnMap          map[netip.AddrPort]netip.AddrPort\n\tpacketConnMap    map[uint16]uint16\n}\n\nfunc newLoopBackDetector(networkManager adapter.NetworkManager) *loopBackDetector {\n\treturn &loopBackDetector{\n\t\tnetworkManager: networkManager,\n\t\tconnMap:        make(map[netip.AddrPort]netip.AddrPort),\n\t\tpacketConnMap:  make(map[uint16]uint16),\n\t}\n}\n\nfunc (l *loopBackDetector) NewConn(conn net.Conn) net.Conn {\n\tsource := M.AddrPortFromNet(conn.LocalAddr())\n\tif !source.IsValid() {\n\t\treturn conn\n\t}\n\tif udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn {\n\t\tif !source.Addr().IsLoopback() {\n\t\t\t_, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr())\n\t\t\tif err != nil {\n\t\t\t\treturn conn\n\t\t\t}\n\t\t}\n\t\tif !N.IsPublicAddr(source.Addr()) {\n\t\t\treturn conn\n\t\t}\n\t\tl.packetConnAccess.Lock()\n\t\tl.packetConnMap[source.Port()] = M.AddrPortFromNet(conn.RemoteAddr()).Port()\n\t\tl.packetConnAccess.Unlock()\n\t\treturn &loopBackDetectUDPWrapper{abstractUDPConn: udpConn, detector: l, connPort: source.Port()}\n\t} else {\n\t\tl.connAccess.Lock()\n\t\tl.connMap[source] = M.AddrPortFromNet(conn.RemoteAddr())\n\t\tl.connAccess.Unlock()\n\t\treturn &loopBackDetectWrapper{Conn: conn, detector: l, connAddr: source}\n\t}\n}\n\nfunc (l *loopBackDetector) NewPacketConn(conn N.NetPacketConn, destination M.Socksaddr) N.NetPacketConn {\n\tsource := M.AddrPortFromNet(conn.LocalAddr())\n\tif !source.IsValid() {\n\t\treturn conn\n\t}\n\tif !source.Addr().IsLoopback() {\n\t\t_, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr())\n\t\tif err != nil {\n\t\t\treturn conn\n\t\t}\n\t}\n\tl.packetConnAccess.Lock()\n\tl.packetConnMap[source.Port()] = destination.AddrPort().Port()\n\tl.packetConnAccess.Unlock()\n\treturn &loopBackDetectPacketWrapper{NetPacketConn: conn, detector: l, connPort: source.Port()}\n}\n\nfunc (l *loopBackDetector) CheckConn(source netip.AddrPort, local netip.AddrPort) bool {\n\tl.connAccess.RLock()\n\tdefer l.connAccess.RUnlock()\n\tdestination, loaded := l.connMap[source]\n\treturn loaded && destination != local\n}\n\nfunc (l *loopBackDetector) CheckPacketConn(source netip.AddrPort, local netip.AddrPort) bool {\n\tif !source.IsValid() {\n\t\treturn false\n\t}\n\tif !source.Addr().IsLoopback() {\n\t\t_, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr())\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t}\n\tif N.IsPublicAddr(source.Addr()) {\n\t\treturn false\n\t}\n\tl.packetConnAccess.RLock()\n\tdefer l.packetConnAccess.RUnlock()\n\tdestinationPort, loaded := l.packetConnMap[source.Port()]\n\treturn loaded && destinationPort != local.Port()\n}\n\ntype loopBackDetectWrapper struct {\n\tnet.Conn\n\tdetector  *loopBackDetector\n\tconnAddr  netip.AddrPort\n\tcloseOnce sync.Once\n}\n\nfunc (w *loopBackDetectWrapper) Close() error {\n\tw.closeOnce.Do(func() {\n\t\tw.detector.connAccess.Lock()\n\t\tdelete(w.detector.connMap, w.connAddr)\n\t\tw.detector.connAccess.Unlock()\n\t})\n\treturn w.Conn.Close()\n}\n\nfunc (w *loopBackDetectWrapper) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (w *loopBackDetectWrapper) WriterReplaceable() bool {\n\treturn true\n}\n\nfunc (w *loopBackDetectWrapper) Upstream() any {\n\treturn w.Conn\n}\n\ntype loopBackDetectPacketWrapper struct {\n\tN.NetPacketConn\n\tdetector  *loopBackDetector\n\tconnPort  uint16\n\tcloseOnce sync.Once\n}\n\nfunc (w *loopBackDetectPacketWrapper) Close() error {\n\tw.closeOnce.Do(func() {\n\t\tw.detector.packetConnAccess.Lock()\n\t\tdelete(w.detector.packetConnMap, w.connPort)\n\t\tw.detector.packetConnAccess.Unlock()\n\t})\n\treturn w.NetPacketConn.Close()\n}\n\nfunc (w *loopBackDetectPacketWrapper) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (w *loopBackDetectPacketWrapper) WriterReplaceable() bool {\n\treturn true\n}\n\nfunc (w *loopBackDetectPacketWrapper) Upstream() any {\n\treturn w.NetPacketConn\n}\n\ntype abstractUDPConn interface {\n\tnet.Conn\n\tnet.PacketConn\n}\n\ntype loopBackDetectUDPWrapper struct {\n\tabstractUDPConn\n\tdetector  *loopBackDetector\n\tconnPort  uint16\n\tcloseOnce sync.Once\n}\n\nfunc (w *loopBackDetectUDPWrapper) Close() error {\n\tw.closeOnce.Do(func() {\n\t\tw.detector.packetConnAccess.Lock()\n\t\tdelete(w.detector.packetConnMap, w.connPort)\n\t\tw.detector.packetConnAccess.Unlock()\n\t})\n\treturn w.abstractUDPConn.Close()\n}\n\nfunc (w *loopBackDetectUDPWrapper) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (w *loopBackDetectUDPWrapper) WriterReplaceable() bool {\n\treturn true\n}\n\nfunc (w *loopBackDetectUDPWrapper) Upstream() any {\n\treturn w.abstractUDPConn\n}\n"
  },
  {
    "path": "protocol/direct/outbound.go",
    "content": "package direct\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"reflect\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing-tun/ping\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.DirectOutboundOptions](registry, C.TypeDirect, NewOutbound)\n}\n\nvar (\n\t_ N.ParallelDialer             = (*Outbound)(nil)\n\t_ dialer.ParallelNetworkDialer = (*Outbound)(nil)\n\t_ dialer.DirectDialer          = (*Outbound)(nil)\n\t_ adapter.DirectRouteOutbound  = (*Outbound)(nil)\n)\n\ntype Outbound struct {\n\toutbound.Adapter\n\tctx            context.Context\n\tlogger         logger.ContextLogger\n\tdialer         dialer.ParallelInterfaceDialer\n\tdomainStrategy C.DomainStrategy\n\tfallbackDelay  time.Duration\n\tisEmpty        bool\n\t// loopBack *loopBackDetector\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) {\n\toptions.UDPFragmentDefault = true\n\tif options.Detour != \"\" {\n\t\treturn nil, E.New(\"`detour` is not supported in direct context\")\n\t}\n\toutboundDialer, err := dialer.NewWithOptions(dialer.Options{\n\t\tContext:        ctx,\n\t\tOptions:        options.DialerOptions,\n\t\tRemoteIsDomain: true,\n\t\tDirectOutbound: true,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound := &Outbound{\n\t\tAdapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),\n\t\tctx:     ctx,\n\t\tlogger:  logger,\n\t\t//nolint:staticcheck\n\t\tdomainStrategy: C.DomainStrategy(options.DomainStrategy),\n\t\tfallbackDelay:  time.Duration(options.FallbackDelay),\n\t\tdialer:         outboundDialer.(dialer.ParallelInterfaceDialer),\n\t\tisEmpty:        reflect.DeepEqual(options.DialerOptions, option.DialerOptions{UDPFragmentDefault: true}),\n\t\t// loopBack:       newLoopBackDetector(router),\n\t}\n\t//nolint:staticcheck\n\tif options.ProxyProtocol != 0 {\n\t\treturn nil, E.New(\"Proxy Protocol is deprecated and removed in sing-box 1.6.0\")\n\t}\n\treturn outbound, nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tnetwork = N.NetworkName(network)\n\tswitch network {\n\tcase N.NetworkTCP:\n\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\tcase N.NetworkUDP:\n\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t}\n\t/*conn, err := h.dialer.DialContext(ctx, network, destination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn h.loopBack.NewConn(conn), nil*/\n\treturn h.dialer.DialContext(ctx, network, destination)\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\th.logger.InfoContext(ctx, \"outbound packet connection\")\n\tconn, err := h.dialer.ListenPacket(ctx, destination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// conn = h.loopBack.NewPacketConn(bufio.NewPacketConn(conn), destination)\n\treturn conn, nil\n}\n\nfunc (h *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\tctx := log.ContextWithNewID(h.ctx)\n\tdestination, err := ping.ConnectDestination(ctx, h.logger, common.MustCast[*dialer.DefaultDialer](h.dialer).DialerForICMPDestination(metadata.Destination.Addr).Control, metadata.Destination.Addr, routeContext, timeout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\th.logger.InfoContext(ctx, \"linked \", metadata.Network, \" connection from \", metadata.Source.AddrString(), \" to \", metadata.Destination.AddrString())\n\treturn destination, nil\n}\n\nfunc (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tnetwork = N.NetworkName(network)\n\tswitch network {\n\tcase N.NetworkTCP:\n\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\tcase N.NetworkUDP:\n\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t}\n\treturn dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), nil, nil, nil, h.fallbackDelay)\n}\n\nfunc (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tnetwork = N.NetworkName(network)\n\tswitch network {\n\tcase N.NetworkTCP:\n\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\tcase N.NetworkUDP:\n\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t}\n\treturn dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), networkStrategy, networkType, fallbackNetworkType, fallbackDelay)\n}\n\nfunc (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\th.logger.InfoContext(ctx, \"outbound packet connection\")\n\tconn, newDestination, err := dialer.ListenSerialNetworkPacket(ctx, h.dialer, destination, destinationAddresses, networkStrategy, networkType, fallbackNetworkType, fallbackDelay)\n\tif err != nil {\n\t\treturn nil, netip.Addr{}, err\n\t}\n\treturn conn, newDestination, nil\n}\n\nfunc (h *Outbound) IsEmpty() bool {\n\treturn h.isEmpty\n}\n\n/*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {\n\tif h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) {\n\t\treturn E.New(\"reject loopback connection to \", metadata.Destination)\n\t}\n\treturn NewConnection(ctx, h, conn, metadata)\n}\n\nfunc (h *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {\n\tif h.loopBack.CheckPacketConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) {\n\t\treturn E.New(\"reject loopback packet connection to \", metadata.Destination)\n\t}\n\treturn NewPacketConnection(ctx, h, conn, metadata)\n}\n*/\n"
  },
  {
    "path": "protocol/dns/handle.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\t\"github.com/sagernet/sing/common/canceler\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/task\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc HandleStreamDNSRequest(ctx context.Context, router adapter.DNSRouter, conn net.Conn, metadata adapter.InboundContext) error {\n\tvar queryLength uint16\n\terr := binary.Read(conn, binary.BigEndian, &queryLength)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif queryLength == 0 {\n\t\treturn dns.RcodeFormatError\n\t}\n\tbuffer := buf.NewSize(int(queryLength))\n\tdefer buffer.Release()\n\t_, err = buffer.ReadFullFrom(conn, int(queryLength))\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar message mDNS.Msg\n\terr = message.Unpack(buffer.Bytes())\n\tif err != nil {\n\t\treturn err\n\t}\n\tmetadataInQuery := metadata\n\tgo func() error {\n\t\tresponse, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message, adapter.DNSQueryOptions{})\n\t\tif err != nil {\n\t\t\tconn.Close()\n\t\t\treturn err\n\t\t}\n\t\tresponseLength := response.Len()\n\t\tresponseBuffer := buf.NewSize(3 + responseLength)\n\t\tdefer responseBuffer.Release()\n\t\tresponseBuffer.Resize(2, 0)\n\t\tn, err := response.PackBuffer(responseBuffer.FreeBytes())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tresponseBuffer.Truncate(len(n))\n\t\tbinary.BigEndian.PutUint16(responseBuffer.ExtendHeader(2), uint16(len(n)))\n\t\t_, err = conn.Write(responseBuffer.Bytes())\n\t\treturn err\n\t}()\n\treturn nil\n}\n\nfunc NewDNSPacketConnection(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, cachedPackets []*N.PacketBuffer, metadata adapter.InboundContext) error {\n\tmetadata.Destination = M.Socksaddr{}\n\tvar reader N.PacketReader = conn\n\tvar counters []N.CountFunc\n\tcachedPackets = common.Reverse(cachedPackets)\n\tfor {\n\t\treader, counters = N.UnwrapCountPacketReader(reader, counters)\n\t\tif cachedReader, isCached := reader.(N.CachedPacketReader); isCached {\n\t\t\tpacket := cachedReader.ReadCachedPacket()\n\t\t\tif packet != nil {\n\t\t\t\tcachedPackets = append(cachedPackets, packet)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif readWaiter, created := bufio.CreatePacketReadWaiter(reader); created {\n\t\t\treadWaiter.InitializeReadWaiter(N.ReadWaitOptions{})\n\t\t\treturn newDNSPacketConnection(ctx, router, conn, readWaiter, counters, cachedPackets, metadata)\n\t\t}\n\t\tbreak\n\t}\n\tfastClose, cancel := common.ContextWithCancelCause(ctx)\n\ttimeout := canceler.New(fastClose, cancel, C.DNSTimeout)\n\tvar group task.Group\n\tgroup.Append0(func(_ context.Context) error {\n\t\tfor {\n\t\t\tvar message mDNS.Msg\n\t\t\tvar destination M.Socksaddr\n\t\t\tvar err error\n\t\t\tif len(cachedPackets) > 0 {\n\t\t\t\tpacket := cachedPackets[0]\n\t\t\t\tcachedPackets = cachedPackets[1:]\n\t\t\t\tfor _, counter := range counters {\n\t\t\t\t\tcounter(int64(packet.Buffer.Len()))\n\t\t\t\t}\n\t\t\t\terr = message.Unpack(packet.Buffer.Bytes())\n\t\t\t\tpacket.Buffer.Release()\n\t\t\t\tif err != nil {\n\t\t\t\t\tcancel(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tdestination = packet.Destination\n\t\t\t} else {\n\t\t\t\tbuffer := buf.NewPacket()\n\t\t\t\tdestination, err = conn.ReadPacket(buffer)\n\t\t\t\tif err != nil {\n\t\t\t\t\tbuffer.Release()\n\t\t\t\t\tcancel(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfor _, counter := range counters {\n\t\t\t\t\tcounter(int64(buffer.Len()))\n\t\t\t\t}\n\t\t\t\terr = message.Unpack(buffer.Bytes())\n\t\t\t\tbuffer.Release()\n\t\t\t\tif err != nil {\n\t\t\t\t\tcancel(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\ttimeout.Update()\n\t\t\t}\n\t\t\tmetadataInQuery := metadata\n\t\t\tgo func() error {\n\t\t\t\tresponse, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message, adapter.DNSQueryOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tcancel(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\ttimeout.Update()\n\t\t\t\tresponseBuffer, err := dns.TruncateDNSMessage(&message, response, 1024)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcancel(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = conn.WritePacket(responseBuffer, destination)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcancel(err)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}()\n\t\t}\n\t})\n\tgroup.Cleanup(func() {\n\t\tconn.Close()\n\t})\n\treturn group.Run(fastClose)\n}\n\nfunc newDNSPacketConnection(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error {\n\tfastClose, cancel := common.ContextWithCancelCause(ctx)\n\ttimeout := canceler.New(fastClose, cancel, C.DNSTimeout)\n\tvar group task.Group\n\tgroup.Append0(func(_ context.Context) error {\n\t\tfor {\n\t\t\tvar (\n\t\t\t\tmessage     mDNS.Msg\n\t\t\t\tdestination M.Socksaddr\n\t\t\t\terr         error\n\t\t\t\tbuffer      *buf.Buffer\n\t\t\t)\n\t\t\tif len(cached) > 0 {\n\t\t\t\tpacket := cached[0]\n\t\t\t\tcached = cached[1:]\n\t\t\t\tfor _, counter := range readCounters {\n\t\t\t\t\tcounter(int64(packet.Buffer.Len()))\n\t\t\t\t}\n\t\t\t\terr = message.Unpack(packet.Buffer.Bytes())\n\t\t\t\tpacket.Buffer.Release()\n\t\t\t\tdestination = packet.Destination\n\t\t\t\tN.PutPacketBuffer(packet)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcancel(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tbuffer, destination, err = readWaiter.WaitReadPacket()\n\t\t\t\tif err != nil {\n\t\t\t\t\tcancel(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tfor _, counter := range readCounters {\n\t\t\t\t\tcounter(int64(buffer.Len()))\n\t\t\t\t}\n\t\t\t\terr = message.Unpack(buffer.Bytes())\n\t\t\t\tbuffer.Release()\n\t\t\t\tif err != nil {\n\t\t\t\t\tcancel(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\ttimeout.Update()\n\t\t\t}\n\t\t\tmetadataInQuery := metadata\n\t\t\tgo func() error {\n\t\t\t\tresponse, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message, adapter.DNSQueryOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tcancel(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\ttimeout.Update()\n\t\t\t\tresponseBuffer, err := dns.TruncateDNSMessage(&message, response, 1024)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcancel(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = conn.WritePacket(responseBuffer, destination)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcancel(err)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}()\n\t\t}\n\t})\n\tgroup.Cleanup(func() {\n\t\tconn.Close()\n\t})\n\treturn group.Run(fastClose)\n}\n"
  },
  {
    "path": "protocol/dns/outbound.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.StubOptions](registry, C.TypeDNS, NewOutbound)\n}\n\ntype Outbound struct {\n\toutbound.Adapter\n\trouter adapter.DNSRouter\n\tlogger logger.ContextLogger\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.StubOptions) (adapter.Outbound, error) {\n\treturn &Outbound{\n\t\tAdapter: outbound.NewAdapter(C.TypeDNS, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil),\n\t\trouter:  service.FromContext[adapter.DNSRouter](ctx),\n\t\tlogger:  logger,\n\t}, nil\n}\n\nfunc (d *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\treturn nil, os.ErrInvalid\n}\n\nfunc (d *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn nil, os.ErrInvalid\n}\n\nfunc (d *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Destination = M.Socksaddr{}\n\tfor {\n\t\tconn.SetReadDeadline(time.Now().Add(C.DNSTimeout))\n\t\terr := HandleStreamDNSRequest(ctx, d.router, conn, metadata)\n\t\tif err != nil {\n\t\t\tconn.Close()\n\t\t\tif onClose != nil {\n\t\t\t\tonClose(err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (d *Outbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tNewDNSPacketConnection(ctx, d.router, conn, nil, metadata)\n}\n"
  },
  {
    "path": "protocol/group/selector.go",
    "content": "package group\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/interrupt\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\ttun \"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nfunc RegisterSelector(registry *outbound.Registry) {\n\toutbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector)\n}\n\nvar (\n\t_ adapter.OutboundGroup             = (*Selector)(nil)\n\t_ adapter.ConnectionHandlerEx       = (*Selector)(nil)\n\t_ adapter.PacketConnectionHandlerEx = (*Selector)(nil)\n)\n\ntype Selector struct {\n\toutbound.Adapter\n\tctx                          context.Context\n\toutbound                     adapter.OutboundManager\n\tconnection                   adapter.ConnectionManager\n\tlogger                       logger.ContextLogger\n\ttags                         []string\n\tdefaultTag                   string\n\toutbounds                    map[string]adapter.Outbound\n\tselected                     common.TypedValue[adapter.Outbound]\n\tinterruptGroup               *interrupt.Group\n\tinterruptExternalConnections bool\n}\n\nfunc NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (adapter.Outbound, error) {\n\toutbound := &Selector{\n\t\tAdapter:                      outbound.NewAdapter(C.TypeSelector, tag, nil, options.Outbounds),\n\t\tctx:                          ctx,\n\t\toutbound:                     service.FromContext[adapter.OutboundManager](ctx),\n\t\tconnection:                   service.FromContext[adapter.ConnectionManager](ctx),\n\t\tlogger:                       logger,\n\t\ttags:                         options.Outbounds,\n\t\tdefaultTag:                   options.Default,\n\t\toutbounds:                    make(map[string]adapter.Outbound),\n\t\tinterruptGroup:               interrupt.NewGroup(),\n\t\tinterruptExternalConnections: options.InterruptExistConnections,\n\t}\n\tif len(outbound.tags) == 0 {\n\t\treturn nil, E.New(\"missing tags\")\n\t}\n\treturn outbound, nil\n}\n\nfunc (s *Selector) Network() []string {\n\tselected := s.selected.Load()\n\tif selected == nil {\n\t\treturn []string{N.NetworkTCP, N.NetworkUDP}\n\t}\n\treturn selected.Network()\n}\n\nfunc (s *Selector) Start() error {\n\tfor i, tag := range s.tags {\n\t\tdetour, loaded := s.outbound.Outbound(tag)\n\t\tif !loaded {\n\t\t\treturn E.New(\"outbound \", i, \" not found: \", tag)\n\t\t}\n\t\ts.outbounds[tag] = detour\n\t}\n\n\tif s.Tag() != \"\" {\n\t\tcacheFile := service.FromContext[adapter.CacheFile](s.ctx)\n\t\tif cacheFile != nil {\n\t\t\tselected := cacheFile.LoadSelected(s.Tag())\n\t\t\tif selected != \"\" {\n\t\t\t\tdetour, loaded := s.outbounds[selected]\n\t\t\t\tif loaded {\n\t\t\t\t\ts.selected.Store(detour)\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif s.defaultTag != \"\" {\n\t\tdetour, loaded := s.outbounds[s.defaultTag]\n\t\tif !loaded {\n\t\t\treturn E.New(\"default outbound not found: \", s.defaultTag)\n\t\t}\n\t\ts.selected.Store(detour)\n\t\treturn nil\n\t}\n\n\ts.selected.Store(s.outbounds[s.tags[0]])\n\treturn nil\n}\n\nfunc (s *Selector) Now() string {\n\tselected := s.selected.Load()\n\tif selected == nil {\n\t\treturn s.tags[0]\n\t}\n\treturn selected.Tag()\n}\n\nfunc (s *Selector) All() []string {\n\treturn s.tags\n}\n\nfunc (s *Selector) SelectOutbound(tag string) bool {\n\tdetour, loaded := s.outbounds[tag]\n\tif !loaded {\n\t\treturn false\n\t}\n\tif s.selected.Swap(detour) == detour {\n\t\treturn true\n\t}\n\tif s.Tag() != \"\" {\n\t\tcacheFile := service.FromContext[adapter.CacheFile](s.ctx)\n\t\tif cacheFile != nil {\n\t\t\terr := cacheFile.StoreSelected(s.Tag(), tag)\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Error(\"store selected: \", err)\n\t\t\t}\n\t\t}\n\t}\n\ts.interruptGroup.Interrupt(s.interruptExternalConnections)\n\treturn true\n}\n\nfunc (s *Selector) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tconn, err := s.selected.Load().DialContext(ctx, network, destination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn s.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil\n}\n\nfunc (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tconn, err := s.selected.Load().ListenPacket(ctx, destination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil\n}\n\nfunc (s *Selector) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tctx = interrupt.ContextWithIsExternalConnection(ctx)\n\tselected := s.selected.Load()\n\tif outboundHandler, isHandler := selected.(adapter.ConnectionHandlerEx); isHandler {\n\t\toutboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)\n\t} else {\n\t\ts.connection.NewConnection(ctx, selected, conn, metadata, onClose)\n\t}\n}\n\nfunc (s *Selector) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tctx = interrupt.ContextWithIsExternalConnection(ctx)\n\tselected := s.selected.Load()\n\tif outboundHandler, isHandler := selected.(adapter.PacketConnectionHandlerEx); isHandler {\n\t\toutboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose)\n\t} else {\n\t\ts.connection.NewPacketConnection(ctx, selected, conn, metadata, onClose)\n\t}\n}\n\nfunc (s *Selector) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\tselected := s.selected.Load()\n\tif !common.Contains(selected.Network(), metadata.Network) {\n\t\treturn nil, E.New(metadata.Network, \" is not supported by outbound: \", selected.Tag())\n\t}\n\treturn selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)\n}\n\nfunc RealTag(detour adapter.Outbound) string {\n\tif group, isGroup := detour.(adapter.OutboundGroup); isGroup {\n\t\treturn group.Now()\n\t}\n\treturn detour.Tag()\n}\n"
  },
  {
    "path": "protocol/group/urltest.go",
    "content": "package group\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/interrupt\"\n\t\"github.com/sagernet/sing-box/common/urltest\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/batch\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/pause\"\n)\n\nfunc RegisterURLTest(registry *outbound.Registry) {\n\toutbound.Register[option.URLTestOutboundOptions](registry, C.TypeURLTest, NewURLTest)\n}\n\nvar _ adapter.OutboundGroup = (*URLTest)(nil)\n\ntype URLTest struct {\n\toutbound.Adapter\n\tctx                          context.Context\n\trouter                       adapter.Router\n\toutbound                     adapter.OutboundManager\n\tconnection                   adapter.ConnectionManager\n\tlogger                       log.ContextLogger\n\ttags                         []string\n\tlink                         string\n\tinterval                     time.Duration\n\ttolerance                    uint16\n\tidleTimeout                  time.Duration\n\tgroup                        *URLTestGroup\n\tinterruptExternalConnections bool\n}\n\nfunc NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (adapter.Outbound, error) {\n\toutbound := &URLTest{\n\t\tAdapter:                      outbound.NewAdapter(C.TypeURLTest, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds),\n\t\tctx:                          ctx,\n\t\trouter:                       router,\n\t\toutbound:                     service.FromContext[adapter.OutboundManager](ctx),\n\t\tconnection:                   service.FromContext[adapter.ConnectionManager](ctx),\n\t\tlogger:                       logger,\n\t\ttags:                         options.Outbounds,\n\t\tlink:                         options.URL,\n\t\tinterval:                     time.Duration(options.Interval),\n\t\ttolerance:                    options.Tolerance,\n\t\tidleTimeout:                  time.Duration(options.IdleTimeout),\n\t\tinterruptExternalConnections: options.InterruptExistConnections,\n\t}\n\tif len(outbound.tags) == 0 {\n\t\treturn nil, E.New(\"missing tags\")\n\t}\n\treturn outbound, nil\n}\n\nfunc (s *URLTest) Start() error {\n\toutbounds := make([]adapter.Outbound, 0, len(s.tags))\n\tfor i, tag := range s.tags {\n\t\tdetour, loaded := s.outbound.Outbound(tag)\n\t\tif !loaded {\n\t\t\treturn E.New(\"outbound \", i, \" not found: \", tag)\n\t\t}\n\t\toutbounds = append(outbounds, detour)\n\t}\n\tgroup, err := NewURLTestGroup(s.ctx, s.outbound, s.logger, outbounds, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.group = group\n\treturn nil\n}\n\nfunc (s *URLTest) PostStart() error {\n\ts.group.PostStart()\n\treturn nil\n}\n\nfunc (s *URLTest) Close() error {\n\treturn common.Close(\n\t\tcommon.PtrOrNil(s.group),\n\t)\n}\n\nfunc (s *URLTest) Now() string {\n\tif s.group.selectedOutboundTCP != nil {\n\t\treturn s.group.selectedOutboundTCP.Tag()\n\t} else if s.group.selectedOutboundUDP != nil {\n\t\treturn s.group.selectedOutboundUDP.Tag()\n\t}\n\treturn \"\"\n}\n\nfunc (s *URLTest) All() []string {\n\treturn s.tags\n}\n\nfunc (s *URLTest) URLTest(ctx context.Context) (map[string]uint16, error) {\n\treturn s.group.URLTest(ctx)\n}\n\nfunc (s *URLTest) CheckOutbounds() {\n\ts.group.CheckOutbounds(true)\n}\n\nfunc (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\ts.group.Touch()\n\tvar outbound adapter.Outbound\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\toutbound = s.group.selectedOutboundTCP\n\tcase N.NetworkUDP:\n\t\toutbound = s.group.selectedOutboundUDP\n\tdefault:\n\t\treturn nil, E.Extend(N.ErrUnknownNetwork, network)\n\t}\n\tif outbound == nil {\n\t\toutbound, _ = s.group.Select(network)\n\t}\n\tif outbound == nil {\n\t\treturn nil, E.New(\"missing supported outbound\")\n\t}\n\tconn, err := outbound.DialContext(ctx, network, destination)\n\tif err == nil {\n\t\treturn s.group.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil\n\t}\n\ts.logger.ErrorContext(ctx, err)\n\ts.group.history.DeleteURLTestHistory(outbound.Tag())\n\treturn nil, err\n}\n\nfunc (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\ts.group.Touch()\n\toutbound := s.group.selectedOutboundUDP\n\tif outbound == nil {\n\t\toutbound, _ = s.group.Select(N.NetworkUDP)\n\t}\n\tif outbound == nil {\n\t\treturn nil, E.New(\"missing supported outbound\")\n\t}\n\tconn, err := outbound.ListenPacket(ctx, destination)\n\tif err == nil {\n\t\treturn s.group.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil\n\t}\n\ts.logger.ErrorContext(ctx, err)\n\ts.group.history.DeleteURLTestHistory(outbound.Tag())\n\treturn nil, err\n}\n\nfunc (s *URLTest) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tctx = interrupt.ContextWithIsExternalConnection(ctx)\n\ts.connection.NewConnection(ctx, s, conn, metadata, onClose)\n}\n\nfunc (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tctx = interrupt.ContextWithIsExternalConnection(ctx)\n\ts.connection.NewPacketConnection(ctx, s, conn, metadata, onClose)\n}\n\nfunc (s *URLTest) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\ts.group.Touch()\n\tselected := s.group.selectedOutboundTCP\n\tif selected == nil {\n\t\tselected, _ = s.group.Select(N.NetworkTCP)\n\t}\n\tif selected == nil {\n\t\treturn nil, E.New(\"missing supported outbound\")\n\t}\n\tif !common.Contains(selected.Network(), metadata.Network) {\n\t\treturn nil, E.New(metadata.Network, \" is not supported by outbound: \", selected.Tag())\n\t}\n\treturn selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)\n}\n\ntype URLTestGroup struct {\n\tctx                          context.Context\n\trouter                       adapter.Router\n\toutbound                     adapter.OutboundManager\n\tpause                        pause.Manager\n\tpauseCallback                *list.Element[pause.Callback]\n\tlogger                       log.Logger\n\toutbounds                    []adapter.Outbound\n\tlink                         string\n\tinterval                     time.Duration\n\ttolerance                    uint16\n\tidleTimeout                  time.Duration\n\thistory                      adapter.URLTestHistoryStorage\n\tchecking                     atomic.Bool\n\tselectedOutboundTCP          adapter.Outbound\n\tselectedOutboundUDP          adapter.Outbound\n\tinterruptGroup               *interrupt.Group\n\tinterruptExternalConnections bool\n\taccess                       sync.Mutex\n\tticker                       *time.Ticker\n\tclose                        chan struct{}\n\tstarted                      bool\n\tlastActive                   common.TypedValue[time.Time]\n}\n\nfunc NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) {\n\tif interval == 0 {\n\t\tinterval = C.DefaultURLTestInterval\n\t}\n\tif tolerance == 0 {\n\t\ttolerance = 50\n\t}\n\tif idleTimeout == 0 {\n\t\tidleTimeout = C.DefaultURLTestIdleTimeout\n\t}\n\tif interval > idleTimeout {\n\t\treturn nil, E.New(\"interval must be less or equal than idle_timeout\")\n\t}\n\tvar history adapter.URLTestHistoryStorage\n\tif historyFromCtx := service.PtrFromContext[urltest.HistoryStorage](ctx); historyFromCtx != nil {\n\t\thistory = historyFromCtx\n\t} else if clashServer := service.FromContext[adapter.ClashServer](ctx); clashServer != nil {\n\t\thistory = clashServer.HistoryStorage()\n\t} else {\n\t\thistory = urltest.NewHistoryStorage()\n\t}\n\treturn &URLTestGroup{\n\t\tctx:                          ctx,\n\t\toutbound:                     outboundManager,\n\t\tlogger:                       logger,\n\t\toutbounds:                    outbounds,\n\t\tlink:                         link,\n\t\tinterval:                     interval,\n\t\ttolerance:                    tolerance,\n\t\tidleTimeout:                  idleTimeout,\n\t\thistory:                      history,\n\t\tclose:                        make(chan struct{}),\n\t\tpause:                        service.FromContext[pause.Manager](ctx),\n\t\tinterruptGroup:               interrupt.NewGroup(),\n\t\tinterruptExternalConnections: interruptExternalConnections,\n\t}, nil\n}\n\nfunc (g *URLTestGroup) PostStart() {\n\tg.access.Lock()\n\tdefer g.access.Unlock()\n\tg.started = true\n\tg.lastActive.Store(time.Now())\n\tgo g.CheckOutbounds(false)\n}\n\nfunc (g *URLTestGroup) Touch() {\n\tif !g.started {\n\t\treturn\n\t}\n\tg.access.Lock()\n\tdefer g.access.Unlock()\n\tif g.ticker != nil {\n\t\tg.lastActive.Store(time.Now())\n\t\treturn\n\t}\n\tg.ticker = time.NewTicker(g.interval)\n\tgo g.loopCheck()\n\tg.pauseCallback = pause.RegisterTicker(g.pause, g.ticker, g.interval, nil)\n}\n\nfunc (g *URLTestGroup) Close() error {\n\tg.access.Lock()\n\tdefer g.access.Unlock()\n\tif g.ticker == nil {\n\t\treturn nil\n\t}\n\tg.ticker.Stop()\n\tg.pause.UnregisterCallback(g.pauseCallback)\n\tclose(g.close)\n\treturn nil\n}\n\nfunc (g *URLTestGroup) Select(network string) (adapter.Outbound, bool) {\n\tvar minDelay uint16\n\tvar minOutbound adapter.Outbound\n\tswitch network {\n\tcase N.NetworkTCP:\n\t\tif g.selectedOutboundTCP != nil {\n\t\t\tif history := g.history.LoadURLTestHistory(RealTag(g.selectedOutboundTCP)); history != nil {\n\t\t\t\tminOutbound = g.selectedOutboundTCP\n\t\t\t\tminDelay = history.Delay\n\t\t\t}\n\t\t}\n\tcase N.NetworkUDP:\n\t\tif g.selectedOutboundUDP != nil {\n\t\t\tif history := g.history.LoadURLTestHistory(RealTag(g.selectedOutboundUDP)); history != nil {\n\t\t\t\tminOutbound = g.selectedOutboundUDP\n\t\t\t\tminDelay = history.Delay\n\t\t\t}\n\t\t}\n\t}\n\tfor _, detour := range g.outbounds {\n\t\tif !common.Contains(detour.Network(), network) {\n\t\t\tcontinue\n\t\t}\n\t\thistory := g.history.LoadURLTestHistory(RealTag(detour))\n\t\tif history == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif minDelay == 0 || minDelay > history.Delay+g.tolerance {\n\t\t\tminDelay = history.Delay\n\t\t\tminOutbound = detour\n\t\t}\n\t}\n\tif minOutbound == nil {\n\t\tfor _, detour := range g.outbounds {\n\t\t\tif !common.Contains(detour.Network(), network) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn detour, false\n\t\t}\n\t\treturn nil, false\n\t}\n\treturn minOutbound, true\n}\n\nfunc (g *URLTestGroup) loopCheck() {\n\tif time.Since(g.lastActive.Load()) > g.interval {\n\t\tg.lastActive.Store(time.Now())\n\t\tg.CheckOutbounds(false)\n\t}\n\tfor {\n\t\tselect {\n\t\tcase <-g.close:\n\t\t\treturn\n\t\tcase <-g.ticker.C:\n\t\t}\n\t\tif time.Since(g.lastActive.Load()) > g.idleTimeout {\n\t\t\tg.access.Lock()\n\t\t\tg.ticker.Stop()\n\t\t\tg.ticker = nil\n\t\t\tg.pause.UnregisterCallback(g.pauseCallback)\n\t\t\tg.pauseCallback = nil\n\t\t\tg.access.Unlock()\n\t\t\treturn\n\t\t}\n\t\tg.CheckOutbounds(false)\n\t}\n}\n\nfunc (g *URLTestGroup) CheckOutbounds(force bool) {\n\t_, _ = g.urlTest(g.ctx, force)\n}\n\nfunc (g *URLTestGroup) URLTest(ctx context.Context) (map[string]uint16, error) {\n\treturn g.urlTest(ctx, false)\n}\n\nfunc (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint16, error) {\n\tresult := make(map[string]uint16)\n\tif g.checking.Swap(true) {\n\t\treturn result, nil\n\t}\n\tdefer g.checking.Store(false)\n\tb, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))\n\tchecked := make(map[string]bool)\n\tvar resultAccess sync.Mutex\n\tfor _, detour := range g.outbounds {\n\t\ttag := detour.Tag()\n\t\trealTag := RealTag(detour)\n\t\tif checked[realTag] {\n\t\t\tcontinue\n\t\t}\n\t\thistory := g.history.LoadURLTestHistory(realTag)\n\t\tif !force && history != nil && time.Since(history.Time) < g.interval {\n\t\t\tcontinue\n\t\t}\n\t\tchecked[realTag] = true\n\t\tp, loaded := g.outbound.Outbound(realTag)\n\t\tif !loaded {\n\t\t\tcontinue\n\t\t}\n\t\tb.Go(realTag, func() (any, error) {\n\t\t\ttestCtx, cancel := context.WithTimeout(g.ctx, C.TCPTimeout)\n\t\t\tdefer cancel()\n\t\t\tt, err := urltest.URLTest(testCtx, g.link, p)\n\t\t\tif err != nil {\n\t\t\t\tg.logger.Debug(\"outbound \", tag, \" unavailable: \", err)\n\t\t\t\tg.history.DeleteURLTestHistory(realTag)\n\t\t\t} else {\n\t\t\t\tg.logger.Debug(\"outbound \", tag, \" available: \", t, \"ms\")\n\t\t\t\tg.history.StoreURLTestHistory(realTag, &adapter.URLTestHistory{\n\t\t\t\t\tTime:  time.Now(),\n\t\t\t\t\tDelay: t,\n\t\t\t\t})\n\t\t\t\tresultAccess.Lock()\n\t\t\t\tresult[tag] = t\n\t\t\t\tresultAccess.Unlock()\n\t\t\t}\n\t\t\treturn nil, nil\n\t\t})\n\t}\n\tb.Wait()\n\tg.performUpdateCheck()\n\treturn result, nil\n}\n\nfunc (g *URLTestGroup) performUpdateCheck() {\n\tvar updated bool\n\tif outbound, exists := g.Select(N.NetworkTCP); outbound != nil && (g.selectedOutboundTCP == nil || (exists && outbound != g.selectedOutboundTCP)) {\n\t\tif g.selectedOutboundTCP != nil {\n\t\t\tupdated = true\n\t\t}\n\t\tg.selectedOutboundTCP = outbound\n\t}\n\tif outbound, exists := g.Select(N.NetworkUDP); outbound != nil && (g.selectedOutboundUDP == nil || (exists && outbound != g.selectedOutboundUDP)) {\n\t\tif g.selectedOutboundUDP != nil {\n\t\t\tupdated = true\n\t\t}\n\t\tg.selectedOutboundUDP = outbound\n\t}\n\tif updated {\n\t\tg.interruptGroup.Interrupt(g.interruptExternalConnections)\n\t}\n}\n"
  },
  {
    "path": "protocol/http/inbound.go",
    "content": "package http\n\nimport (\n\tstd_bufio \"bufio\"\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/common/uot\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/protocol/http\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.HTTPMixedInboundOptions](registry, C.TypeHTTP, NewInbound)\n}\n\nvar _ adapter.TCPInjectableInbound = (*Inbound)(nil)\n\ntype Inbound struct {\n\tinbound.Adapter\n\trouter        adapter.ConnectionRouterEx\n\tlogger        log.ContextLogger\n\tlistener      *listener.Listener\n\tauthenticator *auth.Authenticator\n\ttlsConfig     tls.ServerConfig\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) {\n\tinbound := &Inbound{\n\t\tAdapter:       inbound.NewAdapter(C.TypeHTTP, tag),\n\t\trouter:        uot.NewRouter(router, logger),\n\t\tlogger:        logger,\n\t\tauthenticator: auth.NewAuthenticator(options.Users),\n\t}\n\tif options.TLS != nil {\n\t\ttlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{\n\t\t\tContext:        ctx,\n\t\t\tLogger:         logger,\n\t\t\tOptions:        common.PtrValueOrDefault(options.TLS),\n\t\t\tKTLSCompatible: true,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tinbound.tlsConfig = tlsConfig\n\t}\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tNetwork:           []string{N.NetworkTCP},\n\t\tListen:            options.ListenOptions,\n\t\tConnectionHandler: inbound,\n\t\tSetSystemProxy:    options.SetSystemProxy,\n\t\tSystemProxySOCKS:  false,\n\t})\n\treturn inbound, nil\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif h.tlsConfig != nil {\n\t\terr := h.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"create TLS config\")\n\t\t}\n\t}\n\treturn h.listener.Start()\n}\n\nfunc (h *Inbound) Close() error {\n\treturn common.Close(\n\t\th.listener,\n\t\th.tlsConfig,\n\t)\n}\n\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tif h.tlsConfig != nil {\n\t\ttlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)\n\t\tif err != nil {\n\t\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source, \": TLS handshake\"))\n\t\t\treturn\n\t\t}\n\t\tconn = tlsConn\n\t}\n\terr := http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose)\n\tif err != nil {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source))\n\t}\n}\n\nfunc (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuser, loaded := auth.UserFromContext[string](ctx)\n\tif !loaded {\n\t\th.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\t\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n\t\treturn\n\t}\n\tmetadata.User = user\n\th.logger.InfoContext(ctx, \"[\", user, \"] inbound connection to \", metadata.Destination)\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuser, loaded := auth.UserFromContext[string](ctx)\n\tif !loaded {\n\t\th.logger.InfoContext(ctx, \"inbound packet connection to \", metadata.Destination)\n\t\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n\t\treturn\n\t}\n\tmetadata.User = user\n\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet connection to \", metadata.Destination)\n\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "protocol/http/outbound.go",
    "content": "package http\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\tsHTTP \"github.com/sagernet/sing/protocol/http\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.HTTPOutboundOptions](registry, C.TypeHTTP, NewOutbound)\n}\n\ntype Outbound struct {\n\toutbound.Adapter\n\tlogger logger.ContextLogger\n\tclient *sHTTP.Client\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (adapter.Outbound, error) {\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdetour, err := tls.NewDialerFromOptions(ctx, logger, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Outbound{\n\t\tAdapter: outbound.NewAdapterWithDialerOptions(C.TypeHTTP, tag, []string{N.NetworkTCP}, options.DialerOptions),\n\t\tlogger:  logger,\n\t\tclient: sHTTP.NewClient(sHTTP.Options{\n\t\t\tDialer:   detour,\n\t\t\tServer:   options.ServerOptions.Build(),\n\t\t\tUsername: options.Username,\n\t\t\tPassword: options.Password,\n\t\t\tPath:     options.Path,\n\t\t\tHeaders:  options.Headers.Build(),\n\t\t}),\n\t}, nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\treturn h.client.DialContext(ctx, network, destination)\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn nil, os.ErrInvalid\n}\n"
  },
  {
    "path": "protocol/hysteria/inbound.go",
    "content": "package hysteria\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-quic/hysteria\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.HysteriaInboundOptions](registry, C.TypeHysteria, NewInbound)\n}\n\ntype Inbound struct {\n\tinbound.Adapter\n\trouter       adapter.Router\n\tlogger       log.ContextLogger\n\tlistener     *listener.Listener\n\ttlsConfig    tls.ServerConfig\n\tservice      *hysteria.Service[int]\n\tuserNameList []string\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) {\n\toptions.UDPFragmentDefault = true\n\tif options.TLS == nil || !options.TLS.Enabled {\n\t\treturn nil, C.ErrTLSRequired\n\t}\n\ttlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinbound := &Inbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeHysteria, tag),\n\t\trouter:  router,\n\t\tlogger:  logger,\n\t\tlistener: listener.New(listener.Options{\n\t\t\tContext: ctx,\n\t\t\tLogger:  logger,\n\t\t\tListen:  options.ListenOptions,\n\t\t}),\n\t\ttlsConfig: tlsConfig,\n\t}\n\tvar sendBps, receiveBps uint64\n\tif options.Up.Value() > 0 {\n\t\tsendBps = options.Up.Value()\n\t} else {\n\t\tsendBps = uint64(options.UpMbps) * hysteria.MbpsToBps\n\t}\n\tif options.Down.Value() > 0 {\n\t\treceiveBps = options.Down.Value()\n\t} else {\n\t\treceiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps\n\t}\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\tservice, err := hysteria.NewService[int](hysteria.ServiceOptions{\n\t\tContext:       ctx,\n\t\tLogger:        logger,\n\t\tSendBPS:       sendBps,\n\t\tReceiveBPS:    receiveBps,\n\t\tXPlusPassword: options.Obfs,\n\t\tTLSConfig:     tlsConfig,\n\t\tUDPTimeout:    udpTimeout,\n\t\tHandler:       inbound,\n\n\t\t// Legacy options\n\n\t\tConnReceiveWindow:   options.ReceiveWindowConn,\n\t\tStreamReceiveWindow: options.ReceiveWindowClient,\n\t\tMaxIncomingStreams:  int64(options.MaxConnClient),\n\t\tDisableMTUDiscovery: options.DisableMTUDiscovery,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tuserList := make([]int, 0, len(options.Users))\n\tuserNameList := make([]string, 0, len(options.Users))\n\tuserPasswordList := make([]string, 0, len(options.Users))\n\tfor index, user := range options.Users {\n\t\tuserList = append(userList, index)\n\t\tuserNameList = append(userNameList, user.Name)\n\t\tvar password string\n\t\tif user.AuthString != \"\" {\n\t\t\tpassword = user.AuthString\n\t\t} else {\n\t\t\tpassword = string(user.Auth)\n\t\t}\n\t\tuserPasswordList = append(userPasswordList, password)\n\t}\n\tservice.UpdateUsers(userList, userPasswordList)\n\tinbound.service = service\n\tinbound.userNameList = userNameList\n\treturn inbound, nil\n}\n\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tctx = log.ContextWithNewID(ctx)\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tmetadata.OriginDestination = h.listener.UDPAddr()\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\th.logger.InfoContext(ctx, \"inbound connection from \", metadata.Source)\n\tuserID, _ := auth.UserFromContext[int](ctx)\n\tif userName := h.userNameList[userID]; userName != \"\" {\n\t\tmetadata.User = userName\n\t\th.logger.InfoContext(ctx, \"[\", userName, \"] inbound connection to \", metadata.Destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\t}\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tctx = log.ContextWithNewID(ctx)\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tmetadata.OriginDestination = h.listener.UDPAddr()\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\th.logger.InfoContext(ctx, \"inbound packet connection from \", metadata.Source)\n\tuserID, _ := auth.UserFromContext[int](ctx)\n\tif userName := h.userNameList[userID]; userName != \"\" {\n\t\tmetadata.User = userName\n\t\th.logger.InfoContext(ctx, \"[\", userName, \"] inbound packet connection to \", metadata.Destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"inbound packet connection to \", metadata.Destination)\n\t}\n\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif h.tlsConfig != nil {\n\t\terr := h.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tpacketConn, err := h.listener.ListenUDP()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn h.service.Start(packetConn)\n}\n\nfunc (h *Inbound) Close() error {\n\treturn common.Close(\n\t\th.listener,\n\t\th.tlsConfig,\n\t\tcommon.PtrOrNil(h.service),\n\t)\n}\n"
  },
  {
    "path": "protocol/hysteria/outbound.go",
    "content": "package hysteria\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/protocol/tuic\"\n\t\"github.com/sagernet/sing-quic/hysteria\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, NewOutbound)\n}\n\nvar (\n\t_ adapter.Outbound                = (*tuic.Outbound)(nil)\n\t_ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil)\n)\n\ntype Outbound struct {\n\toutbound.Adapter\n\tlogger logger.ContextLogger\n\tclient *hysteria.Client\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) {\n\toptions.UDPFragmentDefault = true\n\tif options.TLS == nil || !options.TLS.Enabled {\n\t\treturn nil, C.ErrTLSRequired\n\t}\n\ttlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnetworkList := options.Network.Build()\n\tvar password string\n\tif options.AuthString != \"\" {\n\t\tpassword = options.AuthString\n\t} else {\n\t\tpassword = string(options.Auth)\n\t}\n\tvar sendBps, receiveBps uint64\n\tif options.Up.Value() > 0 {\n\t\tsendBps = options.Up.Value()\n\t} else {\n\t\tsendBps = uint64(options.UpMbps) * hysteria.MbpsToBps\n\t}\n\tif options.Down.Value() > 0 {\n\t\treceiveBps = options.Down.Value()\n\t} else {\n\t\treceiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps\n\t}\n\tclient, err := hysteria.NewClient(hysteria.ClientOptions{\n\t\tContext:             ctx,\n\t\tDialer:              outboundDialer,\n\t\tLogger:              logger,\n\t\tServerAddress:       options.ServerOptions.Build(),\n\t\tServerPorts:         options.ServerPorts,\n\t\tHopInterval:         time.Duration(options.HopInterval),\n\t\tSendBPS:             sendBps,\n\t\tReceiveBPS:          receiveBps,\n\t\tXPlusPassword:       options.Obfs,\n\t\tPassword:            password,\n\t\tTLSConfig:           tlsConfig,\n\t\tUDPDisabled:         !common.Contains(networkList, N.NetworkUDP),\n\t\tConnReceiveWindow:   options.ReceiveWindowConn,\n\t\tStreamReceiveWindow: options.ReceiveWindow,\n\t\tDisableMTUDiscovery: options.DisableMTUDiscovery,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Outbound{\n\t\tAdapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria, tag, networkList, options.DialerOptions),\n\t\tlogger:  logger,\n\t\tclient:  client,\n\t}, nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\t\treturn h.client.DialConn(ctx, destination)\n\tcase N.NetworkUDP:\n\t\tconn, err := h.ListenPacket(ctx, destination)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn bufio.NewBindPacketConn(conn, destination), nil\n\tdefault:\n\t\treturn nil, E.New(\"unsupported network: \", network)\n\t}\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\treturn h.client.ListenPacket(ctx, destination)\n}\n\nfunc (h *Outbound) InterfaceUpdated() {\n\th.client.CloseWithError(E.New(\"network changed\"))\n}\n\nfunc (h *Outbound) Close() error {\n\treturn h.client.CloseWithError(os.ErrClosed)\n}\n"
  },
  {
    "path": "protocol/hysteria2/inbound.go",
    "content": "package hysteria2\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-quic/hysteria\"\n\t\"github.com/sagernet/sing-quic/hysteria2\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.Hysteria2InboundOptions](registry, C.TypeHysteria2, NewInbound)\n}\n\ntype Inbound struct {\n\tinbound.Adapter\n\trouter       adapter.Router\n\tlogger       log.ContextLogger\n\tlistener     *listener.Listener\n\ttlsConfig    tls.ServerConfig\n\tservice      *hysteria2.Service[int]\n\tuserNameList []string\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) {\n\toptions.UDPFragmentDefault = true\n\tif options.TLS == nil || !options.TLS.Enabled {\n\t\treturn nil, C.ErrTLSRequired\n\t}\n\ttlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar salamanderPassword string\n\tif options.Obfs != nil {\n\t\tif options.Obfs.Password == \"\" {\n\t\t\treturn nil, E.New(\"missing obfs password\")\n\t\t}\n\t\tswitch options.Obfs.Type {\n\t\tcase hysteria2.ObfsTypeSalamander:\n\t\t\tsalamanderPassword = options.Obfs.Password\n\t\tdefault:\n\t\t\treturn nil, E.New(\"unknown obfs type: \", options.Obfs.Type)\n\t\t}\n\t}\n\tvar masqueradeHandler http.Handler\n\tif options.Masquerade != nil && options.Masquerade.Type != \"\" {\n\t\tswitch options.Masquerade.Type {\n\t\tcase C.Hysterai2MasqueradeTypeFile:\n\t\t\tmasqueradeHandler = http.FileServer(http.Dir(options.Masquerade.FileOptions.Directory))\n\t\tcase C.Hysterai2MasqueradeTypeProxy:\n\t\t\tmasqueradeURL, err := url.Parse(options.Masquerade.ProxyOptions.URL)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, \"parse masquerade URL\")\n\t\t\t}\n\t\t\tmasqueradeHandler = &httputil.ReverseProxy{\n\t\t\t\tRewrite: func(r *httputil.ProxyRequest) {\n\t\t\t\t\tr.SetURL(masqueradeURL)\n\t\t\t\t\tif !options.Masquerade.ProxyOptions.RewriteHost {\n\t\t\t\t\t\tr.Out.Host = r.In.Host\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {\n\t\t\t\t\tw.WriteHeader(http.StatusBadGateway)\n\t\t\t\t},\n\t\t\t}\n\t\tcase C.Hysterai2MasqueradeTypeString:\n\t\t\tmasqueradeHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif options.Masquerade.StringOptions.StatusCode != 0 {\n\t\t\t\t\tw.WriteHeader(options.Masquerade.StringOptions.StatusCode)\n\t\t\t\t}\n\t\t\t\tfor key, values := range options.Masquerade.StringOptions.Headers {\n\t\t\t\t\tfor _, value := range values {\n\t\t\t\t\t\tw.Header().Add(key, value)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tw.Write([]byte(options.Masquerade.StringOptions.Content))\n\t\t\t})\n\t\tdefault:\n\t\t\treturn nil, E.New(\"unknown masquerade type: \", options.Masquerade.Type)\n\t\t}\n\t}\n\tinbound := &Inbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeHysteria2, tag),\n\t\trouter:  router,\n\t\tlogger:  logger,\n\t\tlistener: listener.New(listener.Options{\n\t\t\tContext: ctx,\n\t\t\tLogger:  logger,\n\t\t\tListen:  options.ListenOptions,\n\t\t}),\n\t\ttlsConfig: tlsConfig,\n\t}\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\tservice, err := hysteria2.NewService[int](hysteria2.ServiceOptions{\n\t\tContext:               ctx,\n\t\tLogger:                logger,\n\t\tBrutalDebug:           options.BrutalDebug,\n\t\tSendBPS:               uint64(options.UpMbps * hysteria.MbpsToBps),\n\t\tReceiveBPS:            uint64(options.DownMbps * hysteria.MbpsToBps),\n\t\tSalamanderPassword:    salamanderPassword,\n\t\tTLSConfig:             tlsConfig,\n\t\tIgnoreClientBandwidth: options.IgnoreClientBandwidth,\n\t\tUDPTimeout:            udpTimeout,\n\t\tHandler:               inbound,\n\t\tMasqueradeHandler:     masqueradeHandler,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tuserList := make([]int, 0, len(options.Users))\n\tuserNameList := make([]string, 0, len(options.Users))\n\tuserPasswordList := make([]string, 0, len(options.Users))\n\tfor index, user := range options.Users {\n\t\tuserList = append(userList, index)\n\t\tuserNameList = append(userNameList, user.Name)\n\t\tuserPasswordList = append(userPasswordList, user.Password)\n\t}\n\tservice.UpdateUsers(userList, userPasswordList)\n\tinbound.service = service\n\tinbound.userNameList = userNameList\n\treturn inbound, nil\n}\n\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tctx = log.ContextWithNewID(ctx)\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tmetadata.OriginDestination = h.listener.UDPAddr()\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\th.logger.InfoContext(ctx, \"inbound connection from \", metadata.Source)\n\tuserID, _ := auth.UserFromContext[int](ctx)\n\tif userName := h.userNameList[userID]; userName != \"\" {\n\t\tmetadata.User = userName\n\t\th.logger.InfoContext(ctx, \"[\", userName, \"] inbound connection to \", metadata.Destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\t}\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tctx = log.ContextWithNewID(ctx)\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tmetadata.OriginDestination = h.listener.UDPAddr()\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\th.logger.InfoContext(ctx, \"inbound packet connection from \", metadata.Source)\n\tuserID, _ := auth.UserFromContext[int](ctx)\n\tif userName := h.userNameList[userID]; userName != \"\" {\n\t\tmetadata.User = userName\n\t\th.logger.InfoContext(ctx, \"[\", userName, \"] inbound packet connection to \", metadata.Destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"inbound packet connection to \", metadata.Destination)\n\t}\n\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif h.tlsConfig != nil {\n\t\terr := h.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tpacketConn, err := h.listener.ListenUDP()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn h.service.Start(packetConn)\n}\n\nfunc (h *Inbound) Close() error {\n\treturn common.Close(\n\t\th.listener,\n\t\th.tlsConfig,\n\t\tcommon.PtrOrNil(h.service),\n\t)\n}\n"
  },
  {
    "path": "protocol/hysteria2/outbound.go",
    "content": "package hysteria2\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/protocol/tuic\"\n\t\"github.com/sagernet/sing-quic/hysteria\"\n\t\"github.com/sagernet/sing-quic/hysteria2\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, NewOutbound)\n}\n\nvar (\n\t_ adapter.Outbound                = (*tuic.Outbound)(nil)\n\t_ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil)\n)\n\ntype Outbound struct {\n\toutbound.Adapter\n\tlogger logger.ContextLogger\n\tclient *hysteria2.Client\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) {\n\toptions.UDPFragmentDefault = true\n\tif options.TLS == nil || !options.TLS.Enabled {\n\t\treturn nil, C.ErrTLSRequired\n\t}\n\ttlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar salamanderPassword string\n\tif options.Obfs != nil {\n\t\tif options.Obfs.Password == \"\" {\n\t\t\treturn nil, E.New(\"missing obfs password\")\n\t\t}\n\t\tswitch options.Obfs.Type {\n\t\tcase hysteria2.ObfsTypeSalamander:\n\t\t\tsalamanderPassword = options.Obfs.Password\n\t\tdefault:\n\t\t\treturn nil, E.New(\"unknown obfs type: \", options.Obfs.Type)\n\t\t}\n\t}\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnetworkList := options.Network.Build()\n\tclient, err := hysteria2.NewClient(hysteria2.ClientOptions{\n\t\tContext:            ctx,\n\t\tDialer:             outboundDialer,\n\t\tLogger:             logger,\n\t\tBrutalDebug:        options.BrutalDebug,\n\t\tServerAddress:      options.ServerOptions.Build(),\n\t\tServerPorts:        options.ServerPorts,\n\t\tHopInterval:        time.Duration(options.HopInterval),\n\t\tSendBPS:            uint64(options.UpMbps * hysteria.MbpsToBps),\n\t\tReceiveBPS:         uint64(options.DownMbps * hysteria.MbpsToBps),\n\t\tSalamanderPassword: salamanderPassword,\n\t\tPassword:           options.Password,\n\t\tTLSConfig:          tlsConfig,\n\t\tUDPDisabled:        !common.Contains(networkList, N.NetworkUDP),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Outbound{\n\t\tAdapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria2, tag, networkList, options.DialerOptions),\n\t\tlogger:  logger,\n\t\tclient:  client,\n\t}, nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\t\treturn h.client.DialConn(ctx, destination)\n\tcase N.NetworkUDP:\n\t\tconn, err := h.ListenPacket(ctx, destination)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn bufio.NewBindPacketConn(conn, destination), nil\n\tdefault:\n\t\treturn nil, E.New(\"unsupported network: \", network)\n\t}\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\treturn h.client.ListenPacket(ctx)\n}\n\nfunc (h *Outbound) InterfaceUpdated() {\n\th.client.CloseWithError(E.New(\"network changed\"))\n}\n\nfunc (h *Outbound) Close() error {\n\treturn h.client.CloseWithError(os.ErrClosed)\n}\n"
  },
  {
    "path": "protocol/mixed/inbound.go",
    "content": "package mixed\n\nimport (\n\tstd_bufio \"bufio\"\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/common/uot\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/protocol/http\"\n\t\"github.com/sagernet/sing/protocol/socks\"\n\t\"github.com/sagernet/sing/protocol/socks/socks4\"\n\t\"github.com/sagernet/sing/protocol/socks/socks5\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.HTTPMixedInboundOptions](registry, C.TypeMixed, NewInbound)\n}\n\nvar _ adapter.TCPInjectableInbound = (*Inbound)(nil)\n\ntype Inbound struct {\n\tinbound.Adapter\n\trouter        adapter.ConnectionRouterEx\n\tlogger        log.ContextLogger\n\tlistener      *listener.Listener\n\tauthenticator *auth.Authenticator\n\ttlsConfig     tls.ServerConfig\n\tudpTimeout    time.Duration\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) {\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\tinbound := &Inbound{\n\t\tAdapter:       inbound.NewAdapter(C.TypeMixed, tag),\n\t\trouter:        uot.NewRouter(router, logger),\n\t\tlogger:        logger,\n\t\tauthenticator: auth.NewAuthenticator(options.Users),\n\t\tudpTimeout:    udpTimeout,\n\t}\n\tif options.TLS != nil {\n\t\ttlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{\n\t\t\tContext:        ctx,\n\t\t\tLogger:         logger,\n\t\t\tOptions:        common.PtrValueOrDefault(options.TLS),\n\t\t\tKTLSCompatible: true,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tinbound.tlsConfig = tlsConfig\n\t}\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tNetwork:           []string{N.NetworkTCP},\n\t\tListen:            options.ListenOptions,\n\t\tConnectionHandler: inbound,\n\t\tSetSystemProxy:    options.SetSystemProxy,\n\t\tSystemProxySOCKS:  true,\n\t})\n\treturn inbound, nil\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif h.tlsConfig != nil {\n\t\terr := h.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"create TLS config\")\n\t\t}\n\t}\n\treturn h.listener.Start()\n}\n\nfunc (h *Inbound) Close() error {\n\treturn common.Close(\n\t\th.listener,\n\t\th.tlsConfig,\n\t)\n}\n\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\terr := h.newConnection(ctx, conn, metadata, onClose)\n\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\tif err != nil {\n\t\tif E.IsClosedOrCanceled(err) {\n\t\t\th.logger.DebugContext(ctx, \"connection closed: \", err)\n\t\t} else {\n\t\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source))\n\t\t}\n\t}\n}\n\nfunc (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {\n\tif h.tlsConfig != nil {\n\t\ttlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"TLS handshake\")\n\t\t}\n\t\tconn = tlsConn\n\t}\n\treader := std_bufio.NewReader(conn)\n\theaderBytes, err := reader.Peek(1)\n\tif err != nil {\n\t\treturn E.Cause(err, \"peek first byte\")\n\t}\n\tswitch headerBytes[0] {\n\tcase socks4.Version, socks5.Version:\n\t\treturn socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, h.udpTimeout, metadata.Source, onClose)\n\tdefault:\n\t\treturn http.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose)\n\t}\n}\n\nfunc (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuser, loaded := auth.UserFromContext[string](ctx)\n\tif !loaded {\n\t\th.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\t\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n\t\treturn\n\t}\n\tmetadata.User = user\n\th.logger.InfoContext(ctx, \"[\", user, \"] inbound connection to \", metadata.Destination)\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuser, loaded := auth.UserFromContext[string](ctx)\n\tif !loaded {\n\t\tif !metadata.Destination.IsValid() {\n\t\t\th.logger.InfoContext(ctx, \"inbound packet connection\")\n\t\t} else {\n\t\t\th.logger.InfoContext(ctx, \"inbound packet connection to \", metadata.Destination)\n\t\t}\n\t\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n\t\treturn\n\t}\n\tmetadata.User = user\n\tif !metadata.Destination.IsValid() {\n\t\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet connection\")\n\t} else {\n\t\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet connection to \", metadata.Destination)\n\t}\n\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "protocol/naive/inbound.go",
    "content": "package naive\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/common/uot\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2rayhttp\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\tsHttp \"github.com/sagernet/sing/protocol/http\"\n\n\t\"golang.org/x/net/http2\"\n\t\"golang.org/x/net/http2/h2c\"\n)\n\nvar ConfigureHTTP3ListenerFunc func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.NaiveInboundOptions](registry, C.TypeNaive, NewInbound)\n}\n\ntype Inbound struct {\n\tinbound.Adapter\n\tctx              context.Context\n\trouter           adapter.ConnectionRouterEx\n\tlogger           logger.ContextLogger\n\toptions          option.NaiveInboundOptions\n\tlistener         *listener.Listener\n\tnetwork          []string\n\tnetworkIsDefault bool\n\tauthenticator    *auth.Authenticator\n\ttlsConfig        tls.ServerConfig\n\thttpServer       *http.Server\n\th3Server         io.Closer\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (adapter.Inbound, error) {\n\tinbound := &Inbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeNaive, tag),\n\t\tctx:     ctx,\n\t\trouter:  uot.NewRouter(router, logger),\n\t\tlogger:  logger,\n\t\tlistener: listener.New(listener.Options{\n\t\t\tContext: ctx,\n\t\t\tLogger:  logger,\n\t\t\tListen:  options.ListenOptions,\n\t\t}),\n\t\tnetworkIsDefault: options.Network == \"\",\n\t\tnetwork:          options.Network.Build(),\n\t\tauthenticator:    auth.NewAuthenticator(options.Users),\n\t}\n\tif common.Contains(inbound.network, N.NetworkUDP) {\n\t\tif options.TLS == nil || !options.TLS.Enabled {\n\t\t\treturn nil, E.New(\"TLS is required for QUIC server\")\n\t\t}\n\t}\n\tif len(options.Users) == 0 {\n\t\treturn nil, E.New(\"missing users\")\n\t}\n\tif options.TLS != nil {\n\t\ttlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tinbound.tlsConfig = tlsConfig\n\t}\n\treturn inbound, nil\n}\n\nfunc (n *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif n.tlsConfig != nil {\n\t\terr := n.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"create TLS config\")\n\t\t}\n\t}\n\tif common.Contains(n.network, N.NetworkTCP) {\n\t\ttcpListener, err := n.listener.ListenTCP()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tn.httpServer = &http.Server{\n\t\t\tHandler: h2c.NewHandler(n, &http2.Server{}),\n\t\t\tBaseContext: func(listener net.Listener) context.Context {\n\t\t\t\treturn n.ctx\n\t\t\t},\n\t\t}\n\t\tgo func() {\n\t\t\tlistener := net.Listener(tcpListener)\n\t\t\tif n.tlsConfig != nil {\n\t\t\t\tif len(n.tlsConfig.NextProtos()) == 0 {\n\t\t\t\t\tn.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, \"http/1.1\"})\n\t\t\t\t} else if !common.Contains(n.tlsConfig.NextProtos(), http2.NextProtoTLS) {\n\t\t\t\t\tn.tlsConfig.SetNextProtos(append([]string{http2.NextProtoTLS}, n.tlsConfig.NextProtos()...))\n\t\t\t\t}\n\t\t\t\tlistener = aTLS.NewListener(tcpListener, n.tlsConfig)\n\t\t\t}\n\t\t\tsErr := n.httpServer.Serve(listener)\n\t\t\tif sErr != nil && !errors.Is(sErr, http.ErrServerClosed) {\n\t\t\t\tn.logger.Error(\"http server serve error: \", sErr)\n\t\t\t}\n\t\t}()\n\t}\n\n\tif common.Contains(n.network, N.NetworkUDP) {\n\t\thttp3Server, err := ConfigureHTTP3ListenerFunc(n.ctx, n.logger, n.listener, n, n.tlsConfig, n.options)\n\t\tif err == nil {\n\t\t\tn.h3Server = http3Server\n\t\t} else if len(n.network) > 1 {\n\t\t\tn.logger.Warn(E.Cause(err, \"naive http3 disabled\"))\n\t\t} else {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (n *Inbound) Close() error {\n\treturn common.Close(\n\t\t&n.listener,\n\t\tcommon.PtrOrNil(n.httpServer),\n\t\tn.h3Server,\n\t\tn.tlsConfig,\n\t)\n}\n\nfunc (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) {\n\tctx := log.ContextWithNewID(request.Context())\n\tif request.Method != \"CONNECT\" {\n\t\trejectHTTP(writer, http.StatusBadRequest)\n\t\tn.badRequest(ctx, request, E.New(\"not CONNECT request\"))\n\t\treturn\n\t} else if request.Header.Get(\"Padding\") == \"\" {\n\t\trejectHTTP(writer, http.StatusBadRequest)\n\t\tn.badRequest(ctx, request, E.New(\"missing naive padding\"))\n\t\treturn\n\t}\n\tuserName, password, authOk := sHttp.ParseBasicAuth(request.Header.Get(\"Proxy-Authorization\"))\n\tif authOk {\n\t\tauthOk = n.authenticator.Verify(userName, password)\n\t}\n\tif !authOk {\n\t\trejectHTTP(writer, http.StatusProxyAuthRequired)\n\t\tn.badRequest(ctx, request, E.New(\"authorization failed\"))\n\t\treturn\n\t}\n\twriter.Header().Set(\"Padding\", generatePaddingHeader())\n\twriter.WriteHeader(http.StatusOK)\n\twriter.(http.Flusher).Flush()\n\n\thostPort := request.Header.Get(\"-connect-authority\")\n\tif hostPort == \"\" {\n\t\thostPort = request.URL.Host\n\t\tif hostPort == \"\" {\n\t\t\thostPort = request.Host\n\t\t}\n\t}\n\tsource := sHttp.SourceAddress(request)\n\tdestination := M.ParseSocksaddr(hostPort).Unwrap()\n\n\tif hijacker, isHijacker := writer.(http.Hijacker); isHijacker {\n\t\tconn, _, err := hijacker.Hijack()\n\t\tif err != nil {\n\t\t\tn.badRequest(ctx, request, E.New(\"hijack failed\"))\n\t\t\treturn\n\t\t}\n\t\tn.newConnection(ctx, false, &naiveConn{Conn: conn}, userName, source, destination)\n\t} else {\n\t\tn.newConnection(ctx, true, &naiveH2Conn{\n\t\t\treader:        request.Body,\n\t\t\twriter:        writer,\n\t\t\tflusher:       writer.(http.Flusher),\n\t\t\tremoteAddress: source,\n\t\t}, userName, source, destination)\n\t}\n}\n\nfunc (n *Inbound) newConnection(ctx context.Context, waitForClose bool, conn net.Conn, userName string, source M.Socksaddr, destination M.Socksaddr) {\n\tif userName != \"\" {\n\t\tn.logger.InfoContext(ctx, \"[\", userName, \"] inbound connection from \", source)\n\t\tn.logger.InfoContext(ctx, \"[\", userName, \"] inbound connection to \", destination)\n\t} else {\n\t\tn.logger.InfoContext(ctx, \"inbound connection from \", source)\n\t\tn.logger.InfoContext(ctx, \"inbound connection to \", destination)\n\t}\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = n.Tag()\n\tmetadata.InboundType = n.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = n.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\tmetadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()\n\tmetadata.User = userName\n\tif !waitForClose {\n\t\tn.router.RouteConnectionEx(ctx, conn, metadata, nil)\n\t} else {\n\t\tdone := make(chan struct{})\n\t\twrapper := v2rayhttp.NewHTTP2Wrapper(conn)\n\t\tn.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) {\n\t\t\tclose(done)\n\t\t}))\n\t\t<-done\n\t\twrapper.CloseWrapper()\n\t}\n}\n\nfunc (n *Inbound) badRequest(ctx context.Context, request *http.Request, err error) {\n\tn.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", request.RemoteAddr))\n}\n\nfunc rejectHTTP(writer http.ResponseWriter, statusCode int) {\n\thijacker, ok := writer.(http.Hijacker)\n\tif !ok {\n\t\twriter.WriteHeader(statusCode)\n\t\treturn\n\t}\n\tconn, _, err := hijacker.Hijack()\n\tif err != nil {\n\t\twriter.WriteHeader(statusCode)\n\t\treturn\n\t}\n\tif tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP {\n\t\ttcpConn.SetLinger(0)\n\t}\n\tconn.Close()\n}\n"
  },
  {
    "path": "protocol/naive/inbound_conn.go",
    "content": "package naive\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/baderror\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/rw\"\n)\n\nconst paddingCount = 8\n\nfunc generatePaddingHeader() string {\n\tpaddingLen := rand.Intn(32) + 30\n\tpadding := make([]byte, paddingLen)\n\tbits := rand.Uint64()\n\tfor i := 0; i < 16; i++ {\n\t\tpadding[i] = \"!#$()+<>?@[]^`{}\"[bits&15]\n\t\tbits >>= 4\n\t}\n\tfor i := 16; i < paddingLen; i++ {\n\t\tpadding[i] = '~'\n\t}\n\treturn string(padding)\n}\n\ntype paddingConn struct {\n\treadPadding      int\n\twritePadding     int\n\treadRemaining    int\n\tpaddingRemaining int\n}\n\nfunc (p *paddingConn) readWithPadding(reader io.Reader, buffer []byte) (n int, err error) {\n\tif p.readRemaining > 0 {\n\t\tif len(buffer) > p.readRemaining {\n\t\t\tbuffer = buffer[:p.readRemaining]\n\t\t}\n\t\tn, err = reader.Read(buffer)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tp.readRemaining -= n\n\t\treturn\n\t}\n\tif p.paddingRemaining > 0 {\n\t\terr = rw.SkipN(reader, p.paddingRemaining)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tp.paddingRemaining = 0\n\t}\n\tif p.readPadding < paddingCount {\n\t\tvar paddingHeader []byte\n\t\tif len(buffer) >= 3 {\n\t\t\tpaddingHeader = buffer[:3]\n\t\t} else {\n\t\t\tpaddingHeader = make([]byte, 3)\n\t\t}\n\t\t_, err = io.ReadFull(reader, paddingHeader)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\toriginalDataSize := int(binary.BigEndian.Uint16(paddingHeader[:2]))\n\t\tpaddingSize := int(paddingHeader[2])\n\t\tif len(buffer) > originalDataSize {\n\t\t\tbuffer = buffer[:originalDataSize]\n\t\t}\n\t\tn, err = reader.Read(buffer)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tp.readPadding++\n\t\tp.readRemaining = originalDataSize - n\n\t\tp.paddingRemaining = paddingSize\n\t\treturn\n\t}\n\treturn reader.Read(buffer)\n}\n\nfunc (p *paddingConn) writeWithPadding(writer io.Writer, data []byte) (n int, err error) {\n\tif p.writePadding < paddingCount {\n\t\tpaddingSize := rand.Intn(256)\n\t\tbuffer := buf.NewSize(3 + len(data) + paddingSize)\n\t\tdefer buffer.Release()\n\t\theader := buffer.Extend(3)\n\t\tbinary.BigEndian.PutUint16(header, uint16(len(data)))\n\t\theader[2] = byte(paddingSize)\n\t\tcommon.Must1(buffer.Write(data))\n\t\tbuffer.Extend(paddingSize)\n\t\t_, err = writer.Write(buffer.Bytes())\n\t\tif err == nil {\n\t\t\tn = len(data)\n\t\t}\n\t\tp.writePadding++\n\t\treturn\n\t}\n\treturn writer.Write(data)\n}\n\nfunc (p *paddingConn) writeBufferWithPadding(writer io.Writer, buffer *buf.Buffer) error {\n\tif p.writePadding < paddingCount {\n\t\tbufferLen := buffer.Len()\n\t\tif bufferLen > 65535 {\n\t\t\t_, err := p.writeChunked(writer, buffer.Bytes())\n\t\t\treturn err\n\t\t}\n\t\tpaddingSize := rand.Intn(256)\n\t\theader := buffer.ExtendHeader(3)\n\t\tbinary.BigEndian.PutUint16(header, uint16(bufferLen))\n\t\theader[2] = byte(paddingSize)\n\t\tbuffer.Extend(paddingSize)\n\t\tp.writePadding++\n\t}\n\treturn common.Error(writer.Write(buffer.Bytes()))\n}\n\nfunc (p *paddingConn) writeChunked(writer io.Writer, data []byte) (n int, err error) {\n\tfor len(data) > 0 {\n\t\tvar chunk []byte\n\t\tif len(data) > 65535 {\n\t\t\tchunk = data[:65535]\n\t\t\tdata = data[65535:]\n\t\t} else {\n\t\t\tchunk = data\n\t\t\tdata = nil\n\t\t}\n\t\tvar written int\n\t\twritten, err = p.writeWithPadding(writer, chunk)\n\t\tn += written\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\nfunc (p *paddingConn) frontHeadroom() int {\n\tif p.writePadding < paddingCount {\n\t\treturn 3\n\t}\n\treturn 0\n}\n\nfunc (p *paddingConn) rearHeadroom() int {\n\tif p.writePadding < paddingCount {\n\t\treturn 255\n\t}\n\treturn 0\n}\n\nfunc (p *paddingConn) writerMTU() int {\n\tif p.writePadding < paddingCount {\n\t\treturn 65535\n\t}\n\treturn 0\n}\n\nfunc (p *paddingConn) readerReplaceable() bool {\n\treturn p.readPadding == paddingCount\n}\n\nfunc (p *paddingConn) writerReplaceable() bool {\n\treturn p.writePadding == paddingCount\n}\n\ntype naiveConn struct {\n\tnet.Conn\n\tpaddingConn\n}\n\nfunc (c *naiveConn) Read(p []byte) (n int, err error) {\n\tn, err = c.readWithPadding(c.Conn, p)\n\treturn n, baderror.WrapH2(err)\n}\n\nfunc (c *naiveConn) Write(p []byte) (n int, err error) {\n\tn, err = c.writeChunked(c.Conn, p)\n\treturn n, baderror.WrapH2(err)\n}\n\nfunc (c *naiveConn) WriteBuffer(buffer *buf.Buffer) error {\n\tdefer buffer.Release()\n\terr := c.writeBufferWithPadding(c.Conn, buffer)\n\treturn baderror.WrapH2(err)\n}\n\nfunc (c *naiveConn) FrontHeadroom() int      { return c.frontHeadroom() }\nfunc (c *naiveConn) RearHeadroom() int       { return c.rearHeadroom() }\nfunc (c *naiveConn) WriterMTU() int          { return c.writerMTU() }\nfunc (c *naiveConn) Upstream() any           { return c.Conn }\nfunc (c *naiveConn) ReaderReplaceable() bool { return c.readerReplaceable() }\nfunc (c *naiveConn) WriterReplaceable() bool { return c.writerReplaceable() }\n\ntype naiveH2Conn struct {\n\treader        io.Reader\n\twriter        io.Writer\n\tflusher       http.Flusher\n\tremoteAddress net.Addr\n\tpaddingConn\n}\n\nfunc (c *naiveH2Conn) Read(p []byte) (n int, err error) {\n\tn, err = c.readWithPadding(c.reader, p)\n\treturn n, baderror.WrapH2(err)\n}\n\nfunc (c *naiveH2Conn) Write(p []byte) (n int, err error) {\n\tn, err = c.writeChunked(c.writer, p)\n\tif err == nil {\n\t\tc.flusher.Flush()\n\t}\n\treturn n, baderror.WrapH2(err)\n}\n\nfunc (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {\n\tdefer buffer.Release()\n\terr := c.writeBufferWithPadding(c.writer, buffer)\n\tif err == nil {\n\t\tc.flusher.Flush()\n\t}\n\treturn baderror.WrapH2(err)\n}\n\nfunc (c *naiveH2Conn) Close() error {\n\treturn common.Close(c.reader, c.writer)\n}\n\nfunc (c *naiveH2Conn) LocalAddr() net.Addr                { return M.Socksaddr{} }\nfunc (c *naiveH2Conn) RemoteAddr() net.Addr               { return c.remoteAddress }\nfunc (c *naiveH2Conn) SetDeadline(t time.Time) error      { return os.ErrInvalid }\nfunc (c *naiveH2Conn) SetReadDeadline(t time.Time) error  { return os.ErrInvalid }\nfunc (c *naiveH2Conn) SetWriteDeadline(t time.Time) error { return os.ErrInvalid }\nfunc (c *naiveH2Conn) NeedAdditionalReadDeadline() bool   { return true }\nfunc (c *naiveH2Conn) UpstreamReader() any                { return c.reader }\nfunc (c *naiveH2Conn) UpstreamWriter() any                { return c.writer }\nfunc (c *naiveH2Conn) FrontHeadroom() int                 { return c.frontHeadroom() }\nfunc (c *naiveH2Conn) RearHeadroom() int                  { return c.rearHeadroom() }\nfunc (c *naiveH2Conn) WriterMTU() int                     { return c.writerMTU() }\nfunc (c *naiveH2Conn) ReaderReplaceable() bool            { return c.readerReplaceable() }\nfunc (c *naiveH2Conn) WriterReplaceable() bool            { return c.writerReplaceable() }\n"
  },
  {
    "path": "protocol/naive/outbound.go",
    "content": "//go:build with_naive_outbound\n\npackage naive\n\nimport (\n\t\"context\"\n\t\"encoding/pem\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/sagernet/cronet-go\"\n\t_ \"github.com/sagernet/cronet-go/all\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/uot\"\n\t\"github.com/sagernet/sing/service\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.NaiveOutboundOptions](registry, C.TypeNaive, NewOutbound)\n}\n\ntype Outbound struct {\n\toutbound.Adapter\n\tctx       context.Context\n\tlogger    logger.ContextLogger\n\tclient    *cronet.NaiveClient\n\tuotClient *uot.Client\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveOutboundOptions) (adapter.Outbound, error) {\n\tif options.TLS == nil || !options.TLS.Enabled {\n\t\treturn nil, C.ErrTLSRequired\n\t}\n\tif options.TLS.DisableSNI {\n\t\treturn nil, E.New(\"disable_sni is not supported on naive outbound\")\n\t}\n\tif options.TLS.Insecure {\n\t\treturn nil, E.New(\"insecure is not supported on naive outbound\")\n\t}\n\tif len(options.TLS.ALPN) > 0 {\n\t\treturn nil, E.New(\"alpn is not supported on naive outbound\")\n\t}\n\tif options.TLS.MinVersion != \"\" {\n\t\treturn nil, E.New(\"min_version is not supported on naive outbound\")\n\t}\n\tif options.TLS.MaxVersion != \"\" {\n\t\treturn nil, E.New(\"max_version is not supported on naive outbound\")\n\t}\n\tif len(options.TLS.CipherSuites) > 0 {\n\t\treturn nil, E.New(\"cipher_suites is not supported on naive outbound\")\n\t}\n\tif len(options.TLS.CurvePreferences) > 0 {\n\t\treturn nil, E.New(\"curve_preferences is not supported on naive outbound\")\n\t}\n\tif len(options.TLS.ClientCertificate) > 0 || options.TLS.ClientCertificatePath != \"\" {\n\t\treturn nil, E.New(\"client_certificate is not supported on naive outbound\")\n\t}\n\tif len(options.TLS.ClientKey) > 0 || options.TLS.ClientKeyPath != \"\" {\n\t\treturn nil, E.New(\"client_key is not supported on naive outbound\")\n\t}\n\tif options.TLS.Fragment || options.TLS.RecordFragment {\n\t\treturn nil, E.New(\"fragment is not supported on naive outbound\")\n\t}\n\tif options.TLS.KernelTx || options.TLS.KernelRx {\n\t\treturn nil, E.New(\"kernel TLS is not supported on naive outbound\")\n\t}\n\tif options.TLS.UTLS != nil && options.TLS.UTLS.Enabled {\n\t\treturn nil, E.New(\"uTLS is not supported on naive outbound\")\n\t}\n\tif options.TLS.Reality != nil && options.TLS.Reality.Enabled {\n\t\treturn nil, E.New(\"reality is not supported on naive outbound\")\n\t}\n\n\tserverAddress := options.ServerOptions.Build()\n\n\tvar serverName string\n\tif options.TLS.ServerName != \"\" {\n\t\tserverName = options.TLS.ServerName\n\t} else {\n\t\tserverName = serverAddress.AddrString()\n\t}\n\n\toutboundDialer, err := dialer.NewWithOptions(dialer.Options{\n\t\tContext:          ctx,\n\t\tOptions:          options.DialerOptions,\n\t\tRemoteIsDomain:   true,\n\t\tResolverOnDetour: true,\n\t\tNewDialer:        true,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar trustedRootCertificates string\n\tif len(options.TLS.Certificate) > 0 {\n\t\ttrustedRootCertificates = strings.Join(options.TLS.Certificate, \"\\n\")\n\t} else if options.TLS.CertificatePath != \"\" {\n\t\tcontent, err := os.ReadFile(options.TLS.CertificatePath)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"read certificate\")\n\t\t}\n\t\ttrustedRootCertificates = string(content)\n\t}\n\n\textraHeaders := make(map[string]string)\n\tfor key, values := range options.ExtraHeaders.Build() {\n\t\tif len(values) > 0 {\n\t\t\textraHeaders[key] = values[0]\n\t\t}\n\t}\n\n\tdnsRouter := service.FromContext[adapter.DNSRouter](ctx)\n\tvar dnsResolver cronet.DNSResolverFunc\n\tif dnsRouter != nil {\n\t\tdnsResolver = func(dnsContext context.Context, request *mDNS.Msg) *mDNS.Msg {\n\t\t\tresponse, err := dnsRouter.Exchange(dnsContext, request, outboundDialer.(dialer.ResolveDialer).QueryOptions())\n\t\t\tif err != nil {\n\t\t\t\tlogger.Error(\"DNS exchange failed: \", err)\n\t\t\t\treturn dns.FixedResponseStatus(request, mDNS.RcodeServerFailure)\n\t\t\t}\n\t\t\treturn response\n\t\t}\n\t}\n\n\tvar echEnabled bool\n\tvar echConfigList []byte\n\tvar echQueryServerName string\n\tif options.TLS.ECH != nil && options.TLS.ECH.Enabled {\n\t\techEnabled = true\n\t\techQueryServerName = options.TLS.ECH.QueryServerName\n\t\tvar echConfig []byte\n\t\tif len(options.TLS.ECH.Config) > 0 {\n\t\t\techConfig = []byte(strings.Join(options.TLS.ECH.Config, \"\\n\"))\n\t\t} else if options.TLS.ECH.ConfigPath != \"\" {\n\t\t\tcontent, err := os.ReadFile(options.TLS.ECH.ConfigPath)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, \"read ECH config\")\n\t\t\t}\n\t\t\techConfig = content\n\t\t}\n\t\tif len(echConfig) > 0 {\n\t\t\tblock, rest := pem.Decode(echConfig)\n\t\t\tif block == nil || block.Type != \"ECH CONFIGS\" || len(rest) > 0 {\n\t\t\t\treturn nil, E.New(\"invalid ECH configs pem\")\n\t\t\t}\n\t\t\techConfigList = block.Bytes\n\t\t}\n\t}\n\tvar quicCongestionControl cronet.QUICCongestionControl\n\tswitch options.QUICCongestionControl {\n\tcase \"\":\n\t\tquicCongestionControl = cronet.QUICCongestionControlDefault\n\tcase \"bbr\":\n\t\tquicCongestionControl = cronet.QUICCongestionControlBBR\n\tcase \"bbr2\":\n\t\tquicCongestionControl = cronet.QUICCongestionControlBBRv2\n\tcase \"cubic\":\n\t\tquicCongestionControl = cronet.QUICCongestionControlCubic\n\tcase \"reno\":\n\t\tquicCongestionControl = cronet.QUICCongestionControlReno\n\tdefault:\n\t\treturn nil, E.New(\"unknown quic congestion control: \", options.QUICCongestionControl)\n\t}\n\tclient, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{\n\t\tContext:                 ctx,\n\t\tLogger:                  logger,\n\t\tServerAddress:           serverAddress,\n\t\tServerName:              serverName,\n\t\tUsername:                options.Username,\n\t\tPassword:                options.Password,\n\t\tInsecureConcurrency:     options.InsecureConcurrency,\n\t\tExtraHeaders:            extraHeaders,\n\t\tTrustedRootCertificates: trustedRootCertificates,\n\t\tDialer:                  outboundDialer,\n\t\tDNSResolver:             dnsResolver,\n\t\tECHEnabled:              echEnabled,\n\t\tECHConfigList:           echConfigList,\n\t\tECHQueryServerName:      echQueryServerName,\n\t\tQUIC:                    options.QUIC,\n\t\tQUICCongestionControl:   quicCongestionControl,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar uotClient *uot.Client\n\tuotOptions := common.PtrValueOrDefault(options.UDPOverTCP)\n\tif uotOptions.Enabled {\n\t\tuotClient = &uot.Client{\n\t\t\tDialer:  &naiveDialer{client},\n\t\t\tVersion: uotOptions.Version,\n\t\t}\n\t}\n\tvar networks []string\n\tif uotClient != nil {\n\t\tnetworks = []string{N.NetworkTCP, N.NetworkUDP}\n\t} else {\n\t\tnetworks = []string{N.NetworkTCP}\n\t}\n\treturn &Outbound{\n\t\tAdapter:   outbound.NewAdapterWithDialerOptions(C.TypeNaive, tag, networks, options.DialerOptions),\n\t\tctx:       ctx,\n\t\tlogger:    logger,\n\t\tclient:    client,\n\t\tuotClient: uotClient,\n\t}, nil\n}\n\nfunc (h *Outbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\terr := h.client.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\th.logger.Info(\"NaiveProxy started, version: \", h.client.Engine().Version())\n\treturn nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\t\treturn h.client.DialEarly(ctx, destination)\n\tcase N.NetworkUDP:\n\t\tif h.uotClient == nil {\n\t\t\treturn nil, E.New(\"UDP is not supported unless UDP over TCP is enabled\")\n\t\t}\n\t\th.logger.InfoContext(ctx, \"outbound UoT packet connection to \", destination)\n\t\treturn h.uotClient.DialContext(ctx, network, destination)\n\tdefault:\n\t\treturn nil, E.Extend(N.ErrUnknownNetwork, network)\n\t}\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tif h.uotClient == nil {\n\t\treturn nil, E.New(\"UDP is not supported unless UDP over TCP is enabled\")\n\t}\n\treturn h.uotClient.ListenPacket(ctx, destination)\n}\n\nfunc (h *Outbound) InterfaceUpdated() {\n\th.client.Engine().CloseAllConnections()\n}\n\nfunc (h *Outbound) Close() error {\n\treturn h.client.Close()\n}\n\nfunc (h *Outbound) Client() *cronet.NaiveClient {\n\treturn h.client\n}\n\ntype naiveDialer struct {\n\t*cronet.NaiveClient\n}\n\nfunc (d *naiveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\treturn d.NaiveClient.DialEarly(ctx, destination)\n}\n"
  },
  {
    "path": "protocol/naive/quic/inbound_init.go",
    "content": "package quic\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/sagernet/quic-go\"\n\t\"github.com/sagernet/quic-go/congestion\"\n\t\"github.com/sagernet/quic-go/http3\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/protocol/naive\"\n\t\"github.com/sagernet/sing-quic\"\n\t\"github.com/sagernet/sing-quic/congestion_bbr1\"\n\t\"github.com/sagernet/sing-quic/congestion_bbr2\"\n\tcongestion_meta1 \"github.com/sagernet/sing-quic/congestion_meta1\"\n\tcongestion_meta2 \"github.com/sagernet/sing-quic/congestion_meta2\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/sing/common/ntp\"\n)\n\nfunc init() {\n\tnaive.ConfigureHTTP3ListenerFunc = func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error) {\n\t\terr := qtls.ConfigureHTTP3(tlsConfig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tudpConn, err := listener.ListenUDP()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar congestionControl func(conn *quic.Conn) congestion.CongestionControl\n\t\ttimeFunc := ntp.TimeFuncFromContext(ctx)\n\t\tif timeFunc == nil {\n\t\t\ttimeFunc = time.Now\n\t\t}\n\t\tswitch options.QUICCongestionControl {\n\t\tcase \"\", \"bbr\":\n\t\t\tcongestionControl = func(conn *quic.Conn) congestion.CongestionControl {\n\t\t\t\treturn congestion_meta2.NewBbrSender(\n\t\t\t\t\tcongestion_meta2.DefaultClock{TimeFunc: timeFunc},\n\t\t\t\t\tcongestion.ByteCount(conn.Config().InitialPacketSize),\n\t\t\t\t\tcongestion.ByteCount(congestion_meta1.InitialCongestionWindow),\n\t\t\t\t)\n\t\t\t}\n\t\tcase \"bbr_standard\":\n\t\t\tcongestionControl = func(conn *quic.Conn) congestion.CongestionControl {\n\t\t\t\treturn congestion_bbr1.NewBbrSender(\n\t\t\t\t\tcongestion_bbr1.DefaultClock{TimeFunc: timeFunc},\n\t\t\t\t\tcongestion.ByteCount(conn.Config().InitialPacketSize),\n\t\t\t\t\tcongestion_bbr1.InitialCongestionWindowPackets,\n\t\t\t\t\tcongestion_bbr1.MaxCongestionWindowPackets,\n\t\t\t\t)\n\t\t\t}\n\t\tcase \"bbr2\":\n\t\t\tcongestionControl = func(conn *quic.Conn) congestion.CongestionControl {\n\t\t\t\treturn congestion_bbr2.NewBBR2Sender(\n\t\t\t\t\tcongestion_bbr2.DefaultClock{TimeFunc: timeFunc},\n\t\t\t\t\tcongestion.ByteCount(conn.Config().InitialPacketSize),\n\t\t\t\t\t0,\n\t\t\t\t\tfalse,\n\t\t\t\t)\n\t\t\t}\n\t\tcase \"bbr2_variant\":\n\t\t\tcongestionControl = func(conn *quic.Conn) congestion.CongestionControl {\n\t\t\t\treturn congestion_bbr2.NewBBR2Sender(\n\t\t\t\t\tcongestion_bbr2.DefaultClock{TimeFunc: timeFunc},\n\t\t\t\t\tcongestion.ByteCount(conn.Config().InitialPacketSize),\n\t\t\t\t\t32*congestion.ByteCount(conn.Config().InitialPacketSize),\n\t\t\t\t\ttrue,\n\t\t\t\t)\n\t\t\t}\n\t\tcase \"cubic\":\n\t\t\tcongestionControl = func(conn *quic.Conn) congestion.CongestionControl {\n\t\t\t\treturn congestion_meta1.NewCubicSender(\n\t\t\t\t\tcongestion_meta1.DefaultClock{TimeFunc: timeFunc},\n\t\t\t\t\tcongestion.ByteCount(conn.Config().InitialPacketSize),\n\t\t\t\t\tfalse,\n\t\t\t\t)\n\t\t\t}\n\t\tcase \"reno\":\n\t\t\tcongestionControl = func(conn *quic.Conn) congestion.CongestionControl {\n\t\t\t\treturn congestion_meta1.NewCubicSender(\n\t\t\t\t\tcongestion_meta1.DefaultClock{TimeFunc: timeFunc},\n\t\t\t\t\tcongestion.ByteCount(conn.Config().InitialPacketSize),\n\t\t\t\t\ttrue,\n\t\t\t\t)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn nil, E.New(\"unknown quic congestion control: \", options.QUICCongestionControl)\n\t\t}\n\n\t\tquicListener, err := qtls.ListenEarly(udpConn, tlsConfig, &quic.Config{\n\t\t\tMaxIncomingStreams: 1 << 60,\n\t\t\tAllow0RTT:          true,\n\t\t})\n\t\tif err != nil {\n\t\t\tudpConn.Close()\n\t\t\treturn nil, err\n\t\t}\n\n\t\th3Server := &http3.Server{\n\t\t\tHandler: handler,\n\t\t\tConnContext: func(ctx context.Context, conn *quic.Conn) context.Context {\n\t\t\t\tconn.SetCongestionControl(congestionControl(conn))\n\t\t\t\treturn log.ContextWithNewID(ctx)\n\t\t\t},\n\t\t}\n\n\t\tgo func() {\n\t\t\tsErr := h3Server.ServeListener(quicListener)\n\t\t\tudpConn.Close()\n\t\t\tif sErr != nil && !E.IsClosedOrCanceled(sErr) {\n\t\t\t\tlogger.Error(\"http3 server closed: \", sErr)\n\t\t\t}\n\t\t}()\n\n\t\treturn quicListener, nil\n\t}\n}\n"
  },
  {
    "path": "protocol/redirect/redirect.go",
    "content": "package redirect\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/redir\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterRedirect(registry *inbound.Registry) {\n\tinbound.Register[option.RedirectInboundOptions](registry, C.TypeRedirect, NewRedirect)\n}\n\ntype Redirect struct {\n\tinbound.Adapter\n\trouter   adapter.Router\n\tlogger   log.ContextLogger\n\tlistener *listener.Listener\n}\n\nfunc NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) (adapter.Inbound, error) {\n\tredirect := &Redirect{\n\t\tAdapter: inbound.NewAdapter(C.TypeRedirect, tag),\n\t\trouter:  router,\n\t\tlogger:  logger,\n\t}\n\tredirect.listener = listener.New(listener.Options{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tNetwork:           []string{N.NetworkTCP},\n\t\tListen:            options.ListenOptions,\n\t\tConnectionHandler: redirect,\n\t})\n\treturn redirect, nil\n}\n\nfunc (h *Redirect) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn h.listener.Start()\n}\n\nfunc (h *Redirect) Close() error {\n\treturn h.listener.Close()\n}\n\nfunc (h *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tdestination, err := redir.GetOriginalDestination(conn)\n\tif err != nil {\n\t\tconn.Close()\n\t\th.logger.ErrorContext(ctx, \"process connection from \", conn.RemoteAddr(), \": get redirect destination: \", err)\n\t\treturn\n\t}\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tmetadata.Destination = M.SocksaddrFromNetIP(destination)\n\th.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "protocol/redirect/tproxy.go",
    "content": "package redirect\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/redir\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/control\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/udpnat2\"\n)\n\nfunc RegisterTProxy(registry *inbound.Registry) {\n\tinbound.Register[option.TProxyInboundOptions](registry, C.TypeTProxy, NewTProxy)\n}\n\ntype TProxy struct {\n\tinbound.Adapter\n\tctx      context.Context\n\trouter   adapter.Router\n\tlogger   log.ContextLogger\n\tlistener *listener.Listener\n\tudpNat   *udpnat.Service\n}\n\nfunc NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) (adapter.Inbound, error) {\n\ttproxy := &TProxy{\n\t\tAdapter: inbound.NewAdapter(C.TypeTProxy, tag),\n\t\tctx:     ctx,\n\t\trouter:  router,\n\t\tlogger:  logger,\n\t}\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\ttproxy.udpNat = udpnat.New(tproxy, tproxy.preparePacketConnection, udpTimeout, false)\n\ttproxy.listener = listener.New(listener.Options{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tNetwork:           options.Network.Build(),\n\t\tListen:            options.ListenOptions,\n\t\tConnectionHandler: tproxy,\n\t\tOOBPacketHandler:  tproxy,\n\t\tTProxy:            true,\n\t})\n\treturn tproxy, nil\n}\n\nfunc (t *TProxy) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn t.listener.Start()\n}\n\nfunc (t *TProxy) Close() error {\n\treturn t.listener.Close()\n}\n\nfunc (t *TProxy) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = t.Tag()\n\tmetadata.InboundType = t.Type()\n\tmetadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()\n\tt.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\tt.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (t *TProxy) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tt.logger.InfoContext(ctx, \"inbound packet connection from \", source)\n\tt.logger.InfoContext(ctx, \"inbound packet connection to \", destination)\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = t.Tag()\n\tmetadata.InboundType = t.Type()\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\tmetadata.OriginDestination = t.listener.UDPAddr()\n\tt.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (t *TProxy) NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr) {\n\tdestination, err := redir.GetOriginalDestinationFromOOB(oob)\n\tif err != nil {\n\t\tt.logger.Warn(\"process packet from \", source, \": get tproxy destination: \", err)\n\t\treturn\n\t}\n\tt.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.SocksaddrFromNetIP(destination), nil)\n}\n\nfunc (t *TProxy) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) {\n\tctx := log.ContextWithNewID(t.ctx)\n\twriter := &tproxyPacketWriter{\n\t\tctx:         ctx,\n\t\tlistener:    t.listener,\n\t\tsource:      source.AddrPort(),\n\t\tdestination: destination,\n\t}\n\treturn true, ctx, writer, func(it error) {\n\t\tcommon.Close(common.PtrOrNil(writer.conn))\n\t}\n}\n\ntype tproxyPacketWriter struct {\n\tctx         context.Context\n\tlistener    *listener.Listener\n\tsource      netip.AddrPort\n\tdestination M.Socksaddr\n\tconn        *net.UDPConn\n}\n\nfunc (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {\n\tdefer buffer.Release()\n\tif w.listener.ListenOptions().NetNs == \"\" {\n\t\tconn := w.conn\n\t\tif w.destination == destination && conn != nil {\n\t\t\t_, err := conn.WriteToUDPAddrPort(buffer.Bytes(), w.source)\n\t\t\tif err != nil {\n\t\t\t\tw.conn = nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t}\n\tvar listenConfig net.ListenConfig\n\tlistenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())\n\tlistenConfig.Control = control.Append(listenConfig.Control, redir.TProxyWriteBack())\n\tpacketConn, err := w.listener.ListenPacket(listenConfig, w.ctx, \"udp\", destination.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\tudpConn := packetConn.(*net.UDPConn)\n\tif w.listener.ListenOptions().NetNs == \"\" && w.destination == destination {\n\t\tw.conn = udpConn\n\t} else {\n\t\tdefer udpConn.Close()\n\t}\n\treturn common.Error(udpConn.WriteToUDPAddrPort(buffer.Bytes(), w.source))\n}\n"
  },
  {
    "path": "protocol/shadowsocks/inbound.go",
    "content": "package shadowsocks\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/mux\"\n\t\"github.com/sagernet/sing-box/common/uot\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowsocks\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocks, NewInbound)\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {\n\tif len(options.Users) > 0 && len(options.Destinations) > 0 {\n\t\treturn nil, E.New(\"users and destinations options must not be combined\")\n\t} else if options.Managed && (len(options.Users) > 0 || len(options.Destinations) > 0) {\n\t\treturn nil, E.New(\"users and destinations options are not supported in managed servers\")\n\t}\n\tif len(options.Users) > 0 || options.Managed {\n\t\treturn newMultiInbound(ctx, router, logger, tag, options)\n\t} else if len(options.Destinations) > 0 {\n\t\treturn newRelayInbound(ctx, router, logger, tag, options)\n\t} else {\n\t\treturn newInbound(ctx, router, logger, tag, options)\n\t}\n}\n\nvar _ adapter.TCPInjectableInbound = (*Inbound)(nil)\n\ntype Inbound struct {\n\tinbound.Adapter\n\tctx      context.Context\n\trouter   adapter.ConnectionRouterEx\n\tlogger   logger.ContextLogger\n\tlistener *listener.Listener\n\tservice  shadowsocks.Service\n}\n\nfunc newInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*Inbound, error) {\n\tinbound := &Inbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeShadowsocks, tag),\n\t\tctx:     ctx,\n\t\trouter:  uot.NewRouter(router, logger),\n\t\tlogger:  logger,\n\t}\n\tvar err error\n\tinbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\tswitch {\n\tcase options.Method == shadowsocks.MethodNone:\n\t\tinbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound))\n\tcase common.Contains(shadowaead.List, options.Method):\n\t\tinbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound))\n\tcase common.Contains(shadowaead_2022.List, options.Method):\n\t\tinbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx))\n\tdefault:\n\t\terr = E.New(\"unsupported method: \", options.Method)\n\t}\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:                  ctx,\n\t\tLogger:                   logger,\n\t\tNetwork:                  options.Network.Build(),\n\t\tListen:                   options.ListenOptions,\n\t\tConnectionHandler:        inbound,\n\t\tPacketHandler:            inbound,\n\t\tThreadUnsafePacketWriter: true,\n\t})\n\treturn inbound, err\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn h.listener.Start()\n}\n\nfunc (h *Inbound) Close() error {\n\treturn h.listener.Close()\n}\n\n//nolint:staticcheck\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\terr := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata))\n\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\tif err != nil {\n\t\tif E.IsClosedOrCanceled(err) {\n\t\t\th.logger.DebugContext(ctx, \"connection closed: \", err)\n\t\t} else {\n\t\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source))\n\t\t}\n\t}\n}\n\n//nolint:staticcheck\nfunc (h *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {\n\terr := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source})\n\tif err != nil {\n\t\th.logger.Error(E.Cause(err, \"process packet from \", source))\n\t}\n}\n\nfunc (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {\n\th.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\treturn h.router.RouteConnection(ctx, conn, metadata)\n}\n\nfunc (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {\n\tctx = log.ContextWithNewID(ctx)\n\th.logger.InfoContext(ctx, \"inbound packet connection from \", metadata.Source)\n\th.logger.InfoContext(ctx, \"inbound packet connection to \", metadata.Destination)\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\treturn h.router.RoutePacketConnection(ctx, conn, metadata)\n}\n\nvar _ N.PacketConn = (*stubPacketConn)(nil)\n\ntype stubPacketConn struct {\n\tN.PacketWriter\n}\n\nfunc (c *stubPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {\n\tpanic(\"stub!\")\n}\n\nfunc (c *stubPacketConn) Close() error {\n\treturn nil\n}\n\nfunc (c *stubPacketConn) LocalAddr() net.Addr {\n\tpanic(\"stub!\")\n}\n\nfunc (c *stubPacketConn) SetDeadline(t time.Time) error {\n\tpanic(\"stub!\")\n}\n\nfunc (c *stubPacketConn) SetReadDeadline(t time.Time) error {\n\tpanic(\"stub!\")\n}\n\nfunc (c *stubPacketConn) SetWriteDeadline(t time.Time) error {\n\tpanic(\"stub!\")\n}\n\nfunc (h *Inbound) NewError(ctx context.Context, err error) {\n\tNewError(h.logger, ctx, err)\n}\n\n// Deprecated: remove\nfunc NewError(logger logger.ContextLogger, ctx context.Context, err error) {\n\tcommon.Close(err)\n\tif E.IsClosedOrCanceled(err) {\n\t\tlogger.DebugContext(ctx, \"connection closed: \", err)\n\t\treturn\n\t}\n\tlogger.ErrorContext(ctx, err)\n}\n"
  },
  {
    "path": "protocol/shadowsocks/inbound_multi.go",
    "content": "package shadowsocks\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/mux\"\n\t\"github.com/sagernet/sing-box/common/uot\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowsocks\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n)\n\nvar (\n\t_ adapter.TCPInjectableInbound = (*MultiInbound)(nil)\n\t_ adapter.ManagedSSMServer     = (*MultiInbound)(nil)\n)\n\ntype MultiInbound struct {\n\tinbound.Adapter\n\tctx      context.Context\n\trouter   adapter.ConnectionRouterEx\n\tlogger   logger.ContextLogger\n\tlistener *listener.Listener\n\tservice  shadowsocks.MultiService[int]\n\tusers    []option.ShadowsocksUser\n\ttracker  adapter.SSMTracker\n}\n\nfunc newMultiInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*MultiInbound, error) {\n\tinbound := &MultiInbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeShadowsocks, tag),\n\t\tctx:     ctx,\n\t\trouter:  uot.NewRouter(router, logger),\n\t\tlogger:  logger,\n\t}\n\tvar err error\n\tinbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\tvar service shadowsocks.MultiService[int]\n\tif common.Contains(shadowaead_2022.List, options.Method) {\n\t\tservice, err = shadowaead_2022.NewMultiServiceWithPassword[int](\n\t\t\toptions.Method,\n\t\t\toptions.Password,\n\t\t\tint64(udpTimeout.Seconds()),\n\t\t\tadapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound),\n\t\t\tntp.TimeFuncFromContext(ctx),\n\t\t)\n\t} else if common.Contains(shadowaead.List, options.Method) {\n\t\tservice, err = shadowaead.NewMultiService[int](\n\t\t\toptions.Method,\n\t\t\tint64(udpTimeout.Seconds()),\n\t\t\tadapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound),\n\t\t)\n\t} else {\n\t\treturn nil, E.New(\"unsupported method: \" + options.Method)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(options.Users) > 0 {\n\t\terr = service.UpdateUsersWithPasswords(common.MapIndexed(options.Users, func(index int, user option.ShadowsocksUser) int {\n\t\t\treturn index\n\t\t}), common.Map(options.Users, func(user option.ShadowsocksUser) string {\n\t\t\treturn user.Password\n\t\t}))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tinbound.service = service\n\tinbound.users = options.Users\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:                  ctx,\n\t\tLogger:                   logger,\n\t\tNetwork:                  options.Network.Build(),\n\t\tListen:                   options.ListenOptions,\n\t\tConnectionHandler:        inbound,\n\t\tPacketHandler:            inbound,\n\t\tThreadUnsafePacketWriter: true,\n\t})\n\treturn inbound, err\n}\n\nfunc (h *MultiInbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn h.listener.Start()\n}\n\nfunc (h *MultiInbound) Close() error {\n\treturn h.listener.Close()\n}\n\nfunc (h *MultiInbound) SetTracker(tracker adapter.SSMTracker) {\n\th.tracker = tracker\n}\n\nfunc (h *MultiInbound) UpdateUsers(users []string, uPSKs []string) error {\n\terr := h.service.UpdateUsersWithPasswords(common.MapIndexed(users, func(index int, user string) int {\n\t\treturn index\n\t}), uPSKs)\n\tif err != nil {\n\t\treturn err\n\t}\n\th.users = common.Map(users, func(user string) option.ShadowsocksUser {\n\t\treturn option.ShadowsocksUser{\n\t\t\tName: user,\n\t\t}\n\t})\n\treturn nil\n}\n\n//nolint:staticcheck\nfunc (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\terr := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata))\n\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\tif err != nil {\n\t\tif E.IsClosedOrCanceled(err) {\n\t\t\th.logger.DebugContext(ctx, \"connection closed: \", err)\n\t\t} else {\n\t\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source))\n\t\t}\n\t}\n}\n\n//nolint:staticcheck\nfunc (h *MultiInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {\n\terr := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source})\n\tif err != nil {\n\t\th.logger.Error(E.Cause(err, \"process packet from \", source))\n\t}\n}\n\nfunc (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {\n\tuserIndex, loaded := auth.UserFromContext[int](ctx)\n\tif !loaded {\n\t\treturn os.ErrInvalid\n\t}\n\tuser := h.users[userIndex].Name\n\tif user == \"\" {\n\t\tuser = F.ToString(userIndex)\n\t} else {\n\t\tmetadata.User = user\n\t}\n\th.logger.InfoContext(ctx, \"[\", user, \"] inbound connection to \", metadata.Destination)\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tif h.tracker != nil {\n\t\tconn = h.tracker.TrackConnection(conn, metadata)\n\t}\n\treturn h.router.RouteConnection(ctx, conn, metadata)\n}\n\nfunc (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {\n\tuserIndex, loaded := auth.UserFromContext[int](ctx)\n\tif !loaded {\n\t\treturn os.ErrInvalid\n\t}\n\tuser := h.users[userIndex].Name\n\tif user == \"\" {\n\t\tuser = F.ToString(userIndex)\n\t} else {\n\t\tmetadata.User = user\n\t}\n\tctx = log.ContextWithNewID(ctx)\n\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet connection from \", metadata.Source)\n\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet connection to \", metadata.Destination)\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tif h.tracker != nil {\n\t\tconn = h.tracker.TrackPacketConnection(conn, metadata)\n\t}\n\treturn h.router.RoutePacketConnection(ctx, conn, metadata)\n}\n\n//nolint:staticcheck\nfunc (h *MultiInbound) NewError(ctx context.Context, err error) {\n\tNewError(h.logger, ctx, err)\n}\n"
  },
  {
    "path": "protocol/shadowsocks/inbound_relay.go",
    "content": "package shadowsocks\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/mux\"\n\t\"github.com/sagernet/sing-box/common/uot\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nvar _ adapter.TCPInjectableInbound = (*RelayInbound)(nil)\n\ntype RelayInbound struct {\n\tinbound.Adapter\n\tctx          context.Context\n\trouter       adapter.ConnectionRouterEx\n\tlogger       logger.ContextLogger\n\tlistener     *listener.Listener\n\tservice      *shadowaead_2022.RelayService[int]\n\tdestinations []option.ShadowsocksDestination\n}\n\nfunc newRelayInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*RelayInbound, error) {\n\tinbound := &RelayInbound{\n\t\tAdapter:      inbound.NewAdapter(C.TypeShadowsocks, tag),\n\t\tctx:          ctx,\n\t\trouter:       uot.NewRouter(router, logger),\n\t\tlogger:       logger,\n\t\tdestinations: options.Destinations,\n\t}\n\tvar err error\n\tinbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\tservice, err := shadowaead_2022.NewRelayServiceWithPassword[int](\n\t\toptions.Method,\n\t\toptions.Password,\n\t\tint64(udpTimeout.Seconds()),\n\t\tadapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = service.UpdateUsersWithPasswords(common.MapIndexed(options.Destinations, func(index int, user option.ShadowsocksDestination) int {\n\t\treturn index\n\t}), common.Map(options.Destinations, func(user option.ShadowsocksDestination) string {\n\t\treturn user.Password\n\t}), common.Map(options.Destinations, option.ShadowsocksDestination.Build))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinbound.service = service\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:                  ctx,\n\t\tLogger:                   logger,\n\t\tNetwork:                  options.Network.Build(),\n\t\tListen:                   options.ListenOptions,\n\t\tConnectionHandler:        inbound,\n\t\tPacketHandler:            inbound,\n\t\tThreadUnsafePacketWriter: true,\n\t})\n\treturn inbound, err\n}\n\nfunc (h *RelayInbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn h.listener.Start()\n}\n\nfunc (h *RelayInbound) Close() error {\n\treturn h.listener.Close()\n}\n\n//nolint:staticcheck\nfunc (h *RelayInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\terr := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata))\n\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\tif err != nil {\n\t\tif E.IsClosedOrCanceled(err) {\n\t\t\th.logger.DebugContext(ctx, \"connection closed: \", err)\n\t\t} else {\n\t\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source))\n\t\t}\n\t}\n}\n\n//nolint:staticcheck\nfunc (h *RelayInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {\n\terr := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source})\n\tif err != nil {\n\t\th.logger.Error(E.Cause(err, \"process packet from \", source))\n\t}\n}\n\nfunc (h *RelayInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {\n\tdestinationIndex, loaded := auth.UserFromContext[int](ctx)\n\tif !loaded {\n\t\treturn os.ErrInvalid\n\t}\n\tdestination := h.destinations[destinationIndex].Name\n\tif destination == \"\" {\n\t\tdestination = F.ToString(destinationIndex)\n\t} else {\n\t\tmetadata.User = destination\n\t}\n\th.logger.InfoContext(ctx, \"[\", destination, \"] inbound connection to \", metadata.Destination)\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\treturn h.router.RouteConnection(ctx, conn, metadata)\n}\n\nfunc (h *RelayInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {\n\tdestinationIndex, loaded := auth.UserFromContext[int](ctx)\n\tif !loaded {\n\t\treturn os.ErrInvalid\n\t}\n\tdestination := h.destinations[destinationIndex].Name\n\tif destination == \"\" {\n\t\tdestination = F.ToString(destinationIndex)\n\t} else {\n\t\tmetadata.User = destination\n\t}\n\tctx = log.ContextWithNewID(ctx)\n\th.logger.InfoContext(ctx, \"[\", destination, \"] inbound packet connection from \", metadata.Source)\n\th.logger.InfoContext(ctx, \"[\", destination, \"] inbound packet connection to \", metadata.Destination)\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\treturn h.router.RoutePacketConnection(ctx, conn, metadata)\n}\n\n//nolint:staticcheck\nfunc (h *RelayInbound) NewError(ctx context.Context, err error) {\n\tNewError(h.logger, ctx, err)\n}\n"
  },
  {
    "path": "protocol/shadowsocks/outbound.go",
    "content": "package shadowsocks\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/mux\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/sip003\"\n\t\"github.com/sagernet/sing-shadowsocks2\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/uot\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.ShadowsocksOutboundOptions](registry, C.TypeShadowsocks, NewOutbound)\n}\n\ntype Outbound struct {\n\toutbound.Adapter\n\tlogger          logger.ContextLogger\n\tdialer          N.Dialer\n\tmethod          shadowsocks.Method\n\tserverAddr      M.Socksaddr\n\tplugin          sip003.Plugin\n\tuotClient       *uot.Client\n\tmultiplexDialer *mux.Client\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (adapter.Outbound, error) {\n\tmethod, err := shadowsocks.CreateMethod(ctx, options.Method, shadowsocks.MethodOptions{\n\t\tPassword: options.Password,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound := &Outbound{\n\t\tAdapter:    outbound.NewAdapterWithDialerOptions(C.TypeShadowsocks, tag, options.Network.Build(), options.DialerOptions),\n\t\tlogger:     logger,\n\t\tdialer:     outboundDialer,\n\t\tmethod:     method,\n\t\tserverAddr: options.ServerOptions.Build(),\n\t}\n\tif options.Plugin != \"\" {\n\t\toutbound.plugin, err = sip003.CreatePlugin(ctx, options.Plugin, options.PluginOptions, router, outbound.dialer, outbound.serverAddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tuotOptions := common.PtrValueOrDefault(options.UDPOverTCP)\n\tif !uotOptions.Enabled {\n\t\toutbound.multiplexDialer, err = mux.NewClientWithOptions((*shadowsocksDialer)(outbound), logger, common.PtrValueOrDefault(options.Multiplex))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif uotOptions.Enabled {\n\t\toutbound.uotClient = &uot.Client{\n\t\t\tDialer:  (*shadowsocksDialer)(outbound),\n\t\t\tVersion: uotOptions.Version,\n\t\t}\n\t}\n\treturn outbound, nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tif h.multiplexDialer == nil {\n\t\tswitch N.NetworkName(network) {\n\t\tcase N.NetworkTCP:\n\t\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\t\tcase N.NetworkUDP:\n\t\t\tif h.uotClient != nil {\n\t\t\t\th.logger.InfoContext(ctx, \"outbound UoT connect packet connection to \", destination)\n\t\t\t\treturn h.uotClient.DialContext(ctx, network, destination)\n\t\t\t} else {\n\t\t\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t\t\t}\n\t\t}\n\t\treturn (*shadowsocksDialer)(h).DialContext(ctx, network, destination)\n\t} else {\n\t\tswitch N.NetworkName(network) {\n\t\tcase N.NetworkTCP:\n\t\t\th.logger.InfoContext(ctx, \"outbound multiplex connection to \", destination)\n\t\tcase N.NetworkUDP:\n\t\t\th.logger.InfoContext(ctx, \"outbound multiplex packet connection to \", destination)\n\t\t}\n\t\treturn h.multiplexDialer.DialContext(ctx, network, destination)\n\t}\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tif h.multiplexDialer == nil {\n\t\tif h.uotClient != nil {\n\t\t\th.logger.InfoContext(ctx, \"outbound UoT packet connection to \", destination)\n\t\t\treturn h.uotClient.ListenPacket(ctx, destination)\n\t\t} else {\n\t\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t\t}\n\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t\treturn (*shadowsocksDialer)(h).ListenPacket(ctx, destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"outbound multiplex packet connection to \", destination)\n\t\treturn h.multiplexDialer.ListenPacket(ctx, destination)\n\t}\n}\n\nfunc (h *Outbound) InterfaceUpdated() {\n\tif h.multiplexDialer != nil {\n\t\th.multiplexDialer.Reset()\n\t}\n}\n\nfunc (h *Outbound) Close() error {\n\treturn common.Close(common.PtrOrNil(h.multiplexDialer))\n}\n\nvar _ N.Dialer = (*shadowsocksDialer)(nil)\n\ntype shadowsocksDialer Outbound\n\nfunc (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\tvar outConn net.Conn\n\t\tvar err error\n\t\tif h.plugin != nil {\n\t\t\toutConn, err = h.plugin.DialContext(ctx)\n\t\t} else {\n\t\t\toutConn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn h.method.DialEarlyConn(outConn, destination), nil\n\tcase N.NetworkUDP:\n\t\toutConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn bufio.NewBindPacketConn(h.method.DialPacketConn(outConn), destination), nil\n\tdefault:\n\t\treturn nil, E.Extend(N.ErrUnknownNetwork, network)\n\t}\n}\n\nfunc (h *shadowsocksDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\toutConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn h.method.DialPacketConn(outConn), nil\n}\n"
  },
  {
    "path": "protocol/shadowtls/inbound.go",
    "content": "package shadowtls\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowtls\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.ShadowTLSInboundOptions](registry, C.TypeShadowTLS, NewInbound)\n}\n\ntype Inbound struct {\n\tinbound.Adapter\n\trouter   adapter.Router\n\tlogger   logger.ContextLogger\n\tlistener *listener.Listener\n\tservice  *shadowtls.Service\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (adapter.Inbound, error) {\n\tinbound := &Inbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeShadowTLS, tag),\n\t\trouter:  router,\n\t\tlogger:  logger,\n\t}\n\n\tif options.Version == 0 {\n\t\toptions.Version = 1\n\t}\n\n\tvar handshakeForServerName map[string]shadowtls.HandshakeConfig\n\tif options.Version > 1 {\n\t\thandshakeForServerName = make(map[string]shadowtls.HandshakeConfig)\n\t\tif options.HandshakeForServerName != nil {\n\t\t\tfor _, entry := range options.HandshakeForServerName.Entries() {\n\t\t\t\thandshakeDialer, err := dialer.New(ctx, entry.Value.DialerOptions, entry.Value.ServerIsDomain())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\thandshakeForServerName[entry.Key] = shadowtls.HandshakeConfig{\n\t\t\t\t\tServer: entry.Value.ServerOptions.Build(),\n\t\t\t\t\tDialer: handshakeDialer,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tserverIsDomain := options.Handshake.ServerIsDomain()\n\tif options.WildcardSNI != option.ShadowTLSWildcardSNIOff {\n\t\tserverIsDomain = true\n\t}\n\thandshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions, serverIsDomain)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tservice, err := shadowtls.NewService(shadowtls.ServiceConfig{\n\t\tVersion:  options.Version,\n\t\tPassword: options.Password,\n\t\tUsers: common.Map(options.Users, func(it option.ShadowTLSUser) shadowtls.User {\n\t\t\treturn (shadowtls.User)(it)\n\t\t}),\n\t\tHandshake: shadowtls.HandshakeConfig{\n\t\t\tServer: options.Handshake.ServerOptions.Build(),\n\t\t\tDialer: handshakeDialer,\n\t\t},\n\t\tHandshakeForServerName: handshakeForServerName,\n\t\tStrictMode:             options.StrictMode,\n\t\tWildcardSNI:            shadowtls.WildcardSNI(options.WildcardSNI),\n\t\tHandler:                (*inboundHandler)(inbound),\n\t\tLogger:                 logger,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinbound.service = service\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tNetwork:           []string{N.NetworkTCP},\n\t\tListen:            options.ListenOptions,\n\t\tConnectionHandler: inbound,\n\t})\n\treturn inbound, nil\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn h.listener.Start()\n}\n\nfunc (h *Inbound) Close() error {\n\treturn h.listener.Close()\n}\n\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\terr := h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, metadata.Source, metadata.Destination, onClose)\n\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\tif err != nil {\n\t\tif E.IsClosedOrCanceled(err) {\n\t\t\th.logger.DebugContext(ctx, \"connection closed: \", err)\n\t\t} else {\n\t\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source))\n\t\t}\n\t}\n}\n\ntype inboundHandler Inbound\n\nfunc (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\tif userName, _ := auth.UserFromContext[string](ctx); userName != \"\" {\n\t\tmetadata.User = userName\n\t\th.logger.InfoContext(ctx, \"[\", userName, \"] inbound connection to \", metadata.Destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\t}\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "protocol/shadowtls/outbound.go",
    "content": "package shadowtls\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowtls\"\n\t\"github.com/sagernet/sing/common\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.ShadowTLSOutboundOptions](registry, C.TypeShadowTLS, NewOutbound)\n}\n\ntype Outbound struct {\n\toutbound.Adapter\n\tclient *shadowtls.Client\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (adapter.Outbound, error) {\n\toutbound := &Outbound{\n\t\tAdapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowTLS, tag, []string{N.NetworkTCP}, options.DialerOptions),\n\t}\n\tif options.TLS == nil || !options.TLS.Enabled {\n\t\treturn nil, C.ErrTLSRequired\n\t}\n\n\tif options.Version == 0 {\n\t\toptions.Version = 1\n\t}\n\n\tif options.Version == 1 {\n\t\toptions.TLS.MinVersion = \"1.2\"\n\t\toptions.TLS.MaxVersion = \"1.2\"\n\t}\n\ttlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar tlsHandshakeFunc shadowtls.TLSHandshakeFunc\n\tswitch options.Version {\n\tcase 1, 2:\n\t\ttlsHandshakeFunc = func(ctx context.Context, conn net.Conn, _ shadowtls.TLSSessionIDGeneratorFunc) error {\n\t\t\treturn common.Error(tls.ClientHandshake(ctx, conn, tlsConfig))\n\t\t}\n\tcase 3:\n\t\tif idConfig, loaded := tlsConfig.(tls.WithSessionIDGenerator); loaded {\n\t\t\ttlsHandshakeFunc = func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {\n\t\t\t\tidConfig.SetSessionIDGenerator(sessionIDGenerator)\n\t\t\t\treturn common.Error(tls.ClientHandshake(ctx, conn, tlsConfig))\n\t\t\t}\n\t\t} else {\n\t\t\tstdTLSConfig, err := tlsConfig.STDConfig()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\ttlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(options.Password, stdTLSConfig)\n\t\t}\n\t}\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclient, err := shadowtls.NewClient(shadowtls.ClientConfig{\n\t\tVersion:      options.Version,\n\t\tPassword:     options.Password,\n\t\tServer:       options.ServerOptions.Build(),\n\t\tDialer:       outboundDialer,\n\t\tTLSHandshake: tlsHandshakeFunc,\n\t\tLogger:       logger,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound.client = client\n\treturn outbound, nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\treturn h.client.DialContext(ctx)\n\tdefault:\n\t\treturn nil, os.ErrInvalid\n\t}\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn nil, os.ErrInvalid\n}\n"
  },
  {
    "path": "protocol/socks/inbound.go",
    "content": "package socks\n\nimport (\n\tstd_bufio \"bufio\"\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/uot\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/auth\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/protocol/socks\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.SocksInboundOptions](registry, C.TypeSOCKS, NewInbound)\n}\n\nvar _ adapter.TCPInjectableInbound = (*Inbound)(nil)\n\ntype Inbound struct {\n\tinbound.Adapter\n\trouter        adapter.ConnectionRouterEx\n\tlogger        logger.ContextLogger\n\tlistener      *listener.Listener\n\tauthenticator *auth.Authenticator\n\tudpTimeout    time.Duration\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) (adapter.Inbound, error) {\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\tinbound := &Inbound{\n\t\tAdapter:       inbound.NewAdapter(C.TypeSOCKS, tag),\n\t\trouter:        uot.NewRouter(router, logger),\n\t\tlogger:        logger,\n\t\tauthenticator: auth.NewAuthenticator(options.Users),\n\t\tudpTimeout:    udpTimeout,\n\t}\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tNetwork:           []string{N.NetworkTCP},\n\t\tListen:            options.ListenOptions,\n\t\tConnectionHandler: inbound,\n\t})\n\treturn inbound, nil\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\treturn h.listener.Start()\n}\n\nfunc (h *Inbound) Close() error {\n\treturn h.listener.Close()\n}\n\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\terr := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, h.udpTimeout, metadata.Source, onClose)\n\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\tif err != nil {\n\t\tif E.IsClosedOrCanceled(err) {\n\t\t\th.logger.DebugContext(ctx, \"connection closed: \", err)\n\t\t} else {\n\t\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source))\n\t\t}\n\t}\n}\n\nfunc (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuser, loaded := auth.UserFromContext[string](ctx)\n\tif !loaded {\n\t\th.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\t\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n\t\treturn\n\t}\n\tmetadata.User = user\n\th.logger.InfoContext(ctx, \"[\", user, \"] inbound connection to \", metadata.Destination)\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuser, loaded := auth.UserFromContext[string](ctx)\n\tif !loaded {\n\t\tif !metadata.Destination.IsValid() {\n\t\t\th.logger.InfoContext(ctx, \"inbound packet connection\")\n\t\t} else {\n\t\t\th.logger.InfoContext(ctx, \"inbound packet connection to \", metadata.Destination)\n\t\t}\n\t\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n\t\treturn\n\t}\n\tmetadata.User = user\n\tif !metadata.Destination.IsValid() {\n\t\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet connection\")\n\t} else {\n\t\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet connection to \", metadata.Destination)\n\t}\n\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "protocol/socks/outbound.go",
    "content": "package socks\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/uot\"\n\t\"github.com/sagernet/sing/protocol/socks\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.SOCKSOutboundOptions](registry, C.TypeSOCKS, NewOutbound)\n}\n\nvar _ adapter.Outbound = (*Outbound)(nil)\n\ntype Outbound struct {\n\toutbound.Adapter\n\tdnsRouter adapter.DNSRouter\n\tlogger    logger.ContextLogger\n\tclient    *socks.Client\n\tresolve   bool\n\tuotClient *uot.Client\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SOCKSOutboundOptions) (adapter.Outbound, error) {\n\tvar version socks.Version\n\tvar err error\n\tif options.Version != \"\" {\n\t\tversion, err = socks.ParseVersion(options.Version)\n\t} else {\n\t\tversion = socks.Version5\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound := &Outbound{\n\t\tAdapter:   outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, tag, options.Network.Build(), options.DialerOptions),\n\t\tdnsRouter: service.FromContext[adapter.DNSRouter](ctx),\n\t\tlogger:    logger,\n\t\tclient:    socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password),\n\t\tresolve:   version == socks.Version4,\n\t}\n\tuotOptions := common.PtrValueOrDefault(options.UDPOverTCP)\n\tif uotOptions.Enabled {\n\t\toutbound.uotClient = &uot.Client{\n\t\t\tDialer:  outbound.client,\n\t\t\tVersion: uotOptions.Version,\n\t\t}\n\t}\n\treturn outbound, nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\tcase N.NetworkUDP:\n\t\tif h.uotClient != nil {\n\t\t\th.logger.InfoContext(ctx, \"outbound UoT connect packet connection to \", destination)\n\t\t\treturn h.uotClient.DialContext(ctx, network, destination)\n\t\t}\n\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\tdefault:\n\t\treturn nil, E.Extend(N.ErrUnknownNetwork, network)\n\t}\n\tif h.resolve && destination.IsDomain() {\n\t\tdestinationAddresses, err := h.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn N.DialSerial(ctx, h.client, network, destination, destinationAddresses)\n\t}\n\treturn h.client.DialContext(ctx, network, destination)\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tif h.uotClient != nil {\n\t\th.logger.InfoContext(ctx, \"outbound UoT packet connection to \", destination)\n\t\treturn h.uotClient.ListenPacket(ctx, destination)\n\t}\n\tif h.resolve && destination.IsDomain() {\n\t\tdestinationAddresses, err := h.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpacketConn, _, err := N.ListenSerial(ctx, h.client, destination, destinationAddresses)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn packetConn, nil\n\t}\n\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\treturn h.client.ListenPacket(ctx, destination)\n}\n"
  },
  {
    "path": "protocol/ssh/outbound.go",
    "content": "package ssh\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"golang.org/x/crypto/ssh\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.SSHOutboundOptions](registry, C.TypeSSH, NewOutbound)\n}\n\nvar _ adapter.InterfaceUpdateListener = (*Outbound)(nil)\n\ntype Outbound struct {\n\toutbound.Adapter\n\tctx               context.Context\n\tlogger            logger.ContextLogger\n\tdialer            N.Dialer\n\tserverAddr        M.Socksaddr\n\tuser              string\n\thostKey           []ssh.PublicKey\n\thostKeyAlgorithms []string\n\tclientVersion     string\n\tauthMethod        []ssh.AuthMethod\n\tclientAccess      sync.Mutex\n\tclientConn        net.Conn\n\tclient            *ssh.Client\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (adapter.Outbound, error) {\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound := &Outbound{\n\t\tAdapter:           outbound.NewAdapterWithDialerOptions(C.TypeSSH, tag, []string{N.NetworkTCP}, options.DialerOptions),\n\t\tctx:               ctx,\n\t\tlogger:            logger,\n\t\tdialer:            outboundDialer,\n\t\tserverAddr:        options.ServerOptions.Build(),\n\t\tuser:              options.User,\n\t\thostKeyAlgorithms: options.HostKeyAlgorithms,\n\t\tclientVersion:     options.ClientVersion,\n\t}\n\tif outbound.serverAddr.Port == 0 {\n\t\toutbound.serverAddr.Port = 22\n\t}\n\tif outbound.user == \"\" {\n\t\toutbound.user = \"root\"\n\t}\n\tif outbound.clientVersion == \"\" {\n\t\toutbound.clientVersion = randomVersion()\n\t}\n\tif options.Password != \"\" {\n\t\toutbound.authMethod = append(outbound.authMethod, ssh.Password(options.Password))\n\t}\n\tif len(options.PrivateKey) > 0 || options.PrivateKeyPath != \"\" {\n\t\tvar privateKey []byte\n\t\tif len(options.PrivateKey) > 0 {\n\t\t\tprivateKey = []byte(strings.Join(options.PrivateKey, \"\\n\"))\n\t\t} else {\n\t\t\tvar err error\n\t\t\tprivateKey, err = os.ReadFile(os.ExpandEnv(options.PrivateKeyPath))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, \"read private key\")\n\t\t\t}\n\t\t}\n\t\tvar signer ssh.Signer\n\t\tvar err error\n\t\tif options.PrivateKeyPassphrase == \"\" {\n\t\t\tsigner, err = ssh.ParsePrivateKey(privateKey)\n\t\t} else {\n\t\t\tsigner, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(options.PrivateKeyPassphrase))\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse private key\")\n\t\t}\n\t\toutbound.authMethod = append(outbound.authMethod, ssh.PublicKeys(signer))\n\t}\n\tif len(options.HostKey) > 0 {\n\t\tfor _, hostKey := range options.HostKey {\n\t\t\tkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(hostKey))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.New(\"parse host key \", key)\n\t\t\t}\n\t\t\toutbound.hostKey = append(outbound.hostKey, key)\n\t\t}\n\t}\n\treturn outbound, nil\n}\n\nfunc randomVersion() string {\n\tversion := \"SSH-2.0-OpenSSH_\"\n\tif rand.Intn(2) == 0 {\n\t\tversion += \"7.\" + strconv.Itoa(rand.Intn(10))\n\t} else {\n\t\tversion += \"8.\" + strconv.Itoa(rand.Intn(9))\n\t}\n\treturn version\n}\n\nfunc (s *Outbound) connect() (*ssh.Client, error) {\n\tif s.client != nil {\n\t\treturn s.client, nil\n\t}\n\n\ts.clientAccess.Lock()\n\tdefer s.clientAccess.Unlock()\n\n\tif s.client != nil {\n\t\treturn s.client, nil\n\t}\n\n\tconn, err := s.dialer.DialContext(s.ctx, N.NetworkTCP, s.serverAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconfig := &ssh.ClientConfig{\n\t\tUser:              s.user,\n\t\tAuth:              s.authMethod,\n\t\tClientVersion:     s.clientVersion,\n\t\tHostKeyAlgorithms: s.hostKeyAlgorithms,\n\t\tHostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {\n\t\t\tif len(s.hostKey) == 0 {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tserverKey := key.Marshal()\n\t\t\tfor _, hostKey := range s.hostKey {\n\t\t\t\tif bytes.Equal(serverKey, hostKey.Marshal()) {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn E.New(\"host key mismatch, server send \", key.Type(), \" \", base64.StdEncoding.EncodeToString(serverKey))\n\t\t},\n\t}\n\tclientConn, chans, reqs, err := ssh.NewClientConn(conn, s.serverAddr.Addr.String(), config)\n\tif err != nil {\n\t\tconn.Close()\n\t\treturn nil, E.Cause(err, \"connect to ssh server\")\n\t}\n\n\tclient := ssh.NewClient(clientConn, chans, reqs)\n\n\ts.clientConn = conn\n\ts.client = client\n\n\tgo func() {\n\t\tclient.Wait()\n\t\tconn.Close()\n\t\ts.clientAccess.Lock()\n\t\ts.client = nil\n\t\ts.clientConn = nil\n\t\ts.clientAccess.Unlock()\n\t}()\n\n\treturn client, nil\n}\n\nfunc (s *Outbound) InterfaceUpdated() {\n\tcommon.Close(s.clientConn)\n}\n\nfunc (s *Outbound) Close() error {\n\treturn common.Close(s.clientConn)\n}\n\nfunc (s *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tclient, err := s.connect()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconn, err := client.Dial(network, destination.String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &chanConnWrapper{Conn: conn}, nil\n}\n\nfunc (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn nil, os.ErrInvalid\n}\n\ntype chanConnWrapper struct {\n\tnet.Conn\n}\n\nfunc (c *chanConnWrapper) SetDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *chanConnWrapper) SetReadDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *chanConnWrapper) SetWriteDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n"
  },
  {
    "path": "protocol/tailscale/dns_transport.go",
    "content": "//go:build with_gvisor\n\npackage tailscale\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n\tnDNS \"github.com/sagernet/tailscale/net/dns\"\n\t\"github.com/sagernet/tailscale/types/dnstype\"\n\t\"github.com/sagernet/tailscale/wgengine/router\"\n\t\"github.com/sagernet/tailscale/wgengine/wgcfg\"\n\n\tmDNS \"github.com/miekg/dns\"\n\t\"go4.org/netipx\"\n\t\"golang.org/x/net/http2\"\n)\n\nfunc RegistryTransport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.TailscaleDNSServerOptions](registry, C.DNSTypeTailscale, NewDNSTransport)\n}\n\ntype DNSTransport struct {\n\tdns.TransportAdapter\n\tctx                    context.Context\n\tlogger                 logger.ContextLogger\n\tendpointTag            string\n\tacceptDefaultResolvers bool\n\tdnsRouter              adapter.DNSRouter\n\tendpointManager        adapter.EndpointManager\n\tendpoint               *Endpoint\n\troutePrefixes          []netip.Prefix\n\troutes                 map[string][]adapter.DNSTransport\n\thosts                  map[string][]netip.Addr\n\tdefaultResolvers       []adapter.DNSTransport\n}\n\nfunc NewDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.TailscaleDNSServerOptions) (adapter.DNSTransport, error) {\n\tif options.Endpoint == \"\" {\n\t\treturn nil, E.New(\"missing tailscale endpoint tag\")\n\t}\n\treturn &DNSTransport{\n\t\tTransportAdapter:       dns.NewTransportAdapter(C.DNSTypeTailscale, tag, nil),\n\t\tctx:                    ctx,\n\t\tlogger:                 logger,\n\t\tendpointTag:            options.Endpoint,\n\t\tacceptDefaultResolvers: options.AcceptDefaultResolvers,\n\t\tdnsRouter:              service.FromContext[adapter.DNSRouter](ctx),\n\t\tendpointManager:        service.FromContext[adapter.EndpointManager](ctx),\n\t}, nil\n}\n\nfunc (t *DNSTransport) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateInitialize {\n\t\treturn nil\n\t}\n\trawOutbound, loaded := t.endpointManager.Get(t.endpointTag)\n\tif !loaded {\n\t\treturn E.New(\"endpoint not found: \", t.endpointTag)\n\t}\n\tep, isTailscale := rawOutbound.(*Endpoint)\n\tif !isTailscale {\n\t\treturn E.New(\"endpoint is not Tailscale: \", t.endpointTag)\n\t}\n\tif ep.onReconfigHook != nil {\n\t\treturn E.New(\"only one Tailscale DNS server is allowed for single endpoint\")\n\t}\n\tep.onReconfigHook = t.onReconfig\n\tt.endpoint = ep\n\treturn nil\n}\n\nfunc (t *DNSTransport) Reset() {\n}\n\nfunc (t *DNSTransport) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *nDNS.Config) {\n\terr := t.updateDNSServers(routerCfg, dnsCfg)\n\tif err != nil {\n\t\tt.logger.Error(E.Cause(err, \"update DNS servers\"))\n\t}\n}\n\nfunc (t *DNSTransport) updateDNSServers(routeConfig *router.Config, dnsConfig *nDNS.Config) error {\n\tt.routePrefixes = buildRoutePrefixes(routeConfig)\n\tdirectDialerOnce := sync.OnceValue(func() N.Dialer {\n\t\tdirectDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{}))\n\t\treturn &DNSDialer{transport: t, fallbackDialer: directDialer}\n\t})\n\troutes := make(map[string][]adapter.DNSTransport)\n\tfor domain, resolvers := range dnsConfig.Routes {\n\t\tvar myResolvers []adapter.DNSTransport\n\t\tfor _, resolver := range resolvers {\n\t\t\tmyResolver, err := t.createResolver(directDialerOnce, resolver)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tmyResolvers = append(myResolvers, myResolver)\n\t\t}\n\t\troutes[domain.WithTrailingDot()] = myResolvers\n\t}\n\thosts := make(map[string][]netip.Addr)\n\tfor domain, addresses := range dnsConfig.Hosts {\n\t\thosts[domain.WithTrailingDot()] = addresses\n\t}\n\tvar defaultResolvers []adapter.DNSTransport\n\tfor _, resolver := range dnsConfig.DefaultResolvers {\n\t\tmyResolver, err := t.createResolver(directDialerOnce, resolver)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefaultResolvers = append(defaultResolvers, myResolver)\n\t}\n\tt.routes = routes\n\tt.hosts = hosts\n\tt.defaultResolvers = defaultResolvers\n\tif len(defaultResolvers) > 0 {\n\t\tt.logger.Info(\"updated \", len(routes), \" routes, \", len(hosts), \" hosts, default resolvers: \",\n\t\t\tstrings.Join(common.Map(dnsConfig.DefaultResolvers, func(it *dnstype.Resolver) string { return it.Addr }), \" \"))\n\t} else {\n\t\tt.logger.Info(\"updated \", len(routes), \" routes, \", len(hosts), \" hosts\")\n\t}\n\treturn nil\n}\n\nfunc (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dnstype.Resolver) (adapter.DNSTransport, error) {\n\tserverURL, parseURLErr := url.Parse(resolver.Addr)\n\tvar myDialer N.Dialer\n\tif parseURLErr == nil && serverURL.Scheme == \"http\" {\n\t\tmyDialer = t.endpoint\n\t} else {\n\t\tmyDialer = directDialer()\n\t}\n\tif len(resolver.BootstrapResolution) > 0 {\n\t\tbootstrapTransport := transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, M.SocksaddrFrom(resolver.BootstrapResolution[0], 53))\n\t\tmyDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, \"\", adapter.DNSQueryOptions{Transport: bootstrapTransport}, 0)\n\t}\n\tif serverAddr := M.ParseSocksaddr(resolver.Addr); serverAddr.IsValid() {\n\t\tif serverAddr.Port == 0 {\n\t\t\tserverAddr.Port = 53\n\t\t}\n\t\treturn transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, serverAddr), nil\n\t} else if parseURLErr != nil {\n\t\treturn nil, E.Cause(parseURLErr, \"parse resolver address\")\n\t} else {\n\t\tswitch serverURL.Scheme {\n\t\tcase \"https\":\n\t\t\tserverAddr = M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port())\n\t\t\tif serverAddr.Port == 0 {\n\t\t\t\tserverAddr.Port = 443\n\t\t\t}\n\t\t\ttlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.AddrString(), option.OutboundTLSOptions{\n\t\t\t\tALPN: []string{http2.NextProtoTLS, \"http/1.1\"},\n\t\t\t}))\n\t\t\treturn transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, tlsConfig), nil\n\t\tcase \"http\":\n\t\t\tserverAddr = M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port())\n\t\t\tif serverAddr.Port == 0 {\n\t\t\t\tserverAddr.Port = 80\n\t\t\t}\n\t\t\treturn transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, nil), nil\n\t\t// case \"tls\":\n\t\tdefault:\n\t\t\treturn nil, E.New(\"unknown resolver scheme: \", serverURL.Scheme)\n\t\t}\n\t}\n}\n\nfunc buildRoutePrefixes(routeConfig *router.Config) []netip.Prefix {\n\tvar builder netipx.IPSetBuilder\n\tfor _, localAddr := range routeConfig.LocalAddrs {\n\t\tbuilder.AddPrefix(localAddr)\n\t}\n\tfor _, route := range routeConfig.Routes {\n\t\tbuilder.AddPrefix(route)\n\t}\n\tfor _, route := range routeConfig.LocalRoutes {\n\t\tbuilder.AddPrefix(route)\n\t}\n\tfor _, route := range routeConfig.SubnetRoutes {\n\t\tbuilder.AddPrefix(route)\n\t}\n\tipSet, err := builder.IPSet()\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn ipSet.Prefixes()\n}\n\nfunc (t *DNSTransport) Close() error {\n\treturn nil\n}\n\nfunc (t *DNSTransport) Raw() bool {\n\treturn true\n}\n\nfunc (t *DNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tif len(message.Question) != 1 {\n\t\treturn nil, os.ErrInvalid\n\t}\n\tquestion := message.Question[0]\n\taddresses, hostsLoaded := t.hosts[question.Name]\n\tif hostsLoaded {\n\t\tswitch question.Qtype {\n\t\tcase mDNS.TypeA:\n\t\t\taddresses4 := common.Filter(addresses, func(addr netip.Addr) bool {\n\t\t\t\treturn addr.Is4()\n\t\t\t})\n\t\t\tif len(addresses4) > 0 {\n\t\t\t\treturn dns.FixedResponse(message.Id, question, addresses4, C.DefaultDNSTTL), nil\n\t\t\t}\n\t\tcase mDNS.TypeAAAA:\n\t\t\taddresses6 := common.Filter(addresses, func(addr netip.Addr) bool {\n\t\t\t\treturn addr.Is6()\n\t\t\t})\n\t\t\tif len(addresses6) > 0 {\n\t\t\t\treturn dns.FixedResponse(message.Id, question, addresses6, C.DefaultDNSTTL), nil\n\t\t\t}\n\t\t}\n\t}\n\tfor domainSuffix, transports := range t.routes {\n\t\tif strings.HasSuffix(question.Name, domainSuffix) {\n\t\t\tif len(transports) == 0 {\n\t\t\t\treturn &mDNS.Msg{\n\t\t\t\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\t\t\t\tId:       message.Id,\n\t\t\t\t\t\tRcode:    mDNS.RcodeNameError,\n\t\t\t\t\t\tResponse: true,\n\t\t\t\t\t},\n\t\t\t\t\tQuestion: []mDNS.Question{question},\n\t\t\t\t}, nil\n\t\t\t}\n\t\t\tvar lastErr error\n\t\t\tfor _, dnsTransport := range transports {\n\t\t\t\tresponse, err := dnsTransport.Exchange(ctx, message)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlastErr = err\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn response, nil\n\t\t\t}\n\t\t\treturn nil, lastErr\n\t\t}\n\t}\n\tif t.acceptDefaultResolvers {\n\t\tif len(t.defaultResolvers) > 0 {\n\t\t\tvar lastErr error\n\t\t\tfor _, resolver := range t.defaultResolvers {\n\t\t\t\tresponse, err := resolver.Exchange(ctx, message)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlastErr = err\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn response, nil\n\t\t\t}\n\t\t\treturn nil, lastErr\n\t\t} else {\n\t\t\treturn nil, E.New(\"missing default resolvers\")\n\t\t}\n\t}\n\treturn nil, dns.RcodeNameError\n}\n\ntype DNSDialer struct {\n\ttransport      *DNSTransport\n\tfallbackDialer N.Dialer\n}\n\nfunc (d *DNSDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tif destination.IsDomain() {\n\t\tpanic(\"invalid request here\")\n\t}\n\tfor _, prefix := range d.transport.routePrefixes {\n\t\tif prefix.Contains(destination.Addr) {\n\t\t\treturn d.transport.endpoint.DialContext(ctx, network, destination)\n\t\t}\n\t}\n\treturn d.fallbackDialer.DialContext(ctx, network, destination)\n}\n\nfunc (d *DNSDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tif destination.IsDomain() {\n\t\tpanic(\"invalid request here\")\n\t}\n\tfor _, prefix := range d.transport.routePrefixes {\n\t\tif prefix.Contains(destination.Addr) {\n\t\t\treturn d.transport.endpoint.ListenPacket(ctx, destination)\n\t\t}\n\t}\n\treturn d.fallbackDialer.ListenPacket(ctx, destination)\n}\n"
  },
  {
    "path": "protocol/tailscale/endpoint.go",
    "content": "//go:build with_gvisor\n\npackage tailscale\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/sagernet/gvisor/pkg/tcpip\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/header\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/stack\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/endpoint\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/route/rule\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing-tun/ping\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/filemanager\"\n\t_ \"github.com/sagernet/tailscale/feature/relayserver\"\n\t\"github.com/sagernet/tailscale/ipn\"\n\ttsDNS \"github.com/sagernet/tailscale/net/dns\"\n\t\"github.com/sagernet/tailscale/net/netmon\"\n\t\"github.com/sagernet/tailscale/net/netns\"\n\t\"github.com/sagernet/tailscale/net/tsaddr\"\n\ttsTUN \"github.com/sagernet/tailscale/net/tstun\"\n\t\"github.com/sagernet/tailscale/tsnet\"\n\t\"github.com/sagernet/tailscale/types/ipproto\"\n\t\"github.com/sagernet/tailscale/types/nettype\"\n\t\"github.com/sagernet/tailscale/version\"\n\t\"github.com/sagernet/tailscale/wgengine\"\n\t\"github.com/sagernet/tailscale/wgengine/filter\"\n\t\"github.com/sagernet/tailscale/wgengine/router\"\n\t\"github.com/sagernet/tailscale/wgengine/wgcfg\"\n\n\t\"go4.org/netipx\"\n)\n\nvar (\n\t_ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)\n\t_ adapter.DirectRouteOutbound         = (*Endpoint)(nil)\n\t_ dialer.PacketDialerWithDestination  = (*Endpoint)(nil)\n)\n\nfunc init() {\n\tversion.SetVersion(\"sing-box \" + C.Version)\n}\n\nfunc RegisterEndpoint(registry *endpoint.Registry) {\n\tendpoint.Register[option.TailscaleEndpointOptions](registry, C.TypeTailscale, NewEndpoint)\n}\n\ntype Endpoint struct {\n\tendpoint.Adapter\n\tctx               context.Context\n\trouter            adapter.Router\n\tlogger            logger.ContextLogger\n\tdnsRouter         adapter.DNSRouter\n\tnetwork           adapter.NetworkManager\n\tplatformInterface adapter.PlatformInterface\n\tserver            *tsnet.Server\n\tstack             *stack.Stack\n\ticmpForwarder     *tun.ICMPForwarder\n\tfilter            *atomic.Pointer[filter.Filter]\n\tonReconfigHook    wgengine.ReconfigListener\n\n\tcfg           *wgcfg.Config\n\tdnsCfg        *tsDNS.Config\n\trouteDomains  common.TypedValue[map[string]bool]\n\troutePrefixes atomic.Pointer[netipx.IPSet]\n\n\tacceptRoutes               bool\n\texitNode                   string\n\texitNodeAllowLANAccess     bool\n\tadvertiseRoutes            []netip.Prefix\n\tadvertiseExitNode          bool\n\tadvertiseTags              []string\n\trelayServerPort            *uint16\n\trelayServerStaticEndpoints []netip.AddrPort\n\n\tudpTimeout time.Duration\n\n\tsystemInterface     bool\n\tsystemInterfaceName string\n\tsystemInterfaceMTU  uint32\n\tsystemTun           tun.Tun\n\tsystemDialer        *dialer.DefaultDialer\n\tfallbackTCPCloser   func()\n}\n\nfunc (t *Endpoint) registerNetstackHandlers() {\n\tnetstack := t.server.ExportNetstack()\n\tif netstack == nil {\n\t\treturn\n\t}\n\tpreviousTCP := netstack.GetTCPHandlerForFlow\n\tnetstack.GetTCPHandlerForFlow = func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) {\n\t\tif previousTCP != nil {\n\t\t\thandler, intercept = previousTCP(src, dst)\n\t\t\tif handler != nil || !intercept {\n\t\t\t\treturn handler, intercept\n\t\t\t}\n\t\t}\n\t\treturn func(conn net.Conn) {\n\t\t\tctx := log.ContextWithNewID(t.ctx)\n\t\t\tsource := M.SocksaddrFrom(src.Addr(), src.Port())\n\t\t\tdestination := M.SocksaddrFrom(dst.Addr(), dst.Port())\n\t\t\tt.NewConnectionEx(ctx, conn, source, destination, nil)\n\t\t}, true\n\t}\n\n\tpreviousUDP := netstack.GetUDPHandlerForFlow\n\tnetstack.GetUDPHandlerForFlow = func(src, dst netip.AddrPort) (handler func(nettype.ConnPacketConn), intercept bool) {\n\t\tif previousUDP != nil {\n\t\t\thandler, intercept = previousUDP(src, dst)\n\t\t\tif handler != nil || !intercept {\n\t\t\t\treturn handler, intercept\n\t\t\t}\n\t\t}\n\t\treturn func(conn nettype.ConnPacketConn) {\n\t\t\tctx := log.ContextWithNewID(t.ctx)\n\t\t\tsource := M.SocksaddrFrom(src.Addr(), src.Port())\n\t\t\tdestination := M.SocksaddrFrom(dst.Addr(), dst.Port())\n\t\t\tpacketConn := bufio.NewUnbindPacketConnWithAddr(conn, destination)\n\t\t\tt.NewPacketConnectionEx(ctx, packetConn, source, destination, nil)\n\t\t}, true\n\t}\n}\n\nfunc NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TailscaleEndpointOptions) (adapter.Endpoint, error) {\n\tstateDirectory := options.StateDirectory\n\tif stateDirectory == \"\" {\n\t\tstateDirectory = \"tailscale\"\n\t}\n\thostname := options.Hostname\n\tif hostname == \"\" {\n\t\tosHostname, _ := os.Hostname()\n\t\tosHostname = strings.TrimSpace(osHostname)\n\t\thostname = osHostname\n\t}\n\tif hostname == \"\" {\n\t\thostname = \"sing-box\"\n\t}\n\tstateDirectory = filemanager.BasePath(ctx, os.ExpandEnv(stateDirectory))\n\tstateDirectory, _ = filepath.Abs(stateDirectory)\n\tfor _, advertiseRoute := range options.AdvertiseRoutes {\n\t\tif advertiseRoute.Addr().IsUnspecified() && advertiseRoute.Bits() == 0 {\n\t\t\treturn nil, E.New(\"`advertise_routes` cannot be default, use `advertise_exit_node` instead.\")\n\t\t}\n\t}\n\tif options.AdvertiseExitNode && options.ExitNode != \"\" {\n\t\treturn nil, E.New(\"cannot advertise an exit node and use an exit node at the same time.\")\n\t}\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\tvar remoteIsDomain bool\n\tif options.ControlURL != \"\" {\n\t\tcontrolURL, err := url.Parse(options.ControlURL)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse control URL\")\n\t\t}\n\t\tremoteIsDomain = M.ParseSocksaddr(controlURL.Hostname()).IsDomain()\n\t} else {\n\t\t// controlplane.tailscale.com\n\t\tremoteIsDomain = true\n\t}\n\toutboundDialer, err := dialer.NewWithOptions(dialer.Options{\n\t\tContext:          ctx,\n\t\tOptions:          options.DialerOptions,\n\t\tRemoteIsDomain:   remoteIsDomain,\n\t\tResolverOnDetour: true,\n\t\tNewDialer:        true,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdnsRouter := service.FromContext[adapter.DNSRouter](ctx)\n\tserver := &tsnet.Server{\n\t\tDir:      stateDirectory,\n\t\tHostname: hostname,\n\t\tLogf: func(format string, args ...any) {\n\t\t\tlogger.Trace(fmt.Sprintf(format, args...))\n\t\t},\n\t\tUserLogf: func(format string, args ...any) {\n\t\t\tlogger.Debug(fmt.Sprintf(format, args...))\n\t\t},\n\t\tEphemeral:     options.Ephemeral,\n\t\tAuthKey:       options.AuthKey,\n\t\tControlURL:    options.ControlURL,\n\t\tAdvertiseTags: options.AdvertiseTags,\n\t\tDialer:        &endpointDialer{Dialer: outboundDialer, logger: logger},\n\t\tLookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) {\n\t\t\treturn dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions())\n\t\t},\n\t\tDNS: &dnsConfigurtor{},\n\t\tHTTPClient: &http.Client{\n\t\t\tTransport: &http.Transport{\n\t\t\t\tForceAttemptHTTP2: true,\n\t\t\t\tDialContext: func(ctx context.Context, network, address string) (net.Conn, error) {\n\t\t\t\t\treturn outboundDialer.DialContext(ctx, network, M.ParseSocksaddr(address))\n\t\t\t\t},\n\t\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\t\tRootCAs: adapter.RootPoolFromContext(ctx),\n\t\t\t\t\tTime:    ntp.TimeFuncFromContext(ctx),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\treturn &Endpoint{\n\t\tAdapter:                    endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),\n\t\tctx:                        ctx,\n\t\trouter:                     router,\n\t\tlogger:                     logger,\n\t\tdnsRouter:                  dnsRouter,\n\t\tnetwork:                    service.FromContext[adapter.NetworkManager](ctx),\n\t\tplatformInterface:          service.FromContext[adapter.PlatformInterface](ctx),\n\t\tserver:                     server,\n\t\tacceptRoutes:               options.AcceptRoutes,\n\t\texitNode:                   options.ExitNode,\n\t\texitNodeAllowLANAccess:     options.ExitNodeAllowLANAccess,\n\t\tadvertiseRoutes:            options.AdvertiseRoutes,\n\t\tadvertiseExitNode:          options.AdvertiseExitNode,\n\t\tadvertiseTags:              options.AdvertiseTags,\n\t\trelayServerPort:            options.RelayServerPort,\n\t\trelayServerStaticEndpoints: options.RelayServerStaticEndpoints,\n\t\tudpTimeout:                 udpTimeout,\n\t\tsystemInterface:            options.SystemInterface,\n\t\tsystemInterfaceName:        options.SystemInterfaceName,\n\t\tsystemInterfaceMTU:         options.SystemInterfaceMTU,\n\t}, nil\n}\n\nfunc (t *Endpoint) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif t.platformInterface != nil {\n\t\terr := t.network.UpdateInterfaces()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnetmon.RegisterInterfaceGetter(func() ([]netmon.Interface, error) {\n\t\t\treturn common.Map(t.network.InterfaceFinder().Interfaces(), func(it control.Interface) netmon.Interface {\n\t\t\t\treturn netmon.Interface{\n\t\t\t\t\tInterface: &net.Interface{\n\t\t\t\t\t\tIndex:        it.Index,\n\t\t\t\t\t\tMTU:          it.MTU,\n\t\t\t\t\t\tName:         it.Name,\n\t\t\t\t\t\tHardwareAddr: it.HardwareAddr,\n\t\t\t\t\t\tFlags:        it.Flags,\n\t\t\t\t\t},\n\t\t\t\t\tAltAddrs: common.Map(it.Addresses, func(it netip.Prefix) net.Addr {\n\t\t\t\t\t\treturn &net.IPNet{\n\t\t\t\t\t\t\tIP:   it.Addr().AsSlice(),\n\t\t\t\t\t\t\tMask: net.CIDRMask(it.Bits(), it.Addr().BitLen()),\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t}), nil\n\t\t})\n\t}\n\tif t.systemInterface {\n\t\tmtu := t.systemInterfaceMTU\n\t\tif mtu == 0 {\n\t\t\tmtu = uint32(tsTUN.DefaultTUNMTU())\n\t\t}\n\t\ttunName := t.systemInterfaceName\n\t\tif tunName == \"\" {\n\t\t\ttunName = tun.CalculateInterfaceName(\"tailscale\")\n\t\t}\n\t\ttunOptions := tun.Options{\n\t\t\tName:                      tunName,\n\t\t\tMTU:                       mtu,\n\t\t\tGSO:                       true,\n\t\t\tInterfaceScope:            true,\n\t\t\tInterfaceMonitor:          t.network.InterfaceMonitor(),\n\t\t\tInterfaceFinder:           t.network.InterfaceFinder(),\n\t\t\tLogger:                    t.logger,\n\t\t\tEXP_ExternalConfiguration: true,\n\t\t}\n\t\tsystemTun, err := tun.New(tunOptions)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = systemTun.Start()\n\t\tif err != nil {\n\t\t\t_ = systemTun.Close()\n\t\t\treturn err\n\t\t}\n\t\twgTunDevice, err := newTunDeviceAdapter(systemTun, int(mtu), t.logger)\n\t\tif err != nil {\n\t\t\t_ = systemTun.Close()\n\t\t\treturn err\n\t\t}\n\t\tsystemDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{\n\t\t\tBindInterface: tunName,\n\t\t})\n\t\tif err != nil {\n\t\t\t_ = systemTun.Close()\n\t\t\treturn err\n\t\t}\n\t\tt.systemTun = systemTun\n\t\tt.systemDialer = systemDialer\n\t\tt.server.TunDevice = wgTunDevice\n\t}\n\tif mark := t.network.AutoRedirectOutputMark(); mark > 0 {\n\t\tcontrolFunc := t.network.AutoRedirectOutputMarkFunc()\n\t\tif bindFunc := t.network.AutoDetectInterfaceFunc(); bindFunc != nil {\n\t\t\tcontrolFunc = control.Append(controlFunc, bindFunc)\n\t\t}\n\t\tnetns.SetControlFunc(controlFunc)\n\t} else if runtime.GOOS == \"android\" && t.platformInterface != nil {\n\t\tnetns.SetControlFunc(func(network, address string, c syscall.RawConn) error {\n\t\t\treturn control.Raw(c, func(fd uintptr) error {\n\t\t\t\treturn t.platformInterface.AutoDetectInterfaceControl(int(fd))\n\t\t\t})\n\t\t})\n\t}\n\terr := t.server.Start()\n\tif err != nil {\n\t\tif t.systemTun != nil {\n\t\t\t_ = t.systemTun.Close()\n\t\t}\n\t\treturn err\n\t}\n\tif t.fallbackTCPCloser == nil {\n\t\tt.fallbackTCPCloser = t.server.RegisterFallbackTCPHandler(func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) {\n\t\t\treturn func(conn net.Conn) {\n\t\t\t\tctx := log.ContextWithNewID(t.ctx)\n\t\t\t\tsource := M.SocksaddrFrom(src.Addr(), src.Port())\n\t\t\t\tdestination := M.SocksaddrFrom(dst.Addr(), dst.Port())\n\t\t\t\tt.NewConnectionEx(ctx, conn, source, destination, nil)\n\t\t\t}, true\n\t\t})\n\t}\n\tt.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)\n\n\tipStack := t.server.ExportNetstack().ExportIPStack()\n\tgErr := ipStack.SetSpoofing(tun.DefaultNIC, true)\n\tif gErr != nil {\n\t\treturn gonet.TranslateNetstackError(gErr)\n\t}\n\tgErr = ipStack.SetPromiscuousMode(tun.DefaultNIC, true)\n\tif gErr != nil {\n\t\treturn gonet.TranslateNetstackError(gErr)\n\t}\n\ticmpForwarder := tun.NewICMPForwarder(t.ctx, ipStack, t, t.udpTimeout)\n\tipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)\n\tipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)\n\tt.stack = ipStack\n\tt.icmpForwarder = icmpForwarder\n\tt.registerNetstackHandlers()\n\n\tlocalBackend := t.server.ExportLocalBackend()\n\tperfs := &ipn.MaskedPrefs{\n\t\tPrefs: ipn.Prefs{\n\t\t\tRouteAll:        t.acceptRoutes,\n\t\t\tAdvertiseRoutes: t.advertiseRoutes,\n\t\t},\n\t\tRouteAllSet:                   true,\n\t\tExitNodeIPSet:                 true,\n\t\tAdvertiseRoutesSet:            true,\n\t\tRelayServerPortSet:            true,\n\t\tRelayServerStaticEndpointsSet: true,\n\t}\n\tif t.advertiseExitNode {\n\t\tperfs.AdvertiseRoutes = append(perfs.AdvertiseRoutes, tsaddr.ExitRoutes()...)\n\t}\n\tif t.relayServerPort != nil {\n\t\tperfs.RelayServerPort = t.relayServerPort\n\t}\n\tif len(t.relayServerStaticEndpoints) > 0 {\n\t\tperfs.RelayServerStaticEndpoints = t.relayServerStaticEndpoints\n\t}\n\t_, err = localBackend.EditPrefs(perfs)\n\tif err != nil {\n\t\treturn E.Cause(err, \"update prefs\")\n\t}\n\tt.filter = localBackend.ExportFilter()\n\tgo t.watchState()\n\treturn nil\n}\n\nfunc (t *Endpoint) watchState() {\n\tlocalBackend := t.server.ExportLocalBackend()\n\tlocalBackend.WatchNotifications(t.ctx, ipn.NotifyInitialState, nil, func(roNotify *ipn.Notify) (keepGoing bool) {\n\t\tif roNotify.State != nil && *roNotify.State != ipn.NeedsLogin && *roNotify.State != ipn.NoState {\n\t\t\treturn false\n\t\t}\n\t\tauthURL := localBackend.StatusWithoutPeers().AuthURL\n\t\tif authURL != \"\" {\n\t\t\tt.logger.Info(\"Waiting for authentication: \", authURL)\n\t\t\tif t.platformInterface != nil {\n\t\t\t\terr := t.platformInterface.SendNotification(&adapter.Notification{\n\t\t\t\t\tIdentifier: \"tailscale-authentication\",\n\t\t\t\t\tTypeName:   \"Tailscale Authentication Notifications\",\n\t\t\t\t\tTypeID:     10,\n\t\t\t\t\tTitle:      \"Tailscale Authentication\",\n\t\t\t\t\tBody:       F.ToString(\"Tailscale outbound[\", t.Tag(), \"] is waiting for authentication.\"),\n\t\t\t\t\tOpenURL:    authURL,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.logger.Error(\"send authentication notification: \", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\tif t.exitNode != \"\" {\n\t\tlocalBackend.WatchNotifications(t.ctx, ipn.NotifyInitialState, nil, func(roNotify *ipn.Notify) (keepGoing bool) {\n\t\t\tif roNotify.State == nil || *roNotify.State != ipn.Running {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tstatus, err := common.Must1(t.server.LocalClient()).Status(t.ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.logger.Error(\"set exit node: \", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tperfs := &ipn.MaskedPrefs{\n\t\t\t\tPrefs: ipn.Prefs{\n\t\t\t\t\tExitNodeAllowLANAccess: t.exitNodeAllowLANAccess,\n\t\t\t\t},\n\t\t\t\tExitNodeIPSet:             true,\n\t\t\t\tExitNodeAllowLANAccessSet: true,\n\t\t\t}\n\t\t\terr = perfs.SetExitNodeIP(t.exitNode, status)\n\t\t\tif err != nil {\n\t\t\t\tt.logger.Error(\"set exit node: \", err)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t_, err = localBackend.EditPrefs(perfs)\n\t\t\tif err != nil {\n\t\t\t\tt.logger.Error(\"set exit node: \", err)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t})\n\t}\n}\n\nfunc (t *Endpoint) Close() error {\n\tnetmon.RegisterInterfaceGetter(nil)\n\tnetns.SetControlFunc(nil)\n\tif t.fallbackTCPCloser != nil {\n\t\tt.fallbackTCPCloser()\n\t\tt.fallbackTCPCloser = nil\n\t}\n\terr := common.Close(common.PtrOrNil(t.server))\n\tif t.systemTun != nil {\n\t\tt.systemTun.Close()\n\t\tt.systemTun = nil\n\t}\n\treturn err\n}\n\nfunc (t *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tswitch network {\n\tcase N.NetworkTCP:\n\t\tt.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\tcase N.NetworkUDP:\n\t\tt.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t}\n\tif destination.IsDomain() {\n\t\tdestinationAddresses, err := t.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn N.DialSerial(ctx, t, network, destination, destinationAddresses)\n\t}\n\tif t.systemDialer != nil {\n\t\treturn t.systemDialer.DialContext(ctx, network, destination)\n\t}\n\taddr4, addr6 := t.server.TailscaleIPs()\n\tremoteAddr := tcpip.FullAddress{\n\t\tNIC:  1,\n\t\tPort: destination.Port,\n\t\tAddr: addressFromAddr(destination.Addr),\n\t}\n\tvar localAddr tcpip.FullAddress\n\tvar networkProtocol tcpip.NetworkProtocolNumber\n\tif destination.IsIPv4() {\n\t\tif !addr4.IsValid() {\n\t\t\treturn nil, E.New(\"missing Tailscale IPv4 address\")\n\t\t}\n\t\tnetworkProtocol = header.IPv4ProtocolNumber\n\t\tlocalAddr = tcpip.FullAddress{\n\t\t\tNIC:  1,\n\t\t\tAddr: addressFromAddr(addr4),\n\t\t}\n\t} else {\n\t\tif !addr6.IsValid() {\n\t\t\treturn nil, E.New(\"missing Tailscale IPv6 address\")\n\t\t}\n\t\tnetworkProtocol = header.IPv6ProtocolNumber\n\t\tlocalAddr = tcpip.FullAddress{\n\t\t\tNIC:  1,\n\t\t\tAddr: addressFromAddr(addr6),\n\t\t}\n\t}\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\ttcpConn, err := gonet.DialTCPWithBind(ctx, t.stack, localAddr, remoteAddr, networkProtocol)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn tcpConn, nil\n\tcase N.NetworkUDP:\n\t\tudpConn, err := gonet.DialUDP(t.stack, &localAddr, &remoteAddr, networkProtocol)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn udpConn, nil\n\tdefault:\n\t\treturn nil, E.Extend(N.ErrUnknownNetwork, network)\n\t}\n}\n\nfunc (t *Endpoint) listenPacketWithAddress(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tif t.systemDialer != nil {\n\t\treturn t.systemDialer.ListenPacket(ctx, destination)\n\t}\n\taddr4, addr6 := t.server.TailscaleIPs()\n\tbind := tcpip.FullAddress{\n\t\tNIC: 1,\n\t}\n\tvar networkProtocol tcpip.NetworkProtocolNumber\n\tif destination.IsIPv4() {\n\t\tif !addr4.IsValid() {\n\t\t\treturn nil, E.New(\"missing Tailscale IPv4 address\")\n\t\t}\n\t\tnetworkProtocol = header.IPv4ProtocolNumber\n\t\tbind.Addr = addressFromAddr(addr4)\n\t} else {\n\t\tif !addr6.IsValid() {\n\t\t\treturn nil, E.New(\"missing Tailscale IPv6 address\")\n\t\t}\n\t\tnetworkProtocol = header.IPv6ProtocolNumber\n\t\tbind.Addr = addressFromAddr(addr6)\n\t}\n\tudpConn, err := gonet.DialUDP(t.stack, &bind, nil, networkProtocol)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn udpConn, nil\n}\n\nfunc (t *Endpoint) ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error) {\n\tt.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\tif destination.IsDomain() {\n\t\tdestinationAddresses, err := t.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, netip.Addr{}, err\n\t\t}\n\t\tvar errors []error\n\t\tfor _, address := range destinationAddresses {\n\t\t\tpacketConn, packetErr := t.listenPacketWithAddress(ctx, M.SocksaddrFrom(address, destination.Port))\n\t\t\tif packetErr == nil {\n\t\t\t\treturn packetConn, address, nil\n\t\t\t}\n\t\t\terrors = append(errors, packetErr)\n\t\t}\n\t\treturn nil, netip.Addr{}, E.Errors(errors...)\n\t}\n\tpacketConn, err := t.listenPacketWithAddress(ctx, destination)\n\tif err != nil {\n\t\treturn nil, netip.Addr{}, err\n\t}\n\tif destination.IsIP() {\n\t\treturn packetConn, destination.Addr, nil\n\t}\n\treturn packetConn, netip.Addr{}, nil\n}\n\nfunc (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tpacketConn, destinationAddress, err := t.ListenPacketWithDestination(ctx, destination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif destinationAddress.IsValid() && destination != M.SocksaddrFrom(destinationAddress, destination.Port) {\n\t\treturn bufio.NewNATPacketConn(bufio.NewPacketConn(packetConn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil\n\t}\n\treturn packetConn, nil\n}\n\nfunc (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\ttsFilter := t.filter.Load()\n\tif tsFilter != nil {\n\t\tvar ipProto ipproto.Proto\n\t\tswitch N.NetworkName(network) {\n\t\tcase N.NetworkTCP:\n\t\t\tipProto = ipproto.TCP\n\t\tcase N.NetworkUDP:\n\t\t\tipProto = ipproto.UDP\n\t\tcase N.NetworkICMP:\n\t\t\tif !destination.IsIPv6() {\n\t\t\t\tipProto = ipproto.ICMPv4\n\t\t\t} else {\n\t\t\t\tipProto = ipproto.ICMPv6\n\t\t\t}\n\t\t}\n\t\tresponse := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto)\n\t\tswitch response {\n\t\tcase filter.Drop:\n\t\t\treturn nil, syscall.ECONNREFUSED\n\t\tcase filter.DropSilently:\n\t\t\treturn nil, tun.ErrDrop\n\t\t}\n\t}\n\tvar ipVersion uint8\n\tif !destination.IsIPv6() {\n\t\tipVersion = 4\n\t} else {\n\t\tipVersion = 6\n\t}\n\trouteDestination, err := t.router.PreMatch(adapter.InboundContext{\n\t\tInbound:     t.Tag(),\n\t\tInboundType: t.Type(),\n\t\tIPVersion:   ipVersion,\n\t\tNetwork:     network,\n\t\tSource:      source,\n\t\tDestination: destination,\n\t}, routeContext, timeout, false)\n\tif err != nil {\n\t\tswitch {\n\t\tcase rule.IsBypassed(err):\n\t\t\terr = nil\n\t\tcase rule.IsRejected(err):\n\t\t\tt.logger.Trace(\"reject \", network, \" connection from \", source.AddrString(), \" to \", destination.AddrString())\n\t\tdefault:\n\t\t\tif network == N.NetworkICMP {\n\t\t\t\tt.logger.Warn(E.Cause(err, \"link \", network, \" connection from \", source.AddrString(), \" to \", destination.AddrString()))\n\t\t\t}\n\t\t}\n\t}\n\treturn routeDestination, err\n}\n\nfunc (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = t.Tag()\n\tmetadata.InboundType = t.Type()\n\tmetadata.Source = source\n\taddr4, addr6 := t.server.TailscaleIPs()\n\tswitch destination.Addr {\n\tcase addr4:\n\t\tdestination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})\n\tcase addr6:\n\t\tdestination.Addr = netip.IPv6Loopback()\n\t}\n\tmetadata.Destination = destination\n\tt.logger.InfoContext(ctx, \"inbound connection from \", source)\n\tt.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\tt.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = t.Tag()\n\tmetadata.InboundType = t.Type()\n\tmetadata.Source = source\n\taddr4, addr6 := t.server.TailscaleIPs()\n\tswitch destination.Addr {\n\tcase addr4:\n\t\tmetadata.OriginDestination = destination\n\t\tdestination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})\n\t\tconn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, destination)\n\tcase addr6:\n\t\tmetadata.OriginDestination = destination\n\t\tdestination.Addr = netip.IPv6Loopback()\n\t\tconn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, destination)\n\t}\n\tmetadata.Destination = destination\n\tt.logger.InfoContext(ctx, \"inbound packet connection from \", source)\n\tt.logger.InfoContext(ctx, \"inbound packet connection to \", metadata.Destination)\n\tt.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (t *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\tctx := log.ContextWithNewID(t.ctx)\n\tvar destination tun.DirectRouteDestination\n\tvar err error\n\tif t.systemDialer != nil {\n\t\tdestination, err = ping.ConnectDestination(\n\t\t\tctx, t.logger,\n\t\t\tt.systemDialer.DialerForICMPDestination(metadata.Destination.Addr).Control,\n\t\t\tmetadata.Destination.Addr, routeContext, timeout,\n\t\t)\n\t} else {\n\t\tinet4Address, inet6Address := t.server.TailscaleIPs()\n\t\tif metadata.Destination.Addr.Is4() && !inet4Address.IsValid() || metadata.Destination.Addr.Is6() && !inet6Address.IsValid() {\n\t\t\treturn nil, E.New(\"Tailscale is not ready yet\")\n\t\t}\n\t\tdestination, err = ping.ConnectGVisor(\n\t\t\tctx, t.logger,\n\t\t\tmetadata.Source.Addr, metadata.Destination.Addr,\n\t\t\trouteContext,\n\t\t\tt.stack,\n\t\t\tinet4Address, inet6Address,\n\t\t\ttimeout,\n\t\t)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tt.logger.InfoContext(ctx, \"linked \", metadata.Network, \" connection from \", metadata.Source.AddrString(), \" to \", metadata.Destination.AddrString())\n\treturn destination, nil\n}\n\nfunc (t *Endpoint) PreferredDomain(domain string) bool {\n\trouteDomains := t.routeDomains.Load()\n\tif routeDomains == nil {\n\t\treturn false\n\t}\n\treturn routeDomains[strings.ToLower(domain)]\n}\n\nfunc (t *Endpoint) PreferredAddress(address netip.Addr) bool {\n\troutePrefixes := t.routePrefixes.Load()\n\tif routePrefixes == nil {\n\t\treturn false\n\t}\n\treturn routePrefixes.Contains(address)\n}\n\nfunc (t *Endpoint) Server() *tsnet.Server {\n\treturn t.server\n}\n\nfunc (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *tsDNS.Config) {\n\tif cfg == nil || dnsCfg == nil {\n\t\treturn\n\t}\n\tif (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {\n\t\treturn\n\t}\n\tvar inet4Address, inet6Address netip.Addr\n\tfor _, address := range cfg.Addresses {\n\t\tif address.Addr().Is4() {\n\t\t\tinet4Address = address.Addr()\n\t\t} else if address.Addr().Is6() {\n\t\t\tinet6Address = address.Addr()\n\t\t}\n\t}\n\tt.icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)\n\tt.cfg = cfg\n\tt.dnsCfg = dnsCfg\n\n\trouteDomains := make(map[string]bool)\n\tfor fqdn := range dnsCfg.Routes {\n\t\trouteDomains[fqdn.WithoutTrailingDot()] = true\n\t}\n\tfor _, fqdn := range dnsCfg.SearchDomains {\n\t\trouteDomains[fqdn.WithoutTrailingDot()] = true\n\t}\n\tt.routeDomains.Store(routeDomains)\n\n\tvar builder netipx.IPSetBuilder\n\tfor _, peer := range cfg.Peers {\n\t\tfor _, allowedIP := range peer.AllowedIPs {\n\t\t\tbuilder.AddPrefix(allowedIP)\n\t\t}\n\t}\n\tt.routePrefixes.Store(common.Must1(builder.IPSet()))\n\n\tif t.onReconfigHook != nil {\n\t\tt.onReconfigHook(cfg, routerCfg, dnsCfg)\n\t}\n}\n\nfunc addressFromAddr(destination netip.Addr) tcpip.Address {\n\tif destination.Is6() {\n\t\treturn tcpip.AddrFrom16(destination.As16())\n\t} else {\n\t\treturn tcpip.AddrFrom4(destination.As4())\n\t}\n}\n\ntype endpointDialer struct {\n\tN.Dialer\n\tlogger logger.ContextLogger\n}\n\nfunc (d *endpointDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\td.logger.InfoContext(ctx, \"output connection to \", destination)\n\tcase N.NetworkUDP:\n\t\td.logger.InfoContext(ctx, \"output packet connection to \", destination)\n\t}\n\treturn d.Dialer.DialContext(ctx, network, destination)\n}\n\nfunc (d *endpointDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\td.logger.InfoContext(ctx, \"output packet connection\")\n\treturn d.Dialer.ListenPacket(ctx, destination)\n}\n\ntype dnsConfigurtor struct {\n\tbaseConfig tsDNS.OSConfig\n}\n\nfunc (c *dnsConfigurtor) SetDNS(cfg tsDNS.OSConfig) error {\n\tc.baseConfig = cfg\n\treturn nil\n}\n\nfunc (c *dnsConfigurtor) SupportsSplitDNS() bool {\n\treturn true\n}\n\nfunc (c *dnsConfigurtor) GetBaseConfig() (tsDNS.OSConfig, error) {\n\treturn c.baseConfig, nil\n}\n\nfunc (c *dnsConfigurtor) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "protocol/tailscale/tun_device_unix.go",
    "content": "//go:build with_gvisor && !windows\n\npackage tailscale\n\nimport (\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\tsingTun \"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/logger\"\n\twgTun \"github.com/sagernet/wireguard-go/tun\"\n)\n\ntype tunDeviceAdapter struct {\n\ttun        singTun.Tun\n\tlinuxTUN   singTun.LinuxTUN\n\tevents     chan wgTun.Event\n\tmtu        int\n\tlogger     logger.ContextLogger\n\tdebugTun   bool\n\treadCount  atomic.Uint32\n\twriteCount atomic.Uint32\n\tcloseOnce  sync.Once\n}\n\nfunc newTunDeviceAdapter(tun singTun.Tun, mtu int, logger logger.ContextLogger) (wgTun.Device, error) {\n\tif tun == nil {\n\t\treturn nil, os.ErrInvalid\n\t}\n\tif mtu == 0 {\n\t\tmtu = 1500\n\t}\n\tadapter := &tunDeviceAdapter{\n\t\ttun:      tun,\n\t\tevents:   make(chan wgTun.Event, 1),\n\t\tmtu:      mtu,\n\t\tlogger:   logger,\n\t\tdebugTun: os.Getenv(\"SINGBOX_TS_TUN_DEBUG\") != \"\",\n\t}\n\tif linuxTUN, ok := tun.(singTun.LinuxTUN); ok {\n\t\tadapter.linuxTUN = linuxTUN\n\t}\n\tadapter.events <- wgTun.EventUp\n\treturn adapter, nil\n}\n\nfunc (a *tunDeviceAdapter) File() *os.File {\n\treturn nil\n}\n\nfunc (a *tunDeviceAdapter) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {\n\tif a.linuxTUN != nil {\n\t\tn, err := a.linuxTUN.BatchRead(bufs, offset-singTun.PacketOffset, sizes)\n\t\tif err == nil {\n\t\t\tfor i := 0; i < n; i++ {\n\t\t\t\ta.debugPacket(\"read\", bufs[i][offset:offset+sizes[i]])\n\t\t\t}\n\t\t}\n\t\treturn n, err\n\t}\n\tif offset < singTun.PacketOffset {\n\t\treturn 0, io.ErrShortBuffer\n\t}\n\treadBuf := bufs[0][offset-singTun.PacketOffset:]\n\tn, err := a.tun.Read(readBuf)\n\tif err == nil {\n\t\tif n < singTun.PacketOffset {\n\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t}\n\t\tsizes[0] = n - singTun.PacketOffset\n\t\ta.debugPacket(\"read\", readBuf[singTun.PacketOffset:n])\n\t\treturn 1, nil\n\t}\n\tif errors.Is(err, singTun.ErrTooManySegments) {\n\t\terr = wgTun.ErrTooManySegments\n\t}\n\treturn 0, err\n}\n\nfunc (a *tunDeviceAdapter) Write(bufs [][]byte, offset int) (count int, err error) {\n\tif a.linuxTUN != nil {\n\t\tfor i := range bufs {\n\t\t\ta.debugPacket(\"write\", bufs[i][offset:])\n\t\t}\n\t\treturn a.linuxTUN.BatchWrite(bufs, offset)\n\t}\n\tfor _, packet := range bufs {\n\t\ta.debugPacket(\"write\", packet[offset:])\n\t\tif singTun.PacketOffset > 0 {\n\t\t\tcommon.ClearArray(packet[offset-singTun.PacketOffset : offset])\n\t\t\tsingTun.PacketFillHeader(packet[offset-singTun.PacketOffset:], singTun.PacketIPVersion(packet[offset:]))\n\t\t}\n\t\t_, err = a.tun.Write(packet[offset-singTun.PacketOffset:])\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\t// WireGuard will not read count.\n\treturn 0, nil\n}\n\nfunc (a *tunDeviceAdapter) MTU() (int, error) {\n\treturn a.mtu, nil\n}\n\nfunc (a *tunDeviceAdapter) Name() (string, error) {\n\treturn a.tun.Name()\n}\n\nfunc (a *tunDeviceAdapter) Events() <-chan wgTun.Event {\n\treturn a.events\n}\n\nfunc (a *tunDeviceAdapter) Close() error {\n\tvar err error\n\ta.closeOnce.Do(func() {\n\t\tclose(a.events)\n\t\terr = a.tun.Close()\n\t})\n\treturn err\n}\n\nfunc (a *tunDeviceAdapter) BatchSize() int {\n\tif a.linuxTUN != nil {\n\t\treturn a.linuxTUN.BatchSize()\n\t}\n\treturn 1\n}\n\nfunc (a *tunDeviceAdapter) debugPacket(direction string, packet []byte) {\n\tif !a.debugTun || a.logger == nil {\n\t\treturn\n\t}\n\tvar counter *atomic.Uint32\n\tswitch direction {\n\tcase \"read\":\n\t\tcounter = &a.readCount\n\tcase \"write\":\n\t\tcounter = &a.writeCount\n\tdefault:\n\t\treturn\n\t}\n\tif counter.Add(1) > 8 {\n\t\treturn\n\t}\n\tsample := packet\n\tif len(sample) > 64 {\n\t\tsample = sample[:64]\n\t}\n\ta.logger.Trace(\"tailscale tun \", direction, \" len=\", len(packet), \" head=\", hex.EncodeToString(sample))\n}\n"
  },
  {
    "path": "protocol/tailscale/tun_device_windows.go",
    "content": "//go:build with_gvisor && windows\n\npackage tailscale\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\tsingTun \"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common/logger\"\n\twgTun \"github.com/sagernet/wireguard-go/tun\"\n)\n\ntype tunDeviceAdapter struct {\n\ttun       singTun.WinTun\n\tnativeTun *singTun.NativeTun\n\tevents    chan wgTun.Event\n\tmtu       atomic.Int64\n\tcloseOnce sync.Once\n}\n\nfunc newTunDeviceAdapter(tun singTun.Tun, mtu int, _ logger.ContextLogger) (wgTun.Device, error) {\n\twinTun, ok := tun.(singTun.WinTun)\n\tif !ok {\n\t\treturn nil, errors.New(\"not a windows tun device\")\n\t}\n\tnativeTun, ok := winTun.(*singTun.NativeTun)\n\tif !ok {\n\t\treturn nil, errors.New(\"unsupported windows tun device\")\n\t}\n\tif mtu == 0 {\n\t\tmtu = 1500\n\t}\n\tadapter := &tunDeviceAdapter{\n\t\ttun:       winTun,\n\t\tnativeTun: nativeTun,\n\t\tevents:    make(chan wgTun.Event, 1),\n\t}\n\tadapter.mtu.Store(int64(mtu))\n\tadapter.events <- wgTun.EventUp\n\treturn adapter, nil\n}\n\nfunc (a *tunDeviceAdapter) File() *os.File {\n\treturn nil\n}\n\nfunc (a *tunDeviceAdapter) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {\n\tpacket, release, err := a.tun.ReadPacket()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer release()\n\tsizes[0] = copy(bufs[0][offset-singTun.PacketOffset:], packet)\n\treturn 1, nil\n}\n\nfunc (a *tunDeviceAdapter) Write(bufs [][]byte, offset int) (count int, err error) {\n\tfor _, packet := range bufs {\n\t\tif singTun.PacketOffset > 0 {\n\t\t\tsingTun.PacketFillHeader(packet[offset-singTun.PacketOffset:], singTun.PacketIPVersion(packet[offset:]))\n\t\t}\n\t\t_, err = a.tun.Write(packet[offset-singTun.PacketOffset:])\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\treturn 0, nil\n}\n\nfunc (a *tunDeviceAdapter) MTU() (int, error) {\n\treturn int(a.mtu.Load()), nil\n}\n\nfunc (a *tunDeviceAdapter) ForceMTU(mtu int) {\n\tif mtu <= 0 {\n\t\treturn\n\t}\n\tupdate := int(a.mtu.Load()) != mtu\n\ta.mtu.Store(int64(mtu))\n\tif update {\n\t\tselect {\n\t\tcase a.events <- wgTun.EventMTUUpdate:\n\t\tdefault:\n\t\t}\n\t}\n}\n\nfunc (a *tunDeviceAdapter) LUID() uint64 {\n\tif a.nativeTun == nil {\n\t\treturn 0\n\t}\n\treturn a.nativeTun.LUID()\n}\n\nfunc (a *tunDeviceAdapter) Name() (string, error) {\n\treturn a.tun.Name()\n}\n\nfunc (a *tunDeviceAdapter) Events() <-chan wgTun.Event {\n\treturn a.events\n}\n\nfunc (a *tunDeviceAdapter) Close() error {\n\tvar err error\n\ta.closeOnce.Do(func() {\n\t\tclose(a.events)\n\t\terr = a.tun.Close()\n\t})\n\treturn err\n}\n\nfunc (a *tunDeviceAdapter) BatchSize() int {\n\treturn 1\n}\n"
  },
  {
    "path": "protocol/tor/outbound.go",
    "content": "package tor\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/rw\"\n\t\"github.com/sagernet/sing/protocol/socks\"\n\n\t\"github.com/cretz/bine/control\"\n\t\"github.com/cretz/bine/tor\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.TorOutboundOptions](registry, C.TypeTor, NewOutbound)\n}\n\ntype Outbound struct {\n\toutbound.Adapter\n\tctx         context.Context\n\tlogger      logger.ContextLogger\n\tproxy       *ProxyListener\n\tstartConf   *tor.StartConf\n\toptions     map[string]string\n\tevents      chan control.Event\n\tinstance    *tor.Tor\n\tsocksClient *socks.Client\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (adapter.Outbound, error) {\n\tvar startConf tor.StartConf\n\tstartConf.DataDir = os.ExpandEnv(options.DataDirectory)\n\tstartConf.TempDataDirBase = os.TempDir()\n\tstartConf.ExtraArgs = options.ExtraArgs\n\tif options.DataDirectory != \"\" {\n\t\tdataDirAbs, _ := filepath.Abs(startConf.DataDir)\n\t\tif geoIPPath := filepath.Join(dataDirAbs, \"geoip\"); rw.IsFile(geoIPPath) && !common.Contains(options.ExtraArgs, \"--GeoIPFile\") {\n\t\t\toptions.ExtraArgs = append(options.ExtraArgs, \"--GeoIPFile\", geoIPPath)\n\t\t}\n\t\tif geoIP6Path := filepath.Join(dataDirAbs, \"geoip6\"); rw.IsFile(geoIP6Path) && !common.Contains(options.ExtraArgs, \"--GeoIPv6File\") {\n\t\t\toptions.ExtraArgs = append(options.ExtraArgs, \"--GeoIPv6File\", geoIP6Path)\n\t\t}\n\t}\n\tif options.ExecutablePath != \"\" {\n\t\tstartConf.ExePath = options.ExecutablePath\n\t\tstartConf.ProcessCreator = nil\n\t\tstartConf.UseEmbeddedControlConn = false\n\t}\n\tif startConf.DataDir != \"\" {\n\t\ttorrcFile := filepath.Join(startConf.DataDir, \"torrc\")\n\t\terr := rw.MkdirParent(torrcFile)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !rw.IsFile(torrcFile) {\n\t\t\terr := os.WriteFile(torrcFile, []byte(\"\"), 0o600)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tstartConf.TorrcFile = torrcFile\n\t}\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Outbound{\n\t\tAdapter:   outbound.NewAdapterWithDialerOptions(C.TypeTor, tag, []string{N.NetworkTCP}, options.DialerOptions),\n\t\tctx:       ctx,\n\t\tlogger:    logger,\n\t\tproxy:     NewProxyListener(ctx, logger, outboundDialer),\n\t\tstartConf: &startConf,\n\t\toptions:   options.Options,\n\t}, nil\n}\n\nfunc (t *Outbound) Start() error {\n\terr := t.start()\n\tif err != nil {\n\t\tt.Close()\n\t}\n\treturn err\n}\n\nvar torLogEvents = []control.EventCode{\n\tcontrol.EventCodeLogDebug,\n\tcontrol.EventCodeLogErr,\n\tcontrol.EventCodeLogInfo,\n\tcontrol.EventCodeLogNotice,\n\tcontrol.EventCodeLogWarn,\n}\n\nfunc (t *Outbound) start() error {\n\ttorInstance, err := tor.Start(t.ctx, t.startConf)\n\tif err != nil {\n\t\treturn E.New(strings.ToLower(err.Error()))\n\t}\n\tt.instance = torInstance\n\tt.events = make(chan control.Event, 8)\n\terr = torInstance.Control.AddEventListener(t.events, torLogEvents...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tgo t.recvLoop()\n\terr = t.proxy.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\tproxyPort := \"127.0.0.1:\" + F.ToString(t.proxy.Port())\n\tproxyUsername := t.proxy.Username()\n\tproxyPassword := t.proxy.Password()\n\tt.logger.Trace(\"created upstream proxy at \", proxyPort)\n\tt.logger.Trace(\"upstream proxy username \", proxyUsername)\n\tt.logger.Trace(\"upstream proxy password \", proxyPassword)\n\tconfOptions := []*control.KeyVal{\n\t\tcontrol.NewKeyVal(\"Socks5Proxy\", proxyPort),\n\t\tcontrol.NewKeyVal(\"Socks5ProxyUsername\", proxyUsername),\n\t\tcontrol.NewKeyVal(\"Socks5ProxyPassword\", proxyPassword),\n\t}\n\terr = torInstance.Control.ResetConf(confOptions...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(t.options) > 0 {\n\t\tfor key, value := range t.options {\n\t\t\tswitch key {\n\t\t\tcase \"Socks5Proxy\",\n\t\t\t\t\"Socks5ProxyUsername\",\n\t\t\t\t\"Socks5ProxyPassword\":\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\terr = torInstance.Control.SetConf(control.NewKeyVal(key, value))\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"set \", key, \"=\", value)\n\t\t\t}\n\t\t}\n\t}\n\terr = torInstance.EnableNetwork(t.ctx, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tinfo, err := torInstance.Control.GetInfo(\"net/listeners/socks\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(info) != 1 || info[0].Key != \"net/listeners/socks\" {\n\t\treturn E.New(\"get socks proxy address\")\n\t}\n\tt.logger.Trace(\"obtained tor socks5 address \", info[0].Val)\n\t// TODO: set password for tor socks5 server if supported\n\tt.socksClient = socks.NewClient(N.SystemDialer, M.ParseSocksaddr(info[0].Val), socks.Version5, \"\", \"\")\n\treturn nil\n}\n\nfunc (t *Outbound) recvLoop() {\n\tfor rawEvent := range t.events {\n\t\tswitch event := rawEvent.(type) {\n\t\tcase *control.LogEvent:\n\t\t\tevent.Raw = strings.ToLower(event.Raw)\n\t\t\tswitch event.Severity {\n\t\t\tcase control.EventCodeLogDebug, control.EventCodeLogInfo:\n\t\t\t\tt.logger.Trace(event.Raw)\n\t\t\tcase control.EventCodeLogNotice:\n\t\t\t\tif strings.Contains(event.Raw, \"disablenetwork\") || strings.Contains(event.Raw, \"socks listener\") {\n\t\t\t\t\tt.logger.Trace(event.Raw)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tt.logger.Info(event.Raw)\n\t\t\tcase control.EventCodeLogWarn:\n\t\t\t\tt.logger.Warn(event.Raw)\n\t\t\tcase control.EventCodeLogErr:\n\t\t\t\tt.logger.Error(event.Raw)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *Outbound) Close() error {\n\terr := common.Close(\n\t\tcommon.PtrOrNil(t.proxy),\n\t\tcommon.PtrOrNil(t.instance),\n\t)\n\tif t.events != nil {\n\t\tclose(t.events)\n\t\tt.events = nil\n\t}\n\treturn err\n}\n\nfunc (t *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tt.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\treturn t.socksClient.DialContext(ctx, network, destination)\n}\n\nfunc (t *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn nil, os.ErrInvalid\n}\n"
  },
  {
    "path": "protocol/tor/proxy.go",
    "content": "package tor\n\nimport (\n\tstd_bufio \"bufio\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/protocol/socks\"\n\t\"github.com/sagernet/sing/service\"\n)\n\ntype ProxyListener struct {\n\tctx           context.Context\n\tlogger        log.ContextLogger\n\tdialer        N.Dialer\n\tconnection    adapter.ConnectionManager\n\ttcpListener   *net.TCPListener\n\tusername      string\n\tpassword      string\n\tauthenticator *auth.Authenticator\n}\n\nfunc NewProxyListener(ctx context.Context, logger log.ContextLogger, dialer N.Dialer) *ProxyListener {\n\tvar usernameB [64]byte\n\tvar passwordB [64]byte\n\trand.Read(usernameB[:])\n\trand.Read(passwordB[:])\n\tusername := hex.EncodeToString(usernameB[:])\n\tpassword := hex.EncodeToString(passwordB[:])\n\treturn &ProxyListener{\n\t\tctx:           ctx,\n\t\tlogger:        logger,\n\t\tdialer:        dialer,\n\t\tconnection:    service.FromContext[adapter.ConnectionManager](ctx),\n\t\tauthenticator: auth.NewAuthenticator([]auth.User{{Username: username, Password: password}}),\n\t\tusername:      username,\n\t\tpassword:      password,\n\t}\n}\n\nfunc (l *ProxyListener) Start() error {\n\ttcpListener, err := net.ListenTCP(\"tcp\", &net.TCPAddr{\n\t\tIP: net.IPv4(127, 0, 0, 1),\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tl.tcpListener = tcpListener\n\tgo l.acceptLoop()\n\treturn nil\n}\n\nfunc (l *ProxyListener) Port() uint16 {\n\tif l.tcpListener == nil {\n\t\tpanic(\"start listener first\")\n\t}\n\treturn M.SocksaddrFromNet(l.tcpListener.Addr()).Port\n}\n\nfunc (l *ProxyListener) Username() string {\n\treturn l.username\n}\n\nfunc (l *ProxyListener) Password() string {\n\treturn l.password\n}\n\nfunc (l *ProxyListener) Close() error {\n\treturn common.Close(l.tcpListener)\n}\n\nfunc (l *ProxyListener) acceptLoop() {\n\tfor {\n\t\ttcpConn, err := l.tcpListener.AcceptTCP()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tctx := log.ContextWithNewID(l.ctx)\n\t\tgo func() {\n\t\t\thErr := l.accept(ctx, tcpConn)\n\t\t\tif hErr != nil {\n\t\t\t\tif E.IsClosedOrCanceled(hErr) {\n\t\t\t\t\tl.logger.DebugContext(ctx, E.Cause(hErr, \"proxy connection closed\"))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tl.logger.ErrorContext(ctx, E.Cause(hErr, \"proxy\"))\n\t\t\t}\n\t\t}()\n\t}\n}\n\nfunc (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error {\n\treturn socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), l.authenticator, l, nil, 0, M.SocksaddrFromNet(conn.RemoteAddr()), nil)\n}\n\nfunc (l *ProxyListener) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tvar metadata adapter.InboundContext\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\tmetadata.Network = N.NetworkTCP\n\tl.logger.InfoContext(ctx, \"proxy connection to \", metadata.Destination)\n\tl.connection.NewConnection(ctx, l.dialer, conn, metadata, onClose)\n}\n\nfunc (l *ProxyListener) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tvar metadata adapter.InboundContext\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\tmetadata.Network = N.NetworkUDP\n\tl.logger.InfoContext(ctx, \"proxy packet connection to \", metadata.Destination)\n\tl.connection.NewPacketConnection(ctx, l.dialer, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "protocol/trojan/inbound.go",
    "content": "package trojan\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/mux\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/trojan\"\n\t\"github.com/sagernet/sing-box/transport/v2ray\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.TrojanInboundOptions](registry, C.TypeTrojan, NewInbound)\n}\n\nvar _ adapter.TCPInjectableInbound = (*Inbound)(nil)\n\ntype Inbound struct {\n\tinbound.Adapter\n\trouter                   adapter.ConnectionRouterEx\n\tlogger                   log.ContextLogger\n\tlistener                 *listener.Listener\n\tservice                  *trojan.Service[int]\n\tusers                    []option.TrojanUser\n\ttlsConfig                tls.ServerConfig\n\tfallbackAddr             M.Socksaddr\n\tfallbackAddrTLSNextProto map[string]M.Socksaddr\n\ttransport                adapter.V2RayServerTransport\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (adapter.Inbound, error) {\n\tinbound := &Inbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeTrojan, tag),\n\t\trouter:  router,\n\t\tlogger:  logger,\n\t\tusers:   options.Users,\n\t}\n\tif options.TLS != nil {\n\t\ttlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{\n\t\t\tContext: ctx,\n\t\t\tLogger:  logger,\n\t\t\tOptions: common.PtrValueOrDefault(options.TLS),\n\t\t\tKTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == \"\" &&\n\t\t\t\t!common.PtrValueOrDefault(options.Multiplex).Enabled,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tinbound.tlsConfig = tlsConfig\n\t}\n\tvar fallbackHandler N.TCPConnectionHandlerEx\n\tif options.Fallback != nil && options.Fallback.Server != \"\" || len(options.FallbackForALPN) > 0 {\n\t\tif options.Fallback != nil && options.Fallback.Server != \"\" {\n\t\t\tinbound.fallbackAddr = options.Fallback.Build()\n\t\t\tif !inbound.fallbackAddr.IsValid() {\n\t\t\t\treturn nil, E.New(\"invalid fallback address: \", inbound.fallbackAddr)\n\t\t\t}\n\t\t}\n\t\tif len(options.FallbackForALPN) > 0 {\n\t\t\tif inbound.tlsConfig == nil {\n\t\t\t\treturn nil, E.New(\"fallback for ALPN is not supported without TLS\")\n\t\t\t}\n\t\t\tfallbackAddrNextProto := make(map[string]M.Socksaddr)\n\t\t\tfor nextProto, destination := range options.FallbackForALPN {\n\t\t\t\tfallbackAddr := destination.Build()\n\t\t\t\tif !fallbackAddr.IsValid() {\n\t\t\t\t\treturn nil, E.New(\"invalid fallback address for ALPN \", nextProto, \": \", fallbackAddr)\n\t\t\t\t}\n\t\t\t\tfallbackAddrNextProto[nextProto] = fallbackAddr\n\t\t\t}\n\t\t\tinbound.fallbackAddrTLSNextProto = fallbackAddrNextProto\n\t\t}\n\t\tfallbackHandler = adapter.NewUpstreamContextHandlerEx(inbound.fallbackConnection, nil)\n\t}\n\tservice := trojan.NewService[int](adapter.NewUpstreamContextHandlerEx(inbound.newConnection, inbound.newPacketConnection), fallbackHandler, logger)\n\terr := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.TrojanUser) int {\n\t\treturn index\n\t}), common.Map(options.Users, func(it option.TrojanUser) string {\n\t\treturn it.Password\n\t}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif options.Transport != nil {\n\t\tinbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound))\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"create server transport: \", options.Transport.Type)\n\t\t}\n\t}\n\tinbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinbound.service = service\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tNetwork:           []string{N.NetworkTCP},\n\t\tListen:            options.ListenOptions,\n\t\tConnectionHandler: inbound,\n\t})\n\treturn inbound, nil\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif h.tlsConfig != nil {\n\t\terr := h.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"create TLS config\")\n\t\t}\n\t}\n\tif h.transport == nil {\n\t\treturn h.listener.Start()\n\t}\n\tif common.Contains(h.transport.Network(), N.NetworkTCP) {\n\t\ttcpListener, err := h.listener.ListenTCP()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgo func() {\n\t\t\tsErr := h.transport.Serve(tcpListener)\n\t\t\tif sErr != nil && !E.IsClosed(sErr) {\n\t\t\t\th.logger.Error(\"transport serve error: \", sErr)\n\t\t\t}\n\t\t}()\n\t}\n\tif common.Contains(h.transport.Network(), N.NetworkUDP) {\n\t\tudpConn, err := h.listener.ListenUDP()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgo func() {\n\t\t\tsErr := h.transport.ServePacket(udpConn)\n\t\t\tif sErr != nil && !E.IsClosed(sErr) {\n\t\t\t\th.logger.Error(\"transport serve error: \", sErr)\n\t\t\t}\n\t\t}()\n\t}\n\treturn nil\n}\n\nfunc (h *Inbound) Close() error {\n\treturn common.Close(\n\t\th.listener,\n\t\th.tlsConfig,\n\t\th.transport,\n\t)\n}\n\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tif h.tlsConfig != nil && h.transport == nil {\n\t\ttlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)\n\t\tif err != nil {\n\t\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source, \": TLS handshake\"))\n\t\t\treturn\n\t\t}\n\t\tconn = tlsConn\n\t}\n\terr := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)\n\tif err != nil {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source))\n\t}\n}\n\nfunc (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuserIndex, loaded := auth.UserFromContext[int](ctx)\n\tif !loaded {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)\n\t\treturn\n\t}\n\tuser := h.users[userIndex].Name\n\tif user == \"\" {\n\t\tuser = F.ToString(userIndex)\n\t} else {\n\t\tmetadata.User = user\n\t}\n\th.logger.InfoContext(ctx, \"[\", user, \"] inbound connection to \", metadata.Destination)\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuserIndex, loaded := auth.UserFromContext[int](ctx)\n\tif !loaded {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)\n\t\treturn\n\t}\n\tuser := h.users[userIndex].Name\n\tif user == \"\" {\n\t\tuser = F.ToString(userIndex)\n\t} else {\n\t\tmetadata.User = user\n\t}\n\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet connection to \", metadata.Destination)\n\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tvar fallbackAddr M.Socksaddr\n\tif len(h.fallbackAddrTLSNextProto) > 0 {\n\t\tif tlsConn, loaded := common.Cast[tls.Conn](conn); loaded {\n\t\t\tconnectionState := tlsConn.ConnectionState()\n\t\t\tif connectionState.NegotiatedProtocol != \"\" {\n\t\t\t\tif fallbackAddr, loaded = h.fallbackAddrTLSNextProto[connectionState.NegotiatedProtocol]; !loaded {\n\t\t\t\t\th.logger.DebugContext(ctx, \"process connection from \", metadata.Source, \": fallback disabled for ALPN: \", connectionState.NegotiatedProtocol)\n\t\t\t\t\tN.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif !fallbackAddr.IsValid() {\n\t\tif !h.fallbackAddr.IsValid() {\n\t\t\th.logger.DebugContext(ctx, \"process connection from \", metadata.Source, \": fallback disabled by default\")\n\t\t\tN.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)\n\t\t\treturn\n\t\t}\n\t\tfallbackAddr = h.fallbackAddr\n\t}\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tmetadata.Destination = fallbackAddr\n\th.logger.InfoContext(ctx, \"fallback connection to \", fallbackAddr)\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nvar _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil)\n\ntype inboundTransportHandler Inbound\n\nfunc (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tvar metadata adapter.InboundContext\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\th.logger.InfoContext(ctx, \"inbound connection from \", metadata.Source)\n\t(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "protocol/trojan/outbound.go",
    "content": "package trojan\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/mux\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/trojan\"\n\t\"github.com/sagernet/sing-box/transport/v2ray\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.TrojanOutboundOptions](registry, C.TypeTrojan, NewOutbound)\n}\n\ntype Outbound struct {\n\toutbound.Adapter\n\tlogger          logger.ContextLogger\n\tdialer          N.Dialer\n\tserverAddr      M.Socksaddr\n\tkey             [56]byte\n\tmultiplexDialer *mux.Client\n\ttlsConfig       tls.Config\n\ttlsDialer       tls.Dialer\n\ttransport       adapter.V2RayClientTransport\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (adapter.Outbound, error) {\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound := &Outbound{\n\t\tAdapter:    outbound.NewAdapterWithDialerOptions(C.TypeTrojan, tag, options.Network.Build(), options.DialerOptions),\n\t\tlogger:     logger,\n\t\tdialer:     outboundDialer,\n\t\tserverAddr: options.ServerOptions.Build(),\n\t\tkey:        trojan.Key(options.Password),\n\t}\n\tif options.TLS != nil {\n\t\toutbound.tlsConfig, err = tls.NewClientWithOptions(tls.ClientOptions{\n\t\t\tContext:       ctx,\n\t\t\tLogger:        logger,\n\t\t\tServerAddress: options.Server,\n\t\t\tOptions:       common.PtrValueOrDefault(options.TLS),\n\t\t\tKTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == \"\" &&\n\t\t\t\t!common.PtrValueOrDefault(options.Multiplex).Enabled,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\toutbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)\n\t}\n\tif options.Transport != nil {\n\t\toutbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"create client transport: \", options.Transport.Type)\n\t\t}\n\t}\n\toutbound.multiplexDialer, err = mux.NewClientWithOptions((*trojanDialer)(outbound), logger, common.PtrValueOrDefault(options.Multiplex))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn outbound, nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tif h.multiplexDialer == nil {\n\t\tswitch N.NetworkName(network) {\n\t\tcase N.NetworkTCP:\n\t\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\t\tcase N.NetworkUDP:\n\t\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t\t}\n\t\treturn (*trojanDialer)(h).DialContext(ctx, network, destination)\n\t} else {\n\t\tswitch N.NetworkName(network) {\n\t\tcase N.NetworkTCP:\n\t\t\th.logger.InfoContext(ctx, \"outbound multiplex connection to \", destination)\n\t\tcase N.NetworkUDP:\n\t\t\th.logger.InfoContext(ctx, \"outbound multiplex packet connection to \", destination)\n\t\t}\n\t\treturn h.multiplexDialer.DialContext(ctx, network, destination)\n\t}\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tif h.multiplexDialer == nil {\n\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t\treturn (*trojanDialer)(h).ListenPacket(ctx, destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"outbound multiplex packet connection to \", destination)\n\t\treturn h.multiplexDialer.ListenPacket(ctx, destination)\n\t}\n}\n\nfunc (h *Outbound) InterfaceUpdated() {\n\tif h.transport != nil {\n\t\th.transport.Close()\n\t}\n\tif h.multiplexDialer != nil {\n\t\th.multiplexDialer.Reset()\n\t}\n}\n\nfunc (h *Outbound) Close() error {\n\treturn common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)\n}\n\ntype trojanDialer Outbound\n\nfunc (h *trojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tvar conn net.Conn\n\tvar err error\n\tif h.transport != nil {\n\t\tconn, err = h.transport.DialContext(ctx)\n\t} else if h.tlsDialer != nil {\n\t\tconn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)\n\t} else {\n\t\tconn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)\n\t}\n\tif err != nil {\n\t\tcommon.Close(conn)\n\t\treturn nil, err\n\t}\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\treturn trojan.NewClientConn(conn, h.key, destination), nil\n\tcase N.NetworkUDP:\n\t\treturn bufio.NewBindPacketConn(trojan.NewClientPacketConn(conn, h.key), destination), nil\n\tdefault:\n\t\treturn nil, E.Extend(N.ErrUnknownNetwork, network)\n\t}\n}\n\nfunc (h *trojanDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tconn, err := h.DialContext(ctx, N.NetworkUDP, destination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn conn.(net.PacketConn), nil\n}\n"
  },
  {
    "path": "protocol/tuic/inbound.go",
    "content": "package tuic\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/common/uot\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-quic/tuic\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"github.com/gofrs/uuid/v5\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.TUICInboundOptions](registry, C.TypeTUIC, NewInbound)\n}\n\ntype Inbound struct {\n\tinbound.Adapter\n\trouter       adapter.ConnectionRouterEx\n\tlogger       log.ContextLogger\n\tlistener     *listener.Listener\n\ttlsConfig    tls.ServerConfig\n\tserver       *tuic.Service[int]\n\tuserNameList []string\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) {\n\toptions.UDPFragmentDefault = true\n\tif options.TLS == nil || !options.TLS.Enabled {\n\t\treturn nil, C.ErrTLSRequired\n\t}\n\ttlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinbound := &Inbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeTUIC, tag),\n\t\trouter:  uot.NewRouter(router, logger),\n\t\tlogger:  logger,\n\t\tlistener: listener.New(listener.Options{\n\t\t\tContext: ctx,\n\t\t\tLogger:  logger,\n\t\t\tListen:  options.ListenOptions,\n\t\t}),\n\t\ttlsConfig: tlsConfig,\n\t}\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\tservice, err := tuic.NewService[int](tuic.ServiceOptions{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tTLSConfig:         tlsConfig,\n\t\tCongestionControl: options.CongestionControl,\n\t\tAuthTimeout:       time.Duration(options.AuthTimeout),\n\t\tZeroRTTHandshake:  options.ZeroRTTHandshake,\n\t\tHeartbeat:         time.Duration(options.Heartbeat),\n\t\tUDPTimeout:        udpTimeout,\n\t\tHandler:           inbound,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar userList []int\n\tvar userNameList []string\n\tvar userUUIDList [][16]byte\n\tvar userPasswordList []string\n\tfor index, user := range options.Users {\n\t\tif user.UUID == \"\" {\n\t\t\treturn nil, E.New(\"missing uuid for user \", index)\n\t\t}\n\t\tuserUUID, err := uuid.FromString(user.UUID)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"invalid uuid for user \", index)\n\t\t}\n\t\tuserList = append(userList, index)\n\t\tuserNameList = append(userNameList, user.Name)\n\t\tuserUUIDList = append(userUUIDList, userUUID)\n\t\tuserPasswordList = append(userPasswordList, user.Password)\n\t}\n\tservice.UpdateUsers(userList, userUUIDList, userPasswordList)\n\tinbound.server = service\n\tinbound.userNameList = userNameList\n\treturn inbound, nil\n}\n\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tctx = log.ContextWithNewID(ctx)\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tmetadata.OriginDestination = h.listener.UDPAddr()\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\th.logger.InfoContext(ctx, \"inbound connection from \", metadata.Source)\n\tuserID, _ := auth.UserFromContext[int](ctx)\n\tif userName := h.userNameList[userID]; userName != \"\" {\n\t\tmetadata.User = userName\n\t\th.logger.InfoContext(ctx, \"[\", userName, \"] inbound connection to \", metadata.Destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\t}\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tctx = log.ContextWithNewID(ctx)\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\tmetadata.OriginDestination = h.listener.UDPAddr()\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\th.logger.InfoContext(ctx, \"inbound packet connection from \", metadata.Source)\n\tuserID, _ := auth.UserFromContext[int](ctx)\n\tif userName := h.userNameList[userID]; userName != \"\" {\n\t\tmetadata.User = userName\n\t\th.logger.InfoContext(ctx, \"[\", userName, \"] inbound packet connection to \", metadata.Destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"inbound packet connection to \", metadata.Destination)\n\t}\n\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif h.tlsConfig != nil {\n\t\terr := h.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tpacketConn, err := h.listener.ListenUDP()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn h.server.Start(packetConn)\n}\n\nfunc (h *Inbound) Close() error {\n\treturn common.Close(\n\t\th.listener,\n\t\th.tlsConfig,\n\t\tcommon.PtrOrNil(h.server),\n\t)\n}\n"
  },
  {
    "path": "protocol/tuic/outbound.go",
    "content": "package tuic\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-quic/tuic\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/uot\"\n\n\t\"github.com/gofrs/uuid/v5\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, NewOutbound)\n}\n\nvar _ adapter.InterfaceUpdateListener = (*Outbound)(nil)\n\ntype Outbound struct {\n\toutbound.Adapter\n\tlogger    logger.ContextLogger\n\tclient    *tuic.Client\n\tudpStream bool\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) {\n\toptions.UDPFragmentDefault = true\n\tif options.TLS == nil || !options.TLS.Enabled {\n\t\treturn nil, C.ErrTLSRequired\n\t}\n\ttlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tuserUUID, err := uuid.FromString(options.UUID)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"invalid uuid\")\n\t}\n\tvar tuicUDPStream bool\n\tif options.UDPOverStream && options.UDPRelayMode != \"\" {\n\t\treturn nil, E.New(\"udp_over_stream is conflict with udp_relay_mode\")\n\t}\n\tswitch options.UDPRelayMode {\n\tcase \"native\":\n\tcase \"quic\":\n\t\ttuicUDPStream = true\n\t}\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclient, err := tuic.NewClient(tuic.ClientOptions{\n\t\tContext:           ctx,\n\t\tDialer:            outboundDialer,\n\t\tServerAddress:     options.ServerOptions.Build(),\n\t\tTLSConfig:         tlsConfig,\n\t\tUUID:              userUUID,\n\t\tPassword:          options.Password,\n\t\tCongestionControl: options.CongestionControl,\n\t\tUDPStream:         tuicUDPStream,\n\t\tZeroRTTHandshake:  options.ZeroRTTHandshake,\n\t\tHeartbeat:         time.Duration(options.Heartbeat),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Outbound{\n\t\tAdapter:   outbound.NewAdapterWithDialerOptions(C.TypeTUIC, tag, options.Network.Build(), options.DialerOptions),\n\t\tlogger:    logger,\n\t\tclient:    client,\n\t\tudpStream: options.UDPOverStream,\n\t}, nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\t\treturn h.client.DialConn(ctx, destination)\n\tcase N.NetworkUDP:\n\t\tif h.udpStream {\n\t\t\th.logger.InfoContext(ctx, \"outbound stream packet connection to \", destination)\n\t\t\tstreamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn uot.NewLazyConn(streamConn, uot.Request{\n\t\t\t\tIsConnect:   true,\n\t\t\t\tDestination: destination,\n\t\t\t}), nil\n\t\t} else {\n\t\t\tconn, err := h.ListenPacket(ctx, destination)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn bufio.NewBindPacketConn(conn, destination), nil\n\t\t}\n\tdefault:\n\t\treturn nil, E.New(\"unsupported network: \", network)\n\t}\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tif h.udpStream {\n\t\th.logger.InfoContext(ctx, \"outbound stream packet connection to \", destination)\n\t\tstreamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn uot.NewLazyConn(streamConn, uot.Request{\n\t\t\tIsConnect:   false,\n\t\t\tDestination: destination,\n\t\t}), nil\n\t} else {\n\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t\treturn h.client.ListenPacket(ctx)\n\t}\n}\n\nfunc (h *Outbound) InterfaceUpdated() {\n\t_ = h.client.CloseWithError(E.New(\"network changed\"))\n}\n\nfunc (h *Outbound) Close() error {\n\treturn h.client.CloseWithError(os.ErrClosed)\n}\n"
  },
  {
    "path": "protocol/tun/hook.go",
    "content": "package tun\n\nvar HookBeforeCreatePlatformInterface func()\n"
  },
  {
    "path": "protocol/tun/inbound.go",
    "content": "package tun\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/taskmonitor\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/route/rule\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ranges\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"go4.org/netipx\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.TunInboundOptions](registry, C.TypeTun, NewInbound)\n}\n\ntype Inbound struct {\n\ttag                         string\n\tctx                         context.Context\n\trouter                      adapter.Router\n\tnetworkManager              adapter.NetworkManager\n\tlogger                      log.ContextLogger\n\ttunOptions                  tun.Options\n\tudpTimeout                  time.Duration\n\tstack                       string\n\ttunIf                       tun.Tun\n\ttunStack                    tun.Stack\n\tplatformInterface           adapter.PlatformInterface\n\tplatformOptions             option.TunPlatformOptions\n\tautoRedirect                tun.AutoRedirect\n\trouteRuleSet                []adapter.RuleSet\n\trouteRuleSetCallback        []*list.Element[adapter.RuleSetUpdateCallback]\n\trouteExcludeRuleSet         []adapter.RuleSet\n\trouteExcludeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback]\n\trouteAddressSet             []*netipx.IPSet\n\trouteExcludeAddressSet      []*netipx.IPSet\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (adapter.Inbound, error) {\n\t//nolint:staticcheck\n\tif len(options.Inet4Address) > 0 || len(options.Inet6Address) > 0 ||\n\t\tlen(options.Inet4RouteAddress) > 0 || len(options.Inet6RouteAddress) > 0 ||\n\t\tlen(options.Inet4RouteExcludeAddress) > 0 || len(options.Inet6RouteExcludeAddress) > 0 {\n\t\treturn nil, E.New(\"legacy tun address fields are deprecated in sing-box 1.10.0 and removed in sing-box 1.12.0\")\n\t}\n\t//nolint:staticcheck\n\tif options.GSO {\n\t\treturn nil, E.New(\"GSO option in tun is deprecated in sing-box 1.11.0 and removed in sing-box 1.12.0\")\n\t}\n\n\taddress := options.Address\n\tinet4Address := common.Filter(address, func(it netip.Prefix) bool {\n\t\treturn it.Addr().Is4()\n\t})\n\tinet6Address := common.Filter(address, func(it netip.Prefix) bool {\n\t\treturn it.Addr().Is6()\n\t})\n\n\trouteAddress := options.RouteAddress\n\tinet4RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool {\n\t\treturn it.Addr().Is4()\n\t})\n\tinet6RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool {\n\t\treturn it.Addr().Is6()\n\t})\n\n\trouteExcludeAddress := options.RouteExcludeAddress\n\tinet4RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool {\n\t\treturn it.Addr().Is4()\n\t})\n\tinet6RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool {\n\t\treturn it.Addr().Is6()\n\t})\n\n\tplatformInterface := service.FromContext[adapter.PlatformInterface](ctx)\n\ttunMTU := options.MTU\n\tenableGSO := C.IsLinux && options.Stack == \"gvisor\" && platformInterface == nil && tunMTU > 0 && tunMTU < 49152\n\tif tunMTU == 0 {\n\t\tif platformInterface != nil && platformInterface.UnderNetworkExtension() {\n\t\t\t// In Network Extension, when MTU exceeds 4064 (4096-UTUN_IF_HEADROOM_SIZE), the performance of tun will drop significantly, which may be a system bug.\n\t\t\ttunMTU = 4064\n\t\t} else if C.IsAndroid {\n\t\t\t// Some Android devices report ENOBUFS when using MTU 65535\n\t\t\ttunMTU = 9000\n\t\t} else {\n\t\t\ttunMTU = 65535\n\t\t}\n\t}\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\tvar err error\n\tincludeUID := uidToRange(options.IncludeUID)\n\tif len(options.IncludeUIDRange) > 0 {\n\t\tincludeUID, err = parseRange(includeUID, options.IncludeUIDRange)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse include_uid_range\")\n\t\t}\n\t}\n\texcludeUID := uidToRange(options.ExcludeUID)\n\tif len(options.ExcludeUIDRange) > 0 {\n\t\texcludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse exclude_uid_range\")\n\t\t}\n\t}\n\n\ttableIndex := options.IPRoute2TableIndex\n\tif tableIndex == 0 {\n\t\ttableIndex = tun.DefaultIPRoute2TableIndex\n\t}\n\truleIndex := options.IPRoute2RuleIndex\n\tif ruleIndex == 0 {\n\t\truleIndex = tun.DefaultIPRoute2RuleIndex\n\t}\n\tautoRedirectFallbackRuleIndex := options.AutoRedirectFallbackRuleIndex\n\tif autoRedirectFallbackRuleIndex == 0 {\n\t\tautoRedirectFallbackRuleIndex = tun.DefaultIPRoute2AutoRedirectFallbackRuleIndex\n\t}\n\tinputMark := uint32(options.AutoRedirectInputMark)\n\tif inputMark == 0 {\n\t\tinputMark = tun.DefaultAutoRedirectInputMark\n\t}\n\toutputMark := uint32(options.AutoRedirectOutputMark)\n\tif outputMark == 0 {\n\t\toutputMark = tun.DefaultAutoRedirectOutputMark\n\t}\n\tresetMark := uint32(options.AutoRedirectResetMark)\n\tif resetMark == 0 {\n\t\tresetMark = tun.DefaultAutoRedirectResetMark\n\t}\n\tnfQueue := options.AutoRedirectNFQueue\n\tif nfQueue == 0 {\n\t\tnfQueue = tun.DefaultAutoRedirectNFQueue\n\t}\n\tvar includeMACAddress []net.HardwareAddr\n\tfor i, macString := range options.IncludeMACAddress {\n\t\tmac, macErr := net.ParseMAC(macString)\n\t\tif macErr != nil {\n\t\t\treturn nil, E.Cause(macErr, \"parse include_mac_address[\", i, \"]\")\n\t\t}\n\t\tincludeMACAddress = append(includeMACAddress, mac)\n\t}\n\tvar excludeMACAddress []net.HardwareAddr\n\tfor i, macString := range options.ExcludeMACAddress {\n\t\tmac, macErr := net.ParseMAC(macString)\n\t\tif macErr != nil {\n\t\t\treturn nil, E.Cause(macErr, \"parse exclude_mac_address[\", i, \"]\")\n\t\t}\n\t\texcludeMACAddress = append(excludeMACAddress, mac)\n\t}\n\tnetworkManager := service.FromContext[adapter.NetworkManager](ctx)\n\tmultiPendingPackets := C.IsDarwin && ((options.Stack == \"gvisor\" && tunMTU < 32768) || (options.Stack != \"gvisor\" && options.MTU <= 9000))\n\tinbound := &Inbound{\n\t\ttag:            tag,\n\t\tctx:            ctx,\n\t\trouter:         router,\n\t\tnetworkManager: networkManager,\n\t\tlogger:         logger,\n\t\ttunOptions: tun.Options{\n\t\t\tName:                                  options.InterfaceName,\n\t\t\tMTU:                                   tunMTU,\n\t\t\tGSO:                                   enableGSO,\n\t\t\tInet4Address:                          inet4Address,\n\t\t\tInet6Address:                          inet6Address,\n\t\t\tAutoRoute:                             options.AutoRoute,\n\t\t\tIPRoute2TableIndex:                    tableIndex,\n\t\t\tIPRoute2RuleIndex:                     ruleIndex,\n\t\t\tIPRoute2AutoRedirectFallbackRuleIndex: autoRedirectFallbackRuleIndex,\n\t\t\tAutoRedirectInputMark:                 inputMark,\n\t\t\tAutoRedirectOutputMark:                outputMark,\n\t\t\tAutoRedirectResetMark:                 resetMark,\n\t\t\tAutoRedirectNFQueue:                   nfQueue,\n\t\t\tExcludeMPTCP:                          options.ExcludeMPTCP,\n\t\t\tInet4LoopbackAddress:                  common.Filter(options.LoopbackAddress, netip.Addr.Is4),\n\t\t\tInet6LoopbackAddress:                  common.Filter(options.LoopbackAddress, netip.Addr.Is6),\n\t\t\tStrictRoute:                           options.StrictRoute,\n\t\t\tIncludeInterface:                      options.IncludeInterface,\n\t\t\tExcludeInterface:                      options.ExcludeInterface,\n\t\t\tInet4RouteAddress:                     inet4RouteAddress,\n\t\t\tInet6RouteAddress:                     inet6RouteAddress,\n\t\t\tInet4RouteExcludeAddress:              inet4RouteExcludeAddress,\n\t\t\tInet6RouteExcludeAddress:              inet6RouteExcludeAddress,\n\t\t\tIncludeUID:                            includeUID,\n\t\t\tExcludeUID:                            excludeUID,\n\t\t\tIncludeAndroidUser:                    options.IncludeAndroidUser,\n\t\t\tIncludePackage:                        options.IncludePackage,\n\t\t\tExcludePackage:                        options.ExcludePackage,\n\t\t\tIncludeMACAddress:                     includeMACAddress,\n\t\t\tExcludeMACAddress:                     excludeMACAddress,\n\t\t\tInterfaceMonitor:                      networkManager.InterfaceMonitor(),\n\t\t\tEXP_MultiPendingPackets:               multiPendingPackets,\n\t\t},\n\t\tudpTimeout:        udpTimeout,\n\t\tstack:             options.Stack,\n\t\tplatformInterface: platformInterface,\n\t\tplatformOptions:   common.PtrValueOrDefault(options.Platform),\n\t}\n\tfor _, routeAddressSet := range options.RouteAddressSet {\n\t\truleSet, loaded := router.RuleSet(routeAddressSet)\n\t\tif !loaded {\n\t\t\treturn nil, E.New(\"parse route_address_set: rule-set not found: \", routeAddressSet)\n\t\t}\n\t\tinbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet)\n\t}\n\tfor _, routeExcludeAddressSet := range options.RouteExcludeAddressSet {\n\t\truleSet, loaded := router.RuleSet(routeExcludeAddressSet)\n\t\tif !loaded {\n\t\t\treturn nil, E.New(\"parse route_exclude_address_set: rule-set not found: \", routeExcludeAddressSet)\n\t\t}\n\t\tinbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet)\n\t}\n\tif options.AutoRedirect {\n\t\tif !options.AutoRoute {\n\t\t\treturn nil, E.New(\"`auto_route` is required by `auto_redirect`\")\n\t\t}\n\t\tdisableNFTables, dErr := strconv.ParseBool(os.Getenv(\"DISABLE_NFTABLES\"))\n\t\tinbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{\n\t\t\tTunOptions:             &inbound.tunOptions,\n\t\t\tContext:                ctx,\n\t\t\tHandler:                (*autoRedirectHandler)(inbound),\n\t\t\tLogger:                 logger,\n\t\t\tNetworkMonitor:         networkManager.NetworkMonitor(),\n\t\t\tInterfaceFinder:        networkManager.InterfaceFinder(),\n\t\t\tTableName:              \"sing-box\",\n\t\t\tDisableNFTables:        dErr == nil && disableNFTables,\n\t\t\tRouteAddressSet:        &inbound.routeAddressSet,\n\t\t\tRouteExcludeAddressSet: &inbound.routeExcludeAddressSet,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"initialize auto-redirect\")\n\t\t}\n\t\tif !C.IsAndroid {\n\t\t\tinbound.tunOptions.AutoRedirectMarkMode = true\n\t\t\terr = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\treturn inbound, nil\n}\n\nfunc uidToRange(uidList badoption.Listable[uint32]) []ranges.Range[uint32] {\n\treturn common.Map(uidList, func(uid uint32) ranges.Range[uint32] {\n\t\treturn ranges.NewSingle(uid)\n\t})\n}\n\nfunc parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.Range[uint32], error) {\n\tfor _, uidRange := range rangeList {\n\t\tif !strings.Contains(uidRange, \":\") {\n\t\t\treturn nil, E.New(\"missing ':' in range: \", uidRange)\n\t\t}\n\t\tsubIndex := strings.Index(uidRange, \":\")\n\t\tif subIndex == 0 {\n\t\t\treturn nil, E.New(\"missing range start: \", uidRange)\n\t\t} else if subIndex == len(uidRange)-1 {\n\t\t\treturn nil, E.New(\"missing range end: \", uidRange)\n\t\t}\n\t\tvar start, end uint64\n\t\tvar err error\n\t\tstart, err = strconv.ParseUint(uidRange[:subIndex], 0, 32)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse range start\")\n\t\t}\n\t\tend, err = strconv.ParseUint(uidRange[subIndex+1:], 0, 32)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse range end\")\n\t\t}\n\t\tuidRanges = append(uidRanges, ranges.New(uint32(start), uint32(end)))\n\t}\n\treturn uidRanges, nil\n}\n\nfunc (t *Inbound) Type() string {\n\treturn C.TypeTun\n}\n\nfunc (t *Inbound) Tag() string {\n\treturn t.tag\n}\n\nfunc (t *Inbound) Start(stage adapter.StartStage) error {\n\tswitch stage {\n\tcase adapter.StartStateStart:\n\t\tif C.IsAndroid && t.platformInterface == nil {\n\t\t\tt.tunOptions.BuildAndroidRules(t.networkManager.PackageManager())\n\t\t}\n\t\tif t.tunOptions.Name == \"\" {\n\t\t\tt.tunOptions.Name = tun.CalculateInterfaceName(\"\")\n\t\t}\n\t\tif t.platformInterface == nil {\n\t\t\tt.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)\n\t\t\tfor _, routeRuleSet := range t.routeRuleSet {\n\t\t\t\tipSets := routeRuleSet.ExtractIPSet()\n\t\t\t\tif len(ipSets) == 0 {\n\t\t\t\t\tt.logger.Warn(\"route_address_set: no destination IP CIDR rules found in rule-set: \", routeRuleSet.Name())\n\t\t\t\t}\n\t\t\t\trouteRuleSet.IncRef()\n\t\t\t\tt.routeAddressSet = append(t.routeAddressSet, ipSets...)\n\t\t\t\tif t.autoRedirect != nil {\n\t\t\t\t\tt.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))\n\t\t\t\t}\n\t\t\t}\n\t\t\tt.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)\n\t\t\tfor _, routeExcludeRuleSet := range t.routeExcludeRuleSet {\n\t\t\t\tipSets := routeExcludeRuleSet.ExtractIPSet()\n\t\t\t\tif len(ipSets) == 0 {\n\t\t\t\t\tt.logger.Warn(\"route_address_set: no destination IP CIDR rules found in rule-set: \", routeExcludeRuleSet.Name())\n\t\t\t\t}\n\t\t\t\trouteExcludeRuleSet.IncRef()\n\t\t\t\tt.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)\n\t\t\t\tif t.autoRedirect != nil {\n\t\t\t\t\tt.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tvar (\n\t\t\ttunInterface tun.Tun\n\t\t\terr          error\n\t\t)\n\t\tmonitor := taskmonitor.New(t.logger, C.StartTimeout)\n\t\ttunOptions := t.tunOptions\n\t\tif t.autoRedirect == nil && !(runtime.GOOS == \"android\" && t.platformInterface != nil) {\n\t\t\tfor _, ipSet := range t.routeAddressSet {\n\t\t\t\tfor _, prefix := range ipSet.Prefixes() {\n\t\t\t\t\tif prefix.Addr().Is4() {\n\t\t\t\t\t\ttunOptions.Inet4RouteAddress = append(tunOptions.Inet4RouteAddress, prefix)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttunOptions.Inet6RouteAddress = append(tunOptions.Inet6RouteAddress, prefix)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, ipSet := range t.routeExcludeAddressSet {\n\t\t\t\tfor _, prefix := range ipSet.Prefixes() {\n\t\t\t\t\tif prefix.Addr().Is4() {\n\t\t\t\t\t\ttunOptions.Inet4RouteExcludeAddress = append(tunOptions.Inet4RouteExcludeAddress, prefix)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttunOptions.Inet6RouteExcludeAddress = append(tunOptions.Inet6RouteExcludeAddress, prefix)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tmonitor.Start(\"open interface\")\n\t\tif t.platformInterface != nil && t.platformInterface.UsePlatformInterface() {\n\t\t\ttunInterface, err = t.platformInterface.OpenInterface(&tunOptions, t.platformOptions)\n\t\t} else {\n\t\t\tif HookBeforeCreatePlatformInterface != nil {\n\t\t\t\tHookBeforeCreatePlatformInterface()\n\t\t\t}\n\t\t\ttunInterface, err = tun.New(tunOptions)\n\t\t}\n\t\tmonitor.Finish()\n\t\tt.tunOptions.Name = tunOptions.Name\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"configure tun interface\")\n\t\t}\n\t\tt.logger.Trace(\"creating stack\")\n\t\tt.tunIf = tunInterface\n\t\tvar (\n\t\t\tforwarderBindInterface bool\n\t\t\tincludeAllNetworks     bool\n\t\t)\n\t\tif t.platformInterface != nil {\n\t\t\tforwarderBindInterface = true\n\t\t\tincludeAllNetworks = t.platformInterface.NetworkExtensionIncludeAllNetworks()\n\t\t}\n\t\ttunStack, err := tun.NewStack(t.stack, tun.StackOptions{\n\t\t\tContext:                t.ctx,\n\t\t\tTun:                    tunInterface,\n\t\t\tTunOptions:             t.tunOptions,\n\t\t\tUDPTimeout:             t.udpTimeout,\n\t\t\tHandler:                t,\n\t\t\tLogger:                 t.logger,\n\t\t\tForwarderBindInterface: forwarderBindInterface,\n\t\t\tInterfaceFinder:        t.networkManager.InterfaceFinder(),\n\t\t\tIncludeAllNetworks:     includeAllNetworks,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tt.tunStack = tunStack\n\t\tt.logger.Info(\"started at \", t.tunOptions.Name)\n\tcase adapter.StartStatePostStart:\n\t\tmonitor := taskmonitor.New(t.logger, C.StartTimeout)\n\t\tmonitor.Start(\"starting tun stack\")\n\t\terr := t.tunStack.Start()\n\t\tmonitor.Finish()\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"starting tun stack\")\n\t\t}\n\t\tmonitor.Start(\"starting tun interface\")\n\t\terr = t.tunIf.Start()\n\t\tmonitor.Finish()\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"starting TUN interface\")\n\t\t}\n\t\tif t.autoRedirect != nil {\n\t\t\tmonitor.Start(\"initialize auto-redirect\")\n\t\t\terr := t.autoRedirect.Start()\n\t\t\tmonitor.Finish()\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"auto-redirect\")\n\t\t\t}\n\t\t}\n\t\tt.routeAddressSet = nil\n\t\tt.routeExcludeAddressSet = nil\n\t}\n\treturn nil\n}\n\nfunc (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) {\n\tt.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)\n\tt.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)\n\tt.autoRedirect.UpdateRouteAddressSet()\n\tt.routeAddressSet = nil\n\tt.routeExcludeAddressSet = nil\n}\n\nfunc (t *Inbound) Close() error {\n\treturn common.Close(\n\t\tt.tunStack,\n\t\tt.tunIf,\n\t\tt.autoRedirect,\n\t)\n}\n\nfunc (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\tvar ipVersion uint8\n\tif !destination.IsIPv6() {\n\t\tipVersion = 4\n\t} else {\n\t\tipVersion = 6\n\t}\n\trouteDestination, err := t.router.PreMatch(adapter.InboundContext{\n\t\tInbound:     t.tag,\n\t\tInboundType: C.TypeTun,\n\t\tIPVersion:   ipVersion,\n\t\tNetwork:     network,\n\t\tSource:      source,\n\t\tDestination: destination,\n\t}, routeContext, timeout, false)\n\tif err != nil {\n\t\tswitch {\n\t\tcase rule.IsBypassed(err):\n\t\t\terr = nil\n\t\tcase rule.IsRejected(err):\n\t\t\tt.logger.Trace(\"reject \", network, \" connection from \", source.AddrString(), \" to \", destination.AddrString())\n\t\tdefault:\n\t\t\tif network == N.NetworkICMP {\n\t\t\t\tt.logger.Warn(E.Cause(err, \"link \", network, \" connection from \", source.AddrString(), \" to \", destination.AddrString()))\n\t\t\t}\n\t\t}\n\t}\n\treturn routeDestination, err\n}\n\nfunc (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tctx = log.ContextWithNewID(ctx)\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = t.tag\n\tmetadata.InboundType = C.TypeTun\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\n\tt.logger.InfoContext(ctx, \"inbound connection from \", metadata.Source)\n\tt.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\tt.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tctx = log.ContextWithNewID(ctx)\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = t.tag\n\tmetadata.InboundType = C.TypeTun\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\n\tt.logger.InfoContext(ctx, \"inbound packet connection from \", metadata.Source)\n\tt.logger.InfoContext(ctx, \"inbound packet connection to \", metadata.Destination)\n\tt.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n\ntype autoRedirectHandler Inbound\n\nfunc (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\tvar ipVersion uint8\n\tif !destination.IsIPv6() {\n\t\tipVersion = 4\n\t} else {\n\t\tipVersion = 6\n\t}\n\trouteDestination, err := t.router.PreMatch(adapter.InboundContext{\n\t\tInbound:     t.tag,\n\t\tInboundType: C.TypeTun,\n\t\tIPVersion:   ipVersion,\n\t\tNetwork:     network,\n\t\tSource:      source,\n\t\tDestination: destination,\n\t}, routeContext, timeout, true)\n\tif err != nil {\n\t\tswitch {\n\t\tcase rule.IsBypassed(err):\n\t\t\tt.logger.Trace(\"bypass \", network, \" connection from \", source.AddrString(), \" to \", destination.AddrString())\n\t\tcase rule.IsRejected(err):\n\t\t\tt.logger.Trace(\"reject \", network, \" connection from \", source.AddrString(), \" to \", destination.AddrString())\n\t\tdefault:\n\t\t\tif network == N.NetworkICMP {\n\t\t\t\tt.logger.Warn(E.Cause(err, \"link \", network, \" connection from \", source.AddrString(), \" to \", destination.AddrString()))\n\t\t\t}\n\t\t}\n\t}\n\treturn routeDestination, err\n}\n\nfunc (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tctx = log.ContextWithNewID(ctx)\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = t.tag\n\tmetadata.InboundType = C.TypeTun\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\n\tt.logger.InfoContext(ctx, \"inbound redirect connection from \", metadata.Source)\n\tt.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\tt.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (t *autoRedirectHandler) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tpanic(\"unexcepted\")\n}\n"
  },
  {
    "path": "protocol/vless/inbound.go",
    "content": "package vless\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/mux\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/common/uot\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2ray\"\n\t\"github.com/sagernet/sing-vmess/packetaddr\"\n\t\"github.com/sagernet/sing-vmess/vless\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.VLESSInboundOptions](registry, C.TypeVLESS, NewInbound)\n}\n\nvar _ adapter.TCPInjectableInbound = (*Inbound)(nil)\n\ntype Inbound struct {\n\tinbound.Adapter\n\tctx       context.Context\n\trouter    adapter.ConnectionRouterEx\n\tlogger    logger.ContextLogger\n\tlistener  *listener.Listener\n\tusers     []option.VLESSUser\n\tservice   *vless.Service[int]\n\ttlsConfig tls.ServerConfig\n\ttransport adapter.V2RayServerTransport\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (adapter.Inbound, error) {\n\tinbound := &Inbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeVLESS, tag),\n\t\tctx:     ctx,\n\t\trouter:  uot.NewRouter(router, logger),\n\t\tlogger:  logger,\n\t\tusers:   options.Users,\n\t}\n\tvar err error\n\tinbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tservice := vless.NewService[int](logger, adapter.NewUpstreamContextHandlerEx(inbound.newConnectionEx, inbound.newPacketConnectionEx))\n\tservice.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int {\n\t\treturn index\n\t}), common.Map(inbound.users, func(it option.VLESSUser) string {\n\t\treturn it.UUID\n\t}), common.Map(inbound.users, func(it option.VLESSUser) string {\n\t\treturn it.Flow\n\t}))\n\tinbound.service = service\n\tif options.TLS != nil {\n\t\tinbound.tlsConfig, err = tls.NewServerWithOptions(tls.ServerOptions{\n\t\t\tContext: ctx,\n\t\t\tLogger:  logger,\n\t\t\tOptions: common.PtrValueOrDefault(options.TLS),\n\t\t\tKTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == \"\" &&\n\t\t\t\t!common.PtrValueOrDefault(options.Multiplex).Enabled &&\n\t\t\t\tcommon.All(options.Users, func(it option.VLESSUser) bool {\n\t\t\t\t\treturn it.Flow == \"\"\n\t\t\t\t}),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif options.Transport != nil {\n\t\tinbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound))\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"create server transport: \", options.Transport.Type)\n\t\t}\n\t}\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tNetwork:           []string{N.NetworkTCP},\n\t\tListen:            options.ListenOptions,\n\t\tConnectionHandler: inbound,\n\t})\n\treturn inbound, nil\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif h.tlsConfig != nil {\n\t\terr := h.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif h.transport == nil {\n\t\treturn h.listener.Start()\n\t}\n\tif common.Contains(h.transport.Network(), N.NetworkTCP) {\n\t\ttcpListener, err := h.listener.ListenTCP()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgo func() {\n\t\t\tsErr := h.transport.Serve(tcpListener)\n\t\t\tif sErr != nil && !E.IsClosed(sErr) {\n\t\t\t\th.logger.Error(\"transport serve error: \", sErr)\n\t\t\t}\n\t\t}()\n\t}\n\tif common.Contains(h.transport.Network(), N.NetworkUDP) {\n\t\tudpConn, err := h.listener.ListenUDP()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgo func() {\n\t\t\tsErr := h.transport.ServePacket(udpConn)\n\t\t\tif sErr != nil && !E.IsClosed(sErr) {\n\t\t\t\th.logger.Error(\"transport serve error: \", sErr)\n\t\t\t}\n\t\t}()\n\t}\n\treturn nil\n}\n\nfunc (h *Inbound) Close() error {\n\treturn common.Close(\n\t\th.service,\n\t\th.listener,\n\t\th.tlsConfig,\n\t\th.transport,\n\t)\n}\n\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tif h.tlsConfig != nil && h.transport == nil {\n\t\ttlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)\n\t\tif err != nil {\n\t\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source, \": TLS handshake\"))\n\t\t\treturn\n\t\t}\n\t\tconn = tlsConn\n\t}\n\terr := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)\n\tif err != nil {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source))\n\t}\n}\n\nfunc (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuserIndex, loaded := auth.UserFromContext[int](ctx)\n\tif !loaded {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)\n\t\treturn\n\t}\n\tuser := h.users[userIndex].Name\n\tif user == \"\" {\n\t\tuser = F.ToString(userIndex)\n\t} else {\n\t\tmetadata.User = user\n\t}\n\th.logger.InfoContext(ctx, \"[\", user, \"] inbound connection to \", metadata.Destination)\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuserIndex, loaded := auth.UserFromContext[int](ctx)\n\tif !loaded {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)\n\t\treturn\n\t}\n\tuser := h.users[userIndex].Name\n\tif user == \"\" {\n\t\tuser = F.ToString(userIndex)\n\t} else {\n\t\tmetadata.User = user\n\t}\n\tif metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {\n\t\tmetadata.Destination = M.Socksaddr{}\n\t\tconn = packetaddr.NewConn(bufio.NewNetPacketConn(conn), metadata.Destination)\n\t\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet addr connection\")\n\t} else {\n\t\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet connection to \", metadata.Destination)\n\t}\n\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n\nvar _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil)\n\ntype inboundTransportHandler Inbound\n\nfunc (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tvar metadata adapter.InboundContext\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\th.logger.InfoContext(ctx, \"inbound connection from \", metadata.Source)\n\t(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "protocol/vless/outbound.go",
    "content": "package vless\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/mux\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2ray\"\n\t\"github.com/sagernet/sing-vmess/packetaddr\"\n\t\"github.com/sagernet/sing-vmess/vless\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.VLESSOutboundOptions](registry, C.TypeVLESS, NewOutbound)\n}\n\ntype Outbound struct {\n\toutbound.Adapter\n\tlogger          logger.ContextLogger\n\tdialer          N.Dialer\n\tclient          *vless.Client\n\tserverAddr      M.Socksaddr\n\tmultiplexDialer *mux.Client\n\ttlsConfig       tls.Config\n\ttlsDialer       tls.Dialer\n\ttransport       adapter.V2RayClientTransport\n\tpacketAddr      bool\n\txudp            bool\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (adapter.Outbound, error) {\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound := &Outbound{\n\t\tAdapter:    outbound.NewAdapterWithDialerOptions(C.TypeVLESS, tag, options.Network.Build(), options.DialerOptions),\n\t\tlogger:     logger,\n\t\tdialer:     outboundDialer,\n\t\tserverAddr: options.ServerOptions.Build(),\n\t}\n\tif options.TLS != nil {\n\t\toutbound.tlsConfig, err = tls.NewClientWithOptions(tls.ClientOptions{\n\t\t\tContext:       ctx,\n\t\t\tLogger:        logger,\n\t\t\tServerAddress: options.Server,\n\t\t\tOptions:       common.PtrValueOrDefault(options.TLS),\n\t\t\tKTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == \"\" &&\n\t\t\t\t!common.PtrValueOrDefault(options.Multiplex).Enabled &&\n\t\t\t\toptions.Flow == \"\",\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\toutbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)\n\t}\n\tif options.Transport != nil {\n\t\toutbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"create client transport: \", options.Transport.Type)\n\t\t}\n\t}\n\tif options.PacketEncoding == nil {\n\t\toutbound.xudp = true\n\t} else {\n\t\tswitch *options.PacketEncoding {\n\t\tcase \"\":\n\t\tcase \"packetaddr\":\n\t\t\toutbound.packetAddr = true\n\t\tcase \"xudp\":\n\t\t\toutbound.xudp = true\n\t\tdefault:\n\t\t\treturn nil, E.New(\"unknown packet encoding: \", options.PacketEncoding)\n\t\t}\n\t}\n\toutbound.client, err = vless.NewClient(options.UUID, options.Flow, logger)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound.multiplexDialer, err = mux.NewClientWithOptions((*vlessDialer)(outbound), logger, common.PtrValueOrDefault(options.Multiplex))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn outbound, nil\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tif h.multiplexDialer == nil {\n\t\tswitch N.NetworkName(network) {\n\t\tcase N.NetworkTCP:\n\t\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\t\tcase N.NetworkUDP:\n\t\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t\t}\n\t\treturn (*vlessDialer)(h).DialContext(ctx, network, destination)\n\t} else {\n\t\tswitch N.NetworkName(network) {\n\t\tcase N.NetworkTCP:\n\t\t\th.logger.InfoContext(ctx, \"outbound multiplex connection to \", destination)\n\t\tcase N.NetworkUDP:\n\t\t\th.logger.InfoContext(ctx, \"outbound multiplex packet connection to \", destination)\n\t\t}\n\t\treturn h.multiplexDialer.DialContext(ctx, network, destination)\n\t}\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tif h.multiplexDialer == nil {\n\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t\treturn (*vlessDialer)(h).ListenPacket(ctx, destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"outbound multiplex packet connection to \", destination)\n\t\treturn h.multiplexDialer.ListenPacket(ctx, destination)\n\t}\n}\n\nfunc (h *Outbound) InterfaceUpdated() {\n\tif h.transport != nil {\n\t\th.transport.Close()\n\t}\n\tif h.multiplexDialer != nil {\n\t\th.multiplexDialer.Reset()\n\t}\n}\n\nfunc (h *Outbound) Close() error {\n\treturn common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)\n}\n\ntype vlessDialer Outbound\n\nfunc (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tvar conn net.Conn\n\tvar err error\n\tif h.transport != nil {\n\t\tconn, err = h.transport.DialContext(ctx)\n\t} else if h.tlsDialer != nil {\n\t\tconn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)\n\t} else {\n\t\tconn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\t\treturn h.client.DialEarlyConn(conn, destination)\n\tcase N.NetworkUDP:\n\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t\tif h.xudp {\n\t\t\treturn h.client.DialEarlyXUDPPacketConn(conn, destination)\n\t\t} else if h.packetAddr {\n\t\t\tif destination.IsDomain() {\n\t\t\t\treturn nil, E.New(\"packetaddr: domain destination is not supported\")\n\t\t\t}\n\t\t\tpacketConn, err := h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn bufio.NewBindPacketConn(packetaddr.NewConn(packetConn, destination), destination), nil\n\t\t} else {\n\t\t\treturn h.client.DialEarlyPacketConn(conn, destination)\n\t\t}\n\tdefault:\n\t\treturn nil, E.Extend(N.ErrUnknownNetwork, network)\n\t}\n}\n\nfunc (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tvar conn net.Conn\n\tvar err error\n\tif h.transport != nil {\n\t\tconn, err = h.transport.DialContext(ctx)\n\t} else if h.tlsDialer != nil {\n\t\tconn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)\n\t} else {\n\t\tconn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)\n\t}\n\tif err != nil {\n\t\tcommon.Close(conn)\n\t\treturn nil, err\n\t}\n\tif h.xudp {\n\t\treturn h.client.DialEarlyXUDPPacketConn(conn, destination)\n\t} else if h.packetAddr {\n\t\tif destination.IsDomain() {\n\t\t\treturn nil, E.New(\"packetaddr: domain destination is not supported\")\n\t\t}\n\t\tconn, err := h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn packetaddr.NewConn(conn, destination), nil\n\t} else {\n\t\treturn h.client.DialEarlyPacketConn(conn, destination)\n\t}\n}\n"
  },
  {
    "path": "protocol/vmess/inbound.go",
    "content": "package vmess\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/mux\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/common/uot\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2ray\"\n\t\"github.com/sagernet/sing-vmess\"\n\t\"github.com/sagernet/sing-vmess/packetaddr\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n)\n\nfunc RegisterInbound(registry *inbound.Registry) {\n\tinbound.Register[option.VMessInboundOptions](registry, C.TypeVMess, NewInbound)\n}\n\nvar _ adapter.TCPInjectableInbound = (*Inbound)(nil)\n\ntype Inbound struct {\n\tinbound.Adapter\n\tctx       context.Context\n\trouter    adapter.ConnectionRouterEx\n\tlogger    logger.ContextLogger\n\tlistener  *listener.Listener\n\tservice   *vmess.Service[int]\n\tusers     []option.VMessUser\n\ttlsConfig tls.ServerConfig\n\ttransport adapter.V2RayServerTransport\n}\n\nfunc NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessInboundOptions) (adapter.Inbound, error) {\n\tinbound := &Inbound{\n\t\tAdapter: inbound.NewAdapter(C.TypeVMess, tag),\n\t\tctx:     ctx,\n\t\trouter:  uot.NewRouter(router, logger),\n\t\tlogger:  logger,\n\t\tusers:   options.Users,\n\t}\n\tvar err error\n\tinbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar serviceOptions []vmess.ServiceOption\n\tif timeFunc := ntp.TimeFuncFromContext(ctx); timeFunc != nil {\n\t\tserviceOptions = append(serviceOptions, vmess.ServiceWithTimeFunc(timeFunc))\n\t}\n\tif options.Transport != nil && options.Transport.Type != \"\" {\n\t\tserviceOptions = append(serviceOptions, vmess.ServiceWithDisableHeaderProtection())\n\t}\n\tservice := vmess.NewService[int](adapter.NewUpstreamContextHandlerEx(inbound.newConnectionEx, inbound.newPacketConnectionEx), serviceOptions...)\n\tinbound.service = service\n\terr = service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.VMessUser) int {\n\t\treturn index\n\t}), common.Map(options.Users, func(it option.VMessUser) string {\n\t\treturn it.UUID\n\t}), common.Map(options.Users, func(it option.VMessUser) int {\n\t\treturn it.AlterId\n\t}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif options.TLS != nil {\n\t\tinbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif options.Transport != nil {\n\t\tinbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound))\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"create server transport: \", options.Transport.Type)\n\t\t}\n\t}\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:           ctx,\n\t\tLogger:            logger,\n\t\tNetwork:           []string{N.NetworkTCP},\n\t\tListen:            options.ListenOptions,\n\t\tConnectionHandler: inbound,\n\t})\n\treturn inbound, nil\n}\n\nfunc (h *Inbound) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\terr := h.service.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif h.tlsConfig != nil {\n\t\terr = h.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif h.transport == nil {\n\t\treturn h.listener.Start()\n\t}\n\tif common.Contains(h.transport.Network(), N.NetworkTCP) {\n\t\ttcpListener, err := h.listener.ListenTCP()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgo func() {\n\t\t\tsErr := h.transport.Serve(tcpListener)\n\t\t\tif sErr != nil && !E.IsClosed(sErr) {\n\t\t\t\th.logger.Error(\"transport serve error: \", sErr)\n\t\t\t}\n\t\t}()\n\t}\n\tif common.Contains(h.transport.Network(), N.NetworkUDP) {\n\t\tudpConn, err := h.listener.ListenUDP()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgo func() {\n\t\t\tsErr := h.transport.ServePacket(udpConn)\n\t\t\tif sErr != nil && !E.IsClosed(sErr) {\n\t\t\t\th.logger.Error(\"transport serve error: \", sErr)\n\t\t\t}\n\t\t}()\n\t}\n\treturn nil\n}\n\nfunc (h *Inbound) Close() error {\n\treturn common.Close(\n\t\th.service,\n\t\th.listener,\n\t\th.tlsConfig,\n\t\th.transport,\n\t)\n}\n\nfunc (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tif h.tlsConfig != nil && h.transport == nil {\n\t\ttlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)\n\t\tif err != nil {\n\t\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source, \": TLS handshake\"))\n\t\t\treturn\n\t\t}\n\t\tconn = tlsConn\n\t}\n\terr := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)\n\tif err != nil {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\th.logger.ErrorContext(ctx, E.Cause(err, \"process connection from \", metadata.Source))\n\t}\n}\n\nfunc (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuserIndex, loaded := auth.UserFromContext[int](ctx)\n\tif !loaded {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)\n\t\treturn\n\t}\n\tuser := h.users[userIndex].Name\n\tif user == \"\" {\n\t\tuser = F.ToString(userIndex)\n\t} else {\n\t\tmetadata.User = user\n\t}\n\th.logger.InfoContext(ctx, \"[\", user, \"] inbound connection to \", metadata.Destination)\n\th.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = h.Tag()\n\tmetadata.InboundType = h.Type()\n\tuserIndex, loaded := auth.UserFromContext[int](ctx)\n\tif !loaded {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)\n\t\treturn\n\t}\n\tuser := h.users[userIndex].Name\n\tif user == \"\" {\n\t\tuser = F.ToString(userIndex)\n\t} else {\n\t\tmetadata.User = user\n\t}\n\tif metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {\n\t\tmetadata.Destination = M.Socksaddr{}\n\t\tconn = packetaddr.NewConn(bufio.NewNetPacketConn(conn), metadata.Destination)\n\t\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet addr connection\")\n\t} else {\n\t\th.logger.InfoContext(ctx, \"[\", user, \"] inbound packet connection to \", metadata.Destination)\n\t}\n\th.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n\nvar _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil)\n\ntype inboundTransportHandler Inbound\n\nfunc (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tvar metadata adapter.InboundContext\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\t//nolint:staticcheck\n\tmetadata.InboundDetour = h.listener.ListenOptions().Detour\n\t//nolint:staticcheck\n\th.logger.InfoContext(ctx, \"inbound connection from \", metadata.Source)\n\t(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)\n}\n"
  },
  {
    "path": "protocol/vmess/outbound.go",
    "content": "package vmess\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/mux\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2ray\"\n\t\"github.com/sagernet/sing-vmess\"\n\t\"github.com/sagernet/sing-vmess/packetaddr\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n)\n\nfunc RegisterOutbound(registry *outbound.Registry) {\n\toutbound.Register[option.VMessOutboundOptions](registry, C.TypeVMess, NewOutbound)\n}\n\ntype Outbound struct {\n\toutbound.Adapter\n\tlogger          logger.ContextLogger\n\tdialer          N.Dialer\n\tclient          *vmess.Client\n\tserverAddr      M.Socksaddr\n\tmultiplexDialer *mux.Client\n\ttlsConfig       tls.Config\n\ttlsDialer       tls.Dialer\n\ttransport       adapter.V2RayClientTransport\n\tpacketAddr      bool\n\txudp            bool\n}\n\nfunc NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (adapter.Outbound, error) {\n\toutboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound := &Outbound{\n\t\tAdapter:    outbound.NewAdapterWithDialerOptions(C.TypeVMess, tag, options.Network.Build(), options.DialerOptions),\n\t\tlogger:     logger,\n\t\tdialer:     outboundDialer,\n\t\tserverAddr: options.ServerOptions.Build(),\n\t}\n\tif options.TLS != nil {\n\t\toutbound.tlsConfig, err = tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif outbound.tlsConfig != nil {\n\t\t\toutbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)\n\t\t}\n\t}\n\tif options.Transport != nil {\n\t\toutbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"create client transport: \", options.Transport.Type)\n\t\t}\n\t}\n\toutbound.multiplexDialer, err = mux.NewClientWithOptions((*vmessDialer)(outbound), logger, common.PtrValueOrDefault(options.Multiplex))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch options.PacketEncoding {\n\tcase \"\":\n\tcase \"packetaddr\":\n\t\toutbound.packetAddr = true\n\tcase \"xudp\":\n\t\toutbound.xudp = true\n\tdefault:\n\t\treturn nil, E.New(\"unknown packet encoding: \", options.PacketEncoding)\n\t}\n\tvar clientOptions []vmess.ClientOption\n\tif timeFunc := ntp.TimeFuncFromContext(ctx); timeFunc != nil {\n\t\tclientOptions = append(clientOptions, vmess.ClientWithTimeFunc(timeFunc))\n\t}\n\tif options.GlobalPadding {\n\t\tclientOptions = append(clientOptions, vmess.ClientWithGlobalPadding())\n\t}\n\tif options.AuthenticatedLength {\n\t\tclientOptions = append(clientOptions, vmess.ClientWithAuthenticatedLength())\n\t}\n\tsecurity := options.Security\n\tif security == \"\" {\n\t\tsecurity = \"auto\"\n\t}\n\tif security == \"auto\" && outbound.tlsConfig != nil {\n\t\tsecurity = \"zero\"\n\t}\n\tclient, err := vmess.NewClient(options.UUID, security, options.AlterId, clientOptions...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutbound.client = client\n\treturn outbound, nil\n}\n\nfunc (h *Outbound) InterfaceUpdated() {\n\tif h.transport != nil {\n\t\th.transport.Close()\n\t}\n\tif h.multiplexDialer != nil {\n\t\th.multiplexDialer.Reset()\n\t}\n}\n\nfunc (h *Outbound) Close() error {\n\treturn common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)\n}\n\nfunc (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tif h.multiplexDialer == nil {\n\t\tswitch N.NetworkName(network) {\n\t\tcase N.NetworkTCP:\n\t\t\th.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\t\tcase N.NetworkUDP:\n\t\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t\t}\n\t\treturn (*vmessDialer)(h).DialContext(ctx, network, destination)\n\t} else {\n\t\tswitch N.NetworkName(network) {\n\t\tcase N.NetworkTCP:\n\t\t\th.logger.InfoContext(ctx, \"outbound multiplex connection to \", destination)\n\t\tcase N.NetworkUDP:\n\t\t\th.logger.InfoContext(ctx, \"outbound multiplex packet connection to \", destination)\n\t\t}\n\t\treturn h.multiplexDialer.DialContext(ctx, network, destination)\n\t}\n}\n\nfunc (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tif h.multiplexDialer == nil {\n\t\th.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t\treturn (*vmessDialer)(h).ListenPacket(ctx, destination)\n\t} else {\n\t\th.logger.InfoContext(ctx, \"outbound multiplex packet connection to \", destination)\n\t\treturn h.multiplexDialer.ListenPacket(ctx, destination)\n\t}\n}\n\ntype vmessDialer Outbound\n\nfunc (h *vmessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tvar conn net.Conn\n\tvar err error\n\tif h.transport != nil {\n\t\tconn, err = h.transport.DialContext(ctx)\n\t} else if h.tlsDialer != nil {\n\t\tconn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)\n\t} else {\n\t\tconn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)\n\t}\n\tif err != nil {\n\t\tcommon.Close(conn)\n\t\treturn nil, err\n\t}\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\treturn h.client.DialEarlyConn(conn, destination), nil\n\tcase N.NetworkUDP:\n\t\treturn h.client.DialEarlyPacketConn(conn, destination), nil\n\tdefault:\n\t\treturn nil, E.Extend(N.ErrUnknownNetwork, network)\n\t}\n}\n\nfunc (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tctx, metadata := adapter.ExtendContext(ctx)\n\tmetadata.Outbound = h.Tag()\n\tmetadata.Destination = destination\n\tvar conn net.Conn\n\tvar err error\n\tif h.transport != nil {\n\t\tconn, err = h.transport.DialContext(ctx)\n\t} else if h.tlsDialer != nil {\n\t\tconn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)\n\t} else {\n\t\tconn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif h.packetAddr {\n\t\tif destination.IsDomain() {\n\t\t\treturn nil, E.New(\"packetaddr: domain destination is not supported\")\n\t\t}\n\t\treturn packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination), nil\n\t} else if h.xudp {\n\t\treturn h.client.DialEarlyXUDPPacketConn(conn, destination), nil\n\t} else {\n\t\treturn h.client.DialEarlyPacketConn(conn, destination), nil\n\t}\n}\n"
  },
  {
    "path": "protocol/wireguard/endpoint.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/endpoint\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/route/rule\"\n\t\"github.com/sagernet/sing-box/transport/wireguard\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nvar (\n\t_ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)\n\t_ dialer.PacketDialerWithDestination  = (*Endpoint)(nil)\n)\n\nfunc RegisterEndpoint(registry *endpoint.Registry) {\n\tendpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint)\n}\n\ntype Endpoint struct {\n\tendpoint.Adapter\n\tctx            context.Context\n\trouter         adapter.Router\n\tdnsRouter      adapter.DNSRouter\n\tlogger         logger.ContextLogger\n\tlocalAddresses []netip.Prefix\n\tendpoint       *wireguard.Endpoint\n}\n\nfunc NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) {\n\tep := &Endpoint{\n\t\tAdapter:        endpoint.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),\n\t\tctx:            ctx,\n\t\trouter:         router,\n\t\tdnsRouter:      service.FromContext[adapter.DNSRouter](ctx),\n\t\tlogger:         logger,\n\t\tlocalAddresses: options.Address,\n\t}\n\tif options.Detour != \"\" && options.ListenPort != 0 {\n\t\treturn nil, E.New(\"`listen_port` is conflict with `detour`\")\n\t}\n\toutboundDialer, err := dialer.NewWithOptions(dialer.Options{\n\t\tContext: ctx,\n\t\tOptions: options.DialerOptions,\n\t\tRemoteIsDomain: common.Any(options.Peers, func(it option.WireGuardPeer) bool {\n\t\t\treturn !M.ParseAddr(it.Address).IsValid()\n\t\t}),\n\t\tResolverOnDetour: true,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar udpTimeout time.Duration\n\tif options.UDPTimeout != 0 {\n\t\tudpTimeout = time.Duration(options.UDPTimeout)\n\t} else {\n\t\tudpTimeout = C.UDPTimeout\n\t}\n\twgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{\n\t\tContext:    ctx,\n\t\tLogger:     logger,\n\t\tSystem:     options.System,\n\t\tHandler:    ep,\n\t\tUDPTimeout: udpTimeout,\n\t\tDialer:     outboundDialer,\n\t\tCreateDialer: func(interfaceName string) N.Dialer {\n\t\t\treturn common.Must1(dialer.NewDefault(ctx, option.DialerOptions{\n\t\t\t\tBindInterface: interfaceName,\n\t\t\t}))\n\t\t},\n\t\tName:       options.Name,\n\t\tMTU:        options.MTU,\n\t\tAddress:    options.Address,\n\t\tPrivateKey: options.PrivateKey,\n\t\tListenPort: options.ListenPort,\n\t\tResolvePeer: func(domain string) (netip.Addr, error) {\n\t\t\tendpointAddresses, lookupErr := ep.dnsRouter.Lookup(ctx, domain, outboundDialer.(dialer.ResolveDialer).QueryOptions())\n\t\t\tif lookupErr != nil {\n\t\t\t\treturn netip.Addr{}, lookupErr\n\t\t\t}\n\t\t\treturn endpointAddresses[0], nil\n\t\t},\n\t\tPeers: common.Map(options.Peers, func(it option.WireGuardPeer) wireguard.PeerOptions {\n\t\t\treturn wireguard.PeerOptions{\n\t\t\t\tEndpoint:                    M.ParseSocksaddrHostPort(it.Address, it.Port),\n\t\t\t\tPublicKey:                   it.PublicKey,\n\t\t\t\tPreSharedKey:                it.PreSharedKey,\n\t\t\t\tAllowedIPs:                  it.AllowedIPs,\n\t\t\t\tPersistentKeepaliveInterval: it.PersistentKeepaliveInterval,\n\t\t\t\tReserved:                    it.Reserved,\n\t\t\t}\n\t\t}),\n\t\tWorkers: options.Workers,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tep.endpoint = wgEndpoint\n\treturn ep, nil\n}\n\nfunc (w *Endpoint) Start(stage adapter.StartStage) error {\n\tswitch stage {\n\tcase adapter.StartStateStart:\n\t\treturn w.endpoint.Start(false)\n\tcase adapter.StartStatePostStart:\n\t\treturn w.endpoint.Start(true)\n\t}\n\treturn nil\n}\n\nfunc (w *Endpoint) Close() error {\n\treturn w.endpoint.Close()\n}\n\nfunc (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\tvar ipVersion uint8\n\tif !destination.IsIPv6() {\n\t\tipVersion = 4\n\t} else {\n\t\tipVersion = 6\n\t}\n\trouteDestination, err := w.router.PreMatch(adapter.InboundContext{\n\t\tInbound:     w.Tag(),\n\t\tInboundType: w.Type(),\n\t\tIPVersion:   ipVersion,\n\t\tNetwork:     network,\n\t\tSource:      source,\n\t\tDestination: destination,\n\t}, routeContext, timeout, false)\n\tif err != nil {\n\t\tswitch {\n\t\tcase rule.IsBypassed(err):\n\t\t\terr = nil\n\t\tcase rule.IsRejected(err):\n\t\t\tw.logger.Trace(\"reject \", network, \" connection from \", source.AddrString(), \" to \", destination.AddrString())\n\t\tdefault:\n\t\t\tif network == N.NetworkICMP {\n\t\t\t\tw.logger.Warn(E.Cause(err, \"link \", network, \" connection from \", source.AddrString(), \" to \", destination.AddrString()))\n\t\t\t}\n\t\t}\n\t}\n\treturn routeDestination, err\n}\n\nfunc (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = w.Tag()\n\tmetadata.InboundType = w.Type()\n\tmetadata.Source = source\n\tfor _, localPrefix := range w.localAddresses {\n\t\tif localPrefix.Contains(destination.Addr) {\n\t\t\tmetadata.OriginDestination = destination\n\t\t\tif destination.Addr.Is4() {\n\t\t\t\tdestination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})\n\t\t\t} else {\n\t\t\t\tdestination.Addr = netip.IPv6Loopback()\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tmetadata.Destination = destination\n\tw.logger.InfoContext(ctx, \"inbound connection from \", source)\n\tw.logger.InfoContext(ctx, \"inbound connection to \", metadata.Destination)\n\tw.router.RouteConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (w *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = w.Tag()\n\tmetadata.InboundType = w.Type()\n\tmetadata.Source = source\n\tmetadata.Destination = destination\n\tfor _, localPrefix := range w.localAddresses {\n\t\tif localPrefix.Contains(destination.Addr) {\n\t\t\tmetadata.OriginDestination = destination\n\t\t\tif destination.Addr.Is4() {\n\t\t\t\tmetadata.Destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})\n\t\t\t} else {\n\t\t\t\tmetadata.Destination.Addr = netip.IPv6Loopback()\n\t\t\t}\n\t\t\tconn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)\n\t\t}\n\t}\n\tw.logger.InfoContext(ctx, \"inbound packet connection from \", source)\n\tw.logger.InfoContext(ctx, \"inbound packet connection to \", destination)\n\tw.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)\n}\n\nfunc (w *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tswitch network {\n\tcase N.NetworkTCP:\n\t\tw.logger.InfoContext(ctx, \"outbound connection to \", destination)\n\tcase N.NetworkUDP:\n\t\tw.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\t}\n\tif destination.IsDomain() {\n\t\tdestinationAddresses, err := w.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn N.DialSerial(ctx, w.endpoint, network, destination, destinationAddresses)\n\t} else if !destination.Addr.IsValid() {\n\t\treturn nil, E.New(\"invalid destination: \", destination)\n\t}\n\treturn w.endpoint.DialContext(ctx, network, destination)\n}\n\nfunc (w *Endpoint) ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error) {\n\tw.logger.InfoContext(ctx, \"outbound packet connection to \", destination)\n\tif destination.IsDomain() {\n\t\tdestinationAddresses, err := w.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, netip.Addr{}, err\n\t\t}\n\t\treturn N.ListenSerial(ctx, w.endpoint, destination, destinationAddresses)\n\t}\n\tpacketConn, err := w.endpoint.ListenPacket(ctx, destination)\n\tif err != nil {\n\t\treturn nil, netip.Addr{}, err\n\t}\n\tif destination.IsIP() {\n\t\treturn packetConn, destination.Addr, nil\n\t}\n\treturn packetConn, netip.Addr{}, nil\n}\n\nfunc (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tpacketConn, destinationAddress, err := w.ListenPacketWithDestination(ctx, destination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif destinationAddress.IsValid() && destination != M.SocksaddrFrom(destinationAddress, destination.Port) {\n\t\treturn bufio.NewNATPacketConn(bufio.NewPacketConn(packetConn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil\n\t}\n\treturn packetConn, nil\n}\n\nfunc (w *Endpoint) PreferredDomain(domain string) bool {\n\treturn false\n}\n\nfunc (w *Endpoint) PreferredAddress(address netip.Addr) bool {\n\treturn w.endpoint.Lookup(address) != nil\n}\n\nfunc (w *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\treturn w.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout)\n}\n"
  },
  {
    "path": "release/DEFAULT_BUILD_TAGS",
    "content": "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,badlinkname,tfogo_checklinkname0"
  },
  {
    "path": "release/DEFAULT_BUILD_TAGS_OTHERS",
    "content": "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0"
  },
  {
    "path": "release/DEFAULT_BUILD_TAGS_WINDOWS",
    "content": "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0"
  },
  {
    "path": "release/LDFLAGS",
    "content": "-X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0"
  },
  {
    "path": "release/completions/sing-box.bash",
    "content": "# bash completion for sing-box                             -*- shell-script -*-\n\n__sing-box_debug()\n{\n    if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then\n        echo \"$*\" >> \"${BASH_COMP_DEBUG_FILE}\"\n    fi\n}\n\n# Homebrew on Macs have version 1.3 of bash-completion which doesn't include\n# _init_completion. This is a very minimal version of that function.\n__sing-box_init_completion()\n{\n    COMPREPLY=()\n    _get_comp_words_by_ref \"$@\" cur prev words cword\n}\n\n__sing-box_index_of_word()\n{\n    local w word=$1\n    shift\n    index=0\n    for w in \"$@\"; do\n        [[ $w = \"$word\" ]] && return\n        index=$((index+1))\n    done\n    index=-1\n}\n\n__sing-box_contains_word()\n{\n    local w word=$1; shift\n    for w in \"$@\"; do\n        [[ $w = \"$word\" ]] && return\n    done\n    return 1\n}\n\n__sing-box_handle_go_custom_completion()\n{\n    __sing-box_debug \"${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}\"\n\n    local shellCompDirectiveError=1\n    local shellCompDirectiveNoSpace=2\n    local shellCompDirectiveNoFileComp=4\n    local shellCompDirectiveFilterFileExt=8\n    local shellCompDirectiveFilterDirs=16\n\n    local out requestComp lastParam lastChar comp directive args\n\n    # Prepare the command to request completions for the program.\n    # Calling ${words[0]} instead of directly sing-box allows handling aliases\n    args=(\"${words[@]:1}\")\n    # Disable ActiveHelp which is not supported for bash completion v1\n    requestComp=\"SING_BOX_ACTIVE_HELP=0 ${words[0]} __completeNoDesc ${args[*]}\"\n\n    lastParam=${words[$((${#words[@]}-1))]}\n    lastChar=${lastParam:$((${#lastParam}-1)):1}\n    __sing-box_debug \"${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}\"\n\n    if [ -z \"${cur}\" ] && [ \"${lastChar}\" != \"=\" ]; then\n        # If the last parameter is complete (there is a space following it)\n        # We add an extra empty parameter so we can indicate this to the go method.\n        __sing-box_debug \"${FUNCNAME[0]}: Adding extra empty parameter\"\n        requestComp=\"${requestComp} \\\"\\\"\"\n    fi\n\n    __sing-box_debug \"${FUNCNAME[0]}: calling ${requestComp}\"\n    # Use eval to handle any environment variables and such\n    out=$(eval \"${requestComp}\" 2>/dev/null)\n\n    # Extract the directive integer at the very end of the output following a colon (:)\n    directive=${out##*:}\n    # Remove the directive\n    out=${out%:*}\n    if [ \"${directive}\" = \"${out}\" ]; then\n        # There is not directive specified\n        directive=0\n    fi\n    __sing-box_debug \"${FUNCNAME[0]}: the completion directive is: ${directive}\"\n    __sing-box_debug \"${FUNCNAME[0]}: the completions are: ${out}\"\n\n    if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then\n        # Error code.  No completion.\n        __sing-box_debug \"${FUNCNAME[0]}: received error from custom completion go code\"\n        return\n    else\n        if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then\n            if [[ $(type -t compopt) = \"builtin\" ]]; then\n                __sing-box_debug \"${FUNCNAME[0]}: activating no space\"\n                compopt -o nospace\n            fi\n        fi\n        if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then\n            if [[ $(type -t compopt) = \"builtin\" ]]; then\n                __sing-box_debug \"${FUNCNAME[0]}: activating no file completion\"\n                compopt +o default\n            fi\n        fi\n    fi\n\n    if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then\n        # File extension filtering\n        local fullFilter filter filteringCmd\n        # Do not use quotes around the $out variable or else newline\n        # characters will be kept.\n        for filter in ${out}; do\n            fullFilter+=\"$filter|\"\n        done\n\n        filteringCmd=\"_filedir $fullFilter\"\n        __sing-box_debug \"File filtering command: $filteringCmd\"\n        $filteringCmd\n    elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then\n        # File completion for directories only\n        local subdir\n        # Use printf to strip any trailing newline\n        subdir=$(printf \"%s\" \"${out}\")\n        if [ -n \"$subdir\" ]; then\n            __sing-box_debug \"Listing directories in $subdir\"\n            __sing-box_handle_subdirs_in_dir_flag \"$subdir\"\n        else\n            __sing-box_debug \"Listing directories in .\"\n            _filedir -d\n        fi\n    else\n        while IFS='' read -r comp; do\n            COMPREPLY+=(\"$comp\")\n        done < <(compgen -W \"${out}\" -- \"$cur\")\n    fi\n}\n\n__sing-box_handle_reply()\n{\n    __sing-box_debug \"${FUNCNAME[0]}\"\n    local comp\n    case $cur in\n        -*)\n            if [[ $(type -t compopt) = \"builtin\" ]]; then\n                compopt -o nospace\n            fi\n            local allflags\n            if [ ${#must_have_one_flag[@]} -ne 0 ]; then\n                allflags=(\"${must_have_one_flag[@]}\")\n            else\n                allflags=(\"${flags[*]} ${two_word_flags[*]}\")\n            fi\n            while IFS='' read -r comp; do\n                COMPREPLY+=(\"$comp\")\n            done < <(compgen -W \"${allflags[*]}\" -- \"$cur\")\n            if [[ $(type -t compopt) = \"builtin\" ]]; then\n                [[ \"${COMPREPLY[0]}\" == *= ]] || compopt +o nospace\n            fi\n\n            # complete after --flag=abc\n            if [[ $cur == *=* ]]; then\n                if [[ $(type -t compopt) = \"builtin\" ]]; then\n                    compopt +o nospace\n                fi\n\n                local index flag\n                flag=\"${cur%=*}\"\n                __sing-box_index_of_word \"${flag}\" \"${flags_with_completion[@]}\"\n                COMPREPLY=()\n                if [[ ${index} -ge 0 ]]; then\n                    PREFIX=\"\"\n                    cur=\"${cur#*=}\"\n                    ${flags_completion[${index}]}\n                    if [ -n \"${ZSH_VERSION:-}\" ]; then\n                        # zsh completion needs --flag= prefix\n                        eval \"COMPREPLY=( \\\"\\${COMPREPLY[@]/#/${flag}=}\\\" )\"\n                    fi\n                fi\n            fi\n\n            if [[ -z \"${flag_parsing_disabled}\" ]]; then\n                # If flag parsing is enabled, we have completed the flags and can return.\n                # If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough\n                # to possibly call handle_go_custom_completion.\n                return 0;\n            fi\n            ;;\n    esac\n\n    # check if we are handling a flag with special work handling\n    local index\n    __sing-box_index_of_word \"${prev}\" \"${flags_with_completion[@]}\"\n    if [[ ${index} -ge 0 ]]; then\n        ${flags_completion[${index}]}\n        return\n    fi\n\n    # we are parsing a flag and don't have a special handler, no completion\n    if [[ ${cur} != \"${words[cword]}\" ]]; then\n        return\n    fi\n\n    local completions\n    completions=(\"${commands[@]}\")\n    if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then\n        completions+=(\"${must_have_one_noun[@]}\")\n    elif [[ -n \"${has_completion_function}\" ]]; then\n        # if a go completion function is provided, defer to that function\n        __sing-box_handle_go_custom_completion\n    fi\n    if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then\n        completions+=(\"${must_have_one_flag[@]}\")\n    fi\n    while IFS='' read -r comp; do\n        COMPREPLY+=(\"$comp\")\n    done < <(compgen -W \"${completions[*]}\" -- \"$cur\")\n\n    if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then\n        while IFS='' read -r comp; do\n            COMPREPLY+=(\"$comp\")\n        done < <(compgen -W \"${noun_aliases[*]}\" -- \"$cur\")\n    fi\n\n    if [[ ${#COMPREPLY[@]} -eq 0 ]]; then\n        if declare -F __sing-box_custom_func >/dev/null; then\n            # try command name qualified custom func\n            __sing-box_custom_func\n        else\n            # otherwise fall back to unqualified for compatibility\n            declare -F __custom_func >/dev/null && __custom_func\n        fi\n    fi\n\n    # available in bash-completion >= 2, not always present on macOS\n    if declare -F __ltrim_colon_completions >/dev/null; then\n        __ltrim_colon_completions \"$cur\"\n    fi\n\n    # If there is only 1 completion and it is a flag with an = it will be completed\n    # but we don't want a space after the =\n    if [[ \"${#COMPREPLY[@]}\" -eq \"1\" ]] && [[ $(type -t compopt) = \"builtin\" ]] && [[ \"${COMPREPLY[0]}\" == --*= ]]; then\n       compopt -o nospace\n    fi\n}\n\n# The arguments should be in the form \"ext1|ext2|extn\"\n__sing-box_handle_filename_extension_flag()\n{\n    local ext=\"$1\"\n    _filedir \"@(${ext})\"\n}\n\n__sing-box_handle_subdirs_in_dir_flag()\n{\n    local dir=\"$1\"\n    pushd \"${dir}\" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return\n}\n\n__sing-box_handle_flag()\n{\n    __sing-box_debug \"${FUNCNAME[0]}: c is $c words[c] is ${words[c]}\"\n\n    # if a command required a flag, and we found it, unset must_have_one_flag()\n    local flagname=${words[c]}\n    local flagvalue=\"\"\n    # if the word contained an =\n    if [[ ${words[c]} == *\"=\"* ]]; then\n        flagvalue=${flagname#*=} # take in as flagvalue after the =\n        flagname=${flagname%=*} # strip everything after the =\n        flagname=\"${flagname}=\" # but put the = back\n    fi\n    __sing-box_debug \"${FUNCNAME[0]}: looking for ${flagname}\"\n    if __sing-box_contains_word \"${flagname}\" \"${must_have_one_flag[@]}\"; then\n        must_have_one_flag=()\n    fi\n\n    # if you set a flag which only applies to this command, don't show subcommands\n    if __sing-box_contains_word \"${flagname}\" \"${local_nonpersistent_flags[@]}\"; then\n      commands=()\n    fi\n\n    # keep flag value with flagname as flaghash\n    # flaghash variable is an associative array which is only supported in bash > 3.\n    if [[ -z \"${BASH_VERSION:-}\" || \"${BASH_VERSINFO[0]:-}\" -gt 3 ]]; then\n        if [ -n \"${flagvalue}\" ] ; then\n            flaghash[${flagname}]=${flagvalue}\n        elif [ -n \"${words[ $((c+1)) ]}\" ] ; then\n            flaghash[${flagname}]=${words[ $((c+1)) ]}\n        else\n            flaghash[${flagname}]=\"true\" # pad \"true\" for bool flag\n        fi\n    fi\n\n    # skip the argument to a two word flag\n    if [[ ${words[c]} != *\"=\"* ]] && __sing-box_contains_word \"${words[c]}\" \"${two_word_flags[@]}\"; then\n        __sing-box_debug \"${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument\"\n        c=$((c+1))\n        # if we are looking for a flags value, don't show commands\n        if [[ $c -eq $cword ]]; then\n            commands=()\n        fi\n    fi\n\n    c=$((c+1))\n\n}\n\n__sing-box_handle_noun()\n{\n    __sing-box_debug \"${FUNCNAME[0]}: c is $c words[c] is ${words[c]}\"\n\n    if __sing-box_contains_word \"${words[c]}\" \"${must_have_one_noun[@]}\"; then\n        must_have_one_noun=()\n    elif __sing-box_contains_word \"${words[c]}\" \"${noun_aliases[@]}\"; then\n        must_have_one_noun=()\n    fi\n\n    nouns+=(\"${words[c]}\")\n    c=$((c+1))\n}\n\n__sing-box_handle_command()\n{\n    __sing-box_debug \"${FUNCNAME[0]}: c is $c words[c] is ${words[c]}\"\n\n    local next_command\n    if [[ -n ${last_command} ]]; then\n        next_command=\"_${last_command}_${words[c]//:/__}\"\n    else\n        if [[ $c -eq 0 ]]; then\n            next_command=\"_sing-box_root_command\"\n        else\n            next_command=\"_${words[c]//:/__}\"\n        fi\n    fi\n    c=$((c+1))\n    __sing-box_debug \"${FUNCNAME[0]}: looking for ${next_command}\"\n    declare -F \"$next_command\" >/dev/null && $next_command\n}\n\n__sing-box_handle_word()\n{\n    if [[ $c -ge $cword ]]; then\n        __sing-box_handle_reply\n        return\n    fi\n    __sing-box_debug \"${FUNCNAME[0]}: c is $c words[c] is ${words[c]}\"\n    if [[ \"${words[c]}\" == -* ]]; then\n        __sing-box_handle_flag\n    elif __sing-box_contains_word \"${words[c]}\" \"${commands[@]}\"; then\n        __sing-box_handle_command\n    elif [[ $c -eq 0 ]]; then\n        __sing-box_handle_command\n    elif __sing-box_contains_word \"${words[c]}\" \"${command_aliases[@]}\"; then\n        # aliashash variable is an associative array which is only supported in bash > 3.\n        if [[ -z \"${BASH_VERSION:-}\" || \"${BASH_VERSINFO[0]:-}\" -gt 3 ]]; then\n            words[c]=${aliashash[${words[c]}]}\n            __sing-box_handle_command\n        else\n            __sing-box_handle_noun\n        fi\n    else\n        __sing-box_handle_noun\n    fi\n    __sing-box_handle_word\n}\n\n_sing-box_check()\n{\n    last_command=\"sing-box_check\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_format()\n{\n    last_command=\"sing-box_format\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--write\")\n    flags+=(\"-w\")\n    local_nonpersistent_flags+=(\"--write\")\n    local_nonpersistent_flags+=(\"-w\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_generate_ech-keypair()\n{\n    last_command=\"sing-box_generate_ech-keypair\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--pq-signature-schemes-enabled\")\n    local_nonpersistent_flags+=(\"--pq-signature-schemes-enabled\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_generate_rand()\n{\n    last_command=\"sing-box_generate_rand\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--base64\")\n    local_nonpersistent_flags+=(\"--base64\")\n    flags+=(\"--hex\")\n    local_nonpersistent_flags+=(\"--hex\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_generate_reality-keypair()\n{\n    last_command=\"sing-box_generate_reality-keypair\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_generate_tls-keypair()\n{\n    last_command=\"sing-box_generate_tls-keypair\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--months=\")\n    two_word_flags+=(\"--months\")\n    two_word_flags+=(\"-m\")\n    local_nonpersistent_flags+=(\"--months\")\n    local_nonpersistent_flags+=(\"--months=\")\n    local_nonpersistent_flags+=(\"-m\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_generate_uuid()\n{\n    last_command=\"sing-box_generate_uuid\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_generate_vapid-keypair()\n{\n    last_command=\"sing-box_generate_vapid-keypair\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_generate_wg-keypair()\n{\n    last_command=\"sing-box_generate_wg-keypair\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_generate()\n{\n    last_command=\"sing-box_generate\"\n\n    command_aliases=()\n\n    commands=()\n    commands+=(\"ech-keypair\")\n    commands+=(\"rand\")\n    commands+=(\"reality-keypair\")\n    commands+=(\"tls-keypair\")\n    commands+=(\"uuid\")\n    commands+=(\"vapid-keypair\")\n    commands+=(\"wg-keypair\")\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_geoip_export()\n{\n    last_command=\"sing-box_geoip_export\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--output=\")\n    two_word_flags+=(\"--output\")\n    two_word_flags+=(\"-o\")\n    local_nonpersistent_flags+=(\"--output\")\n    local_nonpersistent_flags+=(\"--output=\")\n    local_nonpersistent_flags+=(\"-o\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n    flags+=(\"--file=\")\n    two_word_flags+=(\"--file\")\n    two_word_flags+=(\"-f\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_geoip_list()\n{\n    last_command=\"sing-box_geoip_list\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n    flags+=(\"--file=\")\n    two_word_flags+=(\"--file\")\n    two_word_flags+=(\"-f\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_geoip_lookup()\n{\n    last_command=\"sing-box_geoip_lookup\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n    flags+=(\"--file=\")\n    two_word_flags+=(\"--file\")\n    two_word_flags+=(\"-f\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_geoip()\n{\n    last_command=\"sing-box_geoip\"\n\n    command_aliases=()\n\n    commands=()\n    commands+=(\"export\")\n    commands+=(\"list\")\n    commands+=(\"lookup\")\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--file=\")\n    two_word_flags+=(\"--file\")\n    two_word_flags+=(\"-f\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_geosite_export()\n{\n    last_command=\"sing-box_geosite_export\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--output=\")\n    two_word_flags+=(\"--output\")\n    two_word_flags+=(\"-o\")\n    local_nonpersistent_flags+=(\"--output\")\n    local_nonpersistent_flags+=(\"--output=\")\n    local_nonpersistent_flags+=(\"-o\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n    flags+=(\"--file=\")\n    two_word_flags+=(\"--file\")\n    two_word_flags+=(\"-f\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_geosite_list()\n{\n    last_command=\"sing-box_geosite_list\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n    flags+=(\"--file=\")\n    two_word_flags+=(\"--file\")\n    two_word_flags+=(\"-f\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_geosite_lookup()\n{\n    last_command=\"sing-box_geosite_lookup\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n    flags+=(\"--file=\")\n    two_word_flags+=(\"--file\")\n    two_word_flags+=(\"-f\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_geosite()\n{\n    last_command=\"sing-box_geosite\"\n\n    command_aliases=()\n\n    commands=()\n    commands+=(\"export\")\n    commands+=(\"list\")\n    commands+=(\"lookup\")\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--file=\")\n    two_word_flags+=(\"--file\")\n    two_word_flags+=(\"-f\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_merge()\n{\n    last_command=\"sing-box_merge\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_rule-set_compile()\n{\n    last_command=\"sing-box_rule-set_compile\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--output=\")\n    two_word_flags+=(\"--output\")\n    two_word_flags+=(\"-o\")\n    local_nonpersistent_flags+=(\"--output\")\n    local_nonpersistent_flags+=(\"--output=\")\n    local_nonpersistent_flags+=(\"-o\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_rule-set_convert()\n{\n    last_command=\"sing-box_rule-set_convert\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--output=\")\n    two_word_flags+=(\"--output\")\n    two_word_flags+=(\"-o\")\n    local_nonpersistent_flags+=(\"--output\")\n    local_nonpersistent_flags+=(\"--output=\")\n    local_nonpersistent_flags+=(\"-o\")\n    flags+=(\"--type=\")\n    two_word_flags+=(\"--type\")\n    two_word_flags+=(\"-t\")\n    local_nonpersistent_flags+=(\"--type\")\n    local_nonpersistent_flags+=(\"--type=\")\n    local_nonpersistent_flags+=(\"-t\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_rule-set_decompile()\n{\n    last_command=\"sing-box_rule-set_decompile\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--output=\")\n    two_word_flags+=(\"--output\")\n    two_word_flags+=(\"-o\")\n    local_nonpersistent_flags+=(\"--output\")\n    local_nonpersistent_flags+=(\"--output=\")\n    local_nonpersistent_flags+=(\"-o\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_rule-set_format()\n{\n    last_command=\"sing-box_rule-set_format\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--write\")\n    flags+=(\"-w\")\n    local_nonpersistent_flags+=(\"--write\")\n    local_nonpersistent_flags+=(\"-w\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_rule-set_match()\n{\n    last_command=\"sing-box_rule-set_match\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--format=\")\n    two_word_flags+=(\"--format\")\n    two_word_flags+=(\"-f\")\n    local_nonpersistent_flags+=(\"--format\")\n    local_nonpersistent_flags+=(\"--format=\")\n    local_nonpersistent_flags+=(\"-f\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_rule-set_merge()\n{\n    last_command=\"sing-box_rule-set_merge\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_rule-set_upgrade()\n{\n    last_command=\"sing-box_rule-set_upgrade\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--write\")\n    flags+=(\"-w\")\n    local_nonpersistent_flags+=(\"--write\")\n    local_nonpersistent_flags+=(\"-w\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_rule-set()\n{\n    last_command=\"sing-box_rule-set\"\n\n    command_aliases=()\n\n    commands=()\n    commands+=(\"compile\")\n    commands+=(\"convert\")\n    commands+=(\"decompile\")\n    commands+=(\"format\")\n    commands+=(\"match\")\n    commands+=(\"merge\")\n    commands+=(\"upgrade\")\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_run()\n{\n    last_command=\"sing-box_run\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_tools_connect()\n{\n    last_command=\"sing-box_tools_connect\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--network=\")\n    two_word_flags+=(\"--network\")\n    two_word_flags+=(\"-n\")\n    local_nonpersistent_flags+=(\"--network\")\n    local_nonpersistent_flags+=(\"--network=\")\n    local_nonpersistent_flags+=(\"-n\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n    flags+=(\"--outbound=\")\n    two_word_flags+=(\"--outbound\")\n    two_word_flags+=(\"-o\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_tools_fetch()\n{\n    last_command=\"sing-box_tools_fetch\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n    flags+=(\"--outbound=\")\n    two_word_flags+=(\"--outbound\")\n    two_word_flags+=(\"-o\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_tools_synctime()\n{\n    last_command=\"sing-box_tools_synctime\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--format=\")\n    two_word_flags+=(\"--format\")\n    two_word_flags+=(\"-f\")\n    local_nonpersistent_flags+=(\"--format\")\n    local_nonpersistent_flags+=(\"--format=\")\n    local_nonpersistent_flags+=(\"-f\")\n    flags+=(\"--server=\")\n    two_word_flags+=(\"--server\")\n    two_word_flags+=(\"-s\")\n    local_nonpersistent_flags+=(\"--server\")\n    local_nonpersistent_flags+=(\"--server=\")\n    local_nonpersistent_flags+=(\"-s\")\n    flags+=(\"--write\")\n    flags+=(\"-w\")\n    local_nonpersistent_flags+=(\"--write\")\n    local_nonpersistent_flags+=(\"-w\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n    flags+=(\"--outbound=\")\n    two_word_flags+=(\"--outbound\")\n    two_word_flags+=(\"-o\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_tools()\n{\n    last_command=\"sing-box_tools\"\n\n    command_aliases=()\n\n    commands=()\n    commands+=(\"connect\")\n    commands+=(\"fetch\")\n    commands+=(\"synctime\")\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--outbound=\")\n    two_word_flags+=(\"--outbound\")\n    two_word_flags+=(\"-o\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_version()\n{\n    last_command=\"sing-box_version\"\n\n    command_aliases=()\n\n    commands=()\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--name\")\n    flags+=(\"-n\")\n    local_nonpersistent_flags+=(\"--name\")\n    local_nonpersistent_flags+=(\"-n\")\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n_sing-box_root_command()\n{\n    last_command=\"sing-box\"\n\n    command_aliases=()\n\n    commands=()\n    commands+=(\"check\")\n    commands+=(\"format\")\n    commands+=(\"generate\")\n    commands+=(\"geoip\")\n    commands+=(\"geosite\")\n    commands+=(\"merge\")\n    commands+=(\"rule-set\")\n    commands+=(\"run\")\n    commands+=(\"tools\")\n    commands+=(\"version\")\n\n    flags=()\n    two_word_flags=()\n    local_nonpersistent_flags=()\n    flags_with_completion=()\n    flags_completion=()\n\n    flags+=(\"--config=\")\n    two_word_flags+=(\"--config\")\n    two_word_flags+=(\"-c\")\n    flags+=(\"--config-directory=\")\n    two_word_flags+=(\"--config-directory\")\n    two_word_flags+=(\"-C\")\n    flags+=(\"--directory=\")\n    two_word_flags+=(\"--directory\")\n    two_word_flags+=(\"-D\")\n    flags+=(\"--disable-color\")\n\n    must_have_one_flag=()\n    must_have_one_noun=()\n    noun_aliases=()\n}\n\n__start_sing-box()\n{\n    local cur prev words cword split\n    declare -A flaghash 2>/dev/null || :\n    declare -A aliashash 2>/dev/null || :\n    if declare -F _init_completion >/dev/null 2>&1; then\n        _init_completion -s || return\n    else\n        __sing-box_init_completion -n \"=\" || return\n    fi\n\n    local c=0\n    local flag_parsing_disabled=\n    local flags=()\n    local two_word_flags=()\n    local local_nonpersistent_flags=()\n    local flags_with_completion=()\n    local flags_completion=()\n    local commands=(\"sing-box\")\n    local command_aliases=()\n    local must_have_one_flag=()\n    local must_have_one_noun=()\n    local has_completion_function=\"\"\n    local last_command=\"\"\n    local nouns=()\n    local noun_aliases=()\n\n    __sing-box_handle_word\n}\n\nif [[ $(type -t compopt) = \"builtin\" ]]; then\n    complete -o default -F __start_sing-box sing-box\nelse\n    complete -o default -o nospace -F __start_sing-box sing-box\nfi\n\n# ex: ts=4 sw=4 et filetype=sh\n"
  },
  {
    "path": "release/completions/sing-box.fish",
    "content": "# fish completion for sing-box                             -*- shell-script -*-\n\nfunction __sing_box_debug\n    set -l file \"$BASH_COMP_DEBUG_FILE\"\n    if test -n \"$file\"\n        echo \"$argv\" >> $file\n    end\nend\n\nfunction __sing_box_perform_completion\n    __sing_box_debug \"Starting __sing_box_perform_completion\"\n\n    # Extract all args except the last one\n    set -l args (commandline -opc)\n    # Extract the last arg and escape it in case it is a space\n    set -l lastArg (string escape -- (commandline -ct))\n\n    __sing_box_debug \"args: $args\"\n    __sing_box_debug \"last arg: $lastArg\"\n\n    # Disable ActiveHelp which is not supported for fish shell\n    set -l requestComp \"SING_BOX_ACTIVE_HELP=0 $args[1] __complete $args[2..-1] $lastArg\"\n\n    __sing_box_debug \"Calling $requestComp\"\n    set -l results (eval $requestComp 2> /dev/null)\n\n    # Some programs may output extra empty lines after the directive.\n    # Let's ignore them or else it will break completion.\n    # Ref: https://github.com/spf13/cobra/issues/1279\n    for line in $results[-1..1]\n        if test (string trim -- $line) = \"\"\n            # Found an empty line, remove it\n            set results $results[1..-2]\n        else\n            # Found non-empty line, we have our proper output\n            break\n        end\n    end\n\n    set -l comps $results[1..-2]\n    set -l directiveLine $results[-1]\n\n    # For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>)\n    # completions must be prefixed with the flag\n    set -l flagPrefix (string match -r -- '-.*=' \"$lastArg\")\n\n    __sing_box_debug \"Comps: $comps\"\n    __sing_box_debug \"DirectiveLine: $directiveLine\"\n    __sing_box_debug \"flagPrefix: $flagPrefix\"\n\n    for comp in $comps\n        printf \"%s%s\\n\" \"$flagPrefix\" \"$comp\"\n    end\n\n    printf \"%s\\n\" \"$directiveLine\"\nend\n\n# this function limits calls to __sing_box_perform_completion, by caching the result behind $__sing_box_perform_completion_once_result\nfunction __sing_box_perform_completion_once\n    __sing_box_debug \"Starting __sing_box_perform_completion_once\"\n\n    if test -n \"$__sing_box_perform_completion_once_result\"\n        __sing_box_debug \"Seems like a valid result already exists, skipping __sing_box_perform_completion\"\n        return 0\n    end\n\n    set --global __sing_box_perform_completion_once_result (__sing_box_perform_completion)\n    if test -z \"$__sing_box_perform_completion_once_result\"\n        __sing_box_debug \"No completions, probably due to a failure\"\n        return 1\n    end\n\n    __sing_box_debug \"Performed completions and set __sing_box_perform_completion_once_result\"\n    return 0\nend\n\n# this function is used to clear the $__sing_box_perform_completion_once_result variable after completions are run\nfunction __sing_box_clear_perform_completion_once_result\n    __sing_box_debug \"\"\n    __sing_box_debug \"========= clearing previously set __sing_box_perform_completion_once_result variable ==========\"\n    set --erase __sing_box_perform_completion_once_result\n    __sing_box_debug \"Successfully erased the variable __sing_box_perform_completion_once_result\"\nend\n\nfunction __sing_box_requires_order_preservation\n    __sing_box_debug \"\"\n    __sing_box_debug \"========= checking if order preservation is required ==========\"\n\n    __sing_box_perform_completion_once\n    if test -z \"$__sing_box_perform_completion_once_result\"\n        __sing_box_debug \"Error determining if order preservation is required\"\n        return 1\n    end\n\n    set -l directive (string sub --start 2 $__sing_box_perform_completion_once_result[-1])\n    __sing_box_debug \"Directive is: $directive\"\n\n    set -l shellCompDirectiveKeepOrder 32\n    set -l keeporder (math (math --scale 0 $directive / $shellCompDirectiveKeepOrder) % 2)\n    __sing_box_debug \"Keeporder is: $keeporder\"\n\n    if test $keeporder -ne 0\n        __sing_box_debug \"This does require order preservation\"\n        return 0\n    end\n\n    __sing_box_debug \"This doesn't require order preservation\"\n    return 1\nend\n\n\n# This function does two things:\n# - Obtain the completions and store them in the global __sing_box_comp_results\n# - Return false if file completion should be performed\nfunction __sing_box_prepare_completions\n    __sing_box_debug \"\"\n    __sing_box_debug \"========= starting completion logic ==========\"\n\n    # Start fresh\n    set --erase __sing_box_comp_results\n\n    __sing_box_perform_completion_once\n    __sing_box_debug \"Completion results: $__sing_box_perform_completion_once_result\"\n\n    if test -z \"$__sing_box_perform_completion_once_result\"\n        __sing_box_debug \"No completion, probably due to a failure\"\n        # Might as well do file completion, in case it helps\n        return 1\n    end\n\n    set -l directive (string sub --start 2 $__sing_box_perform_completion_once_result[-1])\n    set --global __sing_box_comp_results $__sing_box_perform_completion_once_result[1..-2]\n\n    __sing_box_debug \"Completions are: $__sing_box_comp_results\"\n    __sing_box_debug \"Directive is: $directive\"\n\n    set -l shellCompDirectiveError 1\n    set -l shellCompDirectiveNoSpace 2\n    set -l shellCompDirectiveNoFileComp 4\n    set -l shellCompDirectiveFilterFileExt 8\n    set -l shellCompDirectiveFilterDirs 16\n\n    if test -z \"$directive\"\n        set directive 0\n    end\n\n    set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) % 2)\n    if test $compErr -eq 1\n        __sing_box_debug \"Received error directive: aborting.\"\n        # Might as well do file completion, in case it helps\n        return 1\n    end\n\n    set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) % 2)\n    set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) % 2)\n    if test $filefilter -eq 1; or test $dirfilter -eq 1\n        __sing_box_debug \"File extension filtering or directory filtering not supported\"\n        # Do full file completion instead\n        return 1\n    end\n\n    set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) % 2)\n    set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) % 2)\n\n    __sing_box_debug \"nospace: $nospace, nofiles: $nofiles\"\n\n    # If we want to prevent a space, or if file completion is NOT disabled,\n    # we need to count the number of valid completions.\n    # To do so, we will filter on prefix as the completions we have received\n    # may not already be filtered so as to allow fish to match on different\n    # criteria than the prefix.\n    if test $nospace -ne 0; or test $nofiles -eq 0\n        set -l prefix (commandline -t | string escape --style=regex)\n        __sing_box_debug \"prefix: $prefix\"\n\n        set -l completions (string match -r -- \"^$prefix.*\" $__sing_box_comp_results)\n        set --global __sing_box_comp_results $completions\n        __sing_box_debug \"Filtered completions are: $__sing_box_comp_results\"\n\n        # Important not to quote the variable for count to work\n        set -l numComps (count $__sing_box_comp_results)\n        __sing_box_debug \"numComps: $numComps\"\n\n        if test $numComps -eq 1; and test $nospace -ne 0\n            # We must first split on \\t to get rid of the descriptions to be\n            # able to check what the actual completion will be.\n            # We don't need descriptions anyway since there is only a single\n            # real completion which the shell will expand immediately.\n            set -l split (string split --max 1 \\t $__sing_box_comp_results[1])\n\n            # Fish won't add a space if the completion ends with any\n            # of the following characters: @=/:.,\n            set -l lastChar (string sub -s -1 -- $split)\n            if not string match -r -q \"[@=/:.,]\" -- \"$lastChar\"\n                # In other cases, to support the \"nospace\" directive we trick the shell\n                # by outputting an extra, longer completion.\n                __sing_box_debug \"Adding second completion to perform nospace directive\"\n                set --global __sing_box_comp_results $split[1] $split[1].\n                __sing_box_debug \"Completions are now: $__sing_box_comp_results\"\n            end\n        end\n\n        if test $numComps -eq 0; and test $nofiles -eq 0\n            # To be consistent with bash and zsh, we only trigger file\n            # completion when there are no other completions\n            __sing_box_debug \"Requesting file completion\"\n            return 1\n        end\n    end\n\n    return 0\nend\n\n# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves\n# so we can properly delete any completions provided by another script.\n# Only do this if the program can be found, or else fish may print some errors; besides,\n# the existing completions will only be loaded if the program can be found.\nif type -q \"sing-box\"\n    # The space after the program name is essential to trigger completion for the program\n    # and not completion of the program name itself.\n    # Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.\n    complete --do-complete \"sing-box \" > /dev/null 2>&1\nend\n\n# Remove any pre-existing completions for the program since we will be handling all of them.\ncomplete -c sing-box -e\n\n# this will get called after the two calls below and clear the $__sing_box_perform_completion_once_result global\ncomplete -c sing-box -n '__sing_box_clear_perform_completion_once_result'\n# The call to __sing_box_prepare_completions will setup __sing_box_comp_results\n# which provides the program's completion choices.\n# If this doesn't require order preservation, we don't use the -k flag\ncomplete -c sing-box -n 'not __sing_box_requires_order_preservation && __sing_box_prepare_completions' -f -a '$__sing_box_comp_results'\n# otherwise we use the -k flag\ncomplete -k -c sing-box -n '__sing_box_requires_order_preservation && __sing_box_prepare_completions' -f -a '$__sing_box_comp_results'\n"
  },
  {
    "path": "release/completions/sing-box.zsh",
    "content": "#compdef sing-box\ncompdef _sing-box sing-box\n\n# zsh completion for sing-box                             -*- shell-script -*-\n\n__sing-box_debug()\n{\n    local file=\"$BASH_COMP_DEBUG_FILE\"\n    if [[ -n ${file} ]]; then\n        echo \"$*\" >> \"${file}\"\n    fi\n}\n\n_sing-box()\n{\n    local shellCompDirectiveError=1\n    local shellCompDirectiveNoSpace=2\n    local shellCompDirectiveNoFileComp=4\n    local shellCompDirectiveFilterFileExt=8\n    local shellCompDirectiveFilterDirs=16\n    local shellCompDirectiveKeepOrder=32\n\n    local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace keepOrder\n    local -a completions\n\n    __sing-box_debug \"\\n========= starting completion logic ==========\"\n    __sing-box_debug \"CURRENT: ${CURRENT}, words[*]: ${words[*]}\"\n\n    # The user could have moved the cursor backwards on the command-line.\n    # We need to trigger completion from the $CURRENT location, so we need\n    # to truncate the command-line ($words) up to the $CURRENT location.\n    # (We cannot use $CURSOR as its value does not work when a command is an alias.)\n    words=(\"${=words[1,CURRENT]}\")\n    __sing-box_debug \"Truncated words[*]: ${words[*]},\"\n\n    lastParam=${words[-1]}\n    lastChar=${lastParam[-1]}\n    __sing-box_debug \"lastParam: ${lastParam}, lastChar: ${lastChar}\"\n\n    # For zsh, when completing a flag with an = (e.g., sing-box -n=<TAB>)\n    # completions must be prefixed with the flag\n    setopt local_options BASH_REMATCH\n    if [[ \"${lastParam}\" =~ '-.*=' ]]; then\n        # We are dealing with a flag with an =\n        flagPrefix=\"-P ${BASH_REMATCH}\"\n    fi\n\n    # Prepare the command to obtain completions\n    requestComp=\"${words[1]} __complete ${words[2,-1]}\"\n    if [ \"${lastChar}\" = \"\" ]; then\n        # If the last parameter is complete (there is a space following it)\n        # We add an extra empty parameter so we can indicate this to the go completion code.\n        __sing-box_debug \"Adding extra empty parameter\"\n        requestComp=\"${requestComp} \\\"\\\"\"\n    fi\n\n    __sing-box_debug \"About to call: eval ${requestComp}\"\n\n    # Use eval to handle any environment variables and such\n    out=$(eval ${requestComp} 2>/dev/null)\n    __sing-box_debug \"completion output: ${out}\"\n\n    # Extract the directive integer following a : from the last line\n    local lastLine\n    while IFS='\\n' read -r line; do\n        lastLine=${line}\n    done < <(printf \"%s\\n\" \"${out[@]}\")\n    __sing-box_debug \"last line: ${lastLine}\"\n\n    if [ \"${lastLine[1]}\" = : ]; then\n        directive=${lastLine[2,-1]}\n        # Remove the directive including the : and the newline\n        local suffix\n        (( suffix=${#lastLine}+2))\n        out=${out[1,-$suffix]}\n    else\n        # There is no directive specified.  Leave $out as is.\n        __sing-box_debug \"No directive found.  Setting do default\"\n        directive=0\n    fi\n\n    __sing-box_debug \"directive: ${directive}\"\n    __sing-box_debug \"completions: ${out}\"\n    __sing-box_debug \"flagPrefix: ${flagPrefix}\"\n\n    if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then\n        __sing-box_debug \"Completion received error. Ignoring completions.\"\n        return\n    fi\n\n    local activeHelpMarker=\"_activeHelp_ \"\n    local endIndex=${#activeHelpMarker}\n    local startIndex=$((${#activeHelpMarker}+1))\n    local hasActiveHelp=0\n    while IFS='\\n' read -r comp; do\n        # Check if this is an activeHelp statement (i.e., prefixed with $activeHelpMarker)\n        if [ \"${comp[1,$endIndex]}\" = \"$activeHelpMarker\" ];then\n            __sing-box_debug \"ActiveHelp found: $comp\"\n            comp=\"${comp[$startIndex,-1]}\"\n            if [ -n \"$comp\" ]; then\n                compadd -x \"${comp}\"\n                __sing-box_debug \"ActiveHelp will need delimiter\"\n                hasActiveHelp=1\n            fi\n\n            continue\n        fi\n\n        if [ -n \"$comp\" ]; then\n            # If requested, completions are returned with a description.\n            # The description is preceded by a TAB character.\n            # For zsh's _describe, we need to use a : instead of a TAB.\n            # We first need to escape any : as part of the completion itself.\n            comp=${comp//:/\\\\:}\n\n            local tab=\"$(printf '\\t')\"\n            comp=${comp//$tab/:}\n\n            __sing-box_debug \"Adding completion: ${comp}\"\n            completions+=${comp}\n            lastComp=$comp\n        fi\n    done < <(printf \"%s\\n\" \"${out[@]}\")\n\n    # Add a delimiter after the activeHelp statements, but only if:\n    # - there are completions following the activeHelp statements, or\n    # - file completion will be performed (so there will be choices after the activeHelp)\n    if [ $hasActiveHelp -eq 1 ]; then\n        if [ ${#completions} -ne 0 ] || [ $((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then\n            __sing-box_debug \"Adding activeHelp delimiter\"\n            compadd -x \"--\"\n            hasActiveHelp=0\n        fi\n    fi\n\n    if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then\n        __sing-box_debug \"Activating nospace.\"\n        noSpace=\"-S ''\"\n    fi\n\n    if [ $((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then\n        __sing-box_debug \"Activating keep order.\"\n        keepOrder=\"-V\"\n    fi\n\n    if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then\n        # File extension filtering\n        local filteringCmd\n        filteringCmd='_files'\n        for filter in ${completions[@]}; do\n            if [ ${filter[1]} != '*' ]; then\n                # zsh requires a glob pattern to do file filtering\n                filter=\"\\*.$filter\"\n            fi\n            filteringCmd+=\" -g $filter\"\n        done\n        filteringCmd+=\" ${flagPrefix}\"\n\n        __sing-box_debug \"File filtering command: $filteringCmd\"\n        _arguments '*:filename:'\"$filteringCmd\"\n    elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then\n        # File completion for directories only\n        local subdir\n        subdir=\"${completions[1]}\"\n        if [ -n \"$subdir\" ]; then\n            __sing-box_debug \"Listing directories in $subdir\"\n            pushd \"${subdir}\" >/dev/null 2>&1\n        else\n            __sing-box_debug \"Listing directories in .\"\n        fi\n\n        local result\n        _arguments '*:dirname:_files -/'\" ${flagPrefix}\"\n        result=$?\n        if [ -n \"$subdir\" ]; then\n            popd >/dev/null 2>&1\n        fi\n        return $result\n    else\n        __sing-box_debug \"Calling _describe\"\n        if eval _describe $keepOrder \"completions\" completions $flagPrefix $noSpace; then\n            __sing-box_debug \"_describe found some completions\"\n\n            # Return the success of having called _describe\n            return 0\n        else\n            __sing-box_debug \"_describe did not find completions.\"\n            __sing-box_debug \"Checking if we should do file completion.\"\n            if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then\n                __sing-box_debug \"deactivating file completion\"\n\n                # We must return an error code here to let zsh know that there were no\n                # completions found by _describe; this is what will trigger other\n                # matching algorithms to attempt to find completions.\n                # For example zsh can match letters in the middle of words.\n                return 1\n            else\n                # Perform file completion\n                __sing-box_debug \"Activating file completion\"\n\n                # We must return the result of this command, so it must be the\n                # last command, or else we must store its result to return it.\n                _arguments '*:filename:_files'\" ${flagPrefix}\"\n            fi\n        fi\n    fi\n}\n\n# don't run the completion function when being source-ed or eval-ed\nif [ \"$funcstack[1]\" = \"_sing-box\" ]; then\n    _sing-box\nfi\n"
  },
  {
    "path": "release/config/config.json",
    "content": "{\n  \"log\": {\n    \"level\": \"info\"\n  },\n  \"dns\": {\n    \"servers\": [\n      {\n        \"type\": \"tls\",\n        \"tag\": \"google\",\n        \"server\": \"8.8.8.8\"\n      }\n    ]\n  },\n  \"inbounds\": [\n    {\n      \"type\": \"shadowsocks\",\n      \"listen\": \"::\",\n      \"listen_port\": 8080,\n      \"network\": \"tcp\",\n      \"method\": \"2022-blake3-aes-128-gcm\",\n      \"password\": \"Gn1JUS14bLUHgv1cWDDp4A==\",\n      \"multiplex\": {\n        \"enabled\": true,\n        \"padding\": true\n      }\n    }\n  ],\n  \"outbounds\": [\n    {\n      \"type\": \"direct\"\n    }\n  ],\n  \"route\": {\n    \"rules\": [\n      {\n        \"port\": 53,\n        \"action\": \"hijack-dns\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "release/config/openwrt.conf",
    "content": "config sing-box 'main'\n\toption enabled '1'\n\toption conffile '/etc/sing-box/config.json'\n\toption workdir '/usr/share/sing-box'\n\toption log_stderr '1'\n"
  },
  {
    "path": "release/config/openwrt.init",
    "content": "#!/bin/sh /etc/rc.common\n\nUSE_PROCD=1\nSTART=99\nPROG=\"/usr/bin/sing-box\"\n\nstart_service() {\n  config_load \"sing-box\"\n\n  local enabled config_file working_directory\n  local log_stderr\n  config_get_bool enabled \"main\" \"enabled\" \"0\"\n  [ \"$enabled\" -eq \"1\" ] || return 0\n\n  config_get config_file \"main\" \"conffile\" \"/etc/sing-box/config.json\"\n  config_get working_directory \"main\" \"workdir\" \"/usr/share/sing-box\"\n  config_get_bool log_stderr \"main\" \"log_stderr\" \"1\"\n\n  procd_open_instance\n  procd_set_param command \"$PROG\" run -c \"$config_file\" -D \"$working_directory\"\n  procd_set_param file \"$config_file\"\n  procd_set_param stderr \"$log_stderr\"\n  procd_set_param limits core=\"unlimited\"\n  procd_set_param limits nofile=\"1000000 1000000\"\n  procd_set_param respawn\n\n  procd_close_instance\n}\n\nservice_triggers() {\n  procd_add_reload_trigger \"sing-box\"\n}\n"
  },
  {
    "path": "release/config/openwrt.keep",
    "content": "/etc/sing-box/\n"
  },
  {
    "path": "release/config/openwrt.prerm",
    "content": "#!/bin/sh\n[ -s ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0\n. ${IPKG_INSTROOT}/lib/functions.sh\ndefault_prerm $0 $@\n"
  },
  {
    "path": "release/config/sing-box-split-dns.xml",
    "content": "<!DOCTYPE busconfig PUBLIC\n        \"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\"\n        \"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\">\n<busconfig>\n    <policy user=\"root\">\n        <allow own=\"org.freedesktop.resolve1\"/>\n        <allow send_destination=\"org.freedesktop.resolve1\"/>\n        <allow receive_sender=\"org.freedesktop.resolve1\"/>\n    </policy>\n    <policy user=\"sing-box\">\n        <allow own=\"org.freedesktop.resolve1\"/>\n        <allow send_destination=\"org.freedesktop.resolve1\"/>\n        <allow receive_sender=\"org.freedesktop.resolve1\"/>\n    </policy>\n</busconfig>\n"
  },
  {
    "path": "release/config/sing-box.confd",
    "content": "# /etc/conf.d/sing-box: config file for /etc/init.d/sing-box\n\n# sing-box configuration path, could be file or directory\n# SINGBOX_CONFIG=/etc/sing-box\n\n# SINGBOX_WORKDIR=/var/lib/sing-box\n"
  },
  {
    "path": "release/config/sing-box.initd",
    "content": "#!/sbin/openrc-run\n\nname=$RC_SVCNAME\ndescription=\"sing-box service\"\nsupervisor=\"supervise-daemon\"\ncommand=\"/usr/bin/sing-box\"\nextra_commands=\"checkconfig\"\nextra_started_commands=\"reload\"\n\n: ${SINGBOX_CONFIG:=${config:-\"/etc/sing-box\"}}\n\nif [ -d \"$SINGBOX_CONFIG\" ]; then\n\t_config_opt=\"-C $SINGBOX_CONFIG\"\nelif [ -z \"$SINGBOX_CONFIG\" ]; then\n\t_config_opt=\"\"\nelse\n\t_config_opt=\"-c $SINGBOX_CONFIG\"\nfi\n\n_workdir=${SINGBOX_WORKDIR:-${workdir:-\"/var/lib/sing-box\"}}\n\ncommand_args=\"run --disable-color\n\t-D $_workdir\n\t$_config_opt\"\n\ndepend() {\n\tafter net dns\n}\n\ncheckconfig() {\n\tebegin \"Checking $RC_SVCNAME configuration\"\n\tsing-box check -D \"$_workdir\" $_config_opt\n\teend $?\n}\n\nstart_pre() {\n\tcheckconfig\n}\n\nreload() {\n\tebegin \"Reloading $RC_SVCNAME\"\n\tcheckconfig && $supervisor \"$RC_SVCNAME\" --signal HUP\n\teend $?\n}\n"
  },
  {
    "path": "release/config/sing-box.postinst",
    "content": "#!/bin/sh\n\nsystemd-sysusers sing-box.conf\n"
  },
  {
    "path": "release/config/sing-box.rules",
    "content": "polkit.addRule(function(action, subject) {\n    if ((action.id == \"org.freedesktop.resolve1.set-domains\" ||\n         action.id == \"org.freedesktop.resolve1.set-default-route\" ||\n         action.id == \"org.freedesktop.resolve1.set-dns-servers\") &&\n        subject.user == \"sing-box\") {\n        return polkit.Result.YES;\n    }\n});\n"
  },
  {
    "path": "release/config/sing-box.service",
    "content": "[Unit]\nDescription=sing-box service\nDocumentation=https://sing-box.sagernet.org\nAfter=network.target nss-lookup.target network-online.target\n\n[Service]\nUser=sing-box\nStateDirectory=sing-box\nCapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH\nAmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH\nExecStart=/usr/bin/sing-box -D /var/lib/sing-box -C /etc/sing-box run\nExecReload=/bin/kill -HUP $MAINPID\nRestart=on-failure\nRestartSec=10s\nLimitNOFILE=infinity\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "release/config/sing-box.sysusers",
    "content": "u sing-box - \"sing-box Service\"\n"
  },
  {
    "path": "release/config/sing-box@.service",
    "content": "[Unit]\nDescription=sing-box service\nDocumentation=https://sing-box.sagernet.org\nAfter=network.target nss-lookup.target network-online.target\n\n[Service]\nUser=sing-box\nStateDirectory=sing-box-%i\nCapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH\nAmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH\nExecStart=/usr/bin/sing-box -D /var/lib/sing-box-%i -c /etc/sing-box/%i.json run\nExecReload=/bin/kill -HUP $MAINPID\nRestart=on-failure\nRestartSec=10s\nLimitNOFILE=infinity\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "release/local/common.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_DIR=\"$(cd \"$SCRIPT_DIR/../..\" && pwd)\"\nBINARY_NAME=\"sing-box\"\n\nINSTALL_BIN_PATH=\"/usr/local/bin\"\nINSTALL_CONFIG_PATH=\"/usr/local/etc/sing-box\"\nINSTALL_DATA_PATH=\"/var/lib/sing-box\"\nSYSTEMD_SERVICE_PATH=\"/etc/systemd/system\"\n\nDEFAULT_BUILD_TAGS=\"$(cat \"$PROJECT_DIR/release/DEFAULT_BUILD_TAGS_OTHERS\")\"\n\nsetup_environment() {\n    if [ -d /usr/local/go ]; then\n        export PATH=\"$PATH:/usr/local/go/bin\"\n    fi\n\n    if ! command -v go &> /dev/null; then\n        echo \"Error: Go is not installed or not in PATH\"\n        echo \"Run install_go.sh to install Go\"\n        exit 1\n    fi\n}\n\nget_build_tags() {\n    local extra_tags=\"$1\"\n    if [ -n \"$extra_tags\" ]; then\n        echo \"${DEFAULT_BUILD_TAGS},${extra_tags}\"\n    else\n        echo \"${DEFAULT_BUILD_TAGS}\"\n    fi\n}\n\nget_version() {\n    cd \"$PROJECT_DIR\"\n    GOHOSTOS=$(go env GOHOSTOS)\n    GOHOSTARCH=$(go env GOHOSTARCH)\n    CGO_ENABLED=0 GOOS=$GOHOSTOS GOARCH=$GOHOSTARCH go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest\n}\n\nget_ldflags() {\n    local version\n    version=$(get_version)\n    local shared_ldflags\n    shared_ldflags=$(cat \"$PROJECT_DIR/release/LDFLAGS\")\n    echo \"-X 'github.com/sagernet/sing-box/constant.Version=${version}' ${shared_ldflags} -s -w -buildid=\"\n}\n\nbuild_sing_box() {\n    local tags=\"$1\"\n    local ldflags\n    ldflags=$(get_ldflags)\n\n    echo \"Building sing-box with tags: $tags\"\n    cd \"$PROJECT_DIR\"\n    export GOTOOLCHAIN=local\n    go install -v -trimpath -ldflags \"$ldflags\" -tags \"$tags\" ./cmd/sing-box\n}\n\ninstall_binary() {\n    local gopath\n    gopath=$(go env GOPATH)\n    echo \"Installing binary to $INSTALL_BIN_PATH/$BINARY_NAME\"\n    sudo cp \"${gopath}/bin/${BINARY_NAME}\" \"${INSTALL_BIN_PATH}/\"\n}\n\nsetup_config() {\n    echo \"Setting up configuration\"\n    sudo mkdir -p \"$INSTALL_CONFIG_PATH\"\n    if [ ! -f \"$INSTALL_CONFIG_PATH/config.json\" ]; then\n        sudo cp \"$PROJECT_DIR/release/config/config.json\" \"$INSTALL_CONFIG_PATH/config.json\"\n        echo \"Default config installed to $INSTALL_CONFIG_PATH/config.json\"\n    else\n        echo \"Config already exists at $INSTALL_CONFIG_PATH/config.json (not overwriting)\"\n    fi\n}\n\nsetup_systemd() {\n    echo \"Setting up systemd service\"\n    sudo cp \"$SCRIPT_DIR/sing-box.service\" \"$SYSTEMD_SERVICE_PATH/\"\n    sudo systemctl daemon-reload\n}\n\nstop_service() {\n    if systemctl is-active --quiet sing-box; then\n        echo \"Stopping sing-box service\"\n        sudo systemctl stop sing-box\n    fi\n}\n\nstart_service() {\n    echo \"Starting sing-box service\"\n    sudo systemctl start sing-box\n}\n\nrestart_service() {\n    echo \"Restarting sing-box service\"\n    sudo systemctl restart sing-box\n}\n"
  },
  {
    "path": "release/local/debug.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nsource \"$SCRIPT_DIR/common.sh\"\n\nsetup_environment\n\necho \"Updating sing-box from git repository...\"\ncd \"$PROJECT_DIR\"\ngit fetch\ngit reset FETCH_HEAD --hard\ngit clean -fdx\n\nBUILD_TAGS=$(get_build_tags \"debug\")\n\nbuild_sing_box \"$BUILD_TAGS\"\n\nstop_service\ninstall_binary\nstart_service\n\necho \"\"\necho \"Following service logs (Ctrl+C to exit)...\"\nsudo journalctl -u sing-box --output cat -f\n"
  },
  {
    "path": "release/local/enable.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\nsudo systemctl enable sing-box\nsudo systemctl start sing-box\nsudo journalctl -u sing-box --output cat -f\n"
  },
  {
    "path": "release/local/install.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nsource \"$SCRIPT_DIR/common.sh\"\n\nsetup_environment\n\nBUILD_TAGS=$(get_build_tags)\n\nbuild_sing_box \"$BUILD_TAGS\"\ninstall_binary\nsetup_config\nsetup_systemd\n\necho \"\"\necho \"Installation complete!\"\necho \"To enable and start the service, run: $SCRIPT_DIR/enable.sh\"\n"
  },
  {
    "path": "release/local/install_go.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\ngo_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '\"version\": \"[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?\"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/\"//g')\ncurl -Lo go.tar.gz \"https://go.dev/dl/go$go_version.linux-amd64.tar.gz\"\nsudo rm -rf /usr/local/go\nsudo tar -C /usr/local -xzf go.tar.gz\nrm go.tar.gz\n"
  },
  {
    "path": "release/local/reinstall.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nsource \"$SCRIPT_DIR/common.sh\"\n\nsetup_environment\n\nBUILD_TAGS=$(get_build_tags)\n\nbuild_sing_box \"$BUILD_TAGS\"\n\nstop_service\ninstall_binary\nstart_service\n\necho \"\"\necho \"Reinstallation complete!\"\n"
  },
  {
    "path": "release/local/sing-box.service",
    "content": "[Unit]\nDescription=sing-box service\nDocumentation=https://sing-box.sagernet.org\nAfter=network.target nss-lookup.target network-online.target\n\n[Service]\nCapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH\nAmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH\nExecStart=/usr/local/bin/sing-box -D /var/lib/sing-box -C /usr/local/etc/sing-box run\nExecReload=/bin/kill -HUP $MAINPID\nRestart=on-failure\nRestartSec=10s\nLimitNOFILE=infinity\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "release/local/uninstall.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nsource \"$SCRIPT_DIR/common.sh\"\n\necho \"Uninstalling sing-box...\"\n\nif systemctl is-active --quiet sing-box 2>/dev/null; then\n    echo \"Stopping sing-box service...\"\n    sudo systemctl stop sing-box\nfi\n\nif systemctl is-enabled --quiet sing-box 2>/dev/null; then\n    echo \"Disabling sing-box service...\"\n    sudo systemctl disable sing-box\nfi\n\necho \"Removing files...\"\nsudo rm -rf \"$INSTALL_DATA_PATH\"\nsudo rm -rf \"$INSTALL_BIN_PATH/$BINARY_NAME\"\nsudo rm -rf \"$INSTALL_CONFIG_PATH\"\nsudo rm -rf \"$SYSTEMD_SERVICE_PATH/sing-box.service\"\n\necho \"Reloading systemd...\"\nsudo systemctl daemon-reload\n\necho \"\"\necho \"Uninstallation complete!\"\n"
  },
  {
    "path": "release/local/update.sh",
    "content": "#!/usr/bin/env bash\n\nset -e -o pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nsource \"$SCRIPT_DIR/common.sh\"\n\necho \"Updating sing-box from git repository...\"\ncd \"$PROJECT_DIR\"\ngit fetch\ngit reset FETCH_HEAD --hard\ngit clean -fdx\n\necho \"\"\necho \"Running reinstall...\"\nexec \"$SCRIPT_DIR/reinstall.sh\""
  },
  {
    "path": "route/conn.go",
    "content": "package route\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tlsfragment\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\t\"github.com/sagernet/sing/common/canceler\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/x/list\"\n)\n\nvar _ adapter.ConnectionManager = (*ConnectionManager)(nil)\n\ntype ConnectionManager struct {\n\tlogger      logger.ContextLogger\n\taccess      sync.Mutex\n\tconnections list.List[io.Closer]\n}\n\nfunc NewConnectionManager(logger logger.ContextLogger) *ConnectionManager {\n\treturn &ConnectionManager{\n\t\tlogger: logger,\n\t}\n}\n\nfunc (m *ConnectionManager) Start(stage adapter.StartStage) error {\n\treturn nil\n}\n\nfunc (m *ConnectionManager) Count() int {\n\treturn m.connections.Len()\n}\n\nfunc (m *ConnectionManager) CloseAll() {\n\tm.access.Lock()\n\tvar closers []io.Closer\n\tfor element := m.connections.Front(); element != nil; {\n\t\tnextElement := element.Next()\n\t\tclosers = append(closers, element.Value)\n\t\tm.connections.Remove(element)\n\t\telement = nextElement\n\t}\n\tm.access.Unlock()\n\tfor _, closer := range closers {\n\t\tcommon.Close(closer)\n\t}\n}\n\nfunc (m *ConnectionManager) Close() error {\n\tm.CloseAll()\n\treturn nil\n}\n\nfunc (m *ConnectionManager) TrackConn(conn net.Conn) net.Conn {\n\tm.access.Lock()\n\telement := m.connections.PushBack(conn)\n\tm.access.Unlock()\n\treturn &trackedConn{\n\t\tConn:    conn,\n\t\tmanager: m,\n\t\telement: element,\n\t}\n}\n\nfunc (m *ConnectionManager) TrackPacketConn(conn net.PacketConn) net.PacketConn {\n\tm.access.Lock()\n\telement := m.connections.PushBack(conn)\n\tm.access.Unlock()\n\treturn &trackedPacketConn{\n\t\tPacketConn: conn,\n\t\tmanager:    m,\n\t\telement:    element,\n\t}\n}\n\nfunc (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tctx = adapter.WithContext(ctx, &metadata)\n\tvar (\n\t\tremoteConn net.Conn\n\t\terr        error\n\t)\n\tif len(metadata.DestinationAddresses) > 0 || metadata.Destination.IsIP() {\n\t\tremoteConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)\n\t} else {\n\t\tremoteConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)\n\t}\n\tif err != nil {\n\t\tvar remoteString string\n\t\tif len(metadata.DestinationAddresses) > 0 {\n\t\t\tremoteString = \"[\" + strings.Join(common.Map(metadata.DestinationAddresses, netip.Addr.String), \",\") + \"]\"\n\t\t} else {\n\t\t\tremoteString = metadata.Destination.String()\n\t\t}\n\t\tvar dialerString string\n\t\tif outbound, isOutbound := this.(adapter.Outbound); isOutbound {\n\t\t\tdialerString = \" using outbound/\" + outbound.Type() + \"[\" + outbound.Tag() + \"]\"\n\t\t}\n\t\terr = E.Cause(err, \"open connection to \", remoteString, dialerString)\n\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\tm.logger.ErrorContext(ctx, err)\n\t\treturn\n\t}\n\terr = N.ReportConnHandshakeSuccess(conn, remoteConn)\n\tif err != nil {\n\t\terr = E.Cause(err, \"report handshake success\")\n\t\tremoteConn.Close()\n\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\tm.logger.ErrorContext(ctx, err)\n\t\treturn\n\t}\n\tif metadata.TLSFragment || metadata.TLSRecordFragment {\n\t\tremoteConn = tf.NewConn(remoteConn, ctx, metadata.TLSFragment, metadata.TLSRecordFragment, metadata.TLSFragmentFallbackDelay)\n\t}\n\tvar done atomic.Bool\n\tif m.kickWriteHandshake(ctx, conn, remoteConn, false, &done, onClose) {\n\t\treturn\n\t}\n\tif m.kickWriteHandshake(ctx, remoteConn, conn, true, &done, onClose) {\n\t\treturn\n\t}\n\tgo m.connectionCopy(ctx, conn, remoteConn, false, &done, onClose)\n\tgo m.connectionCopy(ctx, remoteConn, conn, true, &done, onClose)\n}\n\nfunc (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tctx = adapter.WithContext(ctx, &metadata)\n\tvar (\n\t\tremotePacketConn   net.PacketConn\n\t\tremoteConn         net.Conn\n\t\tdestinationAddress netip.Addr\n\t\terr                error\n\t)\n\tif metadata.UDPConnect {\n\t\tparallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer)\n\t\tif len(metadata.DestinationAddresses) > 0 {\n\t\t\tif isParallelDialer {\n\t\t\t\tremoteConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)\n\t\t\t} else {\n\t\t\t\tremoteConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)\n\t\t\t}\n\t\t} else if metadata.Destination.IsIP() {\n\t\t\tif isParallelDialer {\n\t\t\t\tremoteConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)\n\t\t\t} else {\n\t\t\t\tremoteConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination)\n\t\t\t}\n\t\t} else {\n\t\t\tremoteConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination)\n\t\t}\n\t\tif err != nil {\n\t\t\tvar remoteString string\n\t\t\tif len(metadata.DestinationAddresses) > 0 {\n\t\t\t\tremoteString = \"[\" + strings.Join(common.Map(metadata.DestinationAddresses, netip.Addr.String), \",\") + \"]\"\n\t\t\t} else {\n\t\t\t\tremoteString = metadata.Destination.String()\n\t\t\t}\n\t\t\tvar dialerString string\n\t\t\tif outbound, isOutbound := this.(adapter.Outbound); isOutbound {\n\t\t\t\tdialerString = \" using outbound/\" + outbound.Type() + \"[\" + outbound.Tag() + \"]\"\n\t\t\t}\n\t\t\terr = E.Cause(err, \"open packet connection to \", remoteString, dialerString)\n\t\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\t\tm.logger.ErrorContext(ctx, err)\n\t\t\treturn\n\t\t}\n\t\tremotePacketConn = bufio.NewUnbindPacketConn(remoteConn)\n\t\tconnRemoteAddr := M.AddrFromNet(remoteConn.RemoteAddr())\n\t\tif connRemoteAddr != metadata.Destination.Addr {\n\t\t\tdestinationAddress = connRemoteAddr\n\t\t}\n\t} else {\n\t\tif len(metadata.DestinationAddresses) > 0 {\n\t\t\tremotePacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, this, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)\n\t\t} else if packetDialer, withDestination := this.(dialer.PacketDialerWithDestination); withDestination {\n\t\t\tremotePacketConn, destinationAddress, err = packetDialer.ListenPacketWithDestination(ctx, metadata.Destination)\n\t\t} else {\n\t\t\tremotePacketConn, err = this.ListenPacket(ctx, metadata.Destination)\n\t\t}\n\t\tif err != nil {\n\t\t\tvar dialerString string\n\t\t\tif outbound, isOutbound := this.(adapter.Outbound); isOutbound {\n\t\t\t\tdialerString = \" using outbound/\" + outbound.Type() + \"[\" + outbound.Tag() + \"]\"\n\t\t\t}\n\t\t\terr = E.Cause(err, \"listen packet connection using \", dialerString)\n\t\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\t\tm.logger.ErrorContext(ctx, err)\n\t\t\treturn\n\t\t}\n\t}\n\terr = N.ReportPacketConnHandshakeSuccess(conn, remotePacketConn)\n\tif err != nil {\n\t\tconn.Close()\n\t\tremotePacketConn.Close()\n\t\tm.logger.ErrorContext(ctx, \"report handshake success: \", err)\n\t\treturn\n\t}\n\tif destinationAddress.IsValid() {\n\t\tvar originDestination M.Socksaddr\n\t\tif metadata.RouteOriginalDestination.IsValid() {\n\t\t\toriginDestination = metadata.RouteOriginalDestination\n\t\t} else {\n\t\t\toriginDestination = metadata.Destination\n\t\t}\n\t\tif natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {\n\t\t\tnatConn.UpdateDestination(destinationAddress)\n\t\t} else {\n\t\t\tdestination := M.SocksaddrFrom(destinationAddress, metadata.Destination.Port)\n\t\t\tif metadata.Destination != destination {\n\t\t\t\tif metadata.UDPDisableDomainUnmapping {\n\t\t\t\t\tremotePacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(remotePacketConn), destination, originDestination)\n\t\t\t\t} else {\n\t\t\t\t\tremotePacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(remotePacketConn), destination, originDestination)\n\t\t\t\t}\n\t\t\t} else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination {\n\t\t\t\tremotePacketConn = bufio.NewDestinationNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination)\n\t\t\t}\n\t\t}\n\t} else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination {\n\t\tremotePacketConn = bufio.NewDestinationNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination)\n\t}\n\tvar udpTimeout time.Duration\n\tif metadata.UDPTimeout > 0 {\n\t\tudpTimeout = metadata.UDPTimeout\n\t} else {\n\t\tprotocol := metadata.Protocol\n\t\tif protocol == \"\" {\n\t\t\tprotocol = C.PortProtocols[metadata.Destination.Port]\n\t\t}\n\t\tif protocol != \"\" {\n\t\t\tudpTimeout = C.ProtocolTimeouts[protocol]\n\t\t}\n\t}\n\tif udpTimeout > 0 {\n\t\tctx, conn = canceler.NewPacketConn(ctx, conn, udpTimeout)\n\t}\n\tdestination := bufio.NewPacketConn(remotePacketConn)\n\tvar done atomic.Bool\n\tgo m.packetConnectionCopy(ctx, conn, destination, false, &done, onClose)\n\tgo m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose)\n}\n\nfunc (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {\n\t_, err := bufio.CopyWithIncreateBuffer(destination, source, bufio.DefaultIncreaseBufferAfter, bufio.DefaultBatchSize)\n\tif err != nil {\n\t\tcommon.Close(source, destination)\n\t} else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex {\n\t\terr = duplexDst.CloseWrite()\n\t\tif err != nil {\n\t\t\tcommon.Close(source, destination)\n\t\t}\n\t} else {\n\t\tdestination.Close()\n\t}\n\tif done.Swap(true) {\n\t\tif onClose != nil {\n\t\t\tonClose(err)\n\t\t}\n\t\tcommon.Close(source, destination)\n\t}\n\tif !direction {\n\t\tif err == nil {\n\t\t\tm.logger.DebugContext(ctx, \"connection upload finished\")\n\t\t} else if !E.IsClosedOrCanceled(err) {\n\t\t\tm.logger.ErrorContext(ctx, \"connection upload closed: \", err)\n\t\t} else {\n\t\t\tm.logger.TraceContext(ctx, \"connection upload closed\")\n\t\t}\n\t} else {\n\t\tif err == nil {\n\t\t\tm.logger.DebugContext(ctx, \"connection download finished\")\n\t\t} else if !E.IsClosedOrCanceled(err) {\n\t\t\tm.logger.ErrorContext(ctx, \"connection download closed: \", err)\n\t\t} else {\n\t\t\tm.logger.TraceContext(ctx, \"connection download closed\")\n\t\t}\n\t}\n}\n\nfunc (m *ConnectionManager) kickWriteHandshake(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) bool {\n\tif !N.NeedHandshakeForWrite(destination) {\n\t\treturn false\n\t}\n\tvar (\n\t\tcachedBuffer *buf.Buffer\n\t\twrotePayload bool\n\t)\n\tsourceReader, readCounters := N.UnwrapCountReader(source, nil)\n\tdestinationWriter, writeCounters := N.UnwrapCountWriter(destination, nil)\n\tif cachedReader, ok := sourceReader.(N.CachedReader); ok {\n\t\tcachedBuffer = cachedReader.ReadCached()\n\t}\n\tvar err error\n\tif cachedBuffer != nil {\n\t\twrotePayload = true\n\t\tdataLen := cachedBuffer.Len()\n\t\t_, err = destinationWriter.Write(cachedBuffer.Bytes())\n\t\tcachedBuffer.Release()\n\t\tif err == nil {\n\t\t\tfor _, counter := range readCounters {\n\t\t\t\tcounter(int64(dataLen))\n\t\t\t}\n\t\t\tfor _, counter := range writeCounters {\n\t\t\t\tcounter(int64(dataLen))\n\t\t\t}\n\t\t}\n\t} else {\n\t\t_ = destination.SetWriteDeadline(time.Now().Add(C.ReadPayloadTimeout))\n\t\t_, err = destinationWriter.Write(nil)\n\t\t_ = destination.SetWriteDeadline(time.Time{})\n\t}\n\tif err == nil {\n\t\treturn false\n\t}\n\tif !wrotePayload && (E.IsMulti(err, os.ErrInvalid, context.DeadlineExceeded, io.EOF) || E.IsTimeout(err)) {\n\t\treturn false\n\t}\n\tif !done.Swap(true) {\n\t\tif onClose != nil {\n\t\t\tonClose(err)\n\t\t}\n\t}\n\tcommon.Close(source, destination)\n\tif !direction {\n\t\tm.logger.ErrorContext(ctx, \"connection upload handshake: \", err)\n\t} else {\n\t\tm.logger.ErrorContext(ctx, \"connection download handshake: \", err)\n\t}\n\treturn true\n}\n\nfunc (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {\n\t_, err := bufio.CopyPacket(destination, source)\n\tif !direction {\n\t\tif err == nil {\n\t\t\tm.logger.DebugContext(ctx, \"packet upload finished\")\n\t\t} else if E.IsClosedOrCanceled(err) {\n\t\t\tm.logger.TraceContext(ctx, \"packet upload closed\")\n\t\t} else {\n\t\t\tm.logger.DebugContext(ctx, \"packet upload closed: \", err)\n\t\t}\n\t} else {\n\t\tif err == nil {\n\t\t\tm.logger.DebugContext(ctx, \"packet download finished\")\n\t\t} else if E.IsClosedOrCanceled(err) {\n\t\t\tm.logger.TraceContext(ctx, \"packet download closed\")\n\t\t} else {\n\t\t\tm.logger.DebugContext(ctx, \"packet download closed: \", err)\n\t\t}\n\t}\n\tif !done.Swap(true) {\n\t\tif onClose != nil {\n\t\t\tonClose(err)\n\t\t}\n\t}\n\tcommon.Close(source, destination)\n}\n\ntype trackedConn struct {\n\tnet.Conn\n\tmanager *ConnectionManager\n\telement *list.Element[io.Closer]\n}\n\nfunc (c *trackedConn) Close() error {\n\tc.manager.access.Lock()\n\tc.manager.connections.Remove(c.element)\n\tc.manager.access.Unlock()\n\treturn c.Conn.Close()\n}\n\nfunc (c *trackedConn) Upstream() any {\n\treturn c.Conn\n}\n\nfunc (c *trackedConn) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (c *trackedConn) WriterReplaceable() bool {\n\treturn true\n}\n\ntype trackedPacketConn struct {\n\tnet.PacketConn\n\tmanager *ConnectionManager\n\telement *list.Element[io.Closer]\n}\n\nfunc (c *trackedPacketConn) Close() error {\n\tc.manager.access.Lock()\n\tc.manager.connections.Remove(c.element)\n\tc.manager.access.Unlock()\n\treturn c.PacketConn.Close()\n}\n\nfunc (c *trackedPacketConn) Upstream() any {\n\treturn bufio.NewPacketConn(c.PacketConn)\n}\n\nfunc (c *trackedPacketConn) ReaderReplaceable() bool {\n\treturn true\n}\n\nfunc (c *trackedPacketConn) WriterReplaceable() bool {\n\treturn true\n}\n"
  },
  {
    "path": "route/dns.go",
    "content": "package route\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\tdnsOutbound \"github.com/sagernet/sing-box/protocol/dns\"\n\tR \"github.com/sagernet/sing-box/route/rule\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/udpnat2\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {\n\tmetadata.Destination = M.Socksaddr{}\n\tfor {\n\t\tconn.SetReadDeadline(time.Now().Add(C.DNSTimeout))\n\t\terr := dnsOutbound.HandleStreamDNSRequest(ctx, r.dns, conn, metadata)\n\t\tif err != nil {\n\t\t\tif !E.IsClosedOrCanceled(err) {\n\t\t\t\treturn err\n\t\t\t} else {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {\n\tif natConn, isNatConn := conn.(udpnat.Conn); isNatConn {\n\t\tmetadata.Destination = M.Socksaddr{}\n\t\tfor _, packet := range packetBuffers {\n\t\t\tbuffer := packet.Buffer\n\t\t\tdestination := packet.Destination\n\t\t\tN.PutPacketBuffer(packet)\n\t\t\tgo ExchangeDNSPacket(ctx, r.dns, r.logger, natConn, buffer, metadata, destination)\n\t\t}\n\t\tnatConn.SetHandler(&dnsHijacker{\n\t\t\trouter:   r.dns,\n\t\t\tlogger:   r.logger,\n\t\t\tconn:     conn,\n\t\t\tctx:      ctx,\n\t\t\tmetadata: metadata,\n\t\t\tonClose:  onClose,\n\t\t})\n\t\treturn nil\n\t}\n\terr := dnsOutbound.NewDNSPacketConnection(ctx, r.dns, conn, packetBuffers, metadata)\n\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\tif err != nil && !E.IsClosedOrCanceled(err) {\n\t\treturn E.Cause(err, \"process DNS packet\")\n\t}\n\treturn nil\n}\n\nfunc ExchangeDNSPacket(ctx context.Context, router adapter.DNSRouter, logger logger.ContextLogger, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) {\n\terr := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination)\n\tif err != nil && !R.IsRejected(err) && !E.IsClosedOrCanceled(err) {\n\t\tlogger.ErrorContext(ctx, E.Cause(err, \"process DNS packet\"))\n\t}\n}\n\nfunc exchangeDNSPacket(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) error {\n\tvar message mDNS.Msg\n\terr := message.Unpack(buffer.Bytes())\n\tbuffer.Release()\n\tif err != nil {\n\t\treturn E.Cause(err, \"unpack request\")\n\t}\n\tresponse, err := router.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tresponseBuffer, err := dns.TruncateDNSMessage(&message, response, 1024)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = conn.WritePacket(responseBuffer, destination)\n\treturn err\n}\n\ntype dnsHijacker struct {\n\trouter   adapter.DNSRouter\n\tlogger   logger.ContextLogger\n\tconn     N.PacketConn\n\tctx      context.Context\n\tmetadata adapter.InboundContext\n\tonClose  N.CloseHandlerFunc\n}\n\nfunc (h *dnsHijacker) NewPacketEx(buffer *buf.Buffer, destination M.Socksaddr) {\n\tgo ExchangeDNSPacket(h.ctx, h.router, h.logger, h.conn, buffer, h.metadata, destination)\n}\n\nfunc (h *dnsHijacker) Close() error {\n\tif h.onClose != nil {\n\t\th.onClose(nil)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "route/neighbor_resolver_darwin.go",
    "content": "//go:build darwin\n\npackage route\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/fswatch\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\n\t\"golang.org/x/net/route\"\n\t\"golang.org/x/sys/unix\"\n)\n\nvar defaultLeaseFiles = []string{\n\t\"/var/db/dhcpd_leases\",\n\t\"/tmp/dhcp.leases\",\n}\n\ntype neighborResolver struct {\n\tlogger          logger.ContextLogger\n\tleaseFiles      []string\n\taccess          sync.RWMutex\n\tneighborIPToMAC map[netip.Addr]net.HardwareAddr\n\tleaseIPToMAC    map[netip.Addr]net.HardwareAddr\n\tipToHostname    map[netip.Addr]string\n\tmacToHostname   map[string]string\n\twatcher         *fswatch.Watcher\n\tdone            chan struct{}\n}\n\nfunc newNeighborResolver(resolverLogger logger.ContextLogger, leaseFiles []string) (adapter.NeighborResolver, error) {\n\tif len(leaseFiles) == 0 {\n\t\tfor _, path := range defaultLeaseFiles {\n\t\t\tinfo, err := os.Stat(path)\n\t\t\tif err == nil && info.Size() > 0 {\n\t\t\t\tleaseFiles = append(leaseFiles, path)\n\t\t\t}\n\t\t}\n\t}\n\treturn &neighborResolver{\n\t\tlogger:          resolverLogger,\n\t\tleaseFiles:      leaseFiles,\n\t\tneighborIPToMAC: make(map[netip.Addr]net.HardwareAddr),\n\t\tleaseIPToMAC:    make(map[netip.Addr]net.HardwareAddr),\n\t\tipToHostname:    make(map[netip.Addr]string),\n\t\tmacToHostname:   make(map[string]string),\n\t\tdone:            make(chan struct{}),\n\t}, nil\n}\n\nfunc (r *neighborResolver) Start() error {\n\terr := r.loadNeighborTable()\n\tif err != nil {\n\t\tr.logger.Warn(E.Cause(err, \"load neighbor table\"))\n\t}\n\tr.doReloadLeaseFiles()\n\tgo r.subscribeNeighborUpdates()\n\tif len(r.leaseFiles) > 0 {\n\t\twatcher, err := fswatch.NewWatcher(fswatch.Options{\n\t\t\tPath:   r.leaseFiles,\n\t\t\tLogger: r.logger,\n\t\t\tCallback: func(_ string) {\n\t\t\t\tr.doReloadLeaseFiles()\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tr.logger.Warn(E.Cause(err, \"create lease file watcher\"))\n\t\t} else {\n\t\t\tr.watcher = watcher\n\t\t\terr = watcher.Start()\n\t\t\tif err != nil {\n\t\t\t\tr.logger.Warn(E.Cause(err, \"start lease file watcher\"))\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *neighborResolver) Close() error {\n\tclose(r.done)\n\tif r.watcher != nil {\n\t\treturn r.watcher.Close()\n\t}\n\treturn nil\n}\n\nfunc (r *neighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) {\n\tr.access.RLock()\n\tdefer r.access.RUnlock()\n\tmac, found := r.neighborIPToMAC[address]\n\tif found {\n\t\treturn mac, true\n\t}\n\tmac, found = r.leaseIPToMAC[address]\n\tif found {\n\t\treturn mac, true\n\t}\n\tmac, found = extractMACFromEUI64(address)\n\tif found {\n\t\treturn mac, true\n\t}\n\treturn nil, false\n}\n\nfunc (r *neighborResolver) LookupHostname(address netip.Addr) (string, bool) {\n\tr.access.RLock()\n\tdefer r.access.RUnlock()\n\thostname, found := r.ipToHostname[address]\n\tif found {\n\t\treturn hostname, true\n\t}\n\tmac, macFound := r.neighborIPToMAC[address]\n\tif !macFound {\n\t\tmac, macFound = r.leaseIPToMAC[address]\n\t}\n\tif !macFound {\n\t\tmac, macFound = extractMACFromEUI64(address)\n\t}\n\tif macFound {\n\t\thostname, found = r.macToHostname[mac.String()]\n\t\tif found {\n\t\t\treturn hostname, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nfunc (r *neighborResolver) loadNeighborTable() error {\n\tentries, err := ReadNeighborEntries()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.access.Lock()\n\tdefer r.access.Unlock()\n\tfor _, entry := range entries {\n\t\tr.neighborIPToMAC[entry.Address] = entry.MACAddress\n\t}\n\treturn nil\n}\n\nfunc (r *neighborResolver) subscribeNeighborUpdates() {\n\trouteSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)\n\tif err != nil {\n\t\tr.logger.Warn(E.Cause(err, \"subscribe neighbor updates\"))\n\t\treturn\n\t}\n\terr = unix.SetNonblock(routeSocket, true)\n\tif err != nil {\n\t\tunix.Close(routeSocket)\n\t\tr.logger.Warn(E.Cause(err, \"set route socket nonblock\"))\n\t\treturn\n\t}\n\trouteSocketFile := os.NewFile(uintptr(routeSocket), \"route\")\n\tdefer routeSocketFile.Close()\n\tbuffer := buf.NewPacket()\n\tdefer buffer.Release()\n\tfor {\n\t\tselect {\n\t\tcase <-r.done:\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\terr = setReadDeadline(routeSocketFile, 3*time.Second)\n\t\tif err != nil {\n\t\t\tr.logger.Warn(E.Cause(err, \"set route socket read deadline\"))\n\t\t\treturn\n\t\t}\n\t\tn, err := routeSocketFile.Read(buffer.FreeBytes())\n\t\tif err != nil {\n\t\t\tif nerr, ok := err.(net.Error); ok && nerr.Timeout() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-r.done:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t\tr.logger.Warn(E.Cause(err, \"receive neighbor update\"))\n\t\t\tcontinue\n\t\t}\n\t\tmessages, err := route.ParseRIB(route.RIBTypeRoute, buffer.FreeBytes()[:n])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, message := range messages {\n\t\t\trouteMessage, isRouteMessage := message.(*route.RouteMessage)\n\t\t\tif !isRouteMessage {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif routeMessage.Flags&unix.RTF_LLINFO == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\taddress, mac, isDelete, ok := ParseRouteNeighborMessage(routeMessage)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.access.Lock()\n\t\t\tif isDelete {\n\t\t\t\tdelete(r.neighborIPToMAC, address)\n\t\t\t} else {\n\t\t\t\tr.neighborIPToMAC[address] = mac\n\t\t\t}\n\t\t\tr.access.Unlock()\n\t\t}\n\t}\n}\n\nfunc (r *neighborResolver) doReloadLeaseFiles() {\n\tleaseIPToMAC, ipToHostname, macToHostname := ReloadLeaseFiles(r.leaseFiles)\n\tr.access.Lock()\n\tr.leaseIPToMAC = leaseIPToMAC\n\tr.ipToHostname = ipToHostname\n\tr.macToHostname = macToHostname\n\tr.access.Unlock()\n}\n\nfunc setReadDeadline(file *os.File, timeout time.Duration) error {\n\trawConn, err := file.SyscallConn()\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar controlErr error\n\terr = rawConn.Control(func(fd uintptr) {\n\t\ttv := unix.NsecToTimeval(int64(timeout))\n\t\tcontrolErr = unix.SetsockoptTimeval(int(fd), unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv)\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn controlErr\n}\n"
  },
  {
    "path": "route/neighbor_resolver_lease.go",
    "content": "package route\n\nimport (\n\t\"bufio\"\n\t\"encoding/hex\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc parseLeaseFile(path string, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {\n\tfile, err := os.Open(path)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer file.Close()\n\tif strings.HasSuffix(path, \"dhcpd_leases\") {\n\t\tparseBootpdLeases(file, ipToMAC, ipToHostname, macToHostname)\n\t\treturn\n\t}\n\tif strings.HasSuffix(path, \"kea-leases4.csv\") {\n\t\tparseKeaCSV4(file, ipToMAC, ipToHostname, macToHostname)\n\t\treturn\n\t}\n\tif strings.HasSuffix(path, \"kea-leases6.csv\") {\n\t\tparseKeaCSV6(file, ipToMAC, ipToHostname, macToHostname)\n\t\treturn\n\t}\n\tif strings.HasSuffix(path, \"dhcpd.leases\") {\n\t\tparseISCDhcpd(file, ipToMAC, ipToHostname, macToHostname)\n\t\treturn\n\t}\n\tparseDnsmasqOdhcpd(file, ipToMAC, ipToHostname, macToHostname)\n}\n\nfunc ReloadLeaseFiles(leaseFiles []string) (leaseIPToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {\n\tleaseIPToMAC = make(map[netip.Addr]net.HardwareAddr)\n\tipToHostname = make(map[netip.Addr]string)\n\tmacToHostname = make(map[string]string)\n\tfor _, path := range leaseFiles {\n\t\tparseLeaseFile(path, leaseIPToMAC, ipToHostname, macToHostname)\n\t}\n\treturn\n}\n\nfunc parseDnsmasqOdhcpd(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {\n\tnow := time.Now().Unix()\n\tscanner := bufio.NewScanner(file)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tif strings.HasPrefix(line, \"duid \") {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(line, \"# \") {\n\t\t\tparseOdhcpdLine(line[2:], ipToMAC, ipToHostname, macToHostname)\n\t\t\tcontinue\n\t\t}\n\t\tfields := strings.Fields(line)\n\t\tif len(fields) < 4 {\n\t\t\tcontinue\n\t\t}\n\t\texpiry, err := strconv.ParseInt(fields[0], 10, 64)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif expiry != 0 && expiry < now {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.Contains(fields[1], \":\") {\n\t\t\tmac, macErr := net.ParseMAC(fields[1])\n\t\t\tif macErr != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\taddress, addrOK := netip.AddrFromSlice(net.ParseIP(fields[2]))\n\t\t\tif !addrOK {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\taddress = address.Unmap()\n\t\t\tipToMAC[address] = mac\n\t\t\thostname := fields[3]\n\t\t\tif hostname != \"*\" {\n\t\t\t\tipToHostname[address] = hostname\n\t\t\t\tmacToHostname[mac.String()] = hostname\n\t\t\t}\n\t\t} else {\n\t\t\tvar mac net.HardwareAddr\n\t\t\tif len(fields) >= 5 {\n\t\t\t\tduid, duidErr := parseDUID(fields[4])\n\t\t\t\tif duidErr == nil {\n\t\t\t\t\tmac, _ = extractMACFromDUID(duid)\n\t\t\t\t}\n\t\t\t}\n\t\t\taddress, addrOK := netip.AddrFromSlice(net.ParseIP(fields[2]))\n\t\t\tif !addrOK {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\taddress = address.Unmap()\n\t\t\tif mac != nil {\n\t\t\t\tipToMAC[address] = mac\n\t\t\t}\n\t\t\thostname := fields[3]\n\t\t\tif hostname != \"*\" {\n\t\t\t\tipToHostname[address] = hostname\n\t\t\t\tif mac != nil {\n\t\t\t\t\tmacToHostname[mac.String()] = hostname\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc parseOdhcpdLine(line string, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {\n\tfields := strings.Fields(line)\n\tif len(fields) < 5 {\n\t\treturn\n\t}\n\tvalidTime, err := strconv.ParseInt(fields[4], 10, 64)\n\tif err != nil {\n\t\treturn\n\t}\n\tif validTime == 0 {\n\t\treturn\n\t}\n\tif validTime > 0 && validTime < time.Now().Unix() {\n\t\treturn\n\t}\n\thostname := fields[3]\n\tif hostname == \"-\" || strings.HasPrefix(hostname, `broken\\x20`) {\n\t\thostname = \"\"\n\t}\n\tif len(fields) >= 8 && fields[2] == \"ipv4\" {\n\t\tmac, macErr := net.ParseMAC(fields[1])\n\t\tif macErr != nil {\n\t\t\treturn\n\t\t}\n\t\taddressField := fields[7]\n\t\tslashIndex := strings.IndexByte(addressField, '/')\n\t\tif slashIndex >= 0 {\n\t\t\taddressField = addressField[:slashIndex]\n\t\t}\n\t\taddress, addrOK := netip.AddrFromSlice(net.ParseIP(addressField))\n\t\tif !addrOK {\n\t\t\treturn\n\t\t}\n\t\taddress = address.Unmap()\n\t\tipToMAC[address] = mac\n\t\tif hostname != \"\" {\n\t\t\tipToHostname[address] = hostname\n\t\t\tmacToHostname[mac.String()] = hostname\n\t\t}\n\t\treturn\n\t}\n\tvar mac net.HardwareAddr\n\tduidHex := fields[1]\n\tduidBytes, hexErr := hex.DecodeString(duidHex)\n\tif hexErr == nil {\n\t\tmac, _ = extractMACFromDUID(duidBytes)\n\t}\n\tfor i := 7; i < len(fields); i++ {\n\t\taddressField := fields[i]\n\t\tslashIndex := strings.IndexByte(addressField, '/')\n\t\tif slashIndex >= 0 {\n\t\t\taddressField = addressField[:slashIndex]\n\t\t}\n\t\taddress, addrOK := netip.AddrFromSlice(net.ParseIP(addressField))\n\t\tif !addrOK {\n\t\t\tcontinue\n\t\t}\n\t\taddress = address.Unmap()\n\t\tif mac != nil {\n\t\t\tipToMAC[address] = mac\n\t\t}\n\t\tif hostname != \"\" {\n\t\t\tipToHostname[address] = hostname\n\t\t\tif mac != nil {\n\t\t\t\tmacToHostname[mac.String()] = hostname\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc parseISCDhcpd(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {\n\tscanner := bufio.NewScanner(file)\n\tvar currentIP netip.Addr\n\tvar currentMAC net.HardwareAddr\n\tvar currentHostname string\n\tvar currentActive bool\n\tvar inLease bool\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif strings.HasPrefix(line, \"lease \") && strings.HasSuffix(line, \"{\") {\n\t\t\tipString := strings.TrimSuffix(strings.TrimPrefix(line, \"lease \"), \" {\")\n\t\t\tparsed, addrOK := netip.AddrFromSlice(net.ParseIP(ipString))\n\t\t\tif addrOK {\n\t\t\t\tcurrentIP = parsed.Unmap()\n\t\t\t\tinLease = true\n\t\t\t\tcurrentMAC = nil\n\t\t\t\tcurrentHostname = \"\"\n\t\t\t\tcurrentActive = false\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif line == \"}\" && inLease {\n\t\t\tif currentActive && currentMAC != nil {\n\t\t\t\tipToMAC[currentIP] = currentMAC\n\t\t\t\tif currentHostname != \"\" {\n\t\t\t\t\tipToHostname[currentIP] = currentHostname\n\t\t\t\t\tmacToHostname[currentMAC.String()] = currentHostname\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdelete(ipToMAC, currentIP)\n\t\t\t\tdelete(ipToHostname, currentIP)\n\t\t\t}\n\t\t\tinLease = false\n\t\t\tcontinue\n\t\t}\n\t\tif !inLease {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(line, \"hardware ethernet \") {\n\t\t\tmacString := strings.TrimSuffix(strings.TrimPrefix(line, \"hardware ethernet \"), \";\")\n\t\t\tparsed, macErr := net.ParseMAC(macString)\n\t\t\tif macErr == nil {\n\t\t\t\tcurrentMAC = parsed\n\t\t\t}\n\t\t} else if strings.HasPrefix(line, \"client-hostname \") {\n\t\t\thostname := strings.TrimSuffix(strings.TrimPrefix(line, \"client-hostname \"), \";\")\n\t\t\thostname = strings.Trim(hostname, \"\\\"\")\n\t\t\tif hostname != \"\" {\n\t\t\t\tcurrentHostname = hostname\n\t\t\t}\n\t\t} else if strings.HasPrefix(line, \"binding state \") {\n\t\t\tstate := strings.TrimSuffix(strings.TrimPrefix(line, \"binding state \"), \";\")\n\t\t\tcurrentActive = state == \"active\"\n\t\t}\n\t}\n}\n\nfunc parseKeaCSV4(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {\n\tscanner := bufio.NewScanner(file)\n\tfirstLine := true\n\tfor scanner.Scan() {\n\t\tif firstLine {\n\t\t\tfirstLine = false\n\t\t\tcontinue\n\t\t}\n\t\tfields := strings.Split(scanner.Text(), \",\")\n\t\tif len(fields) < 10 {\n\t\t\tcontinue\n\t\t}\n\t\tif fields[9] != \"0\" {\n\t\t\tcontinue\n\t\t}\n\t\taddress, addrOK := netip.AddrFromSlice(net.ParseIP(fields[0]))\n\t\tif !addrOK {\n\t\t\tcontinue\n\t\t}\n\t\taddress = address.Unmap()\n\t\tmac, macErr := net.ParseMAC(fields[1])\n\t\tif macErr != nil {\n\t\t\tcontinue\n\t\t}\n\t\tipToMAC[address] = mac\n\t\thostname := \"\"\n\t\tif len(fields) > 8 {\n\t\t\thostname = fields[8]\n\t\t}\n\t\tif hostname != \"\" {\n\t\t\tipToHostname[address] = hostname\n\t\t\tmacToHostname[mac.String()] = hostname\n\t\t}\n\t}\n}\n\nfunc parseKeaCSV6(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {\n\tscanner := bufio.NewScanner(file)\n\tfirstLine := true\n\tfor scanner.Scan() {\n\t\tif firstLine {\n\t\t\tfirstLine = false\n\t\t\tcontinue\n\t\t}\n\t\tfields := strings.Split(scanner.Text(), \",\")\n\t\tif len(fields) < 14 {\n\t\t\tcontinue\n\t\t}\n\t\tif fields[13] != \"0\" {\n\t\t\tcontinue\n\t\t}\n\t\taddress, addrOK := netip.AddrFromSlice(net.ParseIP(fields[0]))\n\t\tif !addrOK {\n\t\t\tcontinue\n\t\t}\n\t\taddress = address.Unmap()\n\t\tvar mac net.HardwareAddr\n\t\tif fields[12] != \"\" {\n\t\t\tmac, _ = net.ParseMAC(fields[12])\n\t\t}\n\t\tif mac == nil {\n\t\t\tduid, duidErr := hex.DecodeString(strings.ReplaceAll(fields[1], \":\", \"\"))\n\t\t\tif duidErr == nil {\n\t\t\t\tmac, _ = extractMACFromDUID(duid)\n\t\t\t}\n\t\t}\n\t\thostname := \"\"\n\t\tif len(fields) > 11 {\n\t\t\thostname = fields[11]\n\t\t}\n\t\tif mac != nil {\n\t\t\tipToMAC[address] = mac\n\t\t}\n\t\tif hostname != \"\" {\n\t\t\tipToHostname[address] = hostname\n\t\t\tif mac != nil {\n\t\t\t\tmacToHostname[mac.String()] = hostname\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc parseBootpdLeases(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {\n\tnow := time.Now().Unix()\n\tscanner := bufio.NewScanner(file)\n\tvar currentName string\n\tvar currentIP netip.Addr\n\tvar currentMAC net.HardwareAddr\n\tvar currentLease int64\n\tvar inBlock bool\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif line == \"{\" {\n\t\t\tinBlock = true\n\t\t\tcurrentName = \"\"\n\t\t\tcurrentIP = netip.Addr{}\n\t\t\tcurrentMAC = nil\n\t\t\tcurrentLease = 0\n\t\t\tcontinue\n\t\t}\n\t\tif line == \"}\" && inBlock {\n\t\t\tif currentMAC != nil && currentIP.IsValid() {\n\t\t\t\tif currentLease == 0 || currentLease >= now {\n\t\t\t\t\tipToMAC[currentIP] = currentMAC\n\t\t\t\t\tif currentName != \"\" {\n\t\t\t\t\t\tipToHostname[currentIP] = currentName\n\t\t\t\t\t\tmacToHostname[currentMAC.String()] = currentName\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tinBlock = false\n\t\t\tcontinue\n\t\t}\n\t\tif !inBlock {\n\t\t\tcontinue\n\t\t}\n\t\tkey, value, found := strings.Cut(line, \"=\")\n\t\tif !found {\n\t\t\tcontinue\n\t\t}\n\t\tswitch key {\n\t\tcase \"name\":\n\t\t\tcurrentName = value\n\t\tcase \"ip_address\":\n\t\t\tparsed, addrOK := netip.AddrFromSlice(net.ParseIP(value))\n\t\t\tif addrOK {\n\t\t\t\tcurrentIP = parsed.Unmap()\n\t\t\t}\n\t\tcase \"hw_address\":\n\t\t\ttypeAndMAC, hasSep := strings.CutPrefix(value, \"1,\")\n\t\t\tif hasSep {\n\t\t\t\tmac, macErr := net.ParseMAC(typeAndMAC)\n\t\t\t\tif macErr == nil {\n\t\t\t\t\tcurrentMAC = mac\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"lease\":\n\t\t\tleaseHex := strings.TrimPrefix(value, \"0x\")\n\t\t\tparsed, parseErr := strconv.ParseInt(leaseHex, 16, 64)\n\t\t\tif parseErr == nil {\n\t\t\t\tcurrentLease = parsed\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "route/neighbor_resolver_linux.go",
    "content": "//go:build linux\n\npackage route\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"slices\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/fswatch\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\n\t\"github.com/jsimonetti/rtnetlink\"\n\t\"github.com/mdlayher/netlink\"\n\t\"golang.org/x/sys/unix\"\n)\n\nvar defaultLeaseFiles = []string{\n\t\"/tmp/dhcp.leases\",\n\t\"/var/lib/dhcp/dhcpd.leases\",\n\t\"/var/lib/dhcpd/dhcpd.leases\",\n\t\"/var/lib/kea/kea-leases4.csv\",\n\t\"/var/lib/kea/kea-leases6.csv\",\n}\n\ntype neighborResolver struct {\n\tlogger          logger.ContextLogger\n\tleaseFiles      []string\n\taccess          sync.RWMutex\n\tneighborIPToMAC map[netip.Addr]net.HardwareAddr\n\tleaseIPToMAC    map[netip.Addr]net.HardwareAddr\n\tipToHostname    map[netip.Addr]string\n\tmacToHostname   map[string]string\n\twatcher         *fswatch.Watcher\n\tdone            chan struct{}\n}\n\nfunc newNeighborResolver(resolverLogger logger.ContextLogger, leaseFiles []string) (adapter.NeighborResolver, error) {\n\tif len(leaseFiles) == 0 {\n\t\tfor _, path := range defaultLeaseFiles {\n\t\t\tinfo, err := os.Stat(path)\n\t\t\tif err == nil && info.Size() > 0 {\n\t\t\t\tleaseFiles = append(leaseFiles, path)\n\t\t\t}\n\t\t}\n\t}\n\treturn &neighborResolver{\n\t\tlogger:          resolverLogger,\n\t\tleaseFiles:      leaseFiles,\n\t\tneighborIPToMAC: make(map[netip.Addr]net.HardwareAddr),\n\t\tleaseIPToMAC:    make(map[netip.Addr]net.HardwareAddr),\n\t\tipToHostname:    make(map[netip.Addr]string),\n\t\tmacToHostname:   make(map[string]string),\n\t\tdone:            make(chan struct{}),\n\t}, nil\n}\n\nfunc (r *neighborResolver) Start() error {\n\terr := r.loadNeighborTable()\n\tif err != nil {\n\t\tr.logger.Warn(E.Cause(err, \"load neighbor table\"))\n\t}\n\tr.doReloadLeaseFiles()\n\tgo r.subscribeNeighborUpdates()\n\tif len(r.leaseFiles) > 0 {\n\t\twatcher, err := fswatch.NewWatcher(fswatch.Options{\n\t\t\tPath:   r.leaseFiles,\n\t\t\tLogger: r.logger,\n\t\t\tCallback: func(_ string) {\n\t\t\t\tr.doReloadLeaseFiles()\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tr.logger.Warn(E.Cause(err, \"create lease file watcher\"))\n\t\t} else {\n\t\t\tr.watcher = watcher\n\t\t\terr = watcher.Start()\n\t\t\tif err != nil {\n\t\t\t\tr.logger.Warn(E.Cause(err, \"start lease file watcher\"))\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *neighborResolver) Close() error {\n\tclose(r.done)\n\tif r.watcher != nil {\n\t\treturn r.watcher.Close()\n\t}\n\treturn nil\n}\n\nfunc (r *neighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) {\n\tr.access.RLock()\n\tdefer r.access.RUnlock()\n\tmac, found := r.neighborIPToMAC[address]\n\tif found {\n\t\treturn mac, true\n\t}\n\tmac, found = r.leaseIPToMAC[address]\n\tif found {\n\t\treturn mac, true\n\t}\n\tmac, found = extractMACFromEUI64(address)\n\tif found {\n\t\treturn mac, true\n\t}\n\treturn nil, false\n}\n\nfunc (r *neighborResolver) LookupHostname(address netip.Addr) (string, bool) {\n\tr.access.RLock()\n\tdefer r.access.RUnlock()\n\thostname, found := r.ipToHostname[address]\n\tif found {\n\t\treturn hostname, true\n\t}\n\tmac, macFound := r.neighborIPToMAC[address]\n\tif !macFound {\n\t\tmac, macFound = r.leaseIPToMAC[address]\n\t}\n\tif !macFound {\n\t\tmac, macFound = extractMACFromEUI64(address)\n\t}\n\tif macFound {\n\t\thostname, found = r.macToHostname[mac.String()]\n\t\tif found {\n\t\t\treturn hostname, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nfunc (r *neighborResolver) loadNeighborTable() error {\n\tconnection, err := rtnetlink.Dial(nil)\n\tif err != nil {\n\t\treturn E.Cause(err, \"dial rtnetlink\")\n\t}\n\tdefer connection.Close()\n\tneighbors, err := connection.Neigh.List()\n\tif err != nil {\n\t\treturn E.Cause(err, \"list neighbors\")\n\t}\n\tr.access.Lock()\n\tdefer r.access.Unlock()\n\tfor _, neigh := range neighbors {\n\t\tif neigh.Attributes == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif neigh.Attributes.LLAddress == nil || len(neigh.Attributes.Address) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\taddress, ok := netip.AddrFromSlice(neigh.Attributes.Address)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tr.neighborIPToMAC[address] = slices.Clone(neigh.Attributes.LLAddress)\n\t}\n\treturn nil\n}\n\nfunc (r *neighborResolver) subscribeNeighborUpdates() {\n\tconnection, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{\n\t\tGroups: 1 << (unix.RTNLGRP_NEIGH - 1),\n\t})\n\tif err != nil {\n\t\tr.logger.Warn(E.Cause(err, \"subscribe neighbor updates\"))\n\t\treturn\n\t}\n\tdefer connection.Close()\n\tfor {\n\t\tselect {\n\t\tcase <-r.done:\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\terr = connection.SetReadDeadline(time.Now().Add(3 * time.Second))\n\t\tif err != nil {\n\t\t\tr.logger.Warn(E.Cause(err, \"set netlink read deadline\"))\n\t\t\treturn\n\t\t}\n\t\tmessages, err := connection.Receive()\n\t\tif err != nil {\n\t\t\tif nerr, ok := err.(net.Error); ok && nerr.Timeout() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-r.done:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t\tr.logger.Warn(E.Cause(err, \"receive neighbor update\"))\n\t\t\tcontinue\n\t\t}\n\t\tfor _, message := range messages {\n\t\t\taddress, mac, isDelete, ok := ParseNeighborMessage(message)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.access.Lock()\n\t\t\tif isDelete {\n\t\t\t\tdelete(r.neighborIPToMAC, address)\n\t\t\t} else {\n\t\t\t\tr.neighborIPToMAC[address] = mac\n\t\t\t}\n\t\t\tr.access.Unlock()\n\t\t}\n\t}\n}\n\nfunc (r *neighborResolver) doReloadLeaseFiles() {\n\tleaseIPToMAC, ipToHostname, macToHostname := ReloadLeaseFiles(r.leaseFiles)\n\tr.access.Lock()\n\tr.leaseIPToMAC = leaseIPToMAC\n\tr.ipToHostname = ipToHostname\n\tr.macToHostname = macToHostname\n\tr.access.Unlock()\n}\n"
  },
  {
    "path": "route/neighbor_resolver_parse.go",
    "content": "package route\n\nimport (\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"net\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strings\"\n)\n\nfunc extractMACFromDUID(duid []byte) (net.HardwareAddr, bool) {\n\tif len(duid) < 4 {\n\t\treturn nil, false\n\t}\n\tduidType := binary.BigEndian.Uint16(duid[0:2])\n\thwType := binary.BigEndian.Uint16(duid[2:4])\n\tif hwType != 1 {\n\t\treturn nil, false\n\t}\n\tswitch duidType {\n\tcase 1:\n\t\tif len(duid) < 14 {\n\t\t\treturn nil, false\n\t\t}\n\t\treturn net.HardwareAddr(slices.Clone(duid[8:14])), true\n\tcase 3:\n\t\tif len(duid) < 10 {\n\t\t\treturn nil, false\n\t\t}\n\t\treturn net.HardwareAddr(slices.Clone(duid[4:10])), true\n\t}\n\treturn nil, false\n}\n\nfunc extractMACFromEUI64(address netip.Addr) (net.HardwareAddr, bool) {\n\tif !address.Is6() {\n\t\treturn nil, false\n\t}\n\tb := address.As16()\n\tif b[11] != 0xff || b[12] != 0xfe {\n\t\treturn nil, false\n\t}\n\treturn net.HardwareAddr{b[8] ^ 0x02, b[9], b[10], b[13], b[14], b[15]}, true\n}\n\nfunc parseDUID(s string) ([]byte, error) {\n\tcleaned := strings.ReplaceAll(s, \":\", \"\")\n\treturn hex.DecodeString(cleaned)\n}\n"
  },
  {
    "path": "route/neighbor_resolver_platform.go",
    "content": "package route\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\ntype platformNeighborResolver struct {\n\tlogger        logger.ContextLogger\n\tplatform      adapter.PlatformInterface\n\taccess        sync.RWMutex\n\tipToMAC       map[netip.Addr]net.HardwareAddr\n\tipToHostname  map[netip.Addr]string\n\tmacToHostname map[string]string\n}\n\nfunc newPlatformNeighborResolver(resolverLogger logger.ContextLogger, platform adapter.PlatformInterface) adapter.NeighborResolver {\n\treturn &platformNeighborResolver{\n\t\tlogger:        resolverLogger,\n\t\tplatform:      platform,\n\t\tipToMAC:       make(map[netip.Addr]net.HardwareAddr),\n\t\tipToHostname:  make(map[netip.Addr]string),\n\t\tmacToHostname: make(map[string]string),\n\t}\n}\n\nfunc (r *platformNeighborResolver) Start() error {\n\treturn r.platform.StartNeighborMonitor(r)\n}\n\nfunc (r *platformNeighborResolver) Close() error {\n\treturn r.platform.CloseNeighborMonitor(r)\n}\n\nfunc (r *platformNeighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) {\n\tr.access.RLock()\n\tdefer r.access.RUnlock()\n\tmac, found := r.ipToMAC[address]\n\tif found {\n\t\treturn mac, true\n\t}\n\treturn extractMACFromEUI64(address)\n}\n\nfunc (r *platformNeighborResolver) LookupHostname(address netip.Addr) (string, bool) {\n\tr.access.RLock()\n\tdefer r.access.RUnlock()\n\thostname, found := r.ipToHostname[address]\n\tif found {\n\t\treturn hostname, true\n\t}\n\tmac, found := r.ipToMAC[address]\n\tif !found {\n\t\tmac, found = extractMACFromEUI64(address)\n\t}\n\tif !found {\n\t\treturn \"\", false\n\t}\n\thostname, found = r.macToHostname[mac.String()]\n\treturn hostname, found\n}\n\nfunc (r *platformNeighborResolver) UpdateNeighborTable(entries []adapter.NeighborEntry) {\n\tipToMAC := make(map[netip.Addr]net.HardwareAddr)\n\tipToHostname := make(map[netip.Addr]string)\n\tmacToHostname := make(map[string]string)\n\tfor _, entry := range entries {\n\t\tipToMAC[entry.Address] = entry.MACAddress\n\t\tif entry.Hostname != \"\" {\n\t\t\tipToHostname[entry.Address] = entry.Hostname\n\t\t\tmacToHostname[entry.MACAddress.String()] = entry.Hostname\n\t\t}\n\t}\n\tr.access.Lock()\n\tr.ipToMAC = ipToMAC\n\tr.ipToHostname = ipToHostname\n\tr.macToHostname = macToHostname\n\tr.access.Unlock()\n\tr.logger.Info(\"updated neighbor table: \", len(entries), \" entries\")\n}\n"
  },
  {
    "path": "route/neighbor_resolver_stub.go",
    "content": "//go:build !linux && !darwin\n\npackage route\n\nimport (\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\nfunc newNeighborResolver(_ logger.ContextLogger, _ []string) (adapter.NeighborResolver, error) {\n\treturn nil, os.ErrInvalid\n}\n"
  },
  {
    "path": "route/neighbor_table_darwin.go",
    "content": "//go:build darwin\n\npackage route\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"golang.org/x/net/route\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc ReadNeighborEntries() ([]adapter.NeighborEntry, error) {\n\tvar entries []adapter.NeighborEntry\n\tipv4Entries, err := readNeighborEntriesAF(syscall.AF_INET)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"read IPv4 neighbors\")\n\t}\n\tentries = append(entries, ipv4Entries...)\n\tipv6Entries, err := readNeighborEntriesAF(syscall.AF_INET6)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"read IPv6 neighbors\")\n\t}\n\tentries = append(entries, ipv6Entries...)\n\treturn entries, nil\n}\n\nfunc readNeighborEntriesAF(addressFamily int) ([]adapter.NeighborEntry, error) {\n\trib, err := route.FetchRIB(addressFamily, route.RIBType(syscall.NET_RT_FLAGS), syscall.RTF_LLINFO)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmessages, err := route.ParseRIB(route.RIBType(syscall.NET_RT_FLAGS), rib)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar entries []adapter.NeighborEntry\n\tfor _, message := range messages {\n\t\trouteMessage, isRouteMessage := message.(*route.RouteMessage)\n\t\tif !isRouteMessage {\n\t\t\tcontinue\n\t\t}\n\t\taddress, macAddress, ok := parseRouteNeighborEntry(routeMessage)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tentries = append(entries, adapter.NeighborEntry{\n\t\t\tAddress:    address,\n\t\t\tMACAddress: macAddress,\n\t\t})\n\t}\n\treturn entries, nil\n}\n\nfunc parseRouteNeighborEntry(message *route.RouteMessage) (address netip.Addr, macAddress net.HardwareAddr, ok bool) {\n\tif len(message.Addrs) <= unix.RTAX_GATEWAY {\n\t\treturn\n\t}\n\tgateway, isLinkAddr := message.Addrs[unix.RTAX_GATEWAY].(*route.LinkAddr)\n\tif !isLinkAddr || len(gateway.Addr) < 6 {\n\t\treturn\n\t}\n\tswitch destination := message.Addrs[unix.RTAX_DST].(type) {\n\tcase *route.Inet4Addr:\n\t\taddress = netip.AddrFrom4(destination.IP)\n\tcase *route.Inet6Addr:\n\t\taddress = netip.AddrFrom16(destination.IP)\n\tdefault:\n\t\treturn\n\t}\n\tmacAddress = net.HardwareAddr(make([]byte, len(gateway.Addr)))\n\tcopy(macAddress, gateway.Addr)\n\tok = true\n\treturn\n}\n\nfunc ParseRouteNeighborMessage(message *route.RouteMessage) (address netip.Addr, macAddress net.HardwareAddr, isDelete bool, ok bool) {\n\tisDelete = message.Type == unix.RTM_DELETE\n\tif len(message.Addrs) <= unix.RTAX_GATEWAY {\n\t\treturn\n\t}\n\tswitch destination := message.Addrs[unix.RTAX_DST].(type) {\n\tcase *route.Inet4Addr:\n\t\taddress = netip.AddrFrom4(destination.IP)\n\tcase *route.Inet6Addr:\n\t\taddress = netip.AddrFrom16(destination.IP)\n\tdefault:\n\t\treturn\n\t}\n\tif !isDelete {\n\t\tgateway, isLinkAddr := message.Addrs[unix.RTAX_GATEWAY].(*route.LinkAddr)\n\t\tif !isLinkAddr || len(gateway.Addr) < 6 {\n\t\t\treturn\n\t\t}\n\t\tmacAddress = net.HardwareAddr(make([]byte, len(gateway.Addr)))\n\t\tcopy(macAddress, gateway.Addr)\n\t}\n\tok = true\n\treturn\n}\n"
  },
  {
    "path": "route/neighbor_table_linux.go",
    "content": "//go:build linux\n\npackage route\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"slices\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"github.com/jsimonetti/rtnetlink\"\n\t\"github.com/mdlayher/netlink\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc ReadNeighborEntries() ([]adapter.NeighborEntry, error) {\n\tconnection, err := rtnetlink.Dial(nil)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"dial rtnetlink\")\n\t}\n\tdefer connection.Close()\n\tneighbors, err := connection.Neigh.List()\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"list neighbors\")\n\t}\n\tvar entries []adapter.NeighborEntry\n\tfor _, neighbor := range neighbors {\n\t\tif neighbor.Attributes == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif neighbor.Attributes.LLAddress == nil || len(neighbor.Attributes.Address) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\taddress, ok := netip.AddrFromSlice(neighbor.Attributes.Address)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tentries = append(entries, adapter.NeighborEntry{\n\t\t\tAddress:    address,\n\t\t\tMACAddress: slices.Clone(neighbor.Attributes.LLAddress),\n\t\t})\n\t}\n\treturn entries, nil\n}\n\nfunc ParseNeighborMessage(message netlink.Message) (address netip.Addr, macAddress net.HardwareAddr, isDelete bool, ok bool) {\n\tvar neighMessage rtnetlink.NeighMessage\n\terr := neighMessage.UnmarshalBinary(message.Data)\n\tif err != nil {\n\t\treturn\n\t}\n\tif neighMessage.Attributes == nil || len(neighMessage.Attributes.Address) == 0 {\n\t\treturn\n\t}\n\taddress, ok = netip.AddrFromSlice(neighMessage.Attributes.Address)\n\tif !ok {\n\t\treturn\n\t}\n\tisDelete = message.Header.Type == unix.RTM_DELNEIGH\n\tif !isDelete && neighMessage.Attributes.LLAddress == nil {\n\t\tok = false\n\t\treturn\n\t}\n\tmacAddress = slices.Clone(neighMessage.Attributes.LLAddress)\n\treturn\n}\n"
  },
  {
    "path": "route/network.go",
    "content": "package route\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/settings\"\n\t\"github.com/sagernet/sing-box/common/taskmonitor\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/winpowrprof\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/pause\"\n\n\t\"golang.org/x/exp/slices\"\n)\n\nvar _ adapter.NetworkManager = (*NetworkManager)(nil)\n\ntype NetworkManager struct {\n\tlogger            logger.ContextLogger\n\tinterfaceFinder   *control.DefaultInterfaceFinder\n\tnetworkInterfaces common.TypedValue[[]adapter.NetworkInterface]\n\n\tautoDetectInterface    bool\n\tdefaultOptions         adapter.NetworkOptions\n\tautoRedirectOutputMark uint32\n\tnetworkMonitor         tun.NetworkUpdateMonitor\n\tinterfaceMonitor       tun.DefaultInterfaceMonitor\n\tpackageManager         tun.PackageManager\n\tpowerListener          winpowrprof.EventListener\n\tpauseManager           pause.Manager\n\tplatformInterface      adapter.PlatformInterface\n\tconnectionManager      adapter.ConnectionManager\n\tendpoint               adapter.EndpointManager\n\tinbound                adapter.InboundManager\n\toutbound               adapter.OutboundManager\n\tneedWIFIState          bool\n\twifiMonitor            settings.WIFIMonitor\n\twifiState              adapter.WIFIState\n\twifiStateMutex         sync.RWMutex\n\tstarted                bool\n}\n\nfunc NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions) (*NetworkManager, error) {\n\tdefaultDomainResolver := common.PtrValueOrDefault(options.DefaultDomainResolver)\n\tif options.AutoDetectInterface && !(C.IsLinux || C.IsDarwin || C.IsWindows) {\n\t\treturn nil, E.New(\"`auto_detect_interface` is only supported on Linux, Windows and macOS\")\n\t} else if options.OverrideAndroidVPN && !C.IsAndroid {\n\t\treturn nil, E.New(\"`override_android_vpn` is only supported on Android\")\n\t} else if options.DefaultInterface != \"\" && !(C.IsLinux || C.IsDarwin || C.IsWindows) {\n\t\treturn nil, E.New(\"`default_interface` is only supported on Linux, Windows and macOS\")\n\t} else if options.DefaultMark != 0 && !C.IsLinux {\n\t\treturn nil, E.New(\"`default_mark` is only supported on linux\")\n\t}\n\tnm := &NetworkManager{\n\t\tlogger:              logger,\n\t\tinterfaceFinder:     control.NewDefaultInterfaceFinder(),\n\t\tautoDetectInterface: options.AutoDetectInterface,\n\t\tdefaultOptions: adapter.NetworkOptions{\n\t\t\tBindInterface:  options.DefaultInterface,\n\t\t\tRoutingMark:    uint32(options.DefaultMark),\n\t\t\tDomainResolver: defaultDomainResolver.Server,\n\t\t\tDomainResolveOptions: adapter.DNSQueryOptions{\n\t\t\t\tStrategy:     C.DomainStrategy(defaultDomainResolver.Strategy),\n\t\t\t\tDisableCache: defaultDomainResolver.DisableCache,\n\t\t\t\tRewriteTTL:   defaultDomainResolver.RewriteTTL,\n\t\t\t\tClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}),\n\t\t\t},\n\t\t\tNetworkStrategy:     (*C.NetworkStrategy)(options.DefaultNetworkStrategy),\n\t\t\tNetworkType:         common.Map(options.DefaultNetworkType, option.InterfaceType.Build),\n\t\t\tFallbackNetworkType: common.Map(options.DefaultFallbackNetworkType, option.InterfaceType.Build),\n\t\t\tFallbackDelay:       time.Duration(options.DefaultFallbackDelay),\n\t\t},\n\t\tpauseManager:      service.FromContext[pause.Manager](ctx),\n\t\tplatformInterface: service.FromContext[adapter.PlatformInterface](ctx),\n\t\tconnectionManager: service.FromContext[adapter.ConnectionManager](ctx),\n\t\tendpoint:          service.FromContext[adapter.EndpointManager](ctx),\n\t\tinbound:           service.FromContext[adapter.InboundManager](ctx),\n\t\toutbound:          service.FromContext[adapter.OutboundManager](ctx),\n\t\tneedWIFIState:     hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule),\n\t}\n\tif options.DefaultNetworkStrategy != nil {\n\t\tif options.DefaultInterface != \"\" {\n\t\t\treturn nil, E.New(\"`default_network_strategy` is conflict with `default_interface`\")\n\t\t}\n\t\tif !options.AutoDetectInterface {\n\t\t\treturn nil, E.New(\"`auto_detect_interface` is required by `default_network_strategy`\")\n\t\t}\n\t}\n\tusePlatformDefaultInterfaceMonitor := nm.platformInterface != nil\n\tenforceInterfaceMonitor := options.AutoDetectInterface\n\tif !usePlatformDefaultInterfaceMonitor {\n\t\tnetworkMonitor, err := tun.NewNetworkUpdateMonitor(logger)\n\t\tif !((err != nil && !enforceInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) {\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, \"create network monitor\")\n\t\t\t}\n\t\t\tnm.networkMonitor = networkMonitor\n\t\t\tinterfaceMonitor, err := tun.NewDefaultInterfaceMonitor(nm.networkMonitor, logger, tun.DefaultInterfaceMonitorOptions{\n\t\t\t\tInterfaceFinder:       nm.interfaceFinder,\n\t\t\t\tOverrideAndroidVPN:    options.OverrideAndroidVPN,\n\t\t\t\tUnderNetworkExtension: nm.platformInterface != nil && nm.platformInterface.UnderNetworkExtension(),\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.New(\"auto_detect_interface unsupported on current platform\")\n\t\t\t}\n\t\t\tinterfaceMonitor.RegisterCallback(nm.notifyInterfaceUpdate)\n\t\t\tnm.interfaceMonitor = interfaceMonitor\n\t\t}\n\t} else {\n\t\tinterfaceMonitor := nm.platformInterface.CreateDefaultInterfaceMonitor(logger)\n\t\tinterfaceMonitor.RegisterCallback(nm.notifyInterfaceUpdate)\n\t\tnm.interfaceMonitor = interfaceMonitor\n\t}\n\treturn nm, nil\n}\n\nfunc (r *NetworkManager) Start(stage adapter.StartStage) error {\n\tmonitor := taskmonitor.New(r.logger, C.StartTimeout)\n\tswitch stage {\n\tcase adapter.StartStateInitialize:\n\t\tif r.networkMonitor != nil {\n\t\t\tmonitor.Start(\"initialize network monitor\")\n\t\t\terr := r.networkMonitor.Start()\n\t\t\tmonitor.Finish()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif r.interfaceMonitor != nil {\n\t\t\tmonitor.Start(\"initialize interface monitor\")\n\t\t\terr := r.interfaceMonitor.Start()\n\t\t\tmonitor.Finish()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\tcase adapter.StartStateStart:\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\tpowerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent)\n\t\t\tif err == nil {\n\t\t\t\tr.powerListener = powerListener\n\t\t\t} else {\n\t\t\t\tr.logger.Warn(\"initialize power listener: \", err)\n\t\t\t}\n\t\t}\n\t\tif r.powerListener != nil {\n\t\t\tmonitor.Start(\"start power listener\")\n\t\t\terr := r.powerListener.Start()\n\t\t\tmonitor.Finish()\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"start power listener\")\n\t\t\t}\n\t\t}\n\t\tif C.IsAndroid && r.platformInterface == nil {\n\t\t\tmonitor.Start(\"initialize package manager\")\n\t\t\tpackageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{\n\t\t\t\tCallback: r,\n\t\t\t\tLogger:   r.logger,\n\t\t\t})\n\t\t\tmonitor.Finish()\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"create package manager\")\n\t\t\t}\n\t\t\tmonitor.Start(\"start package manager\")\n\t\t\terr = packageManager.Start()\n\t\t\tmonitor.Finish()\n\t\t\tif err != nil {\n\t\t\t\tr.logger.Warn(\"initialize package manager: \", err)\n\t\t\t} else {\n\t\t\t\tr.packageManager = packageManager\n\t\t\t}\n\t\t}\n\tcase adapter.StartStatePostStart:\n\t\tif r.needWIFIState && !(r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor()) {\n\t\t\twifiMonitor, err := settings.NewWIFIMonitor(r.onWIFIStateChanged)\n\t\t\tif err != nil {\n\t\t\t\tif err != os.ErrInvalid {\n\t\t\t\t\tr.logger.Warn(E.Cause(err, \"create WIFI monitor\"))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tr.wifiMonitor = wifiMonitor\n\t\t\t\terr = r.wifiMonitor.Start()\n\t\t\t\tif err != nil {\n\t\t\t\t\tr.logger.Warn(E.Cause(err, \"start WIFI monitor\"))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tr.started = true\n\t}\n\treturn nil\n}\n\nfunc (r *NetworkManager) Initialize(ruleSets []adapter.RuleSet) {\n\tfor _, ruleSet := range ruleSets {\n\t\tmetadata := ruleSet.Metadata()\n\t\tif metadata.ContainsWIFIRule {\n\t\t\tr.needWIFIState = true\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (r *NetworkManager) Close() error {\n\tmonitor := taskmonitor.New(r.logger, C.StopTimeout)\n\tvar err error\n\tif r.packageManager != nil {\n\t\tmonitor.Start(\"close package manager\")\n\t\terr = E.Append(err, r.packageManager.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close package manager\")\n\t\t})\n\t\tmonitor.Finish()\n\t}\n\tif r.powerListener != nil {\n\t\tmonitor.Start(\"close power listener\")\n\t\terr = E.Append(err, r.powerListener.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close power listener\")\n\t\t})\n\t\tmonitor.Finish()\n\t}\n\tif r.interfaceMonitor != nil {\n\t\tmonitor.Start(\"close interface monitor\")\n\t\terr = E.Append(err, r.interfaceMonitor.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close interface monitor\")\n\t\t})\n\t\tmonitor.Finish()\n\t}\n\tif r.networkMonitor != nil {\n\t\tmonitor.Start(\"close network monitor\")\n\t\terr = E.Append(err, r.networkMonitor.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close network monitor\")\n\t\t})\n\t\tmonitor.Finish()\n\t}\n\tif r.wifiMonitor != nil {\n\t\tmonitor.Start(\"close WIFI monitor\")\n\t\terr = E.Append(err, r.wifiMonitor.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close WIFI monitor\")\n\t\t})\n\t\tmonitor.Finish()\n\t}\n\treturn err\n}\n\nfunc (r *NetworkManager) InterfaceFinder() control.InterfaceFinder {\n\treturn r.interfaceFinder\n}\n\nfunc (r *NetworkManager) UpdateInterfaces() error {\n\tif r.platformInterface == nil || !r.platformInterface.UsePlatformNetworkInterfaces() {\n\t\treturn r.interfaceFinder.Update()\n\t} else {\n\t\tinterfaces, err := r.platformInterface.NetworkInterfaces()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif C.IsDarwin {\n\t\t\terr = r.interfaceFinder.Update()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// NEInterface only provides name,index and type\n\t\t\tinterfaces = common.Map(interfaces, func(it adapter.NetworkInterface) adapter.NetworkInterface {\n\t\t\t\tiif, _ := r.interfaceFinder.ByIndex(it.Index)\n\t\t\t\tif iif != nil {\n\t\t\t\t\tit.Interface = *iif\n\t\t\t\t}\n\t\t\t\treturn it\n\t\t\t})\n\t\t} else {\n\t\t\tr.interfaceFinder.UpdateInterfaces(common.Map(interfaces, func(it adapter.NetworkInterface) control.Interface { return it.Interface }))\n\t\t}\n\t\toldInterfaces := r.networkInterfaces.Load()\n\t\tnewInterfaces := common.Filter(interfaces, func(it adapter.NetworkInterface) bool {\n\t\t\treturn it.Flags&net.FlagUp != 0\n\t\t})\n\t\tr.networkInterfaces.Store(newInterfaces)\n\t\tif len(newInterfaces) > 0 && !slices.EqualFunc(oldInterfaces, newInterfaces, func(oldInterface adapter.NetworkInterface, newInterface adapter.NetworkInterface) bool {\n\t\t\treturn oldInterface.Interface.Index == newInterface.Interface.Index &&\n\t\t\t\toldInterface.Interface.Name == newInterface.Interface.Name &&\n\t\t\t\toldInterface.Interface.Flags == newInterface.Interface.Flags &&\n\t\t\t\toldInterface.Type == newInterface.Type &&\n\t\t\t\toldInterface.Expensive == newInterface.Expensive &&\n\t\t\t\toldInterface.Constrained == newInterface.Constrained\n\t\t}) {\n\t\t\tr.logger.Info(\"updated available networks: \", strings.Join(common.Map(newInterfaces, func(it adapter.NetworkInterface) string {\n\t\t\t\tvar options []string\n\t\t\t\toptions = append(options, F.ToString(it.Type))\n\t\t\t\tif it.Expensive {\n\t\t\t\t\toptions = append(options, \"expensive\")\n\t\t\t\t}\n\t\t\t\tif it.Constrained {\n\t\t\t\t\toptions = append(options, \"constrained\")\n\t\t\t\t}\n\t\t\t\treturn F.ToString(it.Name, \" (\", strings.Join(options, \", \"), \")\")\n\t\t\t}), \", \"))\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc (r *NetworkManager) DefaultNetworkInterface() *adapter.NetworkInterface {\n\tiif := r.interfaceMonitor.DefaultInterface()\n\tif iif == nil {\n\t\treturn nil\n\t}\n\tfor _, it := range r.networkInterfaces.Load() {\n\t\tif it.Interface.Index == iif.Index {\n\t\t\treturn &it\n\t\t}\n\t}\n\treturn &adapter.NetworkInterface{Interface: *iif}\n}\n\nfunc (r *NetworkManager) NetworkInterfaces() []adapter.NetworkInterface {\n\treturn r.networkInterfaces.Load()\n}\n\nfunc (r *NetworkManager) AutoDetectInterface() bool {\n\treturn r.autoDetectInterface\n}\n\nfunc (r *NetworkManager) AutoDetectInterfaceFunc() control.Func {\n\tif r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() {\n\t\treturn func(network, address string, conn syscall.RawConn) error {\n\t\t\treturn control.Raw(conn, func(fd uintptr) error {\n\t\t\t\treturn r.platformInterface.AutoDetectInterfaceControl(int(fd))\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif r.interfaceMonitor == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) {\n\t\t\tremoteAddr := M.ParseSocksaddr(address).Addr\n\t\t\tif remoteAddr.IsValid() {\n\t\t\t\tiif, err := r.interfaceFinder.ByAddr(remoteAddr)\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn iif.Name, iif.Index, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tdefaultInterface := r.interfaceMonitor.DefaultInterface()\n\t\t\tif defaultInterface == nil {\n\t\t\t\treturn \"\", -1, tun.ErrNoRoute\n\t\t\t}\n\t\t\treturn defaultInterface.Name, defaultInterface.Index, nil\n\t\t})\n\t}\n}\n\nfunc (r *NetworkManager) ProtectFunc() control.Func {\n\tif r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() {\n\t\treturn func(network, address string, conn syscall.RawConn) error {\n\t\t\treturn control.Raw(conn, func(fd uintptr) error {\n\t\t\t\treturn r.platformInterface.AutoDetectInterfaceControl(int(fd))\n\t\t\t})\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *NetworkManager) DefaultOptions() adapter.NetworkOptions {\n\treturn r.defaultOptions\n}\n\nfunc (r *NetworkManager) RegisterAutoRedirectOutputMark(mark uint32) error {\n\tif r.autoRedirectOutputMark > 0 {\n\t\treturn E.New(\"only one auto-redirect can be configured\")\n\t}\n\tr.autoRedirectOutputMark = mark\n\treturn nil\n}\n\nfunc (r *NetworkManager) AutoRedirectOutputMark() uint32 {\n\treturn r.autoRedirectOutputMark\n}\n\nfunc (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func {\n\treturn func(network, address string, conn syscall.RawConn) error {\n\t\tif r.autoRedirectOutputMark == 0 {\n\t\t\treturn nil\n\t\t}\n\t\treturn control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn)\n\t}\n}\n\nfunc (r *NetworkManager) NetworkMonitor() tun.NetworkUpdateMonitor {\n\treturn r.networkMonitor\n}\n\nfunc (r *NetworkManager) InterfaceMonitor() tun.DefaultInterfaceMonitor {\n\treturn r.interfaceMonitor\n}\n\nfunc (r *NetworkManager) PackageManager() tun.PackageManager {\n\treturn r.packageManager\n}\n\nfunc (r *NetworkManager) NeedWIFIState() bool {\n\treturn r.needWIFIState\n}\n\nfunc (r *NetworkManager) WIFIState() adapter.WIFIState {\n\tr.wifiStateMutex.RLock()\n\tdefer r.wifiStateMutex.RUnlock()\n\treturn r.wifiState\n}\n\nfunc (r *NetworkManager) onWIFIStateChanged(state adapter.WIFIState) {\n\tr.wifiStateMutex.Lock()\n\tif state != r.wifiState {\n\t\tr.wifiState = state\n\t\tr.wifiStateMutex.Unlock()\n\t\tif state.SSID != \"\" {\n\t\t\tr.logger.Info(\"WIFI state changed: SSID=\", state.SSID, \", BSSID=\", state.BSSID)\n\t\t} else {\n\t\t\tr.logger.Info(\"WIFI disconnected\")\n\t\t}\n\t} else {\n\t\tr.wifiStateMutex.Unlock()\n\t}\n}\n\nfunc (r *NetworkManager) UpdateWIFIState() {\n\tvar state adapter.WIFIState\n\tif r.wifiMonitor != nil {\n\t\tstate = r.wifiMonitor.ReadWIFIState()\n\t} else if r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor() {\n\t\tstate = r.platformInterface.ReadWIFIState()\n\t} else {\n\t\treturn\n\t}\n\tr.onWIFIStateChanged(state)\n}\n\nfunc (r *NetworkManager) ResetNetwork() {\n\tif r.connectionManager != nil {\n\t\tr.connectionManager.CloseAll()\n\t}\n\n\tfor _, endpoint := range r.endpoint.Endpoints() {\n\t\tlistener, isListener := endpoint.(adapter.InterfaceUpdateListener)\n\t\tif isListener {\n\t\t\tlistener.InterfaceUpdated()\n\t\t}\n\t}\n\n\tfor _, inbound := range r.inbound.Inbounds() {\n\t\tlistener, isListener := inbound.(adapter.InterfaceUpdateListener)\n\t\tif isListener {\n\t\t\tlistener.InterfaceUpdated()\n\t\t}\n\t}\n\n\tfor _, outbound := range r.outbound.Outbounds() {\n\t\tlistener, isListener := outbound.(adapter.InterfaceUpdateListener)\n\t\tif isListener {\n\t\t\tlistener.InterfaceUpdated()\n\t\t}\n\t}\n}\n\nfunc (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interface, flags int) {\n\tif defaultInterface == nil {\n\t\tr.pauseManager.NetworkPause()\n\t\tr.logger.Error(\"missing default interface\")\n\t\treturn\n\t}\n\n\tr.pauseManager.NetworkWake()\n\tvar options []string\n\toptions = append(options, F.ToString(\"index \", defaultInterface.Index))\n\tif C.IsAndroid && r.platformInterface == nil {\n\t\tvar vpnStatus string\n\t\tif r.interfaceMonitor.AndroidVPNEnabled() {\n\t\t\tvpnStatus = \"enabled\"\n\t\t} else {\n\t\t\tvpnStatus = \"disabled\"\n\t\t}\n\t\toptions = append(options, \"vpn \"+vpnStatus)\n\t} else if r.platformInterface != nil {\n\t\tnetworkInterface := common.Find(r.networkInterfaces.Load(), func(it adapter.NetworkInterface) bool {\n\t\t\treturn it.Interface.Index == defaultInterface.Index\n\t\t})\n\t\tif networkInterface.Name == \"\" {\n\t\t\t// race\n\t\t\treturn\n\t\t}\n\t\toptions = append(options, F.ToString(\"type \", networkInterface.Type))\n\t\tif networkInterface.Expensive {\n\t\t\toptions = append(options, \"expensive\")\n\t\t}\n\t\tif networkInterface.Constrained {\n\t\t\toptions = append(options, \"constrained\")\n\t\t}\n\t}\n\tr.logger.Info(\"updated default interface \", defaultInterface.Name, \", \", strings.Join(options, \", \"))\n\tr.UpdateWIFIState()\n\n\tif !r.started {\n\t\treturn\n\t}\n\tr.ResetNetwork()\n}\n\nfunc (r *NetworkManager) notifyWindowsPowerEvent(event int) {\n\tswitch event {\n\tcase winpowrprof.EVENT_SUSPEND:\n\t\tr.pauseManager.DevicePause()\n\t\tr.ResetNetwork()\n\tcase winpowrprof.EVENT_RESUME:\n\t\tif !r.pauseManager.IsDevicePaused() {\n\t\t\treturn\n\t\t}\n\t\tfallthrough\n\tcase winpowrprof.EVENT_RESUME_AUTOMATIC:\n\t\tr.pauseManager.DeviceWake()\n\t\tr.ResetNetwork()\n\t}\n}\n\nfunc (r *NetworkManager) OnPackagesUpdated(packages int, sharedUsers int) {\n\tr.logger.Info(\"updated packages list: \", packages, \" packages, \", sharedUsers, \" shared users\")\n}\n"
  },
  {
    "path": "route/platform_searcher.go",
    "content": "package route\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/process\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype platformSearcher struct {\n\tplatform adapter.PlatformInterface\n}\n\nfunc newPlatformSearcher(platform adapter.PlatformInterface) process.Searcher {\n\treturn &platformSearcher{platform: platform}\n}\n\nfunc (s *platformSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {\n\tif !s.platform.UsePlatformConnectionOwnerFinder() {\n\t\treturn nil, process.ErrNotFound\n\t}\n\n\tvar ipProtocol int32\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\tipProtocol = syscall.IPPROTO_TCP\n\tcase N.NetworkUDP:\n\t\tipProtocol = syscall.IPPROTO_UDP\n\tdefault:\n\t\treturn nil, process.ErrNotFound\n\t}\n\n\trequest := &adapter.FindConnectionOwnerRequest{\n\t\tIpProtocol:         ipProtocol,\n\t\tSourceAddress:      source.Addr().String(),\n\t\tSourcePort:         int32(source.Port()),\n\t\tDestinationAddress: destination.Addr().String(),\n\t\tDestinationPort:    int32(destination.Port()),\n\t}\n\n\treturn s.platform.FindConnectionOwner(request)\n}\n"
  },
  {
    "path": "route/route.go",
    "content": "package route\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/process\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\tR \"github.com/sagernet/sing-box/route/rule\"\n\t\"github.com/sagernet/sing-mux\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing-tun/ping\"\n\t\"github.com/sagernet/sing-vmess\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\t\"github.com/sagernet/sing/common/bufio/deadline\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/uot\"\n\n\t\"golang.org/x/exp/slices\"\n)\n\n// Deprecated: use RouteConnectionEx instead.\nfunc (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {\n\tdone := make(chan interface{})\n\terr := r.routeConnection(ctx, conn, metadata, N.OnceClose(func(it error) {\n\t\tclose(done)\n\t}))\n\tif err != nil {\n\t\treturn err\n\t}\n\tselect {\n\tcase <-done:\n\tcase <-r.ctx.Done():\n\t}\n\treturn nil\n}\n\nfunc (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\terr := r.routeConnection(ctx, conn, metadata, onClose)\n\tif err != nil {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\tif E.IsClosedOrCanceled(err) || R.IsRejected(err) {\n\t\t\tr.logger.DebugContext(ctx, \"connection closed: \", err)\n\t\t} else {\n\t\t\tr.logger.ErrorContext(ctx, err)\n\t\t}\n\t}\n}\n\nfunc (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {\n\t//nolint:staticcheck\n\tif metadata.InboundDetour != \"\" {\n\t\tif metadata.LastInbound == metadata.InboundDetour {\n\t\t\treturn E.New(\"routing loop on detour: \", metadata.InboundDetour)\n\t\t}\n\t\tdetour, loaded := r.inbound.Get(metadata.InboundDetour)\n\t\tif !loaded {\n\t\t\treturn E.New(\"inbound detour not found: \", metadata.InboundDetour)\n\t\t}\n\t\tinjectable, isInjectable := detour.(adapter.TCPInjectableInbound)\n\t\tif !isInjectable {\n\t\t\treturn E.New(\"inbound detour is not TCP injectable: \", metadata.InboundDetour)\n\t\t}\n\t\tmetadata.LastInbound = metadata.Inbound\n\t\tmetadata.Inbound = metadata.InboundDetour\n\t\tmetadata.InboundDetour = \"\"\n\t\tinjectable.NewConnectionEx(ctx, conn, metadata, onClose)\n\t\treturn nil\n\t}\n\tmetadata.Network = N.NetworkTCP\n\tswitch metadata.Destination.Fqdn {\n\tcase mux.Destination.Fqdn:\n\t\treturn E.New(\"global multiplex is deprecated since sing-box v1.7.0, enable multiplex in Inbound fields instead.\")\n\tcase vmess.MuxDestination.Fqdn:\n\t\treturn E.New(\"global multiplex (v2ray legacy) not supported since sing-box v1.7.0.\")\n\tcase uot.MagicAddress:\n\t\treturn E.New(\"global UoT not supported since sing-box v1.7.0.\")\n\tcase uot.LegacyMagicAddress:\n\t\treturn E.New(\"global UoT (legacy) not supported since sing-box v1.7.0.\")\n\t}\n\tif deadline.NeedAdditionalReadDeadline(conn) {\n\t\tconn = deadline.NewConn(conn)\n\t}\n\tselectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, false, conn, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar selectedOutbound adapter.Outbound\n\tif selectedRule != nil {\n\t\tswitch action := selectedRule.Action().(type) {\n\t\tcase *R.RuleActionRoute:\n\t\t\tvar loaded bool\n\t\t\tselectedOutbound, loaded = r.outbound.Outbound(action.Outbound)\n\t\t\tif !loaded {\n\t\t\t\tbuf.ReleaseMulti(buffers)\n\t\t\t\treturn E.New(\"outbound not found: \", action.Outbound)\n\t\t\t}\n\t\t\tif !common.Contains(selectedOutbound.Network(), N.NetworkTCP) {\n\t\t\t\tbuf.ReleaseMulti(buffers)\n\t\t\t\treturn E.New(\"TCP is not supported by outbound: \", selectedOutbound.Tag())\n\t\t\t}\n\t\tcase *R.RuleActionBypass:\n\t\t\tif action.Outbound == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tvar loaded bool\n\t\t\tselectedOutbound, loaded = r.outbound.Outbound(action.Outbound)\n\t\t\tif !loaded {\n\t\t\t\tbuf.ReleaseMulti(buffers)\n\t\t\t\treturn E.New(\"outbound not found: \", action.Outbound)\n\t\t\t}\n\t\t\tif !common.Contains(selectedOutbound.Network(), N.NetworkTCP) {\n\t\t\t\tbuf.ReleaseMulti(buffers)\n\t\t\t\treturn E.New(\"TCP is not supported by outbound: \", selectedOutbound.Tag())\n\t\t\t}\n\t\tcase *R.RuleActionReject:\n\t\t\tbuf.ReleaseMulti(buffers)\n\t\t\tif action.Method == C.RuleActionRejectMethodReply {\n\t\t\t\treturn E.New(\"reject method `reply` is not supported for TCP connections\")\n\t\t\t}\n\t\t\treturn action.Error(ctx)\n\t\tcase *R.RuleActionHijackDNS:\n\t\t\tfor _, buffer := range buffers {\n\t\t\t\tconn = bufio.NewCachedConn(conn, buffer)\n\t\t\t}\n\t\t\tN.CloseOnHandshakeFailure(conn, onClose, r.hijackDNSStream(ctx, conn, metadata))\n\t\t\treturn nil\n\t\t}\n\t}\n\tif selectedRule == nil {\n\t\tdefaultOutbound := r.outbound.Default()\n\t\tif !common.Contains(defaultOutbound.Network(), N.NetworkTCP) {\n\t\t\tbuf.ReleaseMulti(buffers)\n\t\t\treturn E.New(\"TCP is not supported by default outbound: \", defaultOutbound.Tag())\n\t\t}\n\t\tselectedOutbound = defaultOutbound\n\t}\n\n\tfor _, buffer := range buffers {\n\t\tconn = bufio.NewCachedConn(conn, buffer)\n\t}\n\tfor _, tracker := range r.trackers {\n\t\tconn = tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound)\n\t}\n\tif outboundHandler, isHandler := selectedOutbound.(adapter.ConnectionHandlerEx); isHandler {\n\t\toutboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)\n\t} else {\n\t\tr.connection.NewConnection(ctx, selectedOutbound, conn, metadata, onClose)\n\t}\n\treturn nil\n}\n\nfunc (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {\n\tdone := make(chan interface{})\n\terr := r.routePacketConnection(ctx, conn, metadata, N.OnceClose(func(it error) {\n\t\tclose(done)\n\t}))\n\tif err != nil {\n\t\tconn.Close()\n\t\tif E.IsClosedOrCanceled(err) || R.IsRejected(err) {\n\t\t\tr.logger.DebugContext(ctx, \"connection closed: \", err)\n\t\t} else {\n\t\t\tr.logger.ErrorContext(ctx, err)\n\t\t}\n\t}\n\tselect {\n\tcase <-done:\n\tcase <-r.ctx.Done():\n\t}\n\treturn nil\n}\n\nfunc (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\terr := r.routePacketConnection(ctx, conn, metadata, onClose)\n\tif err != nil {\n\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\tif E.IsClosedOrCanceled(err) || R.IsRejected(err) {\n\t\t\tr.logger.DebugContext(ctx, \"connection closed: \", err)\n\t\t} else {\n\t\t\tr.logger.ErrorContext(ctx, err)\n\t\t}\n\t}\n}\n\nfunc (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {\n\t//nolint:staticcheck\n\tif metadata.InboundDetour != \"\" {\n\t\tif metadata.LastInbound == metadata.InboundDetour {\n\t\t\treturn E.New(\"routing loop on detour: \", metadata.InboundDetour)\n\t\t}\n\t\tdetour, loaded := r.inbound.Get(metadata.InboundDetour)\n\t\tif !loaded {\n\t\t\treturn E.New(\"inbound detour not found: \", metadata.InboundDetour)\n\t\t}\n\t\tinjectable, isInjectable := detour.(adapter.UDPInjectableInbound)\n\t\tif !isInjectable {\n\t\t\treturn E.New(\"inbound detour is not UDP injectable: \", metadata.InboundDetour)\n\t\t}\n\t\tmetadata.LastInbound = metadata.Inbound\n\t\tmetadata.Inbound = metadata.InboundDetour\n\t\tmetadata.InboundDetour = \"\"\n\t\tinjectable.NewPacketConnectionEx(ctx, conn, metadata, onClose)\n\t\treturn nil\n\t}\n\t// TODO: move to UoT\n\tmetadata.Network = N.NetworkUDP\n\n\t// Currently we don't have deadline usages for UDP connections\n\t/*if deadline.NeedAdditionalReadDeadline(conn) {\n\t\tconn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn))\n\t}*/\n\n\tselectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, false, nil, conn)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar selectedOutbound adapter.Outbound\n\tvar selectReturn bool\n\tif selectedRule != nil {\n\t\tswitch action := selectedRule.Action().(type) {\n\t\tcase *R.RuleActionRoute:\n\t\t\tvar loaded bool\n\t\t\tselectedOutbound, loaded = r.outbound.Outbound(action.Outbound)\n\t\t\tif !loaded {\n\t\t\t\tN.ReleaseMultiPacketBuffer(packetBuffers)\n\t\t\t\treturn E.New(\"outbound not found: \", action.Outbound)\n\t\t\t}\n\t\t\tif !common.Contains(selectedOutbound.Network(), N.NetworkUDP) {\n\t\t\t\tN.ReleaseMultiPacketBuffer(packetBuffers)\n\t\t\t\treturn E.New(\"UDP is not supported by outbound: \", selectedOutbound.Tag())\n\t\t\t}\n\t\tcase *R.RuleActionBypass:\n\t\t\tif action.Outbound == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tvar loaded bool\n\t\t\tselectedOutbound, loaded = r.outbound.Outbound(action.Outbound)\n\t\t\tif !loaded {\n\t\t\t\tN.ReleaseMultiPacketBuffer(packetBuffers)\n\t\t\t\treturn E.New(\"outbound not found: \", action.Outbound)\n\t\t\t}\n\t\t\tif !common.Contains(selectedOutbound.Network(), N.NetworkUDP) {\n\t\t\t\tN.ReleaseMultiPacketBuffer(packetBuffers)\n\t\t\t\treturn E.New(\"UDP is not supported by outbound: \", selectedOutbound.Tag())\n\t\t\t}\n\t\tcase *R.RuleActionReject:\n\t\t\tN.ReleaseMultiPacketBuffer(packetBuffers)\n\t\t\tif action.Method == C.RuleActionRejectMethodReply {\n\t\t\t\treturn E.New(\"reject method `reply` is not supported for UDP connections\")\n\t\t\t}\n\t\t\treturn action.Error(ctx)\n\t\tcase *R.RuleActionHijackDNS:\n\t\t\treturn r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose)\n\t\t}\n\t}\n\tif selectedRule == nil || selectReturn {\n\t\tdefaultOutbound := r.outbound.Default()\n\t\tif !common.Contains(defaultOutbound.Network(), N.NetworkUDP) {\n\t\t\tN.ReleaseMultiPacketBuffer(packetBuffers)\n\t\t\treturn E.New(\"UDP is not supported by outbound: \", defaultOutbound.Tag())\n\t\t}\n\t\tselectedOutbound = defaultOutbound\n\t}\n\tfor _, buffer := range packetBuffers {\n\t\tconn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination)\n\t\tN.PutPacketBuffer(buffer)\n\t}\n\tfor _, tracker := range r.trackers {\n\t\tconn = tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound)\n\t}\n\tif metadata.FakeIP {\n\t\tconn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)\n\t}\n\tif outboundHandler, isHandler := selectedOutbound.(adapter.PacketConnectionHandlerEx); isHandler {\n\t\toutboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose)\n\t} else {\n\t\tr.connection.NewPacketConnection(ctx, selectedOutbound, conn, metadata, onClose)\n\t}\n\treturn nil\n}\n\nfunc (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error) {\n\tselectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, supportBypass, nil, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar directRouteOutbound adapter.DirectRouteOutbound\n\tif selectedRule != nil {\n\t\tswitch action := selectedRule.Action().(type) {\n\t\tcase *R.RuleActionReject:\n\t\t\tswitch metadata.Network {\n\t\t\tcase N.NetworkTCP:\n\t\t\t\tif action.Method == C.RuleActionRejectMethodReply {\n\t\t\t\t\treturn nil, E.New(\"reject method `reply` is not supported for TCP connections\")\n\t\t\t\t}\n\t\t\tcase N.NetworkUDP:\n\t\t\t\tif action.Method == C.RuleActionRejectMethodReply {\n\t\t\t\t\treturn nil, E.New(\"reject method `reply` is not supported for UDP connections\")\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil, action.Error(context.Background())\n\t\tcase *R.RuleActionBypass:\n\t\t\tif supportBypass {\n\t\t\t\treturn nil, &R.BypassedError{Cause: tun.ErrBypass}\n\t\t\t}\n\t\t\tif routeContext == nil {\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\toutbound, loaded := r.outbound.Outbound(action.Outbound)\n\t\t\tif !loaded {\n\t\t\t\treturn nil, E.New(\"outbound not found: \", action.Outbound)\n\t\t\t}\n\t\t\tif !common.Contains(outbound.Network(), metadata.Network) {\n\t\t\t\treturn nil, E.New(metadata.Network, \" is not supported by outbound: \", action.Outbound)\n\t\t\t}\n\t\t\tdirectRouteOutbound = outbound.(adapter.DirectRouteOutbound)\n\t\tcase *R.RuleActionRoute:\n\t\t\tif routeContext == nil {\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\toutbound, loaded := r.outbound.Outbound(action.Outbound)\n\t\t\tif !loaded {\n\t\t\t\treturn nil, E.New(\"outbound not found: \", action.Outbound)\n\t\t\t}\n\t\t\tif !common.Contains(outbound.Network(), metadata.Network) {\n\t\t\t\treturn nil, E.New(metadata.Network, \" is not supported by outbound: \", action.Outbound)\n\t\t\t}\n\t\t\tdirectRouteOutbound = outbound.(adapter.DirectRouteOutbound)\n\t\t}\n\t}\n\tif directRouteOutbound == nil {\n\t\tif selectedRule != nil || metadata.Network != N.NetworkICMP {\n\t\t\treturn nil, nil\n\t\t}\n\t\tdefaultOutbound := r.outbound.Default()\n\t\tif !common.Contains(defaultOutbound.Network(), metadata.Network) {\n\t\t\treturn nil, E.New(metadata.Network, \" is not supported by default outbound: \", defaultOutbound.Tag())\n\t\t}\n\t\tdirectRouteOutbound = defaultOutbound.(adapter.DirectRouteOutbound)\n\t}\n\tif metadata.Destination.IsDomain() {\n\t\tif len(metadata.DestinationAddresses) == 0 {\n\t\t\tvar strategy C.DomainStrategy\n\t\t\tif metadata.Source.IsIPv4() {\n\t\t\t\tstrategy = C.DomainStrategyIPv4Only\n\t\t\t} else {\n\t\t\t\tstrategy = C.DomainStrategyIPv6Only\n\t\t\t}\n\t\t\terr = r.actionResolve(r.ctx, &metadata, &R.RuleActionResolve{\n\t\t\t\tStrategy: strategy,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tvar newDestination netip.Addr\n\t\tif metadata.Source.IsIPv4() {\n\t\t\tfor _, address := range metadata.DestinationAddresses {\n\t\t\t\tif address.Is4() {\n\t\t\t\t\tnewDestination = address\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor _, address := range metadata.DestinationAddresses {\n\t\t\t\tif address.Is6() {\n\t\t\t\t\tnewDestination = address\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !newDestination.IsValid() {\n\t\t\tif metadata.Source.IsIPv4() {\n\t\t\t\treturn nil, E.New(\"no IPv4 address found for domain: \", metadata.Destination.Fqdn)\n\t\t\t} else {\n\t\t\t\treturn nil, E.New(\"no IPv6 address found for domain: \", metadata.Destination.Fqdn)\n\t\t\t}\n\t\t}\n\t\tmetadata.Destination = M.Socksaddr{\n\t\t\tAddr: newDestination,\n\t\t}\n\t\trouteContext = ping.NewContextDestinationWriter(routeContext, metadata.OriginDestination.Addr)\n\t\tvar routeDestination tun.DirectRouteDestination\n\t\trouteDestination, err = directRouteOutbound.NewDirectRouteConnection(metadata, routeContext, timeout)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn ping.NewDestinationWriter(routeDestination, newDestination), nil\n\t}\n\treturn directRouteOutbound.NewDirectRouteConnection(metadata, routeContext, timeout)\n}\n\nfunc (r *Router) matchRule(\n\tctx context.Context, metadata *adapter.InboundContext, preMatch bool, supportBypass bool,\n\tinputConn net.Conn, inputPacketConn N.PacketConn,\n) (\n\tselectedRule adapter.Rule, selectedRuleIndex int,\n\tbuffers []*buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error,\n) {\n\tif r.processSearcher != nil && metadata.ProcessInfo == nil {\n\t\tvar originDestination netip.AddrPort\n\t\tif metadata.OriginDestination.IsValid() {\n\t\t\toriginDestination = metadata.OriginDestination.AddrPort()\n\t\t} else if metadata.Destination.IsIP() {\n\t\t\toriginDestination = metadata.Destination.AddrPort()\n\t\t}\n\t\tprocessInfo, fErr := process.FindProcessInfo(r.processSearcher, ctx, metadata.Network, metadata.Source.AddrPort(), originDestination)\n\t\tif fErr != nil {\n\t\t\tr.logger.InfoContext(ctx, \"failed to search process: \", fErr)\n\t\t} else {\n\t\t\tif processInfo.ProcessPath != \"\" {\n\t\t\t\tif processInfo.UserName != \"\" {\n\t\t\t\t\tr.logger.InfoContext(ctx, \"found process path: \", processInfo.ProcessPath, \", user: \", processInfo.UserName)\n\t\t\t\t} else if processInfo.UserId != -1 {\n\t\t\t\t\tr.logger.InfoContext(ctx, \"found process path: \", processInfo.ProcessPath, \", user id: \", processInfo.UserId)\n\t\t\t\t} else {\n\t\t\t\t\tr.logger.InfoContext(ctx, \"found process path: \", processInfo.ProcessPath)\n\t\t\t\t}\n\t\t\t} else if processInfo.AndroidPackageName != \"\" {\n\t\t\t\tr.logger.InfoContext(ctx, \"found package name: \", processInfo.AndroidPackageName)\n\t\t\t} else if processInfo.UserId != -1 {\n\t\t\t\tif processInfo.UserName != \"\" {\n\t\t\t\t\tr.logger.InfoContext(ctx, \"found user: \", processInfo.UserName)\n\t\t\t\t} else {\n\t\t\t\t\tr.logger.InfoContext(ctx, \"found user id: \", processInfo.UserId)\n\t\t\t\t}\n\t\t\t}\n\t\t\tmetadata.ProcessInfo = processInfo\n\t\t}\n\t}\n\tif r.neighborResolver != nil && metadata.SourceMACAddress == nil && metadata.Source.Addr.IsValid() {\n\t\tmac, macFound := r.neighborResolver.LookupMAC(metadata.Source.Addr)\n\t\tif macFound {\n\t\t\tmetadata.SourceMACAddress = mac\n\t\t}\n\t\thostname, hostnameFound := r.neighborResolver.LookupHostname(metadata.Source.Addr)\n\t\tif hostnameFound {\n\t\t\tmetadata.SourceHostname = hostname\n\t\t\tif macFound {\n\t\t\t\tr.logger.InfoContext(ctx, \"found neighbor: \", mac, \", hostname: \", hostname)\n\t\t\t} else {\n\t\t\t\tr.logger.InfoContext(ctx, \"found neighbor hostname: \", hostname)\n\t\t\t}\n\t\t} else if macFound {\n\t\t\tr.logger.InfoContext(ctx, \"found neighbor: \", mac)\n\t\t}\n\t}\n\tif metadata.Destination.Addr.IsValid() && r.dnsTransport.FakeIP() != nil && r.dnsTransport.FakeIP().Store().Contains(metadata.Destination.Addr) {\n\t\tdomain, loaded := r.dnsTransport.FakeIP().Store().Lookup(metadata.Destination.Addr)\n\t\tif !loaded {\n\t\t\tfatalErr = E.New(\"missing fakeip record, try enable `experimental.cache_file`\")\n\t\t\treturn\n\t\t}\n\t\tif domain != \"\" {\n\t\t\tmetadata.OriginDestination = metadata.Destination\n\t\t\tmetadata.Destination = M.Socksaddr{\n\t\t\t\tFqdn: domain,\n\t\t\t\tPort: metadata.Destination.Port,\n\t\t\t}\n\t\t\tmetadata.FakeIP = true\n\t\t\tr.logger.DebugContext(ctx, \"found fakeip domain: \", domain)\n\t\t}\n\t} else if metadata.Domain == \"\" {\n\t\tdomain, loaded := r.dns.LookupReverseMapping(metadata.Destination.Addr)\n\t\tif loaded {\n\t\t\tmetadata.Domain = domain\n\t\t\tr.logger.DebugContext(ctx, \"found reserve mapped domain: \", metadata.Domain)\n\t\t}\n\t}\n\tif metadata.Destination.IsIPv4() {\n\t\tmetadata.IPVersion = 4\n\t} else if metadata.Destination.IsIPv6() {\n\t\tmetadata.IPVersion = 6\n\t}\n\nmatch:\n\tfor currentRuleIndex, currentRule := range r.rules {\n\t\tmetadata.ResetRuleCache()\n\t\tif !currentRule.Match(metadata) {\n\t\t\tcontinue\n\t\t}\n\t\tif !preMatch {\n\t\t\truleDescription := currentRule.String()\n\t\t\tif ruleDescription != \"\" {\n\t\t\t\tr.logger.DebugContext(ctx, \"match[\", currentRuleIndex, \"] \", currentRule, \" => \", currentRule.Action())\n\t\t\t} else {\n\t\t\t\tr.logger.DebugContext(ctx, \"match[\", currentRuleIndex, \"] => \", currentRule.Action())\n\t\t\t}\n\t\t} else {\n\t\t\tswitch currentRule.Action().Type() {\n\t\t\tcase C.RuleActionTypeReject:\n\t\t\t\truleDescription := currentRule.String()\n\t\t\t\tif ruleDescription != \"\" {\n\t\t\t\t\tr.logger.DebugContext(ctx, \"pre-match[\", currentRuleIndex, \"] \", currentRule, \" => \", currentRule.Action())\n\t\t\t\t} else {\n\t\t\t\t\tr.logger.DebugContext(ctx, \"pre-match[\", currentRuleIndex, \"] => \", currentRule.Action())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tvar routeOptions *R.RuleActionRouteOptions\n\t\tswitch action := currentRule.Action().(type) {\n\t\tcase *R.RuleActionRoute:\n\t\t\trouteOptions = &action.RuleActionRouteOptions\n\t\tcase *R.RuleActionRouteOptions:\n\t\t\trouteOptions = action\n\t\t}\n\t\tif routeOptions != nil {\n\t\t\t// TODO: add nat\n\t\t\tif (routeOptions.OverrideAddress.IsValid() || routeOptions.OverridePort > 0) && !metadata.RouteOriginalDestination.IsValid() {\n\t\t\t\tmetadata.RouteOriginalDestination = metadata.Destination\n\t\t\t}\n\t\t\tif routeOptions.OverrideAddress.IsValid() {\n\t\t\t\tmetadata.Destination = M.Socksaddr{\n\t\t\t\t\tAddr: routeOptions.OverrideAddress.Addr,\n\t\t\t\t\tPort: metadata.Destination.Port,\n\t\t\t\t\tFqdn: routeOptions.OverrideAddress.Fqdn,\n\t\t\t\t}\n\t\t\t\tmetadata.DestinationAddresses = nil\n\t\t\t}\n\t\t\tif routeOptions.OverridePort > 0 {\n\t\t\t\tmetadata.Destination = M.Socksaddr{\n\t\t\t\t\tAddr: metadata.Destination.Addr,\n\t\t\t\t\tPort: routeOptions.OverridePort,\n\t\t\t\t\tFqdn: metadata.Destination.Fqdn,\n\t\t\t\t}\n\t\t\t}\n\t\t\tif routeOptions.NetworkStrategy != nil {\n\t\t\t\tmetadata.NetworkStrategy = routeOptions.NetworkStrategy\n\t\t\t}\n\t\t\tif len(routeOptions.NetworkType) > 0 {\n\t\t\t\tmetadata.NetworkType = routeOptions.NetworkType\n\t\t\t}\n\t\t\tif len(routeOptions.FallbackNetworkType) > 0 {\n\t\t\t\tmetadata.FallbackNetworkType = routeOptions.FallbackNetworkType\n\t\t\t}\n\t\t\tif routeOptions.FallbackDelay != 0 {\n\t\t\t\tmetadata.FallbackDelay = routeOptions.FallbackDelay\n\t\t\t}\n\t\t\tif routeOptions.UDPDisableDomainUnmapping {\n\t\t\t\tmetadata.UDPDisableDomainUnmapping = true\n\t\t\t}\n\t\t\tif routeOptions.UDPConnect {\n\t\t\t\tmetadata.UDPConnect = true\n\t\t\t}\n\t\t\tif routeOptions.UDPTimeout > 0 {\n\t\t\t\tmetadata.UDPTimeout = routeOptions.UDPTimeout\n\t\t\t}\n\t\t\tif routeOptions.TLSFragment {\n\t\t\t\tmetadata.TLSFragment = true\n\t\t\t\tmetadata.TLSFragmentFallbackDelay = routeOptions.TLSFragmentFallbackDelay\n\t\t\t}\n\t\t\tif routeOptions.TLSRecordFragment {\n\t\t\t\tmetadata.TLSRecordFragment = true\n\t\t\t}\n\t\t}\n\t\tswitch action := currentRule.Action().(type) {\n\t\tcase *R.RuleActionSniff:\n\t\t\tif !preMatch {\n\t\t\t\tnewBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers, packetBuffers)\n\t\t\t\tif newBuffer != nil {\n\t\t\t\t\tbuffers = append(buffers, newBuffer)\n\t\t\t\t} else if len(newPacketBuffers) > 0 {\n\t\t\t\t\tpacketBuffers = append(packetBuffers, newPacketBuffers...)\n\t\t\t\t}\n\t\t\t\tif newErr != nil {\n\t\t\t\t\tfatalErr = newErr\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else if metadata.Network != N.NetworkICMP {\n\t\t\t\tselectedRule = currentRule\n\t\t\t\tselectedRuleIndex = currentRuleIndex\n\t\t\t\tbreak match\n\t\t\t}\n\t\tcase *R.RuleActionResolve:\n\t\t\tfatalErr = r.actionResolve(ctx, metadata, action)\n\t\t\tif fatalErr != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tactionType := currentRule.Action().Type()\n\t\tif actionType == C.RuleActionTypeRoute ||\n\t\t\tactionType == C.RuleActionTypeReject ||\n\t\t\tactionType == C.RuleActionTypeHijackDNS {\n\t\t\tselectedRule = currentRule\n\t\t\tselectedRuleIndex = currentRuleIndex\n\t\t\tbreak match\n\t\t}\n\t\tif actionType == C.RuleActionTypeBypass {\n\t\t\tbypassAction := currentRule.Action().(*R.RuleActionBypass)\n\t\t\tif !supportBypass && bypassAction.Outbound == \"\" {\n\t\t\t\tcontinue match\n\t\t\t}\n\t\t\tselectedRule = currentRule\n\t\t\tselectedRuleIndex = currentRuleIndex\n\t\t\tbreak match\n\t\t}\n\t}\n\treturn\n}\n\nfunc (r *Router) actionSniff(\n\tctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff,\n\tinputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, inputPacketBuffers []*N.PacketBuffer,\n) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {\n\tif sniff.Skip(metadata) {\n\t\tr.logger.DebugContext(ctx, \"sniff skipped due to port considered as server-first\")\n\t\treturn\n\t} else if metadata.Protocol != \"\" {\n\t\tr.logger.DebugContext(ctx, \"duplicate sniff skipped\")\n\t\treturn\n\t}\n\tif inputConn != nil {\n\t\tif len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {\n\t\t\treturn\n\t\t} else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {\n\t\t\tr.logger.DebugContext(ctx, \"packet sniff skipped due to previous error: \", metadata.SniffError)\n\t\t\treturn\n\t\t}\n\t\tvar streamSniffers []sniff.StreamSniffer\n\t\tif len(action.StreamSniffers) > 0 {\n\t\t\tstreamSniffers = action.StreamSniffers\n\t\t} else {\n\t\t\tstreamSniffers = []sniff.StreamSniffer{\n\t\t\t\tsniff.TLSClientHello,\n\t\t\t\tsniff.HTTPHost,\n\t\t\t\tsniff.StreamDomainNameQuery,\n\t\t\t\tsniff.BitTorrent,\n\t\t\t\tsniff.SSH,\n\t\t\t\tsniff.RDP,\n\t\t\t}\n\t\t}\n\t\tsniffBuffer := buf.NewPacket()\n\t\terr := sniff.PeekStream(\n\t\t\tctx,\n\t\t\tmetadata,\n\t\t\tinputConn,\n\t\t\tinputBuffers,\n\t\t\tsniffBuffer,\n\t\t\taction.Timeout,\n\t\t\tstreamSniffers...,\n\t\t)\n\t\tmetadata.SnifferNames = action.SnifferNames\n\t\tmetadata.SniffError = err\n\t\tif err == nil {\n\t\t\t//goland:noinspection GoDeprecation\n\t\t\tif action.OverrideDestination && M.IsDomainName(metadata.Domain) {\n\t\t\t\tmetadata.Destination = M.Socksaddr{\n\t\t\t\t\tFqdn: metadata.Domain,\n\t\t\t\t\tPort: metadata.Destination.Port,\n\t\t\t\t}\n\t\t\t}\n\t\t\tif metadata.Domain != \"\" && metadata.Client != \"\" {\n\t\t\t\tr.logger.DebugContext(ctx, \"sniffed protocol: \", metadata.Protocol, \", domain: \", metadata.Domain, \", client: \", metadata.Client)\n\t\t\t} else if metadata.Domain != \"\" {\n\t\t\t\tr.logger.DebugContext(ctx, \"sniffed protocol: \", metadata.Protocol, \", domain: \", metadata.Domain)\n\t\t\t} else {\n\t\t\t\tr.logger.DebugContext(ctx, \"sniffed protocol: \", metadata.Protocol)\n\t\t\t}\n\t\t}\n\t\tif !sniffBuffer.IsEmpty() {\n\t\t\tbuffer = sniffBuffer\n\t\t} else {\n\t\t\tsniffBuffer.Release()\n\t\t}\n\t} else if inputPacketConn != nil {\n\t\tif len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {\n\t\t\treturn\n\t\t} else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {\n\t\t\tr.logger.DebugContext(ctx, \"packet sniff skipped due to previous error: \", metadata.SniffError)\n\t\t\treturn\n\t\t}\n\t\tquicMoreData := func() bool {\n\t\t\treturn slices.Equal(metadata.SnifferNames, action.SnifferNames) && errors.Is(metadata.SniffError, sniff.ErrNeedMoreData)\n\t\t}\n\t\tvar packetSniffers []sniff.PacketSniffer\n\t\tif len(action.PacketSniffers) > 0 {\n\t\t\tpacketSniffers = action.PacketSniffers\n\t\t} else {\n\t\t\tpacketSniffers = []sniff.PacketSniffer{\n\t\t\t\tsniff.DomainNameQuery,\n\t\t\t\tsniff.QUICClientHello,\n\t\t\t\tsniff.STUNMessage,\n\t\t\t\tsniff.UTP,\n\t\t\t\tsniff.UDPTracker,\n\t\t\t\tsniff.DTLSRecord,\n\t\t\t\tsniff.NTP,\n\t\t\t}\n\t\t}\n\t\tvar err error\n\t\tfor _, packetBuffer := range inputPacketBuffers {\n\t\t\tif quicMoreData() {\n\t\t\t\terr = sniff.PeekPacket(\n\t\t\t\t\tctx,\n\t\t\t\t\tmetadata,\n\t\t\t\t\tpacketBuffer.Buffer.Bytes(),\n\t\t\t\t\tsniff.QUICClientHello,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\terr = sniff.PeekPacket(\n\t\t\t\t\tctx, metadata,\n\t\t\t\t\tpacketBuffer.Buffer.Bytes(),\n\t\t\t\t\tpacketSniffers...,\n\t\t\t\t)\n\t\t\t}\n\t\t\tmetadata.SnifferNames = action.SnifferNames\n\t\t\tmetadata.SniffError = err\n\t\t\tif errors.Is(err, sniff.ErrNeedMoreData) {\n\t\t\t\t// TODO: replace with generic message when there are more multi-packet protocols\n\t\t\t\tr.logger.DebugContext(ctx, \"attempt to sniff fragmented QUIC client hello\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tgoto finally\n\t\t}\n\t\tpacketBuffers = inputPacketBuffers\n\t\tfor {\n\t\t\tvar (\n\t\t\t\tsniffBuffer = buf.NewPacket()\n\t\t\t\tdestination M.Socksaddr\n\t\t\t\tdone        = make(chan struct{})\n\t\t\t)\n\t\t\tgo func() {\n\t\t\t\tsniffTimeout := C.ReadPayloadTimeout\n\t\t\t\tif action.Timeout > 0 {\n\t\t\t\t\tsniffTimeout = action.Timeout\n\t\t\t\t}\n\t\t\t\tinputPacketConn.SetReadDeadline(time.Now().Add(sniffTimeout))\n\t\t\t\tdestination, err = inputPacketConn.ReadPacket(sniffBuffer)\n\t\t\t\tinputPacketConn.SetReadDeadline(time.Time{})\n\t\t\t\tclose(done)\n\t\t\t}()\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\tcase <-ctx.Done():\n\t\t\t\tinputPacketConn.Close()\n\t\t\t\tfatalErr = ctx.Err()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tsniffBuffer.Release()\n\t\t\t\tif !errors.Is(err, context.DeadlineExceeded) {\n\t\t\t\t\tfatalErr = err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif quicMoreData() {\n\t\t\t\t\terr = sniff.PeekPacket(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tmetadata,\n\t\t\t\t\t\tsniffBuffer.Bytes(),\n\t\t\t\t\t\tsniff.QUICClientHello,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\terr = sniff.PeekPacket(\n\t\t\t\t\t\tctx, metadata,\n\t\t\t\t\t\tsniffBuffer.Bytes(),\n\t\t\t\t\t\tpacketSniffers...,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tpacketBuffer := N.NewPacketBuffer()\n\t\t\t\t*packetBuffer = N.PacketBuffer{\n\t\t\t\t\tBuffer:      sniffBuffer,\n\t\t\t\t\tDestination: destination,\n\t\t\t\t}\n\t\t\t\tpacketBuffers = append(packetBuffers, packetBuffer)\n\t\t\t\tmetadata.SnifferNames = action.SnifferNames\n\t\t\t\tmetadata.SniffError = err\n\t\t\t\tif errors.Is(err, sniff.ErrNeedMoreData) {\n\t\t\t\t\t// TODO: replace with generic message when there are more multi-packet protocols\n\t\t\t\t\tr.logger.DebugContext(ctx, \"attempt to sniff fragmented QUIC client hello\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tgoto finally\n\t\t}\n\tfinally:\n\t\tif err == nil {\n\t\t\t//goland:noinspection GoDeprecation\n\t\t\tif action.OverrideDestination && M.IsDomainName(metadata.Domain) {\n\t\t\t\tmetadata.Destination = M.Socksaddr{\n\t\t\t\t\tFqdn: metadata.Domain,\n\t\t\t\t\tPort: metadata.Destination.Port,\n\t\t\t\t}\n\t\t\t}\n\t\t\tif metadata.Domain != \"\" && metadata.Client != \"\" {\n\t\t\t\tr.logger.DebugContext(ctx, \"sniffed packet protocol: \", metadata.Protocol, \", domain: \", metadata.Domain, \", client: \", metadata.Client)\n\t\t\t} else if metadata.Domain != \"\" {\n\t\t\t\tr.logger.DebugContext(ctx, \"sniffed packet protocol: \", metadata.Protocol, \", domain: \", metadata.Domain)\n\t\t\t} else if metadata.Client != \"\" {\n\t\t\t\tr.logger.DebugContext(ctx, \"sniffed packet protocol: \", metadata.Protocol, \", client: \", metadata.Client)\n\t\t\t} else {\n\t\t\t\tr.logger.DebugContext(ctx, \"sniffed packet protocol: \", metadata.Protocol)\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\nfunc (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionResolve) error {\n\tif metadata.Destination.IsDomain() {\n\t\tvar transport adapter.DNSTransport\n\t\tif action.Server != \"\" {\n\t\t\tvar loaded bool\n\t\t\ttransport, loaded = r.dnsTransport.Transport(action.Server)\n\t\t\tif !loaded {\n\t\t\t\treturn E.New(\"DNS server not found: \", action.Server)\n\t\t\t}\n\t\t}\n\t\taddresses, err := r.dns.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, adapter.DNSQueryOptions{\n\t\t\tTransport:    transport,\n\t\t\tStrategy:     action.Strategy,\n\t\t\tDisableCache: action.DisableCache,\n\t\t\tRewriteTTL:   action.RewriteTTL,\n\t\t\tClientSubnet: action.ClientSubnet,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tmetadata.DestinationAddresses = addresses\n\t\tr.logger.DebugContext(ctx, \"resolved [\", strings.Join(F.MapToString(metadata.DestinationAddresses), \" \"), \"]\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "route/router.go",
    "content": "package route\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"runtime\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/process\"\n\t\"github.com/sagernet/sing-box/common/taskmonitor\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tR \"github.com/sagernet/sing-box/route/rule\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/task\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/pause\"\n)\n\nvar _ adapter.Router = (*Router)(nil)\n\ntype Router struct {\n\tctx               context.Context\n\tlogger            log.ContextLogger\n\tinbound           adapter.InboundManager\n\toutbound          adapter.OutboundManager\n\tdns               adapter.DNSRouter\n\tdnsTransport      adapter.DNSTransportManager\n\tconnection        adapter.ConnectionManager\n\tnetwork           adapter.NetworkManager\n\trules             []adapter.Rule\n\tneedFindProcess   bool\n\tneedFindNeighbor  bool\n\tleaseFiles        []string\n\truleSets          []adapter.RuleSet\n\truleSetMap        map[string]adapter.RuleSet\n\tprocessSearcher   process.Searcher\n\tneighborResolver  adapter.NeighborResolver\n\tpauseManager      pause.Manager\n\ttrackers          []adapter.ConnectionTracker\n\tplatformInterface adapter.PlatformInterface\n\tstarted           bool\n}\n\nfunc NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions) *Router {\n\treturn &Router{\n\t\tctx:               ctx,\n\t\tlogger:            logFactory.NewLogger(\"router\"),\n\t\tinbound:           service.FromContext[adapter.InboundManager](ctx),\n\t\toutbound:          service.FromContext[adapter.OutboundManager](ctx),\n\t\tdns:               service.FromContext[adapter.DNSRouter](ctx),\n\t\tdnsTransport:      service.FromContext[adapter.DNSTransportManager](ctx),\n\t\tconnection:        service.FromContext[adapter.ConnectionManager](ctx),\n\t\tnetwork:           service.FromContext[adapter.NetworkManager](ctx),\n\t\trules:             make([]adapter.Rule, 0, len(options.Rules)),\n\t\truleSetMap:        make(map[string]adapter.RuleSet),\n\t\tneedFindProcess:   hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,\n\t\tneedFindNeighbor:  hasRule(options.Rules, isNeighborRule) || hasDNSRule(dnsOptions.Rules, isNeighborDNSRule) || options.FindNeighbor,\n\t\tleaseFiles:        options.DHCPLeaseFiles,\n\t\tpauseManager:      service.FromContext[pause.Manager](ctx),\n\t\tplatformInterface: service.FromContext[adapter.PlatformInterface](ctx),\n\t}\n}\n\nfunc (r *Router) Initialize(rules []option.Rule, ruleSets []option.RuleSet) error {\n\tfor i, options := range rules {\n\t\trule, err := R.NewRule(r.ctx, r.logger, options, false)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"parse rule[\", i, \"]\")\n\t\t}\n\t\tr.rules = append(r.rules, rule)\n\t}\n\tfor i, options := range ruleSets {\n\t\tif _, exists := r.ruleSetMap[options.Tag]; exists {\n\t\t\treturn E.New(\"duplicate rule-set tag: \", options.Tag)\n\t\t}\n\t\truleSet, err := R.NewRuleSet(r.ctx, r.logger, options)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"parse rule-set[\", i, \"]\")\n\t\t}\n\t\tr.ruleSets = append(r.ruleSets, ruleSet)\n\t\tr.ruleSetMap[options.Tag] = ruleSet\n\t}\n\treturn nil\n}\n\nfunc (r *Router) Start(stage adapter.StartStage) error {\n\tmonitor := taskmonitor.New(r.logger, C.StartTimeout)\n\tswitch stage {\n\tcase adapter.StartStateStart:\n\t\tvar cacheContext *adapter.HTTPStartContext\n\t\tif len(r.ruleSets) > 0 {\n\t\t\tmonitor.Start(\"initialize rule-set\")\n\t\t\tcacheContext = adapter.NewHTTPStartContext(r.ctx)\n\t\t\tvar ruleSetStartGroup task.Group\n\t\t\tfor i, ruleSet := range r.ruleSets {\n\t\t\t\truleSetInPlace := ruleSet\n\t\t\t\truleSetStartGroup.Append0(func(ctx context.Context) error {\n\t\t\t\t\terr := ruleSetInPlace.StartContext(ctx, cacheContext)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn E.Cause(err, \"initialize rule-set[\", i, \"]\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t}\n\t\t\truleSetStartGroup.Concurrency(5)\n\t\t\truleSetStartGroup.FastFail()\n\t\t\terr := ruleSetStartGroup.Run(r.ctx)\n\t\t\tmonitor.Finish()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif cacheContext != nil {\n\t\t\tcacheContext.Close()\n\t\t}\n\t\tr.network.Initialize(r.ruleSets)\n\t\tneedFindProcess := r.needFindProcess\n\t\tneedFindNeighbor := r.needFindNeighbor\n\t\tfor _, ruleSet := range r.ruleSets {\n\t\t\tmetadata := ruleSet.Metadata()\n\t\t\tif metadata.ContainsProcessRule {\n\t\t\t\tneedFindProcess = true\n\t\t\t}\n\t\t}\n\t\tif C.IsAndroid && r.platformInterface != nil {\n\t\t\tneedFindProcess = true\n\t\t}\n\t\tr.needFindProcess = needFindProcess\n\t\tif needFindProcess {\n\t\t\tif r.platformInterface != nil && r.platformInterface.UsePlatformConnectionOwnerFinder() {\n\t\t\t\tr.processSearcher = newPlatformSearcher(r.platformInterface)\n\t\t\t} else {\n\t\t\t\tmonitor.Start(\"initialize process searcher\")\n\t\t\t\tsearcher, err := process.NewSearcher(process.Config{\n\t\t\t\t\tLogger:         r.logger,\n\t\t\t\t\tPackageManager: r.network.PackageManager(),\n\t\t\t\t})\n\t\t\t\tmonitor.Finish()\n\t\t\t\tif err != nil {\n\t\t\t\t\tif err != os.ErrInvalid {\n\t\t\t\t\t\tr.logger.Warn(E.Cause(err, \"create process searcher\"))\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tr.processSearcher = searcher\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tr.needFindNeighbor = needFindNeighbor\n\t\tif needFindNeighbor {\n\t\t\tif r.platformInterface != nil && r.platformInterface.UsePlatformNeighborResolver() {\n\t\t\t\tmonitor.Start(\"initialize neighbor resolver\")\n\t\t\t\tresolver := newPlatformNeighborResolver(r.logger, r.platformInterface)\n\t\t\t\terr := resolver.Start()\n\t\t\t\tmonitor.Finish()\n\t\t\t\tif err != nil {\n\t\t\t\t\tr.logger.Error(E.Cause(err, \"start neighbor resolver\"))\n\t\t\t\t} else {\n\t\t\t\t\tr.neighborResolver = resolver\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmonitor.Start(\"initialize neighbor resolver\")\n\t\t\t\tresolver, err := newNeighborResolver(r.logger, r.leaseFiles)\n\t\t\t\tmonitor.Finish()\n\t\t\t\tif err != nil {\n\t\t\t\t\tif err != os.ErrInvalid {\n\t\t\t\t\t\tr.logger.Error(E.Cause(err, \"create neighbor resolver\"))\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\terr = resolver.Start()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tr.logger.Error(E.Cause(err, \"start neighbor resolver\"))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tr.neighborResolver = resolver\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase adapter.StartStatePostStart:\n\t\tfor i, rule := range r.rules {\n\t\t\tmonitor.Start(\"initialize rule[\", i, \"]\")\n\t\t\terr := rule.Start()\n\t\t\tmonitor.Finish()\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"initialize rule[\", i, \"]\")\n\t\t\t}\n\t\t}\n\t\tfor _, ruleSet := range r.ruleSets {\n\t\t\tmonitor.Start(\"post start rule_set[\", ruleSet.Name(), \"]\")\n\t\t\terr := ruleSet.PostStart()\n\t\t\tmonitor.Finish()\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"post start rule_set[\", ruleSet.Name(), \"]\")\n\t\t\t}\n\t\t}\n\t\tr.started = true\n\t\treturn nil\n\tcase adapter.StartStateStarted:\n\t\tfor _, ruleSet := range r.ruleSets {\n\t\t\truleSet.Cleanup()\n\t\t}\n\t\truntime.GC()\n\t}\n\treturn nil\n}\n\nfunc (r *Router) Close() error {\n\tmonitor := taskmonitor.New(r.logger, C.StopTimeout)\n\tvar err error\n\tif r.neighborResolver != nil {\n\t\tmonitor.Start(\"close neighbor resolver\")\n\t\terr = E.Append(err, r.neighborResolver.Close(), func(closeErr error) error {\n\t\t\treturn E.Cause(closeErr, \"close neighbor resolver\")\n\t\t})\n\t\tmonitor.Finish()\n\t}\n\tfor i, rule := range r.rules {\n\t\tmonitor.Start(\"close rule[\", i, \"]\")\n\t\terr = E.Append(err, rule.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close rule[\", i, \"]\")\n\t\t})\n\t\tmonitor.Finish()\n\t}\n\tfor i, ruleSet := range r.ruleSets {\n\t\tmonitor.Start(\"close rule-set[\", i, \"]\")\n\t\terr = E.Append(err, ruleSet.Close(), func(err error) error {\n\t\t\treturn E.Cause(err, \"close rule-set[\", i, \"]\")\n\t\t})\n\t\tmonitor.Finish()\n\t}\n\treturn err\n}\n\nfunc (r *Router) RuleSet(tag string) (adapter.RuleSet, bool) {\n\truleSet, loaded := r.ruleSetMap[tag]\n\treturn ruleSet, loaded\n}\n\nfunc (r *Router) Rules() []adapter.Rule {\n\treturn r.rules\n}\n\nfunc (r *Router) AppendTracker(tracker adapter.ConnectionTracker) {\n\tr.trackers = append(r.trackers, tracker)\n}\n\nfunc (r *Router) NeedFindProcess() bool {\n\treturn r.needFindProcess\n}\n\nfunc (r *Router) NeedFindNeighbor() bool {\n\treturn r.needFindNeighbor\n}\n\nfunc (r *Router) NeighborResolver() adapter.NeighborResolver {\n\treturn r.neighborResolver\n}\n\nfunc (r *Router) ResetNetwork() {\n\tr.network.ResetNetwork()\n\tr.dns.ResetNetwork()\n}\n"
  },
  {
    "path": "route/rule/rule_abstract.go",
    "content": "package rule\n\nimport (\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\ntype abstractDefaultRule struct {\n\titems                   []RuleItem\n\tsourceAddressItems      []RuleItem\n\tsourcePortItems         []RuleItem\n\tdestinationAddressItems []RuleItem\n\tdestinationIPCIDRItems  []RuleItem\n\tdestinationPortItems    []RuleItem\n\tallItems                []RuleItem\n\truleSetItem             RuleItem\n\tinvert                  bool\n\taction                  adapter.RuleAction\n}\n\nfunc (r *abstractDefaultRule) Type() string {\n\treturn C.RuleTypeDefault\n}\n\nfunc (r *abstractDefaultRule) Start() error {\n\tfor _, item := range r.allItems {\n\t\tif starter, isStarter := item.(interface {\n\t\t\tStart() error\n\t\t}); isStarter {\n\t\t\terr := starter.Start()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *abstractDefaultRule) Close() error {\n\tfor _, item := range r.allItems {\n\t\terr := common.Close(item)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {\n\tif len(r.allItems) == 0 {\n\t\treturn true\n\t}\n\n\tif len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch {\n\t\tmetadata.DidMatch = true\n\t\tfor _, item := range r.sourceAddressItems {\n\t\t\tif item.Match(metadata) {\n\t\t\t\tmetadata.SourceAddressMatch = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch {\n\t\tmetadata.DidMatch = true\n\t\tfor _, item := range r.sourcePortItems {\n\t\t\tif item.Match(metadata) {\n\t\t\t\tmetadata.SourcePortMatch = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch {\n\t\tmetadata.DidMatch = true\n\t\tfor _, item := range r.destinationAddressItems {\n\t\t\tif item.Match(metadata) {\n\t\t\t\tmetadata.DestinationAddressMatch = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif !metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0 && !metadata.DestinationAddressMatch {\n\t\tmetadata.DidMatch = true\n\t\tfor _, item := range r.destinationIPCIDRItems {\n\t\t\tif item.Match(metadata) {\n\t\t\t\tmetadata.DestinationAddressMatch = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch {\n\t\tmetadata.DidMatch = true\n\t\tfor _, item := range r.destinationPortItems {\n\t\t\tif item.Match(metadata) {\n\t\t\t\tmetadata.DestinationPortMatch = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, item := range r.items {\n\t\tmetadata.DidMatch = true\n\t\tif !item.Match(metadata) {\n\t\t\treturn r.invert\n\t\t}\n\t}\n\n\tif len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch {\n\t\treturn r.invert\n\t}\n\n\tif len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch {\n\t\treturn r.invert\n\t}\n\n\tif ((!metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0) || len(r.destinationAddressItems) > 0) && !metadata.DestinationAddressMatch {\n\t\treturn r.invert\n\t}\n\n\tif len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch {\n\t\treturn r.invert\n\t}\n\n\tif !metadata.DidMatch {\n\t\treturn true\n\t}\n\n\treturn !r.invert\n}\n\nfunc (r *abstractDefaultRule) Action() adapter.RuleAction {\n\treturn r.action\n}\n\nfunc (r *abstractDefaultRule) String() string {\n\tif !r.invert {\n\t\treturn strings.Join(F.MapToString(r.allItems), \" \")\n\t} else {\n\t\treturn \"!(\" + strings.Join(F.MapToString(r.allItems), \" \") + \")\"\n\t}\n}\n\ntype abstractLogicalRule struct {\n\trules  []adapter.HeadlessRule\n\tmode   string\n\tinvert bool\n\taction adapter.RuleAction\n}\n\nfunc (r *abstractLogicalRule) Type() string {\n\treturn C.RuleTypeLogical\n}\n\nfunc (r *abstractLogicalRule) Start() error {\n\tfor _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (interface {\n\t\tStart() error\n\t}, bool,\n\t) {\n\t\trule, loaded := it.(interface {\n\t\t\tStart() error\n\t\t})\n\t\treturn rule, loaded\n\t}) {\n\t\terr := rule.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *abstractLogicalRule) Close() error {\n\tfor _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (io.Closer, bool) {\n\t\trule, loaded := it.(io.Closer)\n\t\treturn rule, loaded\n\t}) {\n\t\terr := rule.Close()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {\n\tif r.mode == C.LogicalTypeAnd {\n\t\treturn common.All(r.rules, func(it adapter.HeadlessRule) bool {\n\t\t\tmetadata.ResetRuleCache()\n\t\t\treturn it.Match(metadata)\n\t\t}) != r.invert\n\t} else {\n\t\treturn common.Any(r.rules, func(it adapter.HeadlessRule) bool {\n\t\t\tmetadata.ResetRuleCache()\n\t\t\treturn it.Match(metadata)\n\t\t}) != r.invert\n\t}\n}\n\nfunc (r *abstractLogicalRule) Action() adapter.RuleAction {\n\treturn r.action\n}\n\nfunc (r *abstractLogicalRule) String() string {\n\tvar op string\n\tswitch r.mode {\n\tcase C.LogicalTypeAnd:\n\t\top = \"&&\"\n\tcase C.LogicalTypeOr:\n\t\top = \"||\"\n\t}\n\tif !r.invert {\n\t\treturn strings.Join(F.MapToString(r.rules), \" \"+op+\" \")\n\t} else {\n\t\treturn \"!(\" + strings.Join(F.MapToString(r.rules), \" \"+op+\" \") + \")\"\n\t}\n}\n"
  },
  {
    "path": "route/rule/rule_abstract_test.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go4.org/netipx\"\n)\n\ntype fakeRuleSet struct {\n\tmatched bool\n}\n\nfunc (f *fakeRuleSet) Name() string {\n\treturn \"fake-rule-set\"\n}\n\nfunc (f *fakeRuleSet) StartContext(context.Context, *adapter.HTTPStartContext) error {\n\treturn nil\n}\n\nfunc (f *fakeRuleSet) PostStart() error {\n\treturn nil\n}\n\nfunc (f *fakeRuleSet) Metadata() adapter.RuleSetMetadata {\n\treturn adapter.RuleSetMetadata{}\n}\n\nfunc (f *fakeRuleSet) ExtractIPSet() []*netipx.IPSet {\n\treturn nil\n}\n\nfunc (f *fakeRuleSet) IncRef() {}\n\nfunc (f *fakeRuleSet) DecRef() {}\n\nfunc (f *fakeRuleSet) Cleanup() {}\n\nfunc (f *fakeRuleSet) RegisterCallback(adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {\n\treturn nil\n}\n\nfunc (f *fakeRuleSet) UnregisterCallback(*list.Element[adapter.RuleSetUpdateCallback]) {}\n\nfunc (f *fakeRuleSet) Close() error {\n\treturn nil\n}\n\nfunc (f *fakeRuleSet) Match(*adapter.InboundContext) bool {\n\treturn f.matched\n}\n\nfunc (f *fakeRuleSet) String() string {\n\treturn \"fake-rule-set\"\n}\n\ntype fakeRuleItem struct {\n\tmatched bool\n}\n\nfunc (f *fakeRuleItem) Match(*adapter.InboundContext) bool {\n\treturn f.matched\n}\n\nfunc (f *fakeRuleItem) String() string {\n\treturn \"fake-rule-item\"\n}\n\nfunc newRuleSetOnlyRule(ruleSetMatched bool, invert bool) *DefaultRule {\n\truleSetItem := &RuleSetItem{\n\t\tsetList: []adapter.RuleSet{&fakeRuleSet{matched: ruleSetMatched}},\n\t}\n\treturn &DefaultRule{\n\t\tabstractDefaultRule: abstractDefaultRule{\n\t\t\titems:    []RuleItem{ruleSetItem},\n\t\t\tallItems: []RuleItem{ruleSetItem},\n\t\t\tinvert:   invert,\n\t\t},\n\t}\n}\n\nfunc newSingleItemRule(matched bool) *DefaultRule {\n\titem := &fakeRuleItem{matched: matched}\n\treturn &DefaultRule{\n\t\tabstractDefaultRule: abstractDefaultRule{\n\t\t\titems:    []RuleItem{item},\n\t\t\tallItems: []RuleItem{item},\n\t\t},\n\t}\n}\n\nfunc TestAbstractDefaultRule_RuleSetOnly_InvertFalse(t *testing.T) {\n\tt.Parallel()\n\trequire.True(t, newRuleSetOnlyRule(true, false).Match(&adapter.InboundContext{}))\n\trequire.False(t, newRuleSetOnlyRule(false, false).Match(&adapter.InboundContext{}))\n}\n\nfunc TestAbstractDefaultRule_RuleSetOnly_InvertTrue(t *testing.T) {\n\tt.Parallel()\n\trequire.False(t, newRuleSetOnlyRule(true, true).Match(&adapter.InboundContext{}))\n\trequire.True(t, newRuleSetOnlyRule(false, true).Match(&adapter.InboundContext{}))\n}\n\nfunc TestAbstractLogicalRule_And_WithRuleSetInvert(t *testing.T) {\n\tt.Parallel()\n\ttestCases := []struct {\n\t\tname          string\n\t\taMatched      bool\n\t\truleSetBMatch bool\n\t\texpected      bool\n\t}{\n\t\t{\n\t\t\tname:          \"A true B true\",\n\t\t\taMatched:      true,\n\t\t\truleSetBMatch: true,\n\t\t\texpected:      false,\n\t\t},\n\t\t{\n\t\t\tname:          \"A true B false\",\n\t\t\taMatched:      true,\n\t\t\truleSetBMatch: false,\n\t\t\texpected:      true,\n\t\t},\n\t\t{\n\t\t\tname:          \"A false B true\",\n\t\t\taMatched:      false,\n\t\t\truleSetBMatch: true,\n\t\t\texpected:      false,\n\t\t},\n\t\t{\n\t\t\tname:          \"A false B false\",\n\t\t\taMatched:      false,\n\t\t\truleSetBMatch: false,\n\t\t\texpected:      false,\n\t\t},\n\t}\n\tfor _, testCase := range testCases {\n\t\ttestCase := testCase\n\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tlogicalRule := &abstractLogicalRule{\n\t\t\t\tmode: C.LogicalTypeAnd,\n\t\t\t\trules: []adapter.HeadlessRule{\n\t\t\t\t\tnewSingleItemRule(testCase.aMatched),\n\t\t\t\t\tnewRuleSetOnlyRule(testCase.ruleSetBMatch, true),\n\t\t\t\t},\n\t\t\t}\n\t\t\trequire.Equal(t, testCase.expected, logicalRule.Match(&adapter.InboundContext{}))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "route/rule/rule_action.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/sniff\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"github.com/miekg/dns\"\n)\n\nfunc NewRuleAction(ctx context.Context, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) {\n\tswitch action.Action {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase C.RuleActionTypeRoute:\n\t\treturn &RuleActionRoute{\n\t\t\tOutbound: action.RouteOptions.Outbound,\n\t\t\tRuleActionRouteOptions: RuleActionRouteOptions{\n\t\t\t\tOverrideAddress:           M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0),\n\t\t\t\tOverridePort:              action.RouteOptions.OverridePort,\n\t\t\t\tNetworkStrategy:           (*C.NetworkStrategy)(action.RouteOptions.NetworkStrategy),\n\t\t\t\tFallbackDelay:             time.Duration(action.RouteOptions.FallbackDelay),\n\t\t\t\tUDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping,\n\t\t\t\tUDPConnect:                action.RouteOptions.UDPConnect,\n\t\t\t\tTLSFragment:               action.RouteOptions.TLSFragment,\n\t\t\t\tTLSFragmentFallbackDelay:  time.Duration(action.RouteOptions.TLSFragmentFallbackDelay),\n\t\t\t\tTLSRecordFragment:         action.RouteOptions.TLSRecordFragment,\n\t\t\t},\n\t\t}, nil\n\tcase C.RuleActionTypeRouteOptions:\n\t\treturn &RuleActionRouteOptions{\n\t\t\tOverrideAddress:           M.ParseSocksaddrHostPort(action.RouteOptionsOptions.OverrideAddress, 0),\n\t\t\tOverridePort:              action.RouteOptionsOptions.OverridePort,\n\t\t\tNetworkStrategy:           (*C.NetworkStrategy)(action.RouteOptionsOptions.NetworkStrategy),\n\t\t\tFallbackDelay:             time.Duration(action.RouteOptionsOptions.FallbackDelay),\n\t\t\tUDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping,\n\t\t\tUDPConnect:                action.RouteOptionsOptions.UDPConnect,\n\t\t\tUDPTimeout:                time.Duration(action.RouteOptionsOptions.UDPTimeout),\n\t\t\tTLSFragment:               action.RouteOptionsOptions.TLSFragment,\n\t\t\tTLSFragmentFallbackDelay:  time.Duration(action.RouteOptionsOptions.TLSFragmentFallbackDelay),\n\t\t\tTLSRecordFragment:         action.RouteOptionsOptions.TLSRecordFragment,\n\t\t}, nil\n\tcase C.RuleActionTypeBypass:\n\t\treturn &RuleActionBypass{\n\t\t\tOutbound: action.BypassOptions.Outbound,\n\t\t\tRuleActionRouteOptions: RuleActionRouteOptions{\n\t\t\t\tOverrideAddress:           M.ParseSocksaddrHostPort(action.BypassOptions.OverrideAddress, 0),\n\t\t\t\tOverridePort:              action.BypassOptions.OverridePort,\n\t\t\t\tNetworkStrategy:           (*C.NetworkStrategy)(action.BypassOptions.NetworkStrategy),\n\t\t\t\tFallbackDelay:             time.Duration(action.BypassOptions.FallbackDelay),\n\t\t\t\tUDPDisableDomainUnmapping: action.BypassOptions.UDPDisableDomainUnmapping,\n\t\t\t\tUDPConnect:                action.BypassOptions.UDPConnect,\n\t\t\t\tTLSFragment:               action.BypassOptions.TLSFragment,\n\t\t\t\tTLSFragmentFallbackDelay:  time.Duration(action.BypassOptions.TLSFragmentFallbackDelay),\n\t\t\t\tTLSRecordFragment:         action.BypassOptions.TLSRecordFragment,\n\t\t\t},\n\t\t}, nil\n\tcase C.RuleActionTypeDirect:\n\t\tdirectDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar description string\n\t\tdescriptions := action.DirectOptions.Descriptions()\n\t\tswitch len(descriptions) {\n\t\tcase 0:\n\t\tcase 1:\n\t\t\tdescription = F.ToString(\"(\", descriptions[0], \")\")\n\t\tcase 2:\n\t\t\tdescription = F.ToString(\"(\", descriptions[0], \",\", descriptions[1], \")\")\n\t\tdefault:\n\t\t\tdescription = F.ToString(\"(\", descriptions[0], \",\", descriptions[1], \",...)\")\n\t\t}\n\t\treturn &RuleActionDirect{\n\t\t\tDialer:      directDialer,\n\t\t\tdescription: description,\n\t\t}, nil\n\tcase C.RuleActionTypeReject:\n\t\treturn &RuleActionReject{\n\t\t\tMethod: action.RejectOptions.Method,\n\t\t\tNoDrop: action.RejectOptions.NoDrop,\n\t\t\tlogger: logger,\n\t\t}, nil\n\tcase C.RuleActionTypeHijackDNS:\n\t\treturn &RuleActionHijackDNS{}, nil\n\tcase C.RuleActionTypeSniff:\n\t\tsniffAction := &RuleActionSniff{\n\t\t\tSnifferNames: action.SniffOptions.Sniffer,\n\t\t\tTimeout:      time.Duration(action.SniffOptions.Timeout),\n\t\t}\n\t\treturn sniffAction, sniffAction.build()\n\tcase C.RuleActionTypeResolve:\n\t\treturn &RuleActionResolve{\n\t\t\tServer:       action.ResolveOptions.Server,\n\t\t\tStrategy:     C.DomainStrategy(action.ResolveOptions.Strategy),\n\t\t\tDisableCache: action.ResolveOptions.DisableCache,\n\t\t\tRewriteTTL:   action.ResolveOptions.RewriteTTL,\n\t\t\tClientSubnet: action.ResolveOptions.ClientSubnet.Build(netip.Prefix{}),\n\t\t}, nil\n\tdefault:\n\t\tpanic(F.ToString(\"unknown rule action: \", action.Action))\n\t}\n}\n\nfunc NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) adapter.RuleAction {\n\tswitch action.Action {\n\tcase \"\":\n\t\treturn nil\n\tcase C.RuleActionTypeRoute:\n\t\treturn &RuleActionDNSRoute{\n\t\t\tServer: action.RouteOptions.Server,\n\t\t\tRuleActionDNSRouteOptions: RuleActionDNSRouteOptions{\n\t\t\t\tStrategy:     C.DomainStrategy(action.RouteOptions.Strategy),\n\t\t\t\tDisableCache: action.RouteOptions.DisableCache,\n\t\t\t\tRewriteTTL:   action.RouteOptions.RewriteTTL,\n\t\t\t\tClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),\n\t\t\t},\n\t\t}\n\tcase C.RuleActionTypeRouteOptions:\n\t\treturn &RuleActionDNSRouteOptions{\n\t\t\tStrategy:     C.DomainStrategy(action.RouteOptionsOptions.Strategy),\n\t\t\tDisableCache: action.RouteOptionsOptions.DisableCache,\n\t\t\tRewriteTTL:   action.RouteOptionsOptions.RewriteTTL,\n\t\t\tClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)),\n\t\t}\n\tcase C.RuleActionTypeReject:\n\t\treturn &RuleActionReject{\n\t\t\tMethod: action.RejectOptions.Method,\n\t\t\tNoDrop: action.RejectOptions.NoDrop,\n\t\t\tlogger: logger,\n\t\t}\n\tcase C.RuleActionTypePredefined:\n\t\treturn &RuleActionPredefined{\n\t\t\tRcode:  action.PredefinedOptions.Rcode.Build(),\n\t\t\tAnswer: common.Map(action.PredefinedOptions.Answer, option.DNSRecordOptions.Build),\n\t\t\tNs:     common.Map(action.PredefinedOptions.Ns, option.DNSRecordOptions.Build),\n\t\t\tExtra:  common.Map(action.PredefinedOptions.Extra, option.DNSRecordOptions.Build),\n\t\t}\n\tdefault:\n\t\tpanic(F.ToString(\"unknown rule action: \", action.Action))\n\t}\n}\n\ntype RuleActionRoute struct {\n\tOutbound string\n\tRuleActionRouteOptions\n}\n\nfunc (r *RuleActionRoute) Type() string {\n\treturn C.RuleActionTypeRoute\n}\n\nfunc (r *RuleActionRoute) String() string {\n\tvar descriptions []string\n\tdescriptions = append(descriptions, r.Outbound)\n\tdescriptions = append(descriptions, r.Descriptions()...)\n\treturn F.ToString(\"route(\", strings.Join(descriptions, \",\"), \")\")\n}\n\ntype RuleActionBypass struct {\n\tOutbound string\n\tRuleActionRouteOptions\n}\n\nfunc (r *RuleActionBypass) Type() string {\n\treturn C.RuleActionTypeBypass\n}\n\nfunc (r *RuleActionBypass) String() string {\n\tif r.Outbound == \"\" {\n\t\treturn \"bypass()\"\n\t}\n\tvar descriptions []string\n\tdescriptions = append(descriptions, r.Outbound)\n\tdescriptions = append(descriptions, r.Descriptions()...)\n\treturn F.ToString(\"bypass(\", strings.Join(descriptions, \",\"), \")\")\n}\n\ntype RuleActionRouteOptions struct {\n\tOverrideAddress           M.Socksaddr\n\tOverridePort              uint16\n\tNetworkStrategy           *C.NetworkStrategy\n\tNetworkType               []C.InterfaceType\n\tFallbackNetworkType       []C.InterfaceType\n\tFallbackDelay             time.Duration\n\tUDPDisableDomainUnmapping bool\n\tUDPConnect                bool\n\tUDPTimeout                time.Duration\n\tTLSFragment               bool\n\tTLSFragmentFallbackDelay  time.Duration\n\tTLSRecordFragment         bool\n}\n\nfunc (r *RuleActionRouteOptions) Type() string {\n\treturn C.RuleActionTypeRouteOptions\n}\n\nfunc (r *RuleActionRouteOptions) String() string {\n\treturn F.ToString(\"route-options(\", strings.Join(r.Descriptions(), \",\"), \")\")\n}\n\nfunc (r *RuleActionRouteOptions) Descriptions() []string {\n\tvar descriptions []string\n\tif r.OverrideAddress.IsValid() {\n\t\tdescriptions = append(descriptions, F.ToString(\"override-address=\", r.OverrideAddress.AddrString()))\n\t}\n\tif r.OverridePort > 0 {\n\t\tdescriptions = append(descriptions, F.ToString(\"override-port=\", r.OverridePort))\n\t}\n\tif r.NetworkStrategy != nil {\n\t\tdescriptions = append(descriptions, F.ToString(\"network-strategy=\", r.NetworkStrategy))\n\t}\n\tif r.NetworkType != nil {\n\t\tdescriptions = append(descriptions, F.ToString(\"network-type=\", strings.Join(common.Map(r.NetworkType, C.InterfaceType.String), \",\")))\n\t}\n\tif r.FallbackNetworkType != nil {\n\t\tdescriptions = append(descriptions, F.ToString(\"fallback-network-type=\"+strings.Join(common.Map(r.NetworkType, C.InterfaceType.String), \",\")))\n\t}\n\tif r.FallbackDelay > 0 {\n\t\tdescriptions = append(descriptions, F.ToString(\"fallback-delay=\", r.FallbackDelay.String()))\n\t}\n\tif r.UDPDisableDomainUnmapping {\n\t\tdescriptions = append(descriptions, \"udp-disable-domain-unmapping\")\n\t}\n\tif r.UDPConnect {\n\t\tdescriptions = append(descriptions, \"udp-connect\")\n\t}\n\tif r.UDPTimeout > 0 {\n\t\tdescriptions = append(descriptions, \"udp-timeout\")\n\t}\n\tif r.TLSFragment {\n\t\tdescriptions = append(descriptions, \"tls-fragment\")\n\t}\n\tif r.TLSFragmentFallbackDelay > 0 {\n\t\tdescriptions = append(descriptions, F.ToString(\"tls-fragment-fallback-delay=\", r.TLSFragmentFallbackDelay.String()))\n\t}\n\tif r.TLSRecordFragment {\n\t\tdescriptions = append(descriptions, \"tls-record-fragment\")\n\t}\n\treturn descriptions\n}\n\ntype RuleActionDNSRoute struct {\n\tServer string\n\tRuleActionDNSRouteOptions\n}\n\nfunc (r *RuleActionDNSRoute) Type() string {\n\treturn C.RuleActionTypeRoute\n}\n\nfunc (r *RuleActionDNSRoute) String() string {\n\tvar descriptions []string\n\tdescriptions = append(descriptions, r.Server)\n\tif r.DisableCache {\n\t\tdescriptions = append(descriptions, \"disable-cache\")\n\t}\n\tif r.RewriteTTL != nil {\n\t\tdescriptions = append(descriptions, F.ToString(\"rewrite-ttl=\", *r.RewriteTTL))\n\t}\n\tif r.ClientSubnet.IsValid() {\n\t\tdescriptions = append(descriptions, F.ToString(\"client-subnet=\", r.ClientSubnet))\n\t}\n\treturn F.ToString(\"route(\", strings.Join(descriptions, \",\"), \")\")\n}\n\ntype RuleActionDNSRouteOptions struct {\n\tStrategy     C.DomainStrategy\n\tDisableCache bool\n\tRewriteTTL   *uint32\n\tClientSubnet netip.Prefix\n}\n\nfunc (r *RuleActionDNSRouteOptions) Type() string {\n\treturn C.RuleActionTypeRouteOptions\n}\n\nfunc (r *RuleActionDNSRouteOptions) String() string {\n\tvar descriptions []string\n\tif r.DisableCache {\n\t\tdescriptions = append(descriptions, \"disable-cache\")\n\t}\n\tif r.RewriteTTL != nil {\n\t\tdescriptions = append(descriptions, F.ToString(\"rewrite-ttl=\", *r.RewriteTTL))\n\t}\n\tif r.ClientSubnet.IsValid() {\n\t\tdescriptions = append(descriptions, F.ToString(\"client-subnet=\", r.ClientSubnet))\n\t}\n\treturn F.ToString(\"route-options(\", strings.Join(descriptions, \",\"), \")\")\n}\n\ntype RuleActionDirect struct {\n\tDialer      N.Dialer\n\tdescription string\n}\n\nfunc (r *RuleActionDirect) Type() string {\n\treturn C.RuleActionTypeDirect\n}\n\nfunc (r *RuleActionDirect) String() string {\n\treturn \"direct\" + r.description\n}\n\ntype RejectedError struct {\n\tCause error\n}\n\nfunc (r *RejectedError) Error() string {\n\treturn \"rejected\"\n}\n\nfunc (r *RejectedError) Unwrap() error {\n\treturn r.Cause\n}\n\nfunc IsRejected(err error) bool {\n\tvar rejected *RejectedError\n\treturn errors.As(err, &rejected)\n}\n\ntype BypassedError struct {\n\tCause error\n}\n\nfunc (b *BypassedError) Error() string {\n\treturn \"bypassed\"\n}\n\nfunc (b *BypassedError) Unwrap() error {\n\treturn b.Cause\n}\n\nfunc IsBypassed(err error) bool {\n\tvar bypassed *BypassedError\n\treturn errors.As(err, &bypassed)\n}\n\ntype RuleActionReject struct {\n\tMethod      string\n\tNoDrop      bool\n\tlogger      logger.ContextLogger\n\tdropAccess  sync.Mutex\n\tdropCounter []time.Time\n}\n\nfunc (r *RuleActionReject) Type() string {\n\treturn C.RuleActionTypeReject\n}\n\nfunc (r *RuleActionReject) String() string {\n\tif r.Method == C.RuleActionRejectMethodDefault {\n\t\treturn \"reject\"\n\t}\n\treturn F.ToString(\"reject(\", r.Method, \")\")\n}\n\nfunc (r *RuleActionReject) Error(ctx context.Context) error {\n\tvar returnErr error\n\tswitch r.Method {\n\tcase C.RuleActionRejectMethodDefault:\n\t\treturnErr = &RejectedError{tun.ErrReset}\n\tcase C.RuleActionRejectMethodDrop:\n\t\treturn &RejectedError{tun.ErrDrop}\n\tcase C.RuleActionRejectMethodReply:\n\t\treturn nil\n\tdefault:\n\t\tpanic(F.ToString(\"unknown reject method: \", r.Method))\n\t}\n\tif r.NoDrop {\n\t\treturn returnErr\n\t}\n\tr.dropAccess.Lock()\n\tdefer r.dropAccess.Unlock()\n\ttimeNow := time.Now()\n\tr.dropCounter = common.Filter(r.dropCounter, func(t time.Time) bool {\n\t\treturn timeNow.Sub(t) <= 30*time.Second\n\t})\n\tr.dropCounter = append(r.dropCounter, timeNow)\n\tif len(r.dropCounter) > 50 {\n\t\tif ctx != nil {\n\t\t\tr.logger.DebugContext(ctx, \"dropped due to flooding\")\n\t\t}\n\t\treturn &RejectedError{tun.ErrDrop}\n\t}\n\treturn returnErr\n}\n\ntype RuleActionHijackDNS struct{}\n\nfunc (r *RuleActionHijackDNS) Type() string {\n\treturn C.RuleActionTypeHijackDNS\n}\n\nfunc (r *RuleActionHijackDNS) String() string {\n\treturn \"hijack-dns\"\n}\n\ntype RuleActionSniff struct {\n\tSnifferNames   []string\n\tStreamSniffers []sniff.StreamSniffer\n\tPacketSniffers []sniff.PacketSniffer\n\tTimeout        time.Duration\n\t// Deprecated\n\tOverrideDestination bool\n}\n\nfunc (r *RuleActionSniff) Type() string {\n\treturn C.RuleActionTypeSniff\n}\n\nfunc (r *RuleActionSniff) build() error {\n\tfor _, name := range r.SnifferNames {\n\t\tswitch name {\n\t\tcase C.ProtocolTLS:\n\t\t\tr.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello)\n\t\tcase C.ProtocolHTTP:\n\t\t\tr.StreamSniffers = append(r.StreamSniffers, sniff.HTTPHost)\n\t\tcase C.ProtocolQUIC:\n\t\t\tr.PacketSniffers = append(r.PacketSniffers, sniff.QUICClientHello)\n\t\tcase C.ProtocolDNS:\n\t\t\tr.StreamSniffers = append(r.StreamSniffers, sniff.StreamDomainNameQuery)\n\t\t\tr.PacketSniffers = append(r.PacketSniffers, sniff.DomainNameQuery)\n\t\tcase C.ProtocolSTUN:\n\t\t\tr.PacketSniffers = append(r.PacketSniffers, sniff.STUNMessage)\n\t\tcase C.ProtocolBitTorrent:\n\t\t\tr.StreamSniffers = append(r.StreamSniffers, sniff.BitTorrent)\n\t\t\tr.PacketSniffers = append(r.PacketSniffers, sniff.UTP)\n\t\t\tr.PacketSniffers = append(r.PacketSniffers, sniff.UDPTracker)\n\t\tcase C.ProtocolDTLS:\n\t\t\tr.PacketSniffers = append(r.PacketSniffers, sniff.DTLSRecord)\n\t\tcase C.ProtocolSSH:\n\t\t\tr.StreamSniffers = append(r.StreamSniffers, sniff.SSH)\n\t\tcase C.ProtocolRDP:\n\t\t\tr.StreamSniffers = append(r.StreamSniffers, sniff.RDP)\n\t\tcase C.ProtocolNTP:\n\t\t\tr.PacketSniffers = append(r.PacketSniffers, sniff.NTP)\n\t\tdefault:\n\t\t\treturn E.New(\"unknown sniffer: \", name)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *RuleActionSniff) String() string {\n\tif len(r.SnifferNames) == 0 && r.Timeout == 0 {\n\t\treturn \"sniff\"\n\t} else if len(r.SnifferNames) > 0 && r.Timeout == 0 {\n\t\treturn F.ToString(\"sniff(\", strings.Join(r.SnifferNames, \",\"), \")\")\n\t} else if len(r.SnifferNames) == 0 && r.Timeout > 0 {\n\t\treturn F.ToString(\"sniff(\", r.Timeout.String(), \")\")\n\t} else {\n\t\treturn F.ToString(\"sniff(\", strings.Join(r.SnifferNames, \",\"), \",\", r.Timeout.String(), \")\")\n\t}\n}\n\ntype RuleActionResolve struct {\n\tServer       string\n\tStrategy     C.DomainStrategy\n\tDisableCache bool\n\tRewriteTTL   *uint32\n\tClientSubnet netip.Prefix\n}\n\nfunc (r *RuleActionResolve) Type() string {\n\treturn C.RuleActionTypeResolve\n}\n\nfunc (r *RuleActionResolve) String() string {\n\tvar options []string\n\tif r.Server != \"\" {\n\t\toptions = append(options, r.Server)\n\t}\n\tif r.Strategy != C.DomainStrategyAsIS {\n\t\toptions = append(options, F.ToString(option.DomainStrategy(r.Strategy)))\n\t}\n\tif r.DisableCache {\n\t\toptions = append(options, \"disable_cache\")\n\t}\n\tif r.RewriteTTL != nil {\n\t\toptions = append(options, F.ToString(\"rewrite_ttl=\", *r.RewriteTTL))\n\t}\n\tif r.ClientSubnet.IsValid() {\n\t\toptions = append(options, F.ToString(\"client_subnet=\", r.ClientSubnet))\n\t}\n\tif len(options) == 0 {\n\t\treturn \"resolve\"\n\t} else {\n\t\treturn F.ToString(\"resolve(\", strings.Join(options, \",\"), \")\")\n\t}\n}\n\ntype RuleActionPredefined struct {\n\tRcode  int\n\tAnswer []dns.RR\n\tNs     []dns.RR\n\tExtra  []dns.RR\n}\n\nfunc (r *RuleActionPredefined) Type() string {\n\treturn C.RuleActionTypePredefined\n}\n\nfunc (r *RuleActionPredefined) String() string {\n\tvar options []string\n\toptions = append(options, dns.RcodeToString[r.Rcode])\n\toptions = append(options, common.Map(r.Answer, dns.RR.String)...)\n\toptions = append(options, common.Map(r.Ns, dns.RR.String)...)\n\toptions = append(options, common.Map(r.Extra, dns.RR.String)...)\n\treturn F.ToString(\"predefined(\", strings.Join(options, \",\"), \")\")\n}\n\nfunc (r *RuleActionPredefined) Response(request *dns.Msg) *dns.Msg {\n\treturn &dns.Msg{\n\t\tMsgHdr: dns.MsgHdr{\n\t\t\tId:                 request.Id,\n\t\t\tResponse:           true,\n\t\t\tAuthoritative:      true,\n\t\t\tRecursionDesired:   true,\n\t\t\tRecursionAvailable: true,\n\t\t\tRcode:              r.Rcode,\n\t\t},\n\t\tQuestion: request.Question,\n\t\tAnswer:   rewriteRecords(r.Answer, request.Question[0]),\n\t\tNs:       rewriteRecords(r.Ns, request.Question[0]),\n\t\tExtra:    rewriteRecords(r.Extra, request.Question[0]),\n\t}\n}\n\nfunc rewriteRecords(records []dns.RR, question dns.Question) []dns.RR {\n\treturn common.Map(records, func(it dns.RR) dns.RR {\n\t\tif strings.HasPrefix(it.Header().Name, \"*\") {\n\t\t\tif strings.HasSuffix(question.Name, it.Header().Name[1:]) {\n\t\t\t\tit = dns.Copy(it)\n\t\t\t\tit.Header().Name = question.Name\n\t\t\t}\n\t\t}\n\t\treturn it\n\t})\n}\n"
  },
  {
    "path": "route/rule/rule_default.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nfunc NewRule(ctx context.Context, logger log.ContextLogger, options option.Rule, checkOutbound bool) (adapter.Rule, error) {\n\tswitch options.Type {\n\tcase \"\", C.RuleTypeDefault:\n\t\tif !options.DefaultOptions.IsValid() {\n\t\t\treturn nil, E.New(\"missing conditions\")\n\t\t}\n\t\tswitch options.DefaultOptions.Action {\n\t\tcase \"\", C.RuleActionTypeRoute:\n\t\t\tif options.DefaultOptions.RouteOptions.Outbound == \"\" && checkOutbound {\n\t\t\t\treturn nil, E.New(\"missing outbound field\")\n\t\t\t}\n\t\t}\n\t\treturn NewDefaultRule(ctx, logger, options.DefaultOptions)\n\tcase C.RuleTypeLogical:\n\t\tif !options.LogicalOptions.IsValid() {\n\t\t\treturn nil, E.New(\"missing conditions\")\n\t\t}\n\t\tswitch options.LogicalOptions.Action {\n\t\tcase \"\", C.RuleActionTypeRoute:\n\t\t\tif options.LogicalOptions.RouteOptions.Outbound == \"\" && checkOutbound {\n\t\t\t\treturn nil, E.New(\"missing outbound field\")\n\t\t\t}\n\t\t}\n\t\treturn NewLogicalRule(ctx, logger, options.LogicalOptions)\n\tdefault:\n\t\treturn nil, E.New(\"unknown rule type: \", options.Type)\n\t}\n}\n\nvar _ adapter.Rule = (*DefaultRule)(nil)\n\ntype DefaultRule struct {\n\tabstractDefaultRule\n}\n\ntype RuleItem interface {\n\tMatch(metadata *adapter.InboundContext) bool\n\tString() string\n}\n\nfunc NewDefaultRule(ctx context.Context, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) {\n\taction, err := NewRuleAction(ctx, logger, options.RuleAction)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"action\")\n\t}\n\trule := &DefaultRule{\n\t\tabstractDefaultRule{\n\t\t\tinvert: options.Invert,\n\t\t\taction: action,\n\t\t},\n\t}\n\trouter := service.FromContext[adapter.Router](ctx)\n\tnetworkManager := service.FromContext[adapter.NetworkManager](ctx)\n\tif len(options.Inbound) > 0 {\n\t\titem := NewInboundRule(options.Inbound)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.IPVersion > 0 {\n\t\tswitch options.IPVersion {\n\t\tcase 4, 6:\n\t\t\titem := NewIPVersionItem(options.IPVersion == 6)\n\t\t\trule.items = append(rule.items, item)\n\t\t\trule.allItems = append(rule.allItems, item)\n\t\tdefault:\n\t\t\treturn nil, E.New(\"invalid ip version: \", options.IPVersion)\n\t\t}\n\t}\n\tif len(options.Network) > 0 {\n\t\titem := NewNetworkItem(options.Network)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.AuthUser) > 0 {\n\t\titem := NewAuthUserItem(options.AuthUser)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Protocol) > 0 {\n\t\titem := NewProtocolItem(options.Protocol)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Client) > 0 {\n\t\titem := NewClientItem(options.Client)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {\n\t\titem, err := NewDomainItem(options.Domain, options.DomainSuffix)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.DomainKeyword) > 0 {\n\t\titem := NewDomainKeywordItem(options.DomainKeyword)\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.DomainRegex) > 0 {\n\t\titem, err := NewDomainRegexItem(options.DomainRegex)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Geosite) > 0 {\n\t\treturn nil, E.New(\"geosite database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0\")\n\t}\n\tif len(options.SourceGeoIP) > 0 {\n\t\treturn nil, E.New(\"geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0\")\n\t}\n\tif len(options.GeoIP) > 0 {\n\t\treturn nil, E.New(\"geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0\")\n\t}\n\tif len(options.SourceIPCIDR) > 0 {\n\t\titem, err := NewIPCIDRItem(true, options.SourceIPCIDR)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"source_ip_cidr\")\n\t\t}\n\t\trule.sourceAddressItems = append(rule.sourceAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.SourceIPIsPrivate {\n\t\titem := NewIPIsPrivateItem(true)\n\t\trule.sourceAddressItems = append(rule.sourceAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.IPCIDR) > 0 {\n\t\titem, err := NewIPCIDRItem(false, options.IPCIDR)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"ipcidr\")\n\t\t}\n\t\trule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.IPIsPrivate {\n\t\titem := NewIPIsPrivateItem(false)\n\t\trule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.SourcePort) > 0 {\n\t\titem := NewPortItem(true, options.SourcePort)\n\t\trule.sourcePortItems = append(rule.sourcePortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.SourcePortRange) > 0 {\n\t\titem, err := NewPortRangeItem(true, options.SourcePortRange)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"source_port_range\")\n\t\t}\n\t\trule.sourcePortItems = append(rule.sourcePortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Port) > 0 {\n\t\titem := NewPortItem(false, options.Port)\n\t\trule.destinationPortItems = append(rule.destinationPortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.PortRange) > 0 {\n\t\titem, err := NewPortRangeItem(false, options.PortRange)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"port_range\")\n\t\t}\n\t\trule.destinationPortItems = append(rule.destinationPortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.ProcessName) > 0 {\n\t\titem := NewProcessItem(options.ProcessName)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.ProcessPath) > 0 {\n\t\titem := NewProcessPathItem(options.ProcessPath)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.ProcessPathRegex) > 0 {\n\t\titem, err := NewProcessPathRegexItem(options.ProcessPathRegex)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"process_path_regex\")\n\t\t}\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.PackageName) > 0 {\n\t\titem := NewPackageNameItem(options.PackageName)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.User) > 0 {\n\t\titem := NewUserItem(options.User)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.UserID) > 0 {\n\t\titem := NewUserIDItem(options.UserID)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.ClashMode != \"\" {\n\t\titem := NewClashModeItem(ctx, options.ClashMode)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.NetworkType) > 0 {\n\t\titem := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build))\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.NetworkIsExpensive {\n\t\titem := NewNetworkIsExpensiveItem(networkManager)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.NetworkIsConstrained {\n\t\titem := NewNetworkIsConstrainedItem(networkManager)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.WIFISSID) > 0 {\n\t\titem := NewWIFISSIDItem(networkManager, options.WIFISSID)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.WIFIBSSID) > 0 {\n\t\titem := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 {\n\t\titem := NewInterfaceAddressItem(networkManager, options.InterfaceAddress)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {\n\t\titem := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.DefaultInterfaceAddress) > 0 {\n\t\titem := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.SourceMACAddress) > 0 {\n\t\titem := NewSourceMACAddressItem(options.SourceMACAddress)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.SourceHostname) > 0 {\n\t\titem := NewSourceHostnameItem(options.SourceHostname)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.PreferredBy) > 0 {\n\t\titem := NewPreferredByItem(ctx, options.PreferredBy)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.RuleSet) > 0 {\n\t\t//nolint:staticcheck\n\t\tif options.Deprecated_RulesetIPCIDRMatchSource {\n\t\t\treturn nil, E.New(\"rule_set_ipcidr_match_source is deprecated in sing-box 1.10.0 and removed in sing-box 1.11.0\")\n\t\t}\n\t\tvar matchSource bool\n\t\tif options.RuleSetIPCIDRMatchSource {\n\t\t\tmatchSource = true\n\t\t}\n\t\titem := NewRuleSetItem(router, options.RuleSet, matchSource, false)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\treturn rule, nil\n}\n\nvar _ adapter.Rule = (*LogicalRule)(nil)\n\ntype LogicalRule struct {\n\tabstractLogicalRule\n}\n\nfunc NewLogicalRule(ctx context.Context, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {\n\taction, err := NewRuleAction(ctx, logger, options.RuleAction)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"action\")\n\t}\n\trule := &LogicalRule{\n\t\tabstractLogicalRule{\n\t\t\trules:  make([]adapter.HeadlessRule, len(options.Rules)),\n\t\t\tinvert: options.Invert,\n\t\t\taction: action,\n\t\t},\n\t}\n\tswitch options.Mode {\n\tcase C.LogicalTypeAnd:\n\t\trule.mode = C.LogicalTypeAnd\n\tcase C.LogicalTypeOr:\n\t\trule.mode = C.LogicalTypeOr\n\tdefault:\n\t\treturn nil, E.New(\"unknown logical mode: \", options.Mode)\n\t}\n\tfor i, subOptions := range options.Rules {\n\t\tsubRule, err := NewRule(ctx, logger, subOptions, false)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"sub rule[\", i, \"]\")\n\t\t}\n\t\trule.rules[i] = subRule\n\t}\n\treturn rule, nil\n}\n"
  },
  {
    "path": "route/rule/rule_default_interface_address.go",
    "content": "package rule\n\nimport (\n\t\"net/netip\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nvar _ RuleItem = (*DefaultInterfaceAddressItem)(nil)\n\ntype DefaultInterfaceAddressItem struct {\n\tinterfaceMonitor   tun.DefaultInterfaceMonitor\n\tinterfaceAddresses []netip.Prefix\n}\n\nfunc NewDefaultInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses badoption.Listable[*badoption.Prefixable]) *DefaultInterfaceAddressItem {\n\titem := &DefaultInterfaceAddressItem{\n\t\tinterfaceMonitor:   networkManager.InterfaceMonitor(),\n\t\tinterfaceAddresses: make([]netip.Prefix, 0, len(interfaceAddresses)),\n\t}\n\tfor _, prefixable := range interfaceAddresses {\n\t\titem.interfaceAddresses = append(item.interfaceAddresses, prefixable.Build(netip.Prefix{}))\n\t}\n\treturn item\n}\n\nfunc (r *DefaultInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {\n\tdefaultInterface := r.interfaceMonitor.DefaultInterface()\n\tif defaultInterface == nil {\n\t\treturn false\n\t}\n\tfor _, address := range r.interfaceAddresses {\n\t\tif common.All(defaultInterface.Addresses, func(it netip.Prefix) bool {\n\t\t\treturn !address.Overlaps(it)\n\t\t}) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *DefaultInterfaceAddressItem) String() string {\n\taddressLen := len(r.interfaceAddresses)\n\tswitch {\n\tcase addressLen == 1:\n\t\treturn \"default_interface_address=\" + r.interfaceAddresses[0].String()\n\tcase addressLen > 3:\n\t\treturn \"default_interface_address=[\" + strings.Join(common.Map(r.interfaceAddresses[:3], netip.Prefix.String), \" \") + \"...]\"\n\tdefault:\n\t\treturn \"default_interface_address=[\" + strings.Join(common.Map(r.interfaceAddresses, netip.Prefix.String), \" \") + \"]\"\n\t}\n}\n"
  },
  {
    "path": "route/rule/rule_dns.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nfunc NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DNSRule, checkServer bool) (adapter.DNSRule, error) {\n\tswitch options.Type {\n\tcase \"\", C.RuleTypeDefault:\n\t\tif !options.DefaultOptions.IsValid() {\n\t\t\treturn nil, E.New(\"missing conditions\")\n\t\t}\n\t\tswitch options.DefaultOptions.Action {\n\t\tcase \"\", C.RuleActionTypeRoute:\n\t\t\tif options.DefaultOptions.RouteOptions.Server == \"\" && checkServer {\n\t\t\t\treturn nil, E.New(\"missing server field\")\n\t\t\t}\n\t\t}\n\t\treturn NewDefaultDNSRule(ctx, logger, options.DefaultOptions)\n\tcase C.RuleTypeLogical:\n\t\tif !options.LogicalOptions.IsValid() {\n\t\t\treturn nil, E.New(\"missing conditions\")\n\t\t}\n\t\tswitch options.LogicalOptions.Action {\n\t\tcase \"\", C.RuleActionTypeRoute:\n\t\t\tif options.LogicalOptions.RouteOptions.Server == \"\" && checkServer {\n\t\t\t\treturn nil, E.New(\"missing server field\")\n\t\t\t}\n\t\t}\n\t\treturn NewLogicalDNSRule(ctx, logger, options.LogicalOptions)\n\tdefault:\n\t\treturn nil, E.New(\"unknown rule type: \", options.Type)\n\t}\n}\n\nvar _ adapter.DNSRule = (*DefaultDNSRule)(nil)\n\ntype DefaultDNSRule struct {\n\tabstractDefaultRule\n}\n\nfunc NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {\n\trule := &DefaultDNSRule{\n\t\tabstractDefaultRule: abstractDefaultRule{\n\t\t\tinvert: options.Invert,\n\t\t\taction: NewDNSRuleAction(logger, options.DNSRuleAction),\n\t\t},\n\t}\n\tif len(options.Inbound) > 0 {\n\t\titem := NewInboundRule(options.Inbound)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\trouter := service.FromContext[adapter.Router](ctx)\n\tnetworkManager := service.FromContext[adapter.NetworkManager](ctx)\n\tif options.IPVersion > 0 {\n\t\tswitch options.IPVersion {\n\t\tcase 4, 6:\n\t\t\titem := NewIPVersionItem(options.IPVersion == 6)\n\t\t\trule.items = append(rule.items, item)\n\t\t\trule.allItems = append(rule.allItems, item)\n\t\tdefault:\n\t\t\treturn nil, E.New(\"invalid ip version: \", options.IPVersion)\n\t\t}\n\t}\n\tif len(options.QueryType) > 0 {\n\t\titem := NewQueryTypeItem(options.QueryType)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Network) > 0 {\n\t\titem := NewNetworkItem(options.Network)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.AuthUser) > 0 {\n\t\titem := NewAuthUserItem(options.AuthUser)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Protocol) > 0 {\n\t\titem := NewProtocolItem(options.Protocol)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {\n\t\titem, err := NewDomainItem(options.Domain, options.DomainSuffix)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.DomainKeyword) > 0 {\n\t\titem := NewDomainKeywordItem(options.DomainKeyword)\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.DomainRegex) > 0 {\n\t\titem, err := NewDomainRegexItem(options.DomainRegex)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"domain_regex\")\n\t\t}\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Geosite) > 0 {\n\t\treturn nil, E.New(\"geosite database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0\")\n\t}\n\tif len(options.SourceGeoIP) > 0 {\n\t\treturn nil, E.New(\"geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0\")\n\t}\n\tif len(options.GeoIP) > 0 {\n\t\treturn nil, E.New(\"geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0\")\n\t}\n\tif len(options.SourceIPCIDR) > 0 {\n\t\titem, err := NewIPCIDRItem(true, options.SourceIPCIDR)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"source_ip_cidr\")\n\t\t}\n\t\trule.sourceAddressItems = append(rule.sourceAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.IPCIDR) > 0 {\n\t\titem, err := NewIPCIDRItem(false, options.IPCIDR)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"ip_cidr\")\n\t\t}\n\t\trule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.SourceIPIsPrivate {\n\t\titem := NewIPIsPrivateItem(true)\n\t\trule.sourceAddressItems = append(rule.sourceAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.IPIsPrivate {\n\t\titem := NewIPIsPrivateItem(false)\n\t\trule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.IPAcceptAny {\n\t\titem := NewIPAcceptAnyItem()\n\t\trule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.SourcePort) > 0 {\n\t\titem := NewPortItem(true, options.SourcePort)\n\t\trule.sourcePortItems = append(rule.sourcePortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.SourcePortRange) > 0 {\n\t\titem, err := NewPortRangeItem(true, options.SourcePortRange)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"source_port_range\")\n\t\t}\n\t\trule.sourcePortItems = append(rule.sourcePortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Port) > 0 {\n\t\titem := NewPortItem(false, options.Port)\n\t\trule.destinationPortItems = append(rule.destinationPortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.PortRange) > 0 {\n\t\titem, err := NewPortRangeItem(false, options.PortRange)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"port_range\")\n\t\t}\n\t\trule.destinationPortItems = append(rule.destinationPortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.ProcessName) > 0 {\n\t\titem := NewProcessItem(options.ProcessName)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.ProcessPath) > 0 {\n\t\titem := NewProcessPathItem(options.ProcessPath)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.ProcessPathRegex) > 0 {\n\t\titem, err := NewProcessPathRegexItem(options.ProcessPathRegex)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"process_path_regex\")\n\t\t}\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.PackageName) > 0 {\n\t\titem := NewPackageNameItem(options.PackageName)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.User) > 0 {\n\t\titem := NewUserItem(options.User)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.UserID) > 0 {\n\t\titem := NewUserIDItem(options.UserID)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Outbound) > 0 {\n\t\titem := NewOutboundRule(ctx, options.Outbound)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.ClashMode != \"\" {\n\t\titem := NewClashModeItem(ctx, options.ClashMode)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.NetworkType) > 0 {\n\t\titem := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build))\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.NetworkIsExpensive {\n\t\titem := NewNetworkIsExpensiveItem(networkManager)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.NetworkIsConstrained {\n\t\titem := NewNetworkIsConstrainedItem(networkManager)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.WIFISSID) > 0 {\n\t\titem := NewWIFISSIDItem(networkManager, options.WIFISSID)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.WIFIBSSID) > 0 {\n\t\titem := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 {\n\t\titem := NewInterfaceAddressItem(networkManager, options.InterfaceAddress)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {\n\t\titem := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.DefaultInterfaceAddress) > 0 {\n\t\titem := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.SourceMACAddress) > 0 {\n\t\titem := NewSourceMACAddressItem(options.SourceMACAddress)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.SourceHostname) > 0 {\n\t\titem := NewSourceHostnameItem(options.SourceHostname)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.RuleSet) > 0 {\n\t\t//nolint:staticcheck\n\t\tif options.Deprecated_RulesetIPCIDRMatchSource {\n\t\t\treturn nil, E.New(\"rule_set_ipcidr_match_source is deprecated in sing-box 1.10.0 and removed in sing-box 1.11.0\")\n\t\t}\n\t\tvar matchSource bool\n\t\tif options.RuleSetIPCIDRMatchSource {\n\t\t\tmatchSource = true\n\t\t}\n\t\titem := NewRuleSetItem(router, options.RuleSet, matchSource, options.RuleSetIPCIDRAcceptEmpty)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\treturn rule, nil\n}\n\nfunc (r *DefaultDNSRule) Action() adapter.RuleAction {\n\treturn r.action\n}\n\nfunc (r *DefaultDNSRule) WithAddressLimit() bool {\n\tif len(r.destinationIPCIDRItems) > 0 {\n\t\treturn true\n\t}\n\tfor _, rawRule := range r.items {\n\t\truleSet, isRuleSet := rawRule.(*RuleSetItem)\n\t\tif !isRuleSet {\n\t\t\tcontinue\n\t\t}\n\t\tif ruleSet.ContainsDestinationIPCIDRRule() {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {\n\tmetadata.IgnoreDestinationIPCIDRMatch = true\n\tdefer func() {\n\t\tmetadata.IgnoreDestinationIPCIDRMatch = false\n\t}()\n\treturn r.abstractDefaultRule.Match(metadata)\n}\n\nfunc (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {\n\treturn r.abstractDefaultRule.Match(metadata)\n}\n\nvar _ adapter.DNSRule = (*LogicalDNSRule)(nil)\n\ntype LogicalDNSRule struct {\n\tabstractLogicalRule\n}\n\nfunc NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {\n\tr := &LogicalDNSRule{\n\t\tabstractLogicalRule: abstractLogicalRule{\n\t\t\trules:  make([]adapter.HeadlessRule, len(options.Rules)),\n\t\t\tinvert: options.Invert,\n\t\t\taction: NewDNSRuleAction(logger, options.DNSRuleAction),\n\t\t},\n\t}\n\tswitch options.Mode {\n\tcase C.LogicalTypeAnd:\n\t\tr.mode = C.LogicalTypeAnd\n\tcase C.LogicalTypeOr:\n\t\tr.mode = C.LogicalTypeOr\n\tdefault:\n\t\treturn nil, E.New(\"unknown logical mode: \", options.Mode)\n\t}\n\tfor i, subRule := range options.Rules {\n\t\trule, err := NewDNSRule(ctx, logger, subRule, false)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"sub rule[\", i, \"]\")\n\t\t}\n\t\tr.rules[i] = rule\n\t}\n\treturn r, nil\n}\n\nfunc (r *LogicalDNSRule) Action() adapter.RuleAction {\n\treturn r.action\n}\n\nfunc (r *LogicalDNSRule) WithAddressLimit() bool {\n\tfor _, rawRule := range r.rules {\n\t\tswitch rule := rawRule.(type) {\n\t\tcase *DefaultDNSRule:\n\t\t\tif rule.WithAddressLimit() {\n\t\t\t\treturn true\n\t\t\t}\n\t\tcase *LogicalDNSRule:\n\t\t\tif rule.WithAddressLimit() {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {\n\tif r.mode == C.LogicalTypeAnd {\n\t\treturn common.All(r.rules, func(it adapter.HeadlessRule) bool {\n\t\t\tmetadata.ResetRuleCache()\n\t\t\treturn it.(adapter.DNSRule).Match(metadata)\n\t\t}) != r.invert\n\t} else {\n\t\treturn common.Any(r.rules, func(it adapter.HeadlessRule) bool {\n\t\t\tmetadata.ResetRuleCache()\n\t\t\treturn it.(adapter.DNSRule).Match(metadata)\n\t\t}) != r.invert\n\t}\n}\n\nfunc (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {\n\tif r.mode == C.LogicalTypeAnd {\n\t\treturn common.All(r.rules, func(it adapter.HeadlessRule) bool {\n\t\t\tmetadata.ResetRuleCache()\n\t\t\treturn it.(adapter.DNSRule).MatchAddressLimit(metadata)\n\t\t}) != r.invert\n\t} else {\n\t\treturn common.Any(r.rules, func(it adapter.HeadlessRule) bool {\n\t\t\tmetadata.ResetRuleCache()\n\t\t\treturn it.(adapter.DNSRule).MatchAddressLimit(metadata)\n\t\t}) != r.invert\n\t}\n}\n"
  },
  {
    "path": "route/rule/rule_headless.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nfunc NewHeadlessRule(ctx context.Context, options option.HeadlessRule) (adapter.HeadlessRule, error) {\n\tswitch options.Type {\n\tcase \"\", C.RuleTypeDefault:\n\t\tif !options.DefaultOptions.IsValid() {\n\t\t\treturn nil, E.New(\"missing conditions\")\n\t\t}\n\t\treturn NewDefaultHeadlessRule(ctx, options.DefaultOptions)\n\tcase C.RuleTypeLogical:\n\t\tif !options.LogicalOptions.IsValid() {\n\t\t\treturn nil, E.New(\"missing conditions\")\n\t\t}\n\t\treturn NewLogicalHeadlessRule(ctx, options.LogicalOptions)\n\tdefault:\n\t\treturn nil, E.New(\"unknown rule type: \", options.Type)\n\t}\n}\n\nvar _ adapter.HeadlessRule = (*DefaultHeadlessRule)(nil)\n\ntype DefaultHeadlessRule struct {\n\tabstractDefaultRule\n}\n\nfunc NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessRule) (*DefaultHeadlessRule, error) {\n\tnetworkManager := service.FromContext[adapter.NetworkManager](ctx)\n\trule := &DefaultHeadlessRule{\n\t\tabstractDefaultRule{\n\t\t\tinvert: options.Invert,\n\t\t},\n\t}\n\tif len(options.Network) > 0 {\n\t\titem := NewNetworkItem(options.Network)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {\n\t\titem, err := NewDomainItem(options.Domain, options.DomainSuffix)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t} else if options.DomainMatcher != nil {\n\t\titem := NewRawDomainItem(options.DomainMatcher)\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.DomainKeyword) > 0 {\n\t\titem := NewDomainKeywordItem(options.DomainKeyword)\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.DomainRegex) > 0 {\n\t\titem, err := NewDomainRegexItem(options.DomainRegex)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"domain_regex\")\n\t\t}\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.SourceIPCIDR) > 0 {\n\t\titem, err := NewIPCIDRItem(true, options.SourceIPCIDR)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"source_ip_cidr\")\n\t\t}\n\t\trule.sourceAddressItems = append(rule.sourceAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t} else if options.SourceIPSet != nil {\n\t\titem := NewRawIPCIDRItem(true, options.SourceIPSet)\n\t\trule.sourceAddressItems = append(rule.sourceAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.IPCIDR) > 0 {\n\t\titem, err := NewIPCIDRItem(false, options.IPCIDR)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"ipcidr\")\n\t\t}\n\t\trule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t} else if options.IPSet != nil {\n\t\titem := NewRawIPCIDRItem(false, options.IPSet)\n\t\trule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.SourcePort) > 0 {\n\t\titem := NewPortItem(true, options.SourcePort)\n\t\trule.sourcePortItems = append(rule.sourcePortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.SourcePortRange) > 0 {\n\t\titem, err := NewPortRangeItem(true, options.SourcePortRange)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"source_port_range\")\n\t\t}\n\t\trule.sourcePortItems = append(rule.sourcePortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.Port) > 0 {\n\t\titem := NewPortItem(false, options.Port)\n\t\trule.destinationPortItems = append(rule.destinationPortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.PortRange) > 0 {\n\t\titem, err := NewPortRangeItem(false, options.PortRange)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"port_range\")\n\t\t}\n\t\trule.destinationPortItems = append(rule.destinationPortItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.ProcessName) > 0 {\n\t\titem := NewProcessItem(options.ProcessName)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.ProcessPath) > 0 {\n\t\titem := NewProcessPathItem(options.ProcessPath)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.ProcessPathRegex) > 0 {\n\t\titem, err := NewProcessPathRegexItem(options.ProcessPathRegex)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"process_path_regex\")\n\t\t}\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif len(options.PackageName) > 0 {\n\t\titem := NewPackageNameItem(options.PackageName)\n\t\trule.items = append(rule.items, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\tif networkManager != nil {\n\t\tif len(options.NetworkType) > 0 {\n\t\t\titem := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build))\n\t\t\trule.items = append(rule.items, item)\n\t\t\trule.allItems = append(rule.allItems, item)\n\t\t}\n\t\tif options.NetworkIsExpensive {\n\t\t\titem := NewNetworkIsExpensiveItem(networkManager)\n\t\t\trule.items = append(rule.items, item)\n\t\t\trule.allItems = append(rule.allItems, item)\n\t\t}\n\t\tif options.NetworkIsConstrained {\n\t\t\titem := NewNetworkIsConstrainedItem(networkManager)\n\t\t\trule.items = append(rule.items, item)\n\t\t\trule.allItems = append(rule.allItems, item)\n\t\t}\n\t\tif len(options.WIFISSID) > 0 {\n\t\t\titem := NewWIFISSIDItem(networkManager, options.WIFISSID)\n\t\t\trule.items = append(rule.items, item)\n\t\t\trule.allItems = append(rule.allItems, item)\n\t\t}\n\t\tif len(options.WIFIBSSID) > 0 {\n\t\t\titem := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID)\n\t\t\trule.items = append(rule.items, item)\n\t\t\trule.allItems = append(rule.allItems, item)\n\t\t}\n\t\tif options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {\n\t\t\titem := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)\n\t\t\trule.items = append(rule.items, item)\n\t\t\trule.allItems = append(rule.allItems, item)\n\t\t}\n\t\tif len(options.DefaultInterfaceAddress) > 0 {\n\t\t\titem := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)\n\t\t\trule.items = append(rule.items, item)\n\t\t\trule.allItems = append(rule.allItems, item)\n\t\t}\n\t}\n\tif len(options.AdGuardDomain) > 0 {\n\t\titem := NewAdGuardDomainItem(options.AdGuardDomain)\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t} else if options.AdGuardDomainMatcher != nil {\n\t\titem := NewRawAdGuardDomainItem(options.AdGuardDomainMatcher)\n\t\trule.destinationAddressItems = append(rule.destinationAddressItems, item)\n\t\trule.allItems = append(rule.allItems, item)\n\t}\n\treturn rule, nil\n}\n\nvar _ adapter.HeadlessRule = (*LogicalHeadlessRule)(nil)\n\ntype LogicalHeadlessRule struct {\n\tabstractLogicalRule\n}\n\nfunc NewLogicalHeadlessRule(ctx context.Context, options option.LogicalHeadlessRule) (*LogicalHeadlessRule, error) {\n\tr := &LogicalHeadlessRule{\n\t\tabstractLogicalRule{\n\t\t\trules:  make([]adapter.HeadlessRule, len(options.Rules)),\n\t\t\tinvert: options.Invert,\n\t\t},\n\t}\n\tswitch options.Mode {\n\tcase C.LogicalTypeAnd:\n\t\tr.mode = C.LogicalTypeAnd\n\tcase C.LogicalTypeOr:\n\t\tr.mode = C.LogicalTypeOr\n\tdefault:\n\t\treturn nil, E.New(\"unknown logical mode: \", options.Mode)\n\t}\n\tfor i, subRule := range options.Rules {\n\t\trule, err := NewHeadlessRule(ctx, subRule)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"sub rule[\", i, \"]\")\n\t\t}\n\t\tr.rules[i] = rule\n\t}\n\treturn r, nil\n}\n"
  },
  {
    "path": "route/rule/rule_interface_address.go",
    "content": "package rule\n\nimport (\n\t\"net/netip\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/control\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nvar _ RuleItem = (*InterfaceAddressItem)(nil)\n\ntype InterfaceAddressItem struct {\n\tnetworkManager     adapter.NetworkManager\n\tinterfaceAddresses map[string][]netip.Prefix\n\tdescription        string\n}\n\nfunc NewInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]]) *InterfaceAddressItem {\n\titem := &InterfaceAddressItem{\n\t\tnetworkManager:     networkManager,\n\t\tinterfaceAddresses: make(map[string][]netip.Prefix, interfaceAddresses.Size()),\n\t}\n\tvar entryDescriptions []string\n\tfor _, entry := range interfaceAddresses.Entries() {\n\t\tprefixes := make([]netip.Prefix, 0, len(entry.Value))\n\t\tfor _, prefixable := range entry.Value {\n\t\t\tprefixes = append(prefixes, prefixable.Build(netip.Prefix{}))\n\t\t}\n\t\titem.interfaceAddresses[entry.Key] = prefixes\n\t\tentryDescriptions = append(entryDescriptions, entry.Key+\"=\"+strings.Join(common.Map(prefixes, netip.Prefix.String), \",\"))\n\t}\n\titem.description = \"interface_address=[\" + strings.Join(entryDescriptions, \" \") + \"]\"\n\treturn item\n}\n\nfunc (r *InterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {\n\tinterfaces := r.networkManager.InterfaceFinder().Interfaces()\n\tfor ifName, addresses := range r.interfaceAddresses {\n\t\tiface := common.Find(interfaces, func(it control.Interface) bool {\n\t\t\treturn it.Name == ifName\n\t\t})\n\t\tif iface.Name == \"\" {\n\t\t\treturn false\n\t\t}\n\t\tif common.All(addresses, func(address netip.Prefix) bool {\n\t\t\treturn common.All(iface.Addresses, func(it netip.Prefix) bool {\n\t\t\t\treturn !address.Overlaps(it)\n\t\t\t})\n\t\t}) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *InterfaceAddressItem) String() string {\n\treturn r.description\n}\n"
  },
  {
    "path": "route/rule/rule_item_adguard.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common/domain\"\n)\n\nvar _ RuleItem = (*AdGuardDomainItem)(nil)\n\ntype AdGuardDomainItem struct {\n\tmatcher *domain.AdGuardMatcher\n}\n\nfunc NewAdGuardDomainItem(ruleLines []string) *AdGuardDomainItem {\n\treturn &AdGuardDomainItem{\n\t\tdomain.NewAdGuardMatcher(ruleLines),\n\t}\n}\n\nfunc NewRawAdGuardDomainItem(matcher *domain.AdGuardMatcher) *AdGuardDomainItem {\n\treturn &AdGuardDomainItem{\n\t\tmatcher,\n\t}\n}\n\nfunc (r *AdGuardDomainItem) Match(metadata *adapter.InboundContext) bool {\n\tvar domainHost string\n\tif metadata.Domain != \"\" {\n\t\tdomainHost = metadata.Domain\n\t} else {\n\t\tdomainHost = metadata.Destination.Fqdn\n\t}\n\tif domainHost == \"\" {\n\t\treturn false\n\t}\n\treturn r.matcher.Match(strings.ToLower(domainHost))\n}\n\nfunc (r *AdGuardDomainItem) String() string {\n\treturn \"!adguard_domain_rules=<binary>\"\n}\n"
  },
  {
    "path": "route/rule/rule_item_auth_user.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*AuthUserItem)(nil)\n\ntype AuthUserItem struct {\n\tusers   []string\n\tuserMap map[string]bool\n}\n\nfunc NewAuthUserItem(users []string) *AuthUserItem {\n\tuserMap := make(map[string]bool)\n\tfor _, protocol := range users {\n\t\tuserMap[protocol] = true\n\t}\n\treturn &AuthUserItem{\n\t\tusers:   users,\n\t\tuserMap: userMap,\n\t}\n}\n\nfunc (r *AuthUserItem) Match(metadata *adapter.InboundContext) bool {\n\treturn r.userMap[metadata.User]\n}\n\nfunc (r *AuthUserItem) String() string {\n\tif len(r.users) == 1 {\n\t\treturn F.ToString(\"auth_user=\", r.users[0])\n\t}\n\treturn F.ToString(\"auth_user=[\", strings.Join(r.users, \" \"), \"]\")\n}\n"
  },
  {
    "path": "route/rule/rule_item_cidr.go",
    "content": "package rule\n\nimport (\n\t\"net/netip\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"go4.org/netipx\"\n)\n\nvar _ RuleItem = (*IPCIDRItem)(nil)\n\ntype IPCIDRItem struct {\n\tipSet       *netipx.IPSet\n\tisSource    bool\n\tdescription string\n}\n\nfunc NewIPCIDRItem(isSource bool, prefixStrings []string) (*IPCIDRItem, error) {\n\tvar builder netipx.IPSetBuilder\n\tfor i, prefixString := range prefixStrings {\n\t\tprefix, err := netip.ParsePrefix(prefixString)\n\t\tif err == nil {\n\t\t\tbuilder.AddPrefix(prefix)\n\t\t\tcontinue\n\t\t}\n\t\taddr, addrErr := netip.ParseAddr(prefixString)\n\t\tif addrErr == nil {\n\t\t\tbuilder.Add(addr)\n\t\t\tcontinue\n\t\t}\n\t\treturn nil, E.Cause(err, \"parse [\", i, \"]\")\n\t}\n\tvar description string\n\tif isSource {\n\t\tdescription = \"source_ip_cidr=\"\n\t} else {\n\t\tdescription = \"ip_cidr=\"\n\t}\n\tif dLen := len(prefixStrings); dLen == 1 {\n\t\tdescription += prefixStrings[0]\n\t} else if dLen > 3 {\n\t\tdescription += \"[\" + strings.Join(prefixStrings[:3], \" \") + \"...]\"\n\t} else {\n\t\tdescription += \"[\" + strings.Join(prefixStrings, \" \") + \"]\"\n\t}\n\tipSet, err := builder.IPSet()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &IPCIDRItem{\n\t\tipSet:       ipSet,\n\t\tisSource:    isSource,\n\t\tdescription: description,\n\t}, nil\n}\n\nfunc NewRawIPCIDRItem(isSource bool, ipSet *netipx.IPSet) *IPCIDRItem {\n\tvar description string\n\tif isSource {\n\t\tdescription = \"source_ip_cidr=\"\n\t} else {\n\t\tdescription = \"ip_cidr=\"\n\t}\n\tdescription += \"<binary>\"\n\treturn &IPCIDRItem{\n\t\tipSet:       ipSet,\n\t\tisSource:    isSource,\n\t\tdescription: description,\n\t}\n}\n\nfunc (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool {\n\tif r.isSource || metadata.IPCIDRMatchSource {\n\t\treturn r.ipSet.Contains(metadata.Source.Addr)\n\t}\n\tif metadata.Destination.IsIP() {\n\t\treturn r.ipSet.Contains(metadata.Destination.Addr)\n\t}\n\tif len(metadata.DestinationAddresses) > 0 {\n\t\tfor _, address := range metadata.DestinationAddresses {\n\t\t\tif r.ipSet.Contains(address) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\treturn metadata.IPCIDRAcceptEmpty\n}\n\nfunc (r *IPCIDRItem) String() string {\n\treturn r.description\n}\n"
  },
  {
    "path": "route/rule/rule_item_clash_mode.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nvar _ RuleItem = (*ClashModeItem)(nil)\n\ntype ClashModeItem struct {\n\tctx         context.Context\n\tclashServer adapter.ClashServer\n\tmode        string\n}\n\nfunc NewClashModeItem(ctx context.Context, mode string) *ClashModeItem {\n\treturn &ClashModeItem{\n\t\tctx:  ctx,\n\t\tmode: mode,\n\t}\n}\n\nfunc (r *ClashModeItem) Start() error {\n\tr.clashServer = service.FromContext[adapter.ClashServer](r.ctx)\n\treturn nil\n}\n\nfunc (r *ClashModeItem) Match(metadata *adapter.InboundContext) bool {\n\tif r.clashServer == nil {\n\t\treturn false\n\t}\n\treturn strings.EqualFold(r.clashServer.Mode(), r.mode)\n}\n\nfunc (r *ClashModeItem) String() string {\n\treturn \"clash_mode=\" + r.mode\n}\n"
  },
  {
    "path": "route/rule/rule_item_client.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*ClientItem)(nil)\n\ntype ClientItem struct {\n\tclients   []string\n\tclientMap map[string]bool\n}\n\nfunc NewClientItem(clients []string) *ClientItem {\n\tclientMap := make(map[string]bool)\n\tfor _, client := range clients {\n\t\tclientMap[client] = true\n\t}\n\treturn &ClientItem{\n\t\tclients:   clients,\n\t\tclientMap: clientMap,\n\t}\n}\n\nfunc (r *ClientItem) Match(metadata *adapter.InboundContext) bool {\n\treturn r.clientMap[metadata.Client]\n}\n\nfunc (r *ClientItem) String() string {\n\tif len(r.clients) == 1 {\n\t\treturn F.ToString(\"client=\", r.clients[0])\n\t}\n\treturn F.ToString(\"client=[\", strings.Join(r.clients, \" \"), \"]\")\n}\n"
  },
  {
    "path": "route/rule/rule_item_domain.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common/domain\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nvar _ RuleItem = (*DomainItem)(nil)\n\ntype DomainItem struct {\n\tmatcher     *domain.Matcher\n\tdescription string\n}\n\nfunc NewDomainItem(domains []string, domainSuffixes []string) (*DomainItem, error) {\n\tfor _, domainItem := range domains {\n\t\tif domainItem == \"\" {\n\t\t\treturn nil, E.New(\"domain: empty item is not allowed\")\n\t\t}\n\t}\n\tfor _, domainSuffixItem := range domainSuffixes {\n\t\tif domainSuffixItem == \"\" {\n\t\t\treturn nil, E.New(\"domain_suffix: empty item is not allowed\")\n\t\t}\n\t}\n\tvar description string\n\tif dLen := len(domains); dLen > 0 {\n\t\tif dLen == 1 {\n\t\t\tdescription = \"domain=\" + domains[0]\n\t\t} else if dLen > 3 {\n\t\t\tdescription = \"domain=[\" + strings.Join(domains[:3], \" \") + \"...]\"\n\t\t} else {\n\t\t\tdescription = \"domain=[\" + strings.Join(domains, \" \") + \"]\"\n\t\t}\n\t}\n\tif dsLen := len(domainSuffixes); dsLen > 0 {\n\t\tif len(description) > 0 {\n\t\t\tdescription += \" \"\n\t\t}\n\t\tif dsLen == 1 {\n\t\t\tdescription += \"domain_suffix=\" + domainSuffixes[0]\n\t\t} else if dsLen > 3 {\n\t\t\tdescription += \"domain_suffix=[\" + strings.Join(domainSuffixes[:3], \" \") + \"...]\"\n\t\t} else {\n\t\t\tdescription += \"domain_suffix=[\" + strings.Join(domainSuffixes, \" \") + \"]\"\n\t\t}\n\t}\n\treturn &DomainItem{\n\t\tdomain.NewMatcher(domains, domainSuffixes, false),\n\t\tdescription,\n\t}, nil\n}\n\nfunc NewRawDomainItem(matcher *domain.Matcher) *DomainItem {\n\treturn &DomainItem{\n\t\tmatcher,\n\t\t\"domain/domain_suffix=<binary>\",\n\t}\n}\n\nfunc (r *DomainItem) Match(metadata *adapter.InboundContext) bool {\n\tvar domainHost string\n\tif metadata.Domain != \"\" {\n\t\tdomainHost = metadata.Domain\n\t} else {\n\t\tdomainHost = metadata.Destination.Fqdn\n\t}\n\tif domainHost == \"\" {\n\t\treturn false\n\t}\n\treturn r.matcher.Match(strings.ToLower(domainHost))\n}\n\nfunc (r *DomainItem) String() string {\n\treturn r.description\n}\n"
  },
  {
    "path": "route/rule/rule_item_domain_keyword.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\nvar _ RuleItem = (*DomainKeywordItem)(nil)\n\ntype DomainKeywordItem struct {\n\tkeywords []string\n}\n\nfunc NewDomainKeywordItem(keywords []string) *DomainKeywordItem {\n\treturn &DomainKeywordItem{keywords}\n}\n\nfunc (r *DomainKeywordItem) Match(metadata *adapter.InboundContext) bool {\n\tvar domainHost string\n\tif metadata.Domain != \"\" {\n\t\tdomainHost = metadata.Domain\n\t} else {\n\t\tdomainHost = metadata.Destination.Fqdn\n\t}\n\tif domainHost == \"\" {\n\t\treturn false\n\t}\n\tdomainHost = strings.ToLower(domainHost)\n\tfor _, keyword := range r.keywords {\n\t\tif strings.Contains(domainHost, keyword) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *DomainKeywordItem) String() string {\n\tkLen := len(r.keywords)\n\tif kLen == 1 {\n\t\treturn \"domain_keyword=\" + r.keywords[0]\n\t} else if kLen > 3 {\n\t\treturn \"domain_keyword=[\" + strings.Join(r.keywords[:3], \" \") + \"...]\"\n\t} else {\n\t\treturn \"domain_keyword=[\" + strings.Join(r.keywords, \" \") + \"]\"\n\t}\n}\n"
  },
  {
    "path": "route/rule/rule_item_domain_regex.go",
    "content": "package rule\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*DomainRegexItem)(nil)\n\ntype DomainRegexItem struct {\n\tmatchers    []*regexp.Regexp\n\tdescription string\n}\n\nfunc NewDomainRegexItem(expressions []string) (*DomainRegexItem, error) {\n\tmatchers := make([]*regexp.Regexp, 0, len(expressions))\n\tfor i, regex := range expressions {\n\t\tmatcher, err := regexp.Compile(regex)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse expression \", i)\n\t\t}\n\t\tmatchers = append(matchers, matcher)\n\t}\n\tdescription := \"domain_regex=\"\n\teLen := len(expressions)\n\tif eLen == 1 {\n\t\tdescription += expressions[0]\n\t} else if eLen > 3 {\n\t\tdescription += F.ToString(\"[\", strings.Join(expressions[:3], \" \"), \"]\")\n\t} else {\n\t\tdescription += F.ToString(\"[\", strings.Join(expressions, \" \"), \"]\")\n\t}\n\treturn &DomainRegexItem{matchers, description}, nil\n}\n\nfunc (r *DomainRegexItem) Match(metadata *adapter.InboundContext) bool {\n\tvar domainHost string\n\tif metadata.Domain != \"\" {\n\t\tdomainHost = metadata.Domain\n\t} else {\n\t\tdomainHost = metadata.Destination.Fqdn\n\t}\n\tif domainHost == \"\" {\n\t\treturn false\n\t}\n\tdomainHost = strings.ToLower(domainHost)\n\tfor _, matcher := range r.matchers {\n\t\tif matcher.MatchString(domainHost) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *DomainRegexItem) String() string {\n\treturn r.description\n}\n"
  },
  {
    "path": "route/rule/rule_item_inbound.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*InboundItem)(nil)\n\ntype InboundItem struct {\n\tinbounds   []string\n\tinboundMap map[string]bool\n}\n\nfunc NewInboundRule(inbounds []string) *InboundItem {\n\trule := &InboundItem{inbounds, make(map[string]bool)}\n\tfor _, inbound := range inbounds {\n\t\trule.inboundMap[inbound] = true\n\t}\n\treturn rule\n}\n\nfunc (r *InboundItem) Match(metadata *adapter.InboundContext) bool {\n\treturn r.inboundMap[metadata.Inbound]\n}\n\nfunc (r *InboundItem) String() string {\n\tif len(r.inbounds) == 1 {\n\t\treturn F.ToString(\"inbound=\", r.inbounds[0])\n\t} else {\n\t\treturn F.ToString(\"inbound=[\", strings.Join(r.inbounds, \" \"), \"]\")\n\t}\n}\n"
  },
  {
    "path": "route/rule/rule_item_ip_accept_any.go",
    "content": "package rule\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\nvar _ RuleItem = (*IPAcceptAnyItem)(nil)\n\ntype IPAcceptAnyItem struct{}\n\nfunc NewIPAcceptAnyItem() *IPAcceptAnyItem {\n\treturn &IPAcceptAnyItem{}\n}\n\nfunc (r *IPAcceptAnyItem) Match(metadata *adapter.InboundContext) bool {\n\treturn len(metadata.DestinationAddresses) > 0\n}\n\nfunc (r *IPAcceptAnyItem) String() string {\n\treturn \"ip_accept_any=true\"\n}\n"
  },
  {
    "path": "route/rule/rule_item_ip_is_private.go",
    "content": "package rule\n\nimport (\n\t\"net/netip\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nvar _ RuleItem = (*IPIsPrivateItem)(nil)\n\ntype IPIsPrivateItem struct {\n\tisSource bool\n}\n\nfunc NewIPIsPrivateItem(isSource bool) *IPIsPrivateItem {\n\treturn &IPIsPrivateItem{isSource}\n}\n\nfunc (r *IPIsPrivateItem) Match(metadata *adapter.InboundContext) bool {\n\tvar destination netip.Addr\n\tif r.isSource {\n\t\tdestination = metadata.Source.Addr\n\t} else {\n\t\tdestination = metadata.Destination.Addr\n\t}\n\tif destination.IsValid() {\n\t\treturn !N.IsPublicAddr(destination)\n\t}\n\tif !r.isSource {\n\t\tfor _, destinationAddress := range metadata.DestinationAddresses {\n\t\t\tif !N.IsPublicAddr(destinationAddress) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *IPIsPrivateItem) String() string {\n\tif r.isSource {\n\t\treturn \"source_ip_is_private=true\"\n\t} else {\n\t\treturn \"ip_is_private=true\"\n\t}\n}\n"
  },
  {
    "path": "route/rule/rule_item_ipversion.go",
    "content": "package rule\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\nvar _ RuleItem = (*IPVersionItem)(nil)\n\ntype IPVersionItem struct {\n\tisIPv6 bool\n}\n\nfunc NewIPVersionItem(isIPv6 bool) *IPVersionItem {\n\treturn &IPVersionItem{isIPv6}\n}\n\nfunc (r *IPVersionItem) Match(metadata *adapter.InboundContext) bool {\n\treturn metadata.IPVersion != 0 && metadata.IPVersion == 6 == r.isIPv6 ||\n\t\tmetadata.Destination.IsIP() && metadata.Destination.IsIPv6() == r.isIPv6\n}\n\nfunc (r *IPVersionItem) String() string {\n\tvar versionStr string\n\tif r.isIPv6 {\n\t\tversionStr = \"6\"\n\t} else {\n\t\tversionStr = \"4\"\n\t}\n\treturn \"ip_version=\" + versionStr\n}\n"
  },
  {
    "path": "route/rule/rule_item_network.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*NetworkItem)(nil)\n\ntype NetworkItem struct {\n\tnetworks   []string\n\tnetworkMap map[string]bool\n}\n\nfunc NewNetworkItem(networks []string) *NetworkItem {\n\tnetworkMap := make(map[string]bool)\n\tfor _, network := range networks {\n\t\tnetworkMap[network] = true\n\t}\n\treturn &NetworkItem{\n\t\tnetworks:   networks,\n\t\tnetworkMap: networkMap,\n\t}\n}\n\nfunc (r *NetworkItem) Match(metadata *adapter.InboundContext) bool {\n\treturn r.networkMap[metadata.Network]\n}\n\nfunc (r *NetworkItem) String() string {\n\tdescription := \"network=\"\n\n\tpLen := len(r.networks)\n\tif pLen == 1 {\n\t\tdescription += F.ToString(r.networks[0])\n\t} else {\n\t\tdescription += \"[\" + strings.Join(F.MapToString(r.networks), \" \") + \"]\"\n\t}\n\treturn description\n}\n"
  },
  {
    "path": "route/rule/rule_item_network_is_constrained.go",
    "content": "package rule\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\nvar _ RuleItem = (*NetworkIsConstrainedItem)(nil)\n\ntype NetworkIsConstrainedItem struct {\n\tnetworkManager adapter.NetworkManager\n}\n\nfunc NewNetworkIsConstrainedItem(networkManager adapter.NetworkManager) *NetworkIsConstrainedItem {\n\treturn &NetworkIsConstrainedItem{\n\t\tnetworkManager: networkManager,\n\t}\n}\n\nfunc (r *NetworkIsConstrainedItem) Match(metadata *adapter.InboundContext) bool {\n\tnetworkInterface := r.networkManager.DefaultNetworkInterface()\n\tif networkInterface == nil {\n\t\treturn false\n\t}\n\treturn networkInterface.Constrained\n}\n\nfunc (r *NetworkIsConstrainedItem) String() string {\n\treturn \"network_is_expensive=true\"\n}\n"
  },
  {
    "path": "route/rule/rule_item_network_is_expensive.go",
    "content": "package rule\n\nimport (\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\nvar _ RuleItem = (*NetworkIsExpensiveItem)(nil)\n\ntype NetworkIsExpensiveItem struct {\n\tnetworkManager adapter.NetworkManager\n}\n\nfunc NewNetworkIsExpensiveItem(networkManager adapter.NetworkManager) *NetworkIsExpensiveItem {\n\treturn &NetworkIsExpensiveItem{\n\t\tnetworkManager: networkManager,\n\t}\n}\n\nfunc (r *NetworkIsExpensiveItem) Match(metadata *adapter.InboundContext) bool {\n\tnetworkInterface := r.networkManager.DefaultNetworkInterface()\n\tif networkInterface == nil {\n\t\treturn false\n\t}\n\treturn networkInterface.Expensive\n}\n\nfunc (r *NetworkIsExpensiveItem) String() string {\n\treturn \"network_is_expensive=true\"\n}\n"
  },
  {
    "path": "route/rule/rule_item_network_type.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*NetworkTypeItem)(nil)\n\ntype NetworkTypeItem struct {\n\tnetworkManager adapter.NetworkManager\n\tnetworkType    []C.InterfaceType\n}\n\nfunc NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []C.InterfaceType) *NetworkTypeItem {\n\treturn &NetworkTypeItem{\n\t\tnetworkManager: networkManager,\n\t\tnetworkType:    networkType,\n\t}\n}\n\nfunc (r *NetworkTypeItem) Match(metadata *adapter.InboundContext) bool {\n\tnetworkInterface := r.networkManager.DefaultNetworkInterface()\n\tif networkInterface == nil {\n\t\treturn false\n\t}\n\treturn common.Contains(r.networkType, networkInterface.Type)\n}\n\nfunc (r *NetworkTypeItem) String() string {\n\tif len(r.networkType) == 1 {\n\t\treturn F.ToString(\"network_type=\", r.networkType[0])\n\t} else {\n\t\treturn F.ToString(\"network_type=\", \"[\"+strings.Join(F.MapToString(r.networkType), \" \")+\"]\")\n\t}\n}\n"
  },
  {
    "path": "route/rule/rule_item_outbound.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/experimental/deprecated\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*OutboundItem)(nil)\n\ntype OutboundItem struct {\n\toutbounds   []string\n\toutboundMap map[string]bool\n\tmatchAny    bool\n}\n\nfunc NewOutboundRule(ctx context.Context, outbounds []string) *OutboundItem {\n\tdeprecated.Report(ctx, deprecated.OptionOutboundDNSRuleItem)\n\trule := &OutboundItem{outbounds: outbounds, outboundMap: make(map[string]bool)}\n\tfor _, outbound := range outbounds {\n\t\tif outbound == \"any\" {\n\t\t\trule.matchAny = true\n\t\t} else {\n\t\t\trule.outboundMap[outbound] = true\n\t\t}\n\t}\n\treturn rule\n}\n\nfunc (r *OutboundItem) Match(metadata *adapter.InboundContext) bool {\n\tif r.matchAny {\n\t\treturn metadata.Outbound != \"\"\n\t}\n\treturn r.outboundMap[metadata.Outbound]\n}\n\nfunc (r *OutboundItem) String() string {\n\tif len(r.outbounds) == 1 {\n\t\treturn F.ToString(\"outbound=\", r.outbounds[0])\n\t} else {\n\t\treturn F.ToString(\"outbound=[\", strings.Join(r.outbounds, \" \"), \"]\")\n\t}\n}\n"
  },
  {
    "path": "route/rule/rule_item_package_name.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\nvar _ RuleItem = (*PackageNameItem)(nil)\n\ntype PackageNameItem struct {\n\tpackageNames []string\n\tpackageMap   map[string]bool\n}\n\nfunc NewPackageNameItem(packageNameList []string) *PackageNameItem {\n\trule := &PackageNameItem{\n\t\tpackageNames: packageNameList,\n\t\tpackageMap:   make(map[string]bool),\n\t}\n\tfor _, packageName := range packageNameList {\n\t\trule.packageMap[packageName] = true\n\t}\n\treturn rule\n}\n\nfunc (r *PackageNameItem) Match(metadata *adapter.InboundContext) bool {\n\tif metadata.ProcessInfo == nil || metadata.ProcessInfo.AndroidPackageName == \"\" {\n\t\treturn false\n\t}\n\treturn r.packageMap[metadata.ProcessInfo.AndroidPackageName]\n}\n\nfunc (r *PackageNameItem) String() string {\n\tvar description string\n\tpLen := len(r.packageNames)\n\tif pLen == 1 {\n\t\tdescription = \"package_name=\" + r.packageNames[0]\n\t} else {\n\t\tdescription = \"package_name=[\" + strings.Join(r.packageNames, \" \") + \"]\"\n\t}\n\treturn description\n}\n"
  },
  {
    "path": "route/rule/rule_item_port.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*PortItem)(nil)\n\ntype PortItem struct {\n\tports    []uint16\n\tportMap  map[uint16]bool\n\tisSource bool\n}\n\nfunc NewPortItem(isSource bool, ports []uint16) *PortItem {\n\tportMap := make(map[uint16]bool)\n\tfor _, port := range ports {\n\t\tportMap[port] = true\n\t}\n\treturn &PortItem{\n\t\tports:    ports,\n\t\tportMap:  portMap,\n\t\tisSource: isSource,\n\t}\n}\n\nfunc (r *PortItem) Match(metadata *adapter.InboundContext) bool {\n\tif r.isSource {\n\t\treturn r.portMap[metadata.Source.Port]\n\t} else {\n\t\treturn r.portMap[metadata.Destination.Port]\n\t}\n}\n\nfunc (r *PortItem) String() string {\n\tvar description string\n\tif r.isSource {\n\t\tdescription = \"source_port=\"\n\t} else {\n\t\tdescription = \"port=\"\n\t}\n\tpLen := len(r.ports)\n\tif pLen == 1 {\n\t\tdescription += F.ToString(r.ports[0])\n\t} else {\n\t\tdescription += \"[\" + strings.Join(F.MapToString(r.ports), \" \") + \"]\"\n\t}\n\treturn description\n}\n"
  },
  {
    "path": "route/rule/rule_item_port_range.go",
    "content": "package rule\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nvar ErrBadPortRange = E.New(\"bad port range\")\n\nvar _ RuleItem = (*PortRangeItem)(nil)\n\ntype PortRangeItem struct {\n\tisSource      bool\n\tportRanges    []string\n\tportRangeList []rangeItem\n}\n\ntype rangeItem struct {\n\tstart uint16\n\tend   uint16\n}\n\nfunc NewPortRangeItem(isSource bool, rangeList []string) (*PortRangeItem, error) {\n\tportRangeList := make([]rangeItem, 0, len(rangeList))\n\tfor _, portRange := range rangeList {\n\t\tif !strings.Contains(portRange, \":\") {\n\t\t\treturn nil, E.Extend(ErrBadPortRange, portRange)\n\t\t}\n\t\tsubIndex := strings.Index(portRange, \":\")\n\t\tvar start, end uint64\n\t\tvar err error\n\t\tif subIndex > 0 {\n\t\t\tstart, err = strconv.ParseUint(portRange[:subIndex], 10, 16)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, E.Extend(ErrBadPortRange, portRange))\n\t\t\t}\n\t\t}\n\t\tif subIndex == len(portRange)-1 {\n\t\t\tend = 0xFFFF\n\t\t} else {\n\t\t\tend, err = strconv.ParseUint(portRange[subIndex+1:], 10, 16)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, E.Extend(ErrBadPortRange, portRange))\n\t\t\t}\n\t\t}\n\t\tportRangeList = append(portRangeList, rangeItem{uint16(start), uint16(end)})\n\t}\n\treturn &PortRangeItem{\n\t\tisSource:      isSource,\n\t\tportRanges:    rangeList,\n\t\tportRangeList: portRangeList,\n\t}, nil\n}\n\nfunc (r *PortRangeItem) Match(metadata *adapter.InboundContext) bool {\n\tvar port uint16\n\tif r.isSource {\n\t\tport = metadata.Source.Port\n\t} else {\n\t\tport = metadata.Destination.Port\n\t}\n\tfor _, portRange := range r.portRangeList {\n\t\tif port >= portRange.start && port <= portRange.end {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *PortRangeItem) String() string {\n\tvar description string\n\tif r.isSource {\n\t\tdescription = \"source_port_range=\"\n\t} else {\n\t\tdescription = \"port_range=\"\n\t}\n\tpLen := len(r.portRanges)\n\tif pLen == 1 {\n\t\tdescription += r.portRanges[0]\n\t} else {\n\t\tdescription += \"[\" + strings.Join(r.portRanges, \" \") + \"]\"\n\t}\n\treturn description\n}\n"
  },
  {
    "path": "route/rule/rule_item_preferred_by.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nvar _ RuleItem = (*PreferredByItem)(nil)\n\ntype PreferredByItem struct {\n\tctx          context.Context\n\toutboundTags []string\n\toutbounds    []adapter.OutboundWithPreferredRoutes\n}\n\nfunc NewPreferredByItem(ctx context.Context, outboundTags []string) *PreferredByItem {\n\treturn &PreferredByItem{\n\t\tctx:          ctx,\n\t\toutboundTags: outboundTags,\n\t}\n}\n\nfunc (r *PreferredByItem) Start() error {\n\toutboundManager := service.FromContext[adapter.OutboundManager](r.ctx)\n\tfor _, outboundTag := range r.outboundTags {\n\t\trawOutbound, loaded := outboundManager.Outbound(outboundTag)\n\t\tif !loaded {\n\t\t\treturn E.New(\"outbound not found: \", outboundTag)\n\t\t}\n\t\toutboundWithPreferredRoutes, withRoutes := rawOutbound.(adapter.OutboundWithPreferredRoutes)\n\t\tif !withRoutes {\n\t\t\treturn E.New(\"outbound type does not support preferred routes: \", rawOutbound.Type())\n\t\t}\n\t\tr.outbounds = append(r.outbounds, outboundWithPreferredRoutes)\n\t}\n\treturn nil\n}\n\nfunc (r *PreferredByItem) Match(metadata *adapter.InboundContext) bool {\n\tvar domainHost string\n\tif metadata.Domain != \"\" {\n\t\tdomainHost = metadata.Domain\n\t} else {\n\t\tdomainHost = metadata.Destination.Fqdn\n\t}\n\tif domainHost != \"\" {\n\t\tfor _, outbound := range r.outbounds {\n\t\t\tif outbound.PreferredDomain(domainHost) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\tif metadata.Destination.IsIP() {\n\t\tfor _, outbound := range r.outbounds {\n\t\t\tif outbound.PreferredAddress(metadata.Destination.Addr) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\tif len(metadata.DestinationAddresses) > 0 {\n\t\tfor _, address := range metadata.DestinationAddresses {\n\t\t\tfor _, outbound := range r.outbounds {\n\t\t\t\tif outbound.PreferredAddress(address) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *PreferredByItem) String() string {\n\tdescription := \"preferred_by=\"\n\tpLen := len(r.outboundTags)\n\tif pLen == 1 {\n\t\tdescription += F.ToString(r.outboundTags[0])\n\t} else {\n\t\tdescription += \"[\" + strings.Join(F.MapToString(r.outboundTags), \" \") + \"]\"\n\t}\n\treturn description\n}\n"
  },
  {
    "path": "route/rule/rule_item_process_name.go",
    "content": "package rule\n\nimport (\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\nvar _ RuleItem = (*ProcessItem)(nil)\n\ntype ProcessItem struct {\n\tprocesses  []string\n\tprocessMap map[string]bool\n}\n\nfunc NewProcessItem(processNameList []string) *ProcessItem {\n\trule := &ProcessItem{\n\t\tprocesses:  processNameList,\n\t\tprocessMap: make(map[string]bool),\n\t}\n\tfor _, processName := range processNameList {\n\t\trule.processMap[processName] = true\n\t}\n\treturn rule\n}\n\nfunc (r *ProcessItem) Match(metadata *adapter.InboundContext) bool {\n\tif metadata.ProcessInfo == nil || metadata.ProcessInfo.ProcessPath == \"\" {\n\t\treturn false\n\t}\n\treturn r.processMap[filepath.Base(metadata.ProcessInfo.ProcessPath)]\n}\n\nfunc (r *ProcessItem) String() string {\n\tvar description string\n\tpLen := len(r.processes)\n\tif pLen == 1 {\n\t\tdescription = \"process_name=\" + r.processes[0]\n\t} else {\n\t\tdescription = \"process_name=[\" + strings.Join(r.processes, \" \") + \"]\"\n\t}\n\treturn description\n}\n"
  },
  {
    "path": "route/rule/rule_item_process_path.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\nvar _ RuleItem = (*ProcessPathItem)(nil)\n\ntype ProcessPathItem struct {\n\tprocesses  []string\n\tprocessMap map[string]bool\n}\n\nfunc NewProcessPathItem(processNameList []string) *ProcessPathItem {\n\trule := &ProcessPathItem{\n\t\tprocesses:  processNameList,\n\t\tprocessMap: make(map[string]bool),\n\t}\n\tfor _, processName := range processNameList {\n\t\trule.processMap[processName] = true\n\t}\n\treturn rule\n}\n\nfunc (r *ProcessPathItem) Match(metadata *adapter.InboundContext) bool {\n\tif metadata.ProcessInfo == nil || metadata.ProcessInfo.ProcessPath == \"\" {\n\t\treturn false\n\t}\n\treturn r.processMap[metadata.ProcessInfo.ProcessPath]\n}\n\nfunc (r *ProcessPathItem) String() string {\n\tvar description string\n\tpLen := len(r.processes)\n\tif pLen == 1 {\n\t\tdescription = \"process_path=\" + r.processes[0]\n\t} else {\n\t\tdescription = \"process_path=[\" + strings.Join(r.processes, \" \") + \"]\"\n\t}\n\treturn description\n}\n"
  },
  {
    "path": "route/rule/rule_item_process_path_regex.go",
    "content": "package rule\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*ProcessPathRegexItem)(nil)\n\ntype ProcessPathRegexItem struct {\n\tmatchers    []*regexp.Regexp\n\tdescription string\n}\n\nfunc NewProcessPathRegexItem(expressions []string) (*ProcessPathRegexItem, error) {\n\tmatchers := make([]*regexp.Regexp, 0, len(expressions))\n\tfor i, regex := range expressions {\n\t\tmatcher, err := regexp.Compile(regex)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"parse expression \", i)\n\t\t}\n\t\tmatchers = append(matchers, matcher)\n\t}\n\tdescription := \"process_path_regex=\"\n\teLen := len(expressions)\n\tif eLen == 1 {\n\t\tdescription += expressions[0]\n\t} else if eLen > 3 {\n\t\tdescription += F.ToString(\"[\", strings.Join(expressions[:3], \" \"), \"]\")\n\t} else {\n\t\tdescription += F.ToString(\"[\", strings.Join(expressions, \" \"), \"]\")\n\t}\n\treturn &ProcessPathRegexItem{matchers, description}, nil\n}\n\nfunc (r *ProcessPathRegexItem) Match(metadata *adapter.InboundContext) bool {\n\tif metadata.ProcessInfo == nil || metadata.ProcessInfo.ProcessPath == \"\" {\n\t\treturn false\n\t}\n\tfor _, matcher := range r.matchers {\n\t\tif matcher.MatchString(metadata.ProcessInfo.ProcessPath) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *ProcessPathRegexItem) String() string {\n\treturn r.description\n}\n"
  },
  {
    "path": "route/rule/rule_item_protocol.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*ProtocolItem)(nil)\n\ntype ProtocolItem struct {\n\tprotocols   []string\n\tprotocolMap map[string]bool\n}\n\nfunc NewProtocolItem(protocols []string) *ProtocolItem {\n\tprotocolMap := make(map[string]bool)\n\tfor _, protocol := range protocols {\n\t\tprotocolMap[protocol] = true\n\t}\n\treturn &ProtocolItem{\n\t\tprotocols:   protocols,\n\t\tprotocolMap: protocolMap,\n\t}\n}\n\nfunc (r *ProtocolItem) Match(metadata *adapter.InboundContext) bool {\n\treturn r.protocolMap[metadata.Protocol]\n}\n\nfunc (r *ProtocolItem) String() string {\n\tif len(r.protocols) == 1 {\n\t\treturn F.ToString(\"protocol=\", r.protocols[0])\n\t}\n\treturn F.ToString(\"protocol=[\", strings.Join(r.protocols, \" \"), \"]\")\n}\n"
  },
  {
    "path": "route/rule/rule_item_query_type.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n)\n\nvar _ RuleItem = (*QueryTypeItem)(nil)\n\ntype QueryTypeItem struct {\n\ttypeList []uint16\n\ttypeMap  map[uint16]bool\n}\n\nfunc NewQueryTypeItem(typeList []option.DNSQueryType) *QueryTypeItem {\n\trule := &QueryTypeItem{\n\t\ttypeList: common.Map(typeList, func(it option.DNSQueryType) uint16 {\n\t\t\treturn uint16(it)\n\t\t}),\n\t\ttypeMap: make(map[uint16]bool),\n\t}\n\tfor _, userId := range rule.typeList {\n\t\trule.typeMap[userId] = true\n\t}\n\treturn rule\n}\n\nfunc (r *QueryTypeItem) Match(metadata *adapter.InboundContext) bool {\n\tif metadata.QueryType == 0 {\n\t\treturn false\n\t}\n\treturn r.typeMap[metadata.QueryType]\n}\n\nfunc (r *QueryTypeItem) String() string {\n\tvar description string\n\tpLen := len(r.typeList)\n\tif pLen == 1 {\n\t\tdescription = \"query_type=\" + option.DNSQueryTypeToString(r.typeList[0])\n\t} else {\n\t\tdescription = \"query_type=[\" + strings.Join(common.Map(r.typeList, option.DNSQueryTypeToString), \" \") + \"]\"\n\t}\n\treturn description\n}\n"
  },
  {
    "path": "route/rule/rule_item_rule_set.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*RuleSetItem)(nil)\n\ntype RuleSetItem struct {\n\trouter            adapter.Router\n\ttagList           []string\n\tsetList           []adapter.RuleSet\n\tipCidrMatchSource bool\n\tipCidrAcceptEmpty bool\n}\n\nfunc NewRuleSetItem(router adapter.Router, tagList []string, ipCIDRMatchSource bool, ipCidrAcceptEmpty bool) *RuleSetItem {\n\treturn &RuleSetItem{\n\t\trouter:            router,\n\t\ttagList:           tagList,\n\t\tipCidrMatchSource: ipCIDRMatchSource,\n\t\tipCidrAcceptEmpty: ipCidrAcceptEmpty,\n\t}\n}\n\nfunc (r *RuleSetItem) Start() error {\n\tfor _, tag := range r.tagList {\n\t\truleSet, loaded := r.router.RuleSet(tag)\n\t\tif !loaded {\n\t\t\treturn E.New(\"rule-set not found: \", tag)\n\t\t}\n\t\truleSet.IncRef()\n\t\tr.setList = append(r.setList, ruleSet)\n\t}\n\treturn nil\n}\n\nfunc (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {\n\tmetadata.IPCIDRMatchSource = r.ipCidrMatchSource\n\tmetadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty\n\tfor _, ruleSet := range r.setList {\n\t\tif ruleSet.Match(metadata) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool {\n\tif r.ipCidrMatchSource {\n\t\treturn false\n\t}\n\treturn common.Any(r.setList, func(ruleSet adapter.RuleSet) bool {\n\t\treturn ruleSet.Metadata().ContainsIPCIDRRule\n\t})\n}\n\nfunc (r *RuleSetItem) String() string {\n\tif len(r.tagList) == 1 {\n\t\treturn F.ToString(\"rule_set=\", r.tagList[0])\n\t} else {\n\t\treturn F.ToString(\"rule_set=[\", strings.Join(r.tagList, \" \"), \"]\")\n\t}\n}\n"
  },
  {
    "path": "route/rule/rule_item_source_hostname.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\nvar _ RuleItem = (*SourceHostnameItem)(nil)\n\ntype SourceHostnameItem struct {\n\thostnames   []string\n\thostnameMap map[string]bool\n}\n\nfunc NewSourceHostnameItem(hostnameList []string) *SourceHostnameItem {\n\trule := &SourceHostnameItem{\n\t\thostnames:   hostnameList,\n\t\thostnameMap: make(map[string]bool),\n\t}\n\tfor _, hostname := range hostnameList {\n\t\trule.hostnameMap[hostname] = true\n\t}\n\treturn rule\n}\n\nfunc (r *SourceHostnameItem) Match(metadata *adapter.InboundContext) bool {\n\tif metadata.SourceHostname == \"\" {\n\t\treturn false\n\t}\n\treturn r.hostnameMap[metadata.SourceHostname]\n}\n\nfunc (r *SourceHostnameItem) String() string {\n\tvar description string\n\tif len(r.hostnames) == 1 {\n\t\tdescription = \"source_hostname=\" + r.hostnames[0]\n\t} else {\n\t\tdescription = \"source_hostname=[\" + strings.Join(r.hostnames, \" \") + \"]\"\n\t}\n\treturn description\n}\n"
  },
  {
    "path": "route/rule/rule_item_source_mac_address.go",
    "content": "package rule\n\nimport (\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n)\n\nvar _ RuleItem = (*SourceMACAddressItem)(nil)\n\ntype SourceMACAddressItem struct {\n\taddresses  []string\n\taddressMap map[string]bool\n}\n\nfunc NewSourceMACAddressItem(addressList []string) *SourceMACAddressItem {\n\trule := &SourceMACAddressItem{\n\t\taddresses:  addressList,\n\t\taddressMap: make(map[string]bool),\n\t}\n\tfor _, address := range addressList {\n\t\tparsed, err := net.ParseMAC(address)\n\t\tif err == nil {\n\t\t\trule.addressMap[parsed.String()] = true\n\t\t} else {\n\t\t\trule.addressMap[address] = true\n\t\t}\n\t}\n\treturn rule\n}\n\nfunc (r *SourceMACAddressItem) Match(metadata *adapter.InboundContext) bool {\n\tif metadata.SourceMACAddress == nil {\n\t\treturn false\n\t}\n\treturn r.addressMap[metadata.SourceMACAddress.String()]\n}\n\nfunc (r *SourceMACAddressItem) String() string {\n\tvar description string\n\tif len(r.addresses) == 1 {\n\t\tdescription = \"source_mac_address=\" + r.addresses[0]\n\t} else {\n\t\tdescription = \"source_mac_address=[\" + strings.Join(r.addresses, \" \") + \"]\"\n\t}\n\treturn description\n}\n"
  },
  {
    "path": "route/rule/rule_item_user.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*UserItem)(nil)\n\ntype UserItem struct {\n\tusers   []string\n\tuserMap map[string]bool\n}\n\nfunc NewUserItem(users []string) *UserItem {\n\tuserMap := make(map[string]bool)\n\tfor _, protocol := range users {\n\t\tuserMap[protocol] = true\n\t}\n\treturn &UserItem{\n\t\tusers:   users,\n\t\tuserMap: userMap,\n\t}\n}\n\nfunc (r *UserItem) Match(metadata *adapter.InboundContext) bool {\n\tif metadata.ProcessInfo == nil || metadata.ProcessInfo.UserName == \"\" {\n\t\treturn false\n\t}\n\treturn r.userMap[metadata.ProcessInfo.UserName]\n}\n\nfunc (r *UserItem) String() string {\n\tif len(r.users) == 1 {\n\t\treturn F.ToString(\"user=\", r.users[0])\n\t}\n\treturn F.ToString(\"user=[\", strings.Join(r.users, \" \"), \"]\")\n}\n"
  },
  {
    "path": "route/rule/rule_item_user_id.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*UserIdItem)(nil)\n\ntype UserIdItem struct {\n\tuserIds   []int32\n\tuserIdMap map[int32]bool\n}\n\nfunc NewUserIDItem(userIdList []int32) *UserIdItem {\n\trule := &UserIdItem{\n\t\tuserIds:   userIdList,\n\t\tuserIdMap: make(map[int32]bool),\n\t}\n\tfor _, userId := range userIdList {\n\t\trule.userIdMap[userId] = true\n\t}\n\treturn rule\n}\n\nfunc (r *UserIdItem) Match(metadata *adapter.InboundContext) bool {\n\tif metadata.ProcessInfo == nil || metadata.ProcessInfo.UserId == -1 {\n\t\treturn false\n\t}\n\treturn r.userIdMap[metadata.ProcessInfo.UserId]\n}\n\nfunc (r *UserIdItem) String() string {\n\tvar description string\n\tpLen := len(r.userIds)\n\tif pLen == 1 {\n\t\tdescription = \"user_id=\" + F.ToString(r.userIds[0])\n\t} else {\n\t\tdescription = \"user_id=[\" + strings.Join(F.MapToString(r.userIds), \" \") + \"]\"\n\t}\n\treturn description\n}\n"
  },
  {
    "path": "route/rule/rule_item_wifi_bssid.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*WIFIBSSIDItem)(nil)\n\ntype WIFIBSSIDItem struct {\n\tbssidList      []string\n\tbssidMap       map[string]bool\n\tnetworkManager adapter.NetworkManager\n}\n\nfunc NewWIFIBSSIDItem(networkManager adapter.NetworkManager, bssidList []string) *WIFIBSSIDItem {\n\tbssidMap := make(map[string]bool)\n\tfor _, bssid := range bssidList {\n\t\tbssidMap[bssid] = true\n\t}\n\treturn &WIFIBSSIDItem{\n\t\tbssidList,\n\t\tbssidMap,\n\t\tnetworkManager,\n\t}\n}\n\nfunc (r *WIFIBSSIDItem) Match(metadata *adapter.InboundContext) bool {\n\treturn r.bssidMap[r.networkManager.WIFIState().BSSID]\n}\n\nfunc (r *WIFIBSSIDItem) String() string {\n\tif len(r.bssidList) == 1 {\n\t\treturn F.ToString(\"wifi_bssid=\", r.bssidList[0])\n\t}\n\treturn F.ToString(\"wifi_bssid=[\", strings.Join(r.bssidList, \" \"), \"]\")\n}\n"
  },
  {
    "path": "route/rule/rule_item_wifi_ssid.go",
    "content": "package rule\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tF \"github.com/sagernet/sing/common/format\"\n)\n\nvar _ RuleItem = (*WIFISSIDItem)(nil)\n\ntype WIFISSIDItem struct {\n\tssidList       []string\n\tssidMap        map[string]bool\n\tnetworkManager adapter.NetworkManager\n}\n\nfunc NewWIFISSIDItem(networkManager adapter.NetworkManager, ssidList []string) *WIFISSIDItem {\n\tssidMap := make(map[string]bool)\n\tfor _, ssid := range ssidList {\n\t\tssidMap[ssid] = true\n\t}\n\treturn &WIFISSIDItem{\n\t\tssidList,\n\t\tssidMap,\n\t\tnetworkManager,\n\t}\n}\n\nfunc (r *WIFISSIDItem) Match(metadata *adapter.InboundContext) bool {\n\treturn r.ssidMap[r.networkManager.WIFIState().SSID]\n}\n\nfunc (r *WIFISSIDItem) String() string {\n\tif len(r.ssidList) == 1 {\n\t\treturn F.ToString(\"wifi_ssid=\", r.ssidList[0])\n\t}\n\treturn F.ToString(\"wifi_ssid=[\", strings.Join(r.ssidList, \" \"), \"]\")\n}\n"
  },
  {
    "path": "route/rule/rule_network_interface_address.go",
    "content": "package rule\n\nimport (\n\t\"net/netip\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nvar _ RuleItem = (*NetworkInterfaceAddressItem)(nil)\n\ntype NetworkInterfaceAddressItem struct {\n\tnetworkManager     adapter.NetworkManager\n\tinterfaceAddresses map[C.InterfaceType][]netip.Prefix\n\tdescription        string\n}\n\nfunc NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]]) *NetworkInterfaceAddressItem {\n\titem := &NetworkInterfaceAddressItem{\n\t\tnetworkManager:     networkManager,\n\t\tinterfaceAddresses: make(map[C.InterfaceType][]netip.Prefix, interfaceAddresses.Size()),\n\t}\n\tvar entryDescriptions []string\n\tfor _, entry := range interfaceAddresses.Entries() {\n\t\tprefixes := make([]netip.Prefix, 0, len(entry.Value))\n\t\tfor _, prefixable := range entry.Value {\n\t\t\tprefixes = append(prefixes, prefixable.Build(netip.Prefix{}))\n\t\t}\n\t\titem.interfaceAddresses[entry.Key.Build()] = prefixes\n\t\tentryDescriptions = append(entryDescriptions, entry.Key.Build().String()+\"=\"+strings.Join(common.Map(prefixes, netip.Prefix.String), \",\"))\n\t}\n\titem.description = \"network_interface_address=[\" + strings.Join(entryDescriptions, \" \") + \"]\"\n\treturn item\n}\n\nfunc (r *NetworkInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {\n\tinterfaces := r.networkManager.NetworkInterfaces()\nmatch:\n\tfor ifType, addresses := range r.interfaceAddresses {\n\t\tfor _, networkInterface := range interfaces {\n\t\t\tif networkInterface.Type != ifType {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif common.Any(networkInterface.Addresses, func(it netip.Prefix) bool {\n\t\t\t\treturn common.Any(addresses, func(prefix netip.Prefix) bool {\n\t\t\t\t\treturn prefix.Overlaps(it)\n\t\t\t\t})\n\t\t\t}) {\n\t\t\t\tcontinue match\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (r *NetworkInterfaceAddressItem) String() string {\n\treturn r.description\n}\n"
  },
  {
    "path": "route/rule/rule_set.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\n\t\"go4.org/netipx\"\n)\n\nfunc NewRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) {\n\tswitch options.Type {\n\tcase C.RuleSetTypeInline, C.RuleSetTypeLocal, \"\":\n\t\treturn NewLocalRuleSet(ctx, logger, options)\n\tcase C.RuleSetTypeRemote:\n\t\treturn NewRemoteRuleSet(ctx, logger, options), nil\n\tdefault:\n\t\treturn nil, E.New(\"unknown rule-set type: \", options.Type)\n\t}\n}\n\nfunc extractIPSetFromRule(rawRule adapter.HeadlessRule) []*netipx.IPSet {\n\tswitch rule := rawRule.(type) {\n\tcase *DefaultHeadlessRule:\n\t\treturn common.FlatMap(rule.destinationIPCIDRItems, func(rawItem RuleItem) []*netipx.IPSet {\n\t\t\tswitch item := rawItem.(type) {\n\t\t\tcase *IPCIDRItem:\n\t\t\t\treturn []*netipx.IPSet{item.ipSet}\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t})\n\tcase *LogicalHeadlessRule:\n\t\treturn common.FlatMap(rule.rules, extractIPSetFromRule)\n\tdefault:\n\t\tpanic(\"unexpected rule type\")\n\t}\n}\n\nfunc HasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {\n\tfor _, rule := range rules {\n\t\tswitch rule.Type {\n\t\tcase C.RuleTypeDefault:\n\t\t\tif cond(rule.DefaultOptions) {\n\t\t\t\treturn true\n\t\t\t}\n\t\tcase C.RuleTypeLogical:\n\t\t\tif HasHeadlessRule(rule.LogicalOptions.Rules, cond) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool {\n\treturn len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0\n}\n\nfunc isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool {\n\treturn len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0\n}\n\nfunc isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool {\n\treturn len(rule.IPCIDR) > 0 || rule.IPSet != nil\n}\n"
  },
  {
    "path": "route/rule/rule_set_local.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/sagernet/fswatch\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/srs\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\t\"github.com/sagernet/sing/service/filemanager\"\n\n\t\"go4.org/netipx\"\n)\n\nvar _ adapter.RuleSet = (*LocalRuleSet)(nil)\n\ntype LocalRuleSet struct {\n\tctx        context.Context\n\tlogger     logger.Logger\n\ttag        string\n\taccess     sync.RWMutex\n\trules      []adapter.HeadlessRule\n\tmetadata   adapter.RuleSetMetadata\n\tfileFormat string\n\twatcher    *fswatch.Watcher\n\tcallbacks  list.List[adapter.RuleSetUpdateCallback]\n\trefs       atomic.Int32\n}\n\nfunc NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {\n\truleSet := &LocalRuleSet{\n\t\tctx:        ctx,\n\t\tlogger:     logger,\n\t\ttag:        options.Tag,\n\t\tfileFormat: options.Format,\n\t}\n\tif options.Type == C.RuleSetTypeInline {\n\t\tif len(options.InlineOptions.Rules) == 0 {\n\t\t\treturn nil, E.New(\"empty inline rule-set\")\n\t\t}\n\t\terr := ruleSet.reloadRules(options.InlineOptions.Rules)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tfilePath := filemanager.BasePath(ctx, options.LocalOptions.Path)\n\t\tfilePath, _ = filepath.Abs(filePath)\n\t\terr := ruleSet.reloadFile(filePath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\twatcher, err := fswatch.NewWatcher(fswatch.Options{\n\t\t\tPath: []string{filePath},\n\t\t\tCallback: func(path string) {\n\t\t\t\tuErr := ruleSet.reloadFile(path)\n\t\t\t\tif uErr != nil {\n\t\t\t\t\tlogger.Error(E.Cause(uErr, \"reload rule-set \", options.Tag))\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\truleSet.watcher = watcher\n\t}\n\treturn ruleSet, nil\n}\n\nfunc (s *LocalRuleSet) Name() string {\n\treturn s.tag\n}\n\nfunc (s *LocalRuleSet) String() string {\n\treturn strings.Join(F.MapToString(s.rules), \" \")\n}\n\nfunc (s *LocalRuleSet) StartContext(ctx context.Context, startContext *adapter.HTTPStartContext) error {\n\tif s.watcher != nil {\n\t\terr := s.watcher.Start()\n\t\tif err != nil {\n\t\t\ts.logger.Error(E.Cause(err, \"watch rule-set file\"))\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *LocalRuleSet) reloadFile(path string) error {\n\tvar ruleSet option.PlainRuleSetCompat\n\tswitch s.fileFormat {\n\tcase C.RuleSetFormatSource, \"\":\n\t\tcontent, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\truleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\tcase C.RuleSetFormatBinary:\n\t\tsetFile, err := os.Open(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\truleSet, err = srs.Read(setFile, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\tdefault:\n\t\treturn E.New(\"unknown rule-set format: \", s.fileFormat)\n\t}\n\tplainRuleSet, err := ruleSet.Upgrade()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn s.reloadRules(plainRuleSet.Rules)\n}\n\nfunc (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {\n\trules := make([]adapter.HeadlessRule, len(headlessRules))\n\tvar err error\n\tfor i, ruleOptions := range headlessRules {\n\t\trules[i], err = NewHeadlessRule(s.ctx, ruleOptions)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"parse rule_set.rules.[\", i, \"]\")\n\t\t}\n\t}\n\tvar metadata adapter.RuleSetMetadata\n\tmetadata.ContainsProcessRule = HasHeadlessRule(headlessRules, isProcessHeadlessRule)\n\tmetadata.ContainsWIFIRule = HasHeadlessRule(headlessRules, isWIFIHeadlessRule)\n\tmetadata.ContainsIPCIDRRule = HasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)\n\ts.access.Lock()\n\ts.rules = rules\n\ts.metadata = metadata\n\tcallbacks := s.callbacks.Array()\n\ts.access.Unlock()\n\tfor _, callback := range callbacks {\n\t\tcallback(s)\n\t}\n\treturn nil\n}\n\nfunc (s *LocalRuleSet) PostStart() error {\n\treturn nil\n}\n\nfunc (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata {\n\ts.access.RLock()\n\tdefer s.access.RUnlock()\n\treturn s.metadata\n}\n\nfunc (s *LocalRuleSet) ExtractIPSet() []*netipx.IPSet {\n\ts.access.RLock()\n\tdefer s.access.RUnlock()\n\treturn common.FlatMap(s.rules, extractIPSetFromRule)\n}\n\nfunc (s *LocalRuleSet) IncRef() {\n\ts.refs.Add(1)\n}\n\nfunc (s *LocalRuleSet) DecRef() {\n\tif s.refs.Add(-1) < 0 {\n\t\tpanic(\"rule-set: negative refs\")\n\t}\n}\n\nfunc (s *LocalRuleSet) Cleanup() {\n\tif s.refs.Load() == 0 {\n\t\ts.rules = nil\n\t}\n}\n\nfunc (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {\n\ts.access.Lock()\n\tdefer s.access.Unlock()\n\treturn s.callbacks.PushBack(callback)\n}\n\nfunc (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {\n\ts.access.Lock()\n\tdefer s.access.Unlock()\n\ts.callbacks.Remove(element)\n}\n\nfunc (s *LocalRuleSet) Close() error {\n\ts.rules = nil\n\treturn common.Close(common.PtrOrNil(s.watcher))\n}\n\nfunc (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {\n\tfor _, rule := range s.rules {\n\t\tif rule.Match(metadata) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "route/rule/rule_set_remote.go",
    "content": "package rule\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/srs\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/pause\"\n\n\t\"go4.org/netipx\"\n)\n\nvar _ adapter.RuleSet = (*RemoteRuleSet)(nil)\n\ntype RemoteRuleSet struct {\n\tctx            context.Context\n\tcancel         context.CancelFunc\n\tlogger         logger.ContextLogger\n\toutbound       adapter.OutboundManager\n\toptions        option.RuleSet\n\tupdateInterval time.Duration\n\tdialer         N.Dialer\n\taccess         sync.RWMutex\n\trules          []adapter.HeadlessRule\n\tmetadata       adapter.RuleSetMetadata\n\tlastUpdated    time.Time\n\tlastEtag       string\n\tupdateTicker   *time.Ticker\n\tcacheFile      adapter.CacheFile\n\tpauseManager   pause.Manager\n\tcallbacks      list.List[adapter.RuleSetUpdateCallback]\n\trefs           atomic.Int32\n}\n\nfunc NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet {\n\tctx, cancel := context.WithCancel(ctx)\n\tvar updateInterval time.Duration\n\tif options.RemoteOptions.UpdateInterval > 0 {\n\t\tupdateInterval = time.Duration(options.RemoteOptions.UpdateInterval)\n\t} else {\n\t\tupdateInterval = 24 * time.Hour\n\t}\n\treturn &RemoteRuleSet{\n\t\tctx:            ctx,\n\t\tcancel:         cancel,\n\t\toutbound:       service.FromContext[adapter.OutboundManager](ctx),\n\t\tlogger:         logger,\n\t\toptions:        options,\n\t\tupdateInterval: updateInterval,\n\t\tpauseManager:   service.FromContext[pause.Manager](ctx),\n\t}\n}\n\nfunc (s *RemoteRuleSet) Name() string {\n\treturn s.options.Tag\n}\n\nfunc (s *RemoteRuleSet) String() string {\n\treturn strings.Join(F.MapToString(s.rules), \" \")\n}\n\nfunc (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.HTTPStartContext) error {\n\ts.cacheFile = service.FromContext[adapter.CacheFile](s.ctx)\n\tvar dialer N.Dialer\n\tif s.options.RemoteOptions.DownloadDetour != \"\" {\n\t\toutbound, loaded := s.outbound.Outbound(s.options.RemoteOptions.DownloadDetour)\n\t\tif !loaded {\n\t\t\treturn E.New(\"download detour not found: \", s.options.RemoteOptions.DownloadDetour)\n\t\t}\n\t\tdialer = outbound\n\t} else {\n\t\tdialer = s.outbound.Default()\n\t}\n\ts.dialer = dialer\n\tif s.cacheFile != nil {\n\t\tif savedSet := s.cacheFile.LoadRuleSet(s.options.Tag); savedSet != nil {\n\t\t\terr := s.loadBytes(savedSet.Content)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"restore cached rule-set\")\n\t\t\t}\n\t\t\ts.lastUpdated = savedSet.LastUpdated\n\t\t\ts.lastEtag = savedSet.LastEtag\n\t\t}\n\t}\n\tif s.lastUpdated.IsZero() {\n\t\terr := s.fetch(ctx, startContext)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"initial rule-set: \", s.options.Tag)\n\t\t}\n\t}\n\ts.updateTicker = time.NewTicker(s.updateInterval)\n\treturn nil\n}\n\nfunc (s *RemoteRuleSet) PostStart() error {\n\tgo s.loopUpdate()\n\treturn nil\n}\n\nfunc (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {\n\ts.access.RLock()\n\tdefer s.access.RUnlock()\n\treturn s.metadata\n}\n\nfunc (s *RemoteRuleSet) ExtractIPSet() []*netipx.IPSet {\n\ts.access.RLock()\n\tdefer s.access.RUnlock()\n\treturn common.FlatMap(s.rules, extractIPSetFromRule)\n}\n\nfunc (s *RemoteRuleSet) IncRef() {\n\ts.refs.Add(1)\n}\n\nfunc (s *RemoteRuleSet) DecRef() {\n\tif s.refs.Add(-1) < 0 {\n\t\tpanic(\"rule-set: negative refs\")\n\t}\n}\n\nfunc (s *RemoteRuleSet) Cleanup() {\n\tif s.refs.Load() == 0 {\n\t\ts.rules = nil\n\t}\n}\n\nfunc (s *RemoteRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {\n\ts.access.Lock()\n\tdefer s.access.Unlock()\n\treturn s.callbacks.PushBack(callback)\n}\n\nfunc (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {\n\ts.access.Lock()\n\tdefer s.access.Unlock()\n\ts.callbacks.Remove(element)\n}\n\nfunc (s *RemoteRuleSet) loadBytes(content []byte) error {\n\tvar (\n\t\truleSet option.PlainRuleSetCompat\n\t\terr     error\n\t)\n\tswitch s.options.Format {\n\tcase C.RuleSetFormatSource:\n\t\truleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\tcase C.RuleSetFormatBinary:\n\t\truleSet, err = srs.Read(bytes.NewReader(content), false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\tdefault:\n\t\treturn E.New(\"unknown rule-set format: \", s.options.Format)\n\t}\n\tplainRuleSet, err := ruleSet.Upgrade()\n\tif err != nil {\n\t\treturn err\n\t}\n\trules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules))\n\tfor i, ruleOptions := range plainRuleSet.Rules {\n\t\trules[i], err = NewHeadlessRule(s.ctx, ruleOptions)\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"parse rule_set.rules.[\", i, \"]\")\n\t\t}\n\t}\n\ts.access.Lock()\n\ts.metadata.ContainsProcessRule = HasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)\n\ts.metadata.ContainsWIFIRule = HasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)\n\ts.metadata.ContainsIPCIDRRule = HasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)\n\ts.rules = rules\n\tcallbacks := s.callbacks.Array()\n\ts.access.Unlock()\n\tfor _, callback := range callbacks {\n\t\tcallback(s)\n\t}\n\treturn nil\n}\n\nfunc (s *RemoteRuleSet) loopUpdate() {\n\tif time.Since(s.lastUpdated) > s.updateInterval {\n\t\terr := s.fetch(s.ctx, nil)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"fetch rule-set \", s.options.Tag, \": \", err)\n\t\t} else if s.refs.Load() == 0 {\n\t\t\ts.rules = nil\n\t\t}\n\t}\n\tfor {\n\t\truntime.GC()\n\t\tselect {\n\t\tcase <-s.ctx.Done():\n\t\t\treturn\n\t\tcase <-s.updateTicker.C:\n\t\t\ts.updateOnce()\n\t\t}\n\t}\n}\n\nfunc (s *RemoteRuleSet) updateOnce() {\n\terr := s.fetch(s.ctx, nil)\n\tif err != nil {\n\t\ts.logger.Error(\"fetch rule-set \", s.options.Tag, \": \", err)\n\t} else if s.refs.Load() == 0 {\n\t\ts.rules = nil\n\t}\n}\n\nfunc (s *RemoteRuleSet) fetch(ctx context.Context, startContext *adapter.HTTPStartContext) error {\n\ts.logger.Debug(\"updating rule-set \", s.options.Tag, \" from URL: \", s.options.RemoteOptions.URL)\n\tvar httpClient *http.Client\n\tif startContext != nil {\n\t\thttpClient = startContext.HTTPClient(s.options.RemoteOptions.DownloadDetour, s.dialer)\n\t} else {\n\t\thttpClient = &http.Client{\n\t\t\tTransport: &http.Transport{\n\t\t\t\tForceAttemptHTTP2:   true,\n\t\t\t\tTLSHandshakeTimeout: C.TCPTimeout,\n\t\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\t\treturn s.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t\t\t\t},\n\t\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\t\tTime:    ntp.TimeFuncFromContext(s.ctx),\n\t\t\t\t\tRootCAs: adapter.RootPoolFromContext(s.ctx),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\trequest, err := http.NewRequest(\"GET\", s.options.RemoteOptions.URL, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif s.lastEtag != \"\" {\n\t\trequest.Header.Set(\"If-None-Match\", s.lastEtag)\n\t}\n\tresponse, err := httpClient.Do(request.WithContext(ctx))\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch response.StatusCode {\n\tcase http.StatusOK:\n\tcase http.StatusNotModified:\n\t\ts.lastUpdated = time.Now()\n\t\tif s.cacheFile != nil {\n\t\t\tsavedRuleSet := s.cacheFile.LoadRuleSet(s.options.Tag)\n\t\t\tif savedRuleSet != nil {\n\t\t\t\tsavedRuleSet.LastUpdated = s.lastUpdated\n\t\t\t\terr = s.cacheFile.SaveRuleSet(s.options.Tag, savedRuleSet)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.logger.Error(\"save rule-set updated time: \", err)\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Info(\"update rule-set \", s.options.Tag, \": not modified\")\n\t\treturn nil\n\tdefault:\n\t\treturn E.New(\"unexpected status: \", response.Status)\n\t}\n\tcontent, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\tresponse.Body.Close()\n\t\treturn err\n\t}\n\terr = s.loadBytes(content)\n\tif err != nil {\n\t\tresponse.Body.Close()\n\t\treturn err\n\t}\n\tresponse.Body.Close()\n\teTagHeader := response.Header.Get(\"Etag\")\n\tif eTagHeader != \"\" {\n\t\ts.lastEtag = eTagHeader\n\t}\n\ts.lastUpdated = time.Now()\n\tif s.cacheFile != nil {\n\t\terr = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedBinary{\n\t\t\tLastUpdated: s.lastUpdated,\n\t\t\tContent:     content,\n\t\t\tLastEtag:    s.lastEtag,\n\t\t})\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"save rule-set cache: \", err)\n\t\t}\n\t}\n\ts.logger.Info(\"updated rule-set \", s.options.Tag)\n\treturn nil\n}\n\nfunc (s *RemoteRuleSet) Close() error {\n\ts.rules = nil\n\ts.cancel()\n\tif s.updateTicker != nil {\n\t\ts.updateTicker.Stop()\n\t}\n\treturn nil\n}\n\nfunc (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {\n\tfor _, rule := range s.rules {\n\t\tif rule.Match(metadata) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "route/rule_conds.go",
    "content": "package route\n\nimport (\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n)\n\nfunc hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {\n\tfor _, rule := range rules {\n\t\tswitch rule.Type {\n\t\tcase C.RuleTypeDefault:\n\t\t\tif cond(rule.DefaultOptions) {\n\t\t\t\treturn true\n\t\t\t}\n\t\tcase C.RuleTypeLogical:\n\t\t\tif hasRule(rule.LogicalOptions.Rules, cond) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {\n\tfor _, rule := range rules {\n\t\tswitch rule.Type {\n\t\tcase C.RuleTypeDefault:\n\t\t\tif cond(rule.DefaultOptions) {\n\t\t\t\treturn true\n\t\t\t}\n\t\tcase C.RuleTypeLogical:\n\t\t\tif hasDNSRule(rule.LogicalOptions.Rules, cond) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc isProcessRule(rule option.DefaultRule) bool {\n\treturn len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0\n}\n\nfunc isProcessDNSRule(rule option.DefaultDNSRule) bool {\n\treturn len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0\n}\n\nfunc isNeighborRule(rule option.DefaultRule) bool {\n\treturn len(rule.SourceMACAddress) > 0 || len(rule.SourceHostname) > 0\n}\n\nfunc isNeighborDNSRule(rule option.DefaultDNSRule) bool {\n\treturn len(rule.SourceMACAddress) > 0 || len(rule.SourceHostname) > 0\n}\n\nfunc isWIFIRule(rule option.DefaultRule) bool {\n\treturn len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0\n}\n\nfunc isWIFIDNSRule(rule option.DefaultDNSRule) bool {\n\treturn len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0\n}\n"
  },
  {
    "path": "service/ccm/credential.go",
    "content": "package ccm\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"time\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nconst (\n\toauth2ClientID          = \"9d1c250a-e61b-44d9-88ed-5944d1962f5e\"\n\toauth2TokenURL          = \"https://console.anthropic.com/v1/oauth/token\"\n\tclaudeAPIBaseURL        = \"https://api.anthropic.com\"\n\ttokenRefreshBufferMs    = 60000\n\tanthropicBetaOAuthValue = \"oauth-2025-04-20\"\n)\n\nfunc getRealUser() (*user.User, error) {\n\tif sudoUser := os.Getenv(\"SUDO_USER\"); sudoUser != \"\" {\n\t\tsudoUserInfo, err := user.Lookup(sudoUser)\n\t\tif err == nil {\n\t\t\treturn sudoUserInfo, nil\n\t\t}\n\t}\n\treturn user.Current()\n}\n\nfunc getDefaultCredentialsPath() (string, error) {\n\tif configDir := os.Getenv(\"CLAUDE_CONFIG_DIR\"); configDir != \"\" {\n\t\treturn filepath.Join(configDir, \".credentials.json\"), nil\n\t}\n\tuserInfo, err := getRealUser()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filepath.Join(userInfo.HomeDir, \".claude\", \".credentials.json\"), nil\n}\n\nfunc readCredentialsFromFile(path string) (*oauthCredentials, error) {\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar credentialsContainer struct {\n\t\tClaudeAIAuth *oauthCredentials `json:\"claudeAiOauth,omitempty\"`\n\t}\n\terr = json.Unmarshal(data, &credentialsContainer)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif credentialsContainer.ClaudeAIAuth == nil {\n\t\treturn nil, E.New(\"claudeAiOauth field not found in credentials\")\n\t}\n\treturn credentialsContainer.ClaudeAIAuth, nil\n}\n\nfunc writeCredentialsToFile(oauthCredentials *oauthCredentials, path string) error {\n\tdata, err := json.MarshalIndent(map[string]any{\n\t\t\"claudeAiOauth\": oauthCredentials,\n\t}, \"\", \"  \")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn os.WriteFile(path, data, 0o600)\n}\n\ntype oauthCredentials struct {\n\tAccessToken      string   `json:\"accessToken\"`\n\tRefreshToken     string   `json:\"refreshToken\"`\n\tExpiresAt        int64    `json:\"expiresAt\"`\n\tScopes           []string `json:\"scopes,omitempty\"`\n\tSubscriptionType string   `json:\"subscriptionType,omitempty\"`\n\tIsMax            bool     `json:\"isMax,omitempty\"`\n}\n\nfunc (c *oauthCredentials) needsRefresh() bool {\n\tif c.ExpiresAt == 0 {\n\t\treturn false\n\t}\n\treturn time.Now().UnixMilli() >= c.ExpiresAt-tokenRefreshBufferMs\n}\n\nfunc refreshToken(httpClient *http.Client, credentials *oauthCredentials) (*oauthCredentials, error) {\n\tif credentials.RefreshToken == \"\" {\n\t\treturn nil, E.New(\"refresh token is empty\")\n\t}\n\n\trequestBody, err := json.Marshal(map[string]string{\n\t\t\"grant_type\":    \"refresh_token\",\n\t\t\"refresh_token\": credentials.RefreshToken,\n\t\t\"client_id\":     oauth2ClientID,\n\t})\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"marshal request\")\n\t}\n\n\trequest, err := http.NewRequest(\"POST\", oauth2TokenURL, bytes.NewReader(requestBody))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trequest.Header.Set(\"Content-Type\", \"application/json\")\n\trequest.Header.Set(\"Accept\", \"application/json\")\n\n\tresponse, err := httpClient.Do(request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer response.Body.Close()\n\n\tif response.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(response.Body)\n\t\treturn nil, E.New(\"refresh failed: \", response.Status, \" \", string(body))\n\t}\n\n\tvar tokenResponse struct {\n\t\tAccessToken  string `json:\"access_token\"`\n\t\tRefreshToken string `json:\"refresh_token\"`\n\t\tExpiresIn    int    `json:\"expires_in\"`\n\t}\n\terr = json.NewDecoder(response.Body).Decode(&tokenResponse)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"decode response\")\n\t}\n\n\tnewCredentials := *credentials\n\tnewCredentials.AccessToken = tokenResponse.AccessToken\n\tif tokenResponse.RefreshToken != \"\" {\n\t\tnewCredentials.RefreshToken = tokenResponse.RefreshToken\n\t}\n\tnewCredentials.ExpiresAt = time.Now().UnixMilli() + int64(tokenResponse.ExpiresIn)*1000\n\n\treturn &newCredentials, nil\n}\n"
  },
  {
    "path": "service/ccm/credential_darwin.go",
    "content": "//go:build darwin && cgo\n\npackage ccm\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"github.com/keybase/go-keychain\"\n)\n\nfunc getKeychainServiceName() string {\n\tconfigDirectory := os.Getenv(\"CLAUDE_CONFIG_DIR\")\n\tif configDirectory == \"\" {\n\t\treturn \"Claude Code-credentials\"\n\t}\n\n\tuserInfo, err := getRealUser()\n\tif err != nil {\n\t\treturn \"Claude Code-credentials\"\n\t}\n\tdefaultConfigDirectory := filepath.Join(userInfo.HomeDir, \".claude\")\n\tif configDirectory == defaultConfigDirectory {\n\t\treturn \"Claude Code-credentials\"\n\t}\n\n\thash := sha256.Sum256([]byte(configDirectory))\n\treturn \"Claude Code-credentials-\" + hex.EncodeToString(hash[:])[:8]\n}\n\nfunc platformReadCredentials(customPath string) (*oauthCredentials, error) {\n\tif customPath != \"\" {\n\t\treturn readCredentialsFromFile(customPath)\n\t}\n\n\tuserInfo, err := getRealUser()\n\tif err == nil {\n\t\tquery := keychain.NewItem()\n\t\tquery.SetSecClass(keychain.SecClassGenericPassword)\n\t\tquery.SetService(getKeychainServiceName())\n\t\tquery.SetAccount(userInfo.Username)\n\t\tquery.SetMatchLimit(keychain.MatchLimitOne)\n\t\tquery.SetReturnData(true)\n\n\t\tresults, err := keychain.QueryItem(query)\n\t\tif err == nil && len(results) == 1 {\n\t\t\tvar container struct {\n\t\t\t\tClaudeAIAuth *oauthCredentials `json:\"claudeAiOauth,omitempty\"`\n\t\t\t}\n\t\t\tunmarshalErr := json.Unmarshal(results[0].Data, &container)\n\t\t\tif unmarshalErr == nil && container.ClaudeAIAuth != nil {\n\t\t\t\treturn container.ClaudeAIAuth, nil\n\t\t\t}\n\t\t}\n\t\tif err != nil && err != keychain.ErrorItemNotFound {\n\t\t\treturn nil, E.Cause(err, \"query keychain\")\n\t\t}\n\t}\n\n\tdefaultPath, err := getDefaultCredentialsPath()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn readCredentialsFromFile(defaultPath)\n}\n\nfunc platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error {\n\tif customPath != \"\" {\n\t\treturn writeCredentialsToFile(oauthCredentials, customPath)\n\t}\n\n\tuserInfo, err := getRealUser()\n\tif err == nil {\n\t\tdata, err := json.Marshal(map[string]any{\"claudeAiOauth\": oauthCredentials})\n\t\tif err == nil {\n\t\t\tserviceName := getKeychainServiceName()\n\t\t\titem := keychain.NewItem()\n\t\t\titem.SetSecClass(keychain.SecClassGenericPassword)\n\t\t\titem.SetService(serviceName)\n\t\t\titem.SetAccount(userInfo.Username)\n\t\t\titem.SetData(data)\n\t\t\titem.SetAccessible(keychain.AccessibleWhenUnlocked)\n\n\t\t\terr = keychain.AddItem(item)\n\t\t\tif err == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif err == keychain.ErrorDuplicateItem {\n\t\t\t\tquery := keychain.NewItem()\n\t\t\t\tquery.SetSecClass(keychain.SecClassGenericPassword)\n\t\t\t\tquery.SetService(serviceName)\n\t\t\t\tquery.SetAccount(userInfo.Username)\n\n\t\t\t\tupdateItem := keychain.NewItem()\n\t\t\t\tupdateItem.SetData(data)\n\n\t\t\t\tupdateErr := keychain.UpdateItem(query, updateItem)\n\t\t\t\tif updateErr == nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tdefaultPath, err := getDefaultCredentialsPath()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn writeCredentialsToFile(oauthCredentials, defaultPath)\n}\n"
  },
  {
    "path": "service/ccm/credential_other.go",
    "content": "//go:build !darwin\n\npackage ccm\n\nfunc platformReadCredentials(customPath string) (*oauthCredentials, error) {\n\tif customPath == \"\" {\n\t\tvar err error\n\t\tcustomPath, err = getDefaultCredentialsPath()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn readCredentialsFromFile(customPath)\n}\n\nfunc platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error {\n\tif customPath == \"\" {\n\t\tvar err error\n\t\tcustomPath, err = getDefaultCredentialsPath()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn writeCredentialsToFile(oauthCredentials, customPath)\n}\n"
  },
  {
    "path": "service/ccm/service.go",
    "content": "package ccm\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\tstdTLS \"crypto/tls\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"mime\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tboxService \"github.com/sagernet/sing-box/adapter/service\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\n\t\"github.com/anthropics/anthropic-sdk-go\"\n\t\"github.com/go-chi/chi/v5\"\n\t\"golang.org/x/net/http2\"\n)\n\nconst (\n\tcontextWindowStandard   = 200000\n\tcontextWindowPremium    = 1000000\n\tpremiumContextThreshold = 200000\n)\n\nfunc RegisterService(registry *boxService.Registry) {\n\tboxService.Register[option.CCMServiceOptions](registry, C.TypeCCM, NewService)\n}\n\ntype errorResponse struct {\n\tType      string       `json:\"type\"`\n\tError     errorDetails `json:\"error\"`\n\tRequestID string       `json:\"request_id,omitempty\"`\n}\n\ntype errorDetails struct {\n\tType    string `json:\"type\"`\n\tMessage string `json:\"message\"`\n}\n\nfunc writeJSONError(w http.ResponseWriter, r *http.Request, statusCode int, errorType string, message string) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.WriteHeader(statusCode)\n\n\tjson.NewEncoder(w).Encode(errorResponse{\n\t\tType: \"error\",\n\t\tError: errorDetails{\n\t\t\tType:    errorType,\n\t\t\tMessage: message,\n\t\t},\n\t\tRequestID: r.Header.Get(\"Request-Id\"),\n\t})\n}\n\nfunc isHopByHopHeader(header string) bool {\n\tswitch strings.ToLower(header) {\n\tcase \"connection\", \"keep-alive\", \"proxy-authenticate\", \"proxy-authorization\", \"te\", \"trailers\", \"transfer-encoding\", \"upgrade\", \"host\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nconst (\n\tweeklyWindowSeconds = 604800\n\tweeklyWindowMinutes = weeklyWindowSeconds / 60\n)\n\nfunc parseInt64Header(headers http.Header, headerName string) (int64, bool) {\n\theaderValue := strings.TrimSpace(headers.Get(headerName))\n\tif headerValue == \"\" {\n\t\treturn 0, false\n\t}\n\tparsedValue, parseError := strconv.ParseInt(headerValue, 10, 64)\n\tif parseError != nil {\n\t\treturn 0, false\n\t}\n\treturn parsedValue, true\n}\n\nfunc extractWeeklyCycleHint(headers http.Header) *WeeklyCycleHint {\n\tresetAtUnix, hasResetAt := parseInt64Header(headers, \"anthropic-ratelimit-unified-7d-reset\")\n\tif !hasResetAt || resetAtUnix <= 0 {\n\t\treturn nil\n\t}\n\n\treturn &WeeklyCycleHint{\n\t\tWindowMinutes: weeklyWindowMinutes,\n\t\tResetAt:       time.Unix(resetAtUnix, 0).UTC(),\n\t}\n}\n\ntype Service struct {\n\tboxService.Adapter\n\tctx            context.Context\n\tlogger         log.ContextLogger\n\tcredentialPath string\n\tcredentials    *oauthCredentials\n\tusers          []option.CCMUser\n\thttpClient     *http.Client\n\thttpHeaders    http.Header\n\tlistener       *listener.Listener\n\ttlsConfig      tls.ServerConfig\n\thttpServer     *http.Server\n\tuserManager    *UserManager\n\taccessMutex    sync.RWMutex\n\tusageTracker   *AggregatedUsage\n\ttrackingGroup  sync.WaitGroup\n\tshuttingDown   bool\n}\n\nfunc NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) {\n\tserviceDialer, err := dialer.NewWithOptions(dialer.Options{\n\t\tContext: ctx,\n\t\tOptions: option.DialerOptions{\n\t\t\tDetour: options.Detour,\n\t\t},\n\t\tRemoteIsDomain: true,\n\t})\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"create dialer\")\n\t}\n\n\thttpClient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tForceAttemptHTTP2: true,\n\t\t\tTLSClientConfig: &stdTLS.Config{\n\t\t\t\tRootCAs: adapter.RootPoolFromContext(ctx),\n\t\t\t\tTime:    ntp.TimeFuncFromContext(ctx),\n\t\t\t},\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\treturn serviceDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t\t\t},\n\t\t},\n\t}\n\n\tuserManager := &UserManager{\n\t\ttokenMap: make(map[string]string),\n\t}\n\n\tvar usageTracker *AggregatedUsage\n\tif options.UsagesPath != \"\" {\n\t\tusageTracker = &AggregatedUsage{\n\t\t\tLastUpdated:  time.Now(),\n\t\t\tCombinations: make([]CostCombination, 0),\n\t\t\tfilePath:     options.UsagesPath,\n\t\t\tlogger:       logger,\n\t\t}\n\t}\n\n\tservice := &Service{\n\t\tAdapter:        boxService.NewAdapter(C.TypeCCM, tag),\n\t\tctx:            ctx,\n\t\tlogger:         logger,\n\t\tcredentialPath: options.CredentialPath,\n\t\tusers:          options.Users,\n\t\thttpClient:     httpClient,\n\t\thttpHeaders:    options.Headers.Build(),\n\t\tlistener: listener.New(listener.Options{\n\t\t\tContext: ctx,\n\t\t\tLogger:  logger,\n\t\t\tNetwork: []string{N.NetworkTCP},\n\t\t\tListen:  options.ListenOptions,\n\t\t}),\n\t\tuserManager:  userManager,\n\t\tusageTracker: usageTracker,\n\t}\n\n\tif options.TLS != nil {\n\t\ttlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tservice.tlsConfig = tlsConfig\n\t}\n\n\treturn service, nil\n}\n\nfunc (s *Service) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\n\ts.userManager.UpdateUsers(s.users)\n\n\tcredentials, err := platformReadCredentials(s.credentialPath)\n\tif err != nil {\n\t\treturn E.Cause(err, \"read credentials\")\n\t}\n\ts.credentials = credentials\n\n\tif s.usageTracker != nil {\n\t\terr = s.usageTracker.Load()\n\t\tif err != nil {\n\t\t\ts.logger.Warn(\"load usage statistics: \", err)\n\t\t}\n\t}\n\n\trouter := chi.NewRouter()\n\trouter.Mount(\"/\", s)\n\n\ts.httpServer = &http.Server{Handler: router}\n\n\tif s.tlsConfig != nil {\n\t\terr = s.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"create TLS config\")\n\t\t}\n\t}\n\n\ttcpListener, err := s.listener.ListenTCP()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif s.tlsConfig != nil {\n\t\tif !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {\n\t\t\ts.tlsConfig.SetNextProtos(append([]string{\"h2\"}, s.tlsConfig.NextProtos()...))\n\t\t}\n\t\ttcpListener = aTLS.NewListener(tcpListener, s.tlsConfig)\n\t}\n\n\tgo func() {\n\t\tserveErr := s.httpServer.Serve(tcpListener)\n\t\tif serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) {\n\t\t\ts.logger.Error(\"serve error: \", serveErr)\n\t\t}\n\t}()\n\n\treturn nil\n}\n\nfunc (s *Service) getAccessToken() (string, error) {\n\ts.accessMutex.RLock()\n\tif !s.credentials.needsRefresh() {\n\t\ttoken := s.credentials.AccessToken\n\t\ts.accessMutex.RUnlock()\n\t\treturn token, nil\n\t}\n\ts.accessMutex.RUnlock()\n\n\ts.accessMutex.Lock()\n\tdefer s.accessMutex.Unlock()\n\n\tif !s.credentials.needsRefresh() {\n\t\treturn s.credentials.AccessToken, nil\n\t}\n\n\tnewCredentials, err := refreshToken(s.httpClient, s.credentials)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\ts.credentials = newCredentials\n\n\terr = platformWriteCredentials(newCredentials, s.credentialPath)\n\tif err != nil {\n\t\ts.logger.Warn(\"persist refreshed token: \", err)\n\t}\n\n\treturn newCredentials.AccessToken, nil\n}\n\nfunc detectContextWindow(betaHeader string, totalInputTokens int64) int {\n\tif totalInputTokens > premiumContextThreshold {\n\t\tfeatures := strings.Split(betaHeader, \",\")\n\t\tfor _, feature := range features {\n\t\t\tif strings.HasPrefix(strings.TrimSpace(feature), \"context-1m\") {\n\t\t\t\treturn contextWindowPremium\n\t\t\t}\n\t\t}\n\t}\n\treturn contextWindowStandard\n}\n\nfunc (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tif !strings.HasPrefix(r.URL.Path, \"/v1/\") {\n\t\twriteJSONError(w, r, http.StatusNotFound, \"not_found_error\", \"Not found\")\n\t\treturn\n\t}\n\n\tvar username string\n\tif len(s.users) > 0 {\n\t\tauthHeader := r.Header.Get(\"Authorization\")\n\t\tif authHeader == \"\" {\n\t\t\ts.logger.Warn(\"authentication failed for request from \", r.RemoteAddr, \": missing Authorization header\")\n\t\t\twriteJSONError(w, r, http.StatusUnauthorized, \"authentication_error\", \"missing api key\")\n\t\t\treturn\n\t\t}\n\t\tclientToken := strings.TrimPrefix(authHeader, \"Bearer \")\n\t\tif clientToken == authHeader {\n\t\t\ts.logger.Warn(\"authentication failed for request from \", r.RemoteAddr, \": invalid Authorization format\")\n\t\t\twriteJSONError(w, r, http.StatusUnauthorized, \"authentication_error\", \"invalid api key format\")\n\t\t\treturn\n\t\t}\n\t\tvar ok bool\n\t\tusername, ok = s.userManager.Authenticate(clientToken)\n\t\tif !ok {\n\t\t\ts.logger.Warn(\"authentication failed for request from \", r.RemoteAddr, \": unknown key: \", clientToken)\n\t\t\twriteJSONError(w, r, http.StatusUnauthorized, \"authentication_error\", \"invalid api key\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tvar requestModel string\n\tvar messagesCount int\n\n\tif s.usageTracker != nil && r.Body != nil {\n\t\tbodyBytes, err := io.ReadAll(r.Body)\n\t\tif err == nil {\n\t\t\tvar request struct {\n\t\t\t\tModel    string                   `json:\"model\"`\n\t\t\t\tMessages []anthropic.MessageParam `json:\"messages\"`\n\t\t\t}\n\t\t\terr := json.Unmarshal(bodyBytes, &request)\n\t\t\tif err == nil {\n\t\t\t\trequestModel = request.Model\n\t\t\t\tmessagesCount = len(request.Messages)\n\t\t\t}\n\t\t\tr.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))\n\t\t}\n\t}\n\n\taccessToken, err := s.getAccessToken()\n\tif err != nil {\n\t\ts.logger.Error(\"get access token: \", err)\n\t\twriteJSONError(w, r, http.StatusUnauthorized, \"authentication_error\", \"Authentication failed\")\n\t\treturn\n\t}\n\n\tproxyURL := claudeAPIBaseURL + r.URL.RequestURI()\n\tproxyRequest, err := http.NewRequestWithContext(r.Context(), r.Method, proxyURL, r.Body)\n\tif err != nil {\n\t\ts.logger.Error(\"create proxy request: \", err)\n\t\twriteJSONError(w, r, http.StatusInternalServerError, \"api_error\", \"Internal server error\")\n\t\treturn\n\t}\n\n\tfor key, values := range r.Header {\n\t\tif !isHopByHopHeader(key) && key != \"Authorization\" {\n\t\t\tproxyRequest.Header[key] = values\n\t\t}\n\t}\n\n\tserviceOverridesAcceptEncoding := len(s.httpHeaders.Values(\"Accept-Encoding\")) > 0\n\tif s.usageTracker != nil && !serviceOverridesAcceptEncoding {\n\t\t// Strip Accept-Encoding so Go Transport adds it automatically\n\t\t// and transparently decompresses the response for correct usage counting.\n\t\tproxyRequest.Header.Del(\"Accept-Encoding\")\n\t}\n\n\tanthropicBetaHeader := proxyRequest.Header.Get(\"anthropic-beta\")\n\tif anthropicBetaHeader != \"\" {\n\t\tproxyRequest.Header.Set(\"anthropic-beta\", anthropicBetaOAuthValue+\",\"+anthropicBetaHeader)\n\t} else {\n\t\tproxyRequest.Header.Set(\"anthropic-beta\", anthropicBetaOAuthValue)\n\t}\n\n\tfor key, values := range s.httpHeaders {\n\t\tproxyRequest.Header.Del(key)\n\t\tproxyRequest.Header[key] = values\n\t}\n\n\tproxyRequest.Header.Set(\"Authorization\", \"Bearer \"+accessToken)\n\n\tresponse, err := s.httpClient.Do(proxyRequest)\n\tif err != nil {\n\t\twriteJSONError(w, r, http.StatusBadGateway, \"api_error\", err.Error())\n\t\treturn\n\t}\n\tdefer response.Body.Close()\n\n\tfor key, values := range response.Header {\n\t\tif !isHopByHopHeader(key) {\n\t\t\tw.Header()[key] = values\n\t\t}\n\t}\n\tw.WriteHeader(response.StatusCode)\n\n\tif s.usageTracker != nil && response.StatusCode == http.StatusOK {\n\t\ts.handleResponseWithTracking(w, response, requestModel, anthropicBetaHeader, messagesCount, username)\n\t} else {\n\t\tmediaType, _, err := mime.ParseMediaType(response.Header.Get(\"Content-Type\"))\n\t\tif err == nil && mediaType != \"text/event-stream\" {\n\t\t\t_, _ = io.Copy(w, response.Body)\n\t\t\treturn\n\t\t}\n\t\tflusher, ok := w.(http.Flusher)\n\t\tif !ok {\n\t\t\ts.logger.Error(\"streaming not supported\")\n\t\t\treturn\n\t\t}\n\t\tbuffer := make([]byte, buf.BufferSize)\n\t\tfor {\n\t\t\tn, err := response.Body.Read(buffer)\n\t\t\tif n > 0 {\n\t\t\t\t_, writeError := w.Write(buffer[:n])\n\t\t\t\tif writeError != nil {\n\t\t\t\t\ts.logger.Error(\"write streaming response: \", writeError)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tflusher.Flush()\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, requestModel string, anthropicBetaHeader string, messagesCount int, username string) {\n\tweeklyCycleHint := extractWeeklyCycleHint(response.Header)\n\tmediaType, _, err := mime.ParseMediaType(response.Header.Get(\"Content-Type\"))\n\tisStreaming := err == nil && mediaType == \"text/event-stream\"\n\n\tif !isStreaming {\n\t\tbodyBytes, err := io.ReadAll(response.Body)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"read response body: \", err)\n\t\t\treturn\n\t\t}\n\n\t\tvar message anthropic.Message\n\t\tvar usage anthropic.Usage\n\t\tvar responseModel string\n\t\terr = json.Unmarshal(bodyBytes, &message)\n\t\tif err == nil {\n\t\t\tresponseModel = string(message.Model)\n\t\t\tusage = message.Usage\n\t\t}\n\t\tif responseModel == \"\" {\n\t\t\tresponseModel = requestModel\n\t\t}\n\n\t\tif usage.InputTokens > 0 || usage.OutputTokens > 0 {\n\t\t\tif responseModel != \"\" {\n\t\t\t\ttotalInputTokens := usage.InputTokens + usage.CacheCreationInputTokens + usage.CacheReadInputTokens\n\t\t\t\tcontextWindow := detectContextWindow(anthropicBetaHeader, totalInputTokens)\n\t\t\t\ts.usageTracker.AddUsageWithCycleHint(\n\t\t\t\t\tresponseModel,\n\t\t\t\t\tcontextWindow,\n\t\t\t\t\tmessagesCount,\n\t\t\t\t\tusage.InputTokens,\n\t\t\t\t\tusage.OutputTokens,\n\t\t\t\t\tusage.CacheReadInputTokens,\n\t\t\t\t\tusage.CacheCreationInputTokens,\n\t\t\t\t\tusage.CacheCreation.Ephemeral5mInputTokens,\n\t\t\t\t\tusage.CacheCreation.Ephemeral1hInputTokens,\n\t\t\t\t\tusername,\n\t\t\t\t\ttime.Now(),\n\t\t\t\t\tweeklyCycleHint,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t_, _ = writer.Write(bodyBytes)\n\t\treturn\n\t}\n\n\tflusher, ok := writer.(http.Flusher)\n\tif !ok {\n\t\ts.logger.Error(\"streaming not supported\")\n\t\treturn\n\t}\n\n\tvar accumulatedUsage anthropic.Usage\n\tvar responseModel string\n\tbuffer := make([]byte, buf.BufferSize)\n\tvar leftover []byte\n\n\tfor {\n\t\tn, err := response.Body.Read(buffer)\n\t\tif n > 0 {\n\t\t\tdata := append(leftover, buffer[:n]...)\n\t\t\tlines := bytes.Split(data, []byte(\"\\n\"))\n\n\t\t\tif err == nil {\n\t\t\t\tleftover = lines[len(lines)-1]\n\t\t\t\tlines = lines[:len(lines)-1]\n\t\t\t} else {\n\t\t\t\tleftover = nil\n\t\t\t}\n\n\t\t\tfor _, line := range lines {\n\t\t\t\tline = bytes.TrimSpace(line)\n\t\t\t\tif len(line) == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif bytes.HasPrefix(line, []byte(\"data: \")) {\n\t\t\t\t\teventData := bytes.TrimPrefix(line, []byte(\"data: \"))\n\t\t\t\t\tif bytes.Equal(eventData, []byte(\"[DONE]\")) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tvar event anthropic.MessageStreamEventUnion\n\t\t\t\t\terr := json.Unmarshal(eventData, &event)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch event.Type {\n\t\t\t\t\tcase \"message_start\":\n\t\t\t\t\t\tmessageStart := event.AsMessageStart()\n\t\t\t\t\t\tif messageStart.Message.Model != \"\" {\n\t\t\t\t\t\t\tresponseModel = string(messageStart.Message.Model)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif messageStart.Message.Usage.InputTokens > 0 {\n\t\t\t\t\t\t\taccumulatedUsage.InputTokens = messageStart.Message.Usage.InputTokens\n\t\t\t\t\t\t\taccumulatedUsage.CacheReadInputTokens = messageStart.Message.Usage.CacheReadInputTokens\n\t\t\t\t\t\t\taccumulatedUsage.CacheCreationInputTokens = messageStart.Message.Usage.CacheCreationInputTokens\n\t\t\t\t\t\t\taccumulatedUsage.CacheCreation.Ephemeral5mInputTokens = messageStart.Message.Usage.CacheCreation.Ephemeral5mInputTokens\n\t\t\t\t\t\t\taccumulatedUsage.CacheCreation.Ephemeral1hInputTokens = messageStart.Message.Usage.CacheCreation.Ephemeral1hInputTokens\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"message_delta\":\n\t\t\t\t\t\tmessageDelta := event.AsMessageDelta()\n\t\t\t\t\t\tif messageDelta.Usage.OutputTokens > 0 {\n\t\t\t\t\t\t\taccumulatedUsage.OutputTokens = messageDelta.Usage.OutputTokens\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t_, writeError := writer.Write(buffer[:n])\n\t\t\tif writeError != nil {\n\t\t\t\ts.logger.Error(\"write streaming response: \", writeError)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tflusher.Flush()\n\t\t}\n\n\t\tif err != nil {\n\t\t\tif responseModel == \"\" {\n\t\t\t\tresponseModel = requestModel\n\t\t\t}\n\n\t\t\tif accumulatedUsage.InputTokens > 0 || accumulatedUsage.OutputTokens > 0 {\n\t\t\t\tif responseModel != \"\" {\n\t\t\t\t\ttotalInputTokens := accumulatedUsage.InputTokens + accumulatedUsage.CacheCreationInputTokens + accumulatedUsage.CacheReadInputTokens\n\t\t\t\t\tcontextWindow := detectContextWindow(anthropicBetaHeader, totalInputTokens)\n\t\t\t\t\ts.usageTracker.AddUsageWithCycleHint(\n\t\t\t\t\t\tresponseModel,\n\t\t\t\t\t\tcontextWindow,\n\t\t\t\t\t\tmessagesCount,\n\t\t\t\t\t\taccumulatedUsage.InputTokens,\n\t\t\t\t\t\taccumulatedUsage.OutputTokens,\n\t\t\t\t\t\taccumulatedUsage.CacheReadInputTokens,\n\t\t\t\t\t\taccumulatedUsage.CacheCreationInputTokens,\n\t\t\t\t\t\taccumulatedUsage.CacheCreation.Ephemeral5mInputTokens,\n\t\t\t\t\t\taccumulatedUsage.CacheCreation.Ephemeral1hInputTokens,\n\t\t\t\t\t\tusername,\n\t\t\t\t\t\ttime.Now(),\n\t\t\t\t\t\tweeklyCycleHint,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (s *Service) Close() error {\n\terr := common.Close(\n\t\tcommon.PtrOrNil(s.httpServer),\n\t\tcommon.PtrOrNil(s.listener),\n\t\ts.tlsConfig,\n\t)\n\n\tif s.usageTracker != nil {\n\t\ts.usageTracker.cancelPendingSave()\n\t\tsaveErr := s.usageTracker.Save()\n\t\tif saveErr != nil {\n\t\t\ts.logger.Error(\"save usage statistics: \", saveErr)\n\t\t}\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "service/ccm/service_usage.go",
    "content": "package ccm\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"regexp\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype UsageStats struct {\n\tRequestCount                    int   `json:\"request_count\"`\n\tMessagesCount                   int   `json:\"messages_count\"`\n\tInputTokens                     int64 `json:\"input_tokens\"`\n\tOutputTokens                    int64 `json:\"output_tokens\"`\n\tCacheReadInputTokens            int64 `json:\"cache_read_input_tokens\"`\n\tCacheCreationInputTokens        int64 `json:\"cache_creation_input_tokens\"`\n\tCacheCreation5MinuteInputTokens int64 `json:\"cache_creation_5m_input_tokens,omitempty\"`\n\tCacheCreation1HourInputTokens   int64 `json:\"cache_creation_1h_input_tokens,omitempty\"`\n}\n\ntype CostCombination struct {\n\tModel         string                `json:\"model\"`\n\tContextWindow int                   `json:\"context_window\"`\n\tWeekStartUnix int64                 `json:\"week_start_unix,omitempty\"`\n\tTotal         UsageStats            `json:\"total\"`\n\tByUser        map[string]UsageStats `json:\"by_user\"`\n}\n\ntype AggregatedUsage struct {\n\tLastUpdated  time.Time         `json:\"last_updated\"`\n\tCombinations []CostCombination `json:\"combinations\"`\n\tmutex        sync.Mutex\n\tfilePath     string\n\tlogger       log.ContextLogger\n\tlastSaveTime time.Time\n\tpendingSave  bool\n\tsaveTimer    *time.Timer\n\tsaveMutex    sync.Mutex\n}\n\ntype UsageStatsJSON struct {\n\tRequestCount                    int     `json:\"request_count\"`\n\tMessagesCount                   int     `json:\"messages_count\"`\n\tInputTokens                     int64   `json:\"input_tokens\"`\n\tOutputTokens                    int64   `json:\"output_tokens\"`\n\tCacheReadInputTokens            int64   `json:\"cache_read_input_tokens\"`\n\tCacheCreationInputTokens        int64   `json:\"cache_creation_input_tokens\"`\n\tCacheCreation5MinuteInputTokens int64   `json:\"cache_creation_5m_input_tokens,omitempty\"`\n\tCacheCreation1HourInputTokens   int64   `json:\"cache_creation_1h_input_tokens,omitempty\"`\n\tCostUSD                         float64 `json:\"cost_usd\"`\n}\n\ntype CostCombinationJSON struct {\n\tModel         string                    `json:\"model\"`\n\tContextWindow int                       `json:\"context_window\"`\n\tWeekStartUnix int64                     `json:\"week_start_unix,omitempty\"`\n\tTotal         UsageStatsJSON            `json:\"total\"`\n\tByUser        map[string]UsageStatsJSON `json:\"by_user\"`\n}\n\ntype CostsSummaryJSON struct {\n\tTotalUSD      float64                       `json:\"total_usd\"`\n\tByUser        map[string]float64            `json:\"by_user\"`\n\tByWeek        map[string]float64            `json:\"by_week,omitempty\"`\n\tByUserAndWeek map[string]map[string]float64 `json:\"by_user_and_week,omitempty\"`\n}\n\ntype AggregatedUsageJSON struct {\n\tLastUpdated  time.Time             `json:\"last_updated\"`\n\tCosts        CostsSummaryJSON      `json:\"costs\"`\n\tCombinations []CostCombinationJSON `json:\"combinations\"`\n}\n\ntype WeeklyCycleHint struct {\n\tWindowMinutes int64\n\tResetAt       time.Time\n}\n\ntype ModelPricing struct {\n\tInputPrice             float64\n\tOutputPrice            float64\n\tCacheReadPrice         float64\n\tCacheWritePrice5Minute float64\n\tCacheWritePrice1Hour   float64\n}\n\ntype modelFamily struct {\n\tpattern         *regexp.Regexp\n\tstandardPricing ModelPricing\n\tpremiumPricing  *ModelPricing\n}\n\nvar (\n\topus46StandardPricing = ModelPricing{\n\t\tInputPrice:             5.0,\n\t\tOutputPrice:            25.0,\n\t\tCacheReadPrice:         0.5,\n\t\tCacheWritePrice5Minute: 6.25,\n\t\tCacheWritePrice1Hour:   10.0,\n\t}\n\n\topus46PremiumPricing = ModelPricing{\n\t\tInputPrice:             10.0,\n\t\tOutputPrice:            37.5,\n\t\tCacheReadPrice:         1.0,\n\t\tCacheWritePrice5Minute: 12.5,\n\t\tCacheWritePrice1Hour:   20.0,\n\t}\n\n\topus45Pricing = ModelPricing{\n\t\tInputPrice:             5.0,\n\t\tOutputPrice:            25.0,\n\t\tCacheReadPrice:         0.5,\n\t\tCacheWritePrice5Minute: 6.25,\n\t\tCacheWritePrice1Hour:   10.0,\n\t}\n\n\topus4Pricing = ModelPricing{\n\t\tInputPrice:             15.0,\n\t\tOutputPrice:            75.0,\n\t\tCacheReadPrice:         1.5,\n\t\tCacheWritePrice5Minute: 18.75,\n\t\tCacheWritePrice1Hour:   30.0,\n\t}\n\n\tsonnet46StandardPricing = ModelPricing{\n\t\tInputPrice:             3.0,\n\t\tOutputPrice:            15.0,\n\t\tCacheReadPrice:         0.3,\n\t\tCacheWritePrice5Minute: 3.75,\n\t\tCacheWritePrice1Hour:   6.0,\n\t}\n\n\tsonnet46PremiumPricing = ModelPricing{\n\t\tInputPrice:             6.0,\n\t\tOutputPrice:            22.5,\n\t\tCacheReadPrice:         0.6,\n\t\tCacheWritePrice5Minute: 7.5,\n\t\tCacheWritePrice1Hour:   12.0,\n\t}\n\n\tsonnet45StandardPricing = ModelPricing{\n\t\tInputPrice:             3.0,\n\t\tOutputPrice:            15.0,\n\t\tCacheReadPrice:         0.3,\n\t\tCacheWritePrice5Minute: 3.75,\n\t\tCacheWritePrice1Hour:   6.0,\n\t}\n\n\tsonnet45PremiumPricing = ModelPricing{\n\t\tInputPrice:             6.0,\n\t\tOutputPrice:            22.5,\n\t\tCacheReadPrice:         0.6,\n\t\tCacheWritePrice5Minute: 7.5,\n\t\tCacheWritePrice1Hour:   12.0,\n\t}\n\n\tsonnet4StandardPricing = ModelPricing{\n\t\tInputPrice:             3.0,\n\t\tOutputPrice:            15.0,\n\t\tCacheReadPrice:         0.3,\n\t\tCacheWritePrice5Minute: 3.75,\n\t\tCacheWritePrice1Hour:   6.0,\n\t}\n\n\tsonnet4PremiumPricing = ModelPricing{\n\t\tInputPrice:             6.0,\n\t\tOutputPrice:            22.5,\n\t\tCacheReadPrice:         0.6,\n\t\tCacheWritePrice5Minute: 7.5,\n\t\tCacheWritePrice1Hour:   12.0,\n\t}\n\n\tsonnet37Pricing = ModelPricing{\n\t\tInputPrice:             3.0,\n\t\tOutputPrice:            15.0,\n\t\tCacheReadPrice:         0.3,\n\t\tCacheWritePrice5Minute: 3.75,\n\t\tCacheWritePrice1Hour:   6.0,\n\t}\n\n\tsonnet35Pricing = ModelPricing{\n\t\tInputPrice:             3.0,\n\t\tOutputPrice:            15.0,\n\t\tCacheReadPrice:         0.3,\n\t\tCacheWritePrice5Minute: 3.75,\n\t\tCacheWritePrice1Hour:   6.0,\n\t}\n\n\thaiku45Pricing = ModelPricing{\n\t\tInputPrice:             1.0,\n\t\tOutputPrice:            5.0,\n\t\tCacheReadPrice:         0.1,\n\t\tCacheWritePrice5Minute: 1.25,\n\t\tCacheWritePrice1Hour:   2.0,\n\t}\n\n\thaiku4Pricing = ModelPricing{\n\t\tInputPrice:             1.0,\n\t\tOutputPrice:            5.0,\n\t\tCacheReadPrice:         0.1,\n\t\tCacheWritePrice5Minute: 1.25,\n\t\tCacheWritePrice1Hour:   2.0,\n\t}\n\n\thaiku35Pricing = ModelPricing{\n\t\tInputPrice:             0.8,\n\t\tOutputPrice:            4.0,\n\t\tCacheReadPrice:         0.08,\n\t\tCacheWritePrice5Minute: 1.0,\n\t\tCacheWritePrice1Hour:   1.6,\n\t}\n\n\thaiku3Pricing = ModelPricing{\n\t\tInputPrice:             0.25,\n\t\tOutputPrice:            1.25,\n\t\tCacheReadPrice:         0.03,\n\t\tCacheWritePrice5Minute: 0.3,\n\t\tCacheWritePrice1Hour:   0.5,\n\t}\n\n\topus3Pricing = ModelPricing{\n\t\tInputPrice:             15.0,\n\t\tOutputPrice:            75.0,\n\t\tCacheReadPrice:         1.5,\n\t\tCacheWritePrice5Minute: 18.75,\n\t\tCacheWritePrice1Hour:   30.0,\n\t}\n\n\tmodelFamilies = []modelFamily{\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-opus-4-6(?:-|$)`),\n\t\t\tstandardPricing: opus46StandardPricing,\n\t\t\tpremiumPricing:  &opus46PremiumPricing,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-opus-4-5(?:-|$)`),\n\t\t\tstandardPricing: opus45Pricing,\n\t\t\tpremiumPricing:  nil,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-(?:opus-4(?:-|$)|4-opus-)`),\n\t\t\tstandardPricing: opus4Pricing,\n\t\t\tpremiumPricing:  nil,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-(?:opus-3(?:-|$)|3-opus-)`),\n\t\t\tstandardPricing: opus3Pricing,\n\t\t\tpremiumPricing:  nil,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-(?:sonnet-4-6(?:-|$)|4-6-sonnet-)`),\n\t\t\tstandardPricing: sonnet46StandardPricing,\n\t\t\tpremiumPricing:  &sonnet46PremiumPricing,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-(?:sonnet-4-5(?:-|$)|4-5-sonnet-)`),\n\t\t\tstandardPricing: sonnet45StandardPricing,\n\t\t\tpremiumPricing:  &sonnet45PremiumPricing,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-(?:sonnet-4(?:-|$)|4-sonnet-)`),\n\t\t\tstandardPricing: sonnet4StandardPricing,\n\t\t\tpremiumPricing:  &sonnet4PremiumPricing,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-3-7-sonnet(?:-|$)`),\n\t\t\tstandardPricing: sonnet37Pricing,\n\t\t\tpremiumPricing:  nil,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-3-5-sonnet(?:-|$)`),\n\t\t\tstandardPricing: sonnet35Pricing,\n\t\t\tpremiumPricing:  nil,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-(?:haiku-4-5(?:-|$)|4-5-haiku-)`),\n\t\t\tstandardPricing: haiku45Pricing,\n\t\t\tpremiumPricing:  nil,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-haiku-4(?:-|$)`),\n\t\t\tstandardPricing: haiku4Pricing,\n\t\t\tpremiumPricing:  nil,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-3-5-haiku(?:-|$)`),\n\t\t\tstandardPricing: haiku35Pricing,\n\t\t\tpremiumPricing:  nil,\n\t\t},\n\t\t{\n\t\t\tpattern:         regexp.MustCompile(`^claude-3-haiku(?:-|$)`),\n\t\t\tstandardPricing: haiku3Pricing,\n\t\t\tpremiumPricing:  nil,\n\t\t},\n\t}\n)\n\nfunc getPricing(model string, contextWindow int) ModelPricing {\n\tisPremium := contextWindow >= contextWindowPremium\n\n\tfor _, family := range modelFamilies {\n\t\tif family.pattern.MatchString(model) {\n\t\t\tif isPremium && family.premiumPricing != nil {\n\t\t\t\treturn *family.premiumPricing\n\t\t\t}\n\t\t\treturn family.standardPricing\n\t\t}\n\t}\n\n\treturn sonnet4StandardPricing\n}\n\nfunc calculateCost(stats UsageStats, model string, contextWindow int) float64 {\n\tpricing := getPricing(model, contextWindow)\n\n\tcacheCreationCost := 0.0\n\tif stats.CacheCreation5MinuteInputTokens > 0 || stats.CacheCreation1HourInputTokens > 0 {\n\t\tcacheCreationCost = float64(stats.CacheCreation5MinuteInputTokens)*pricing.CacheWritePrice5Minute +\n\t\t\tfloat64(stats.CacheCreation1HourInputTokens)*pricing.CacheWritePrice1Hour\n\t} else {\n\t\t// Backward compatibility for usage files generated before TTL split tracking.\n\t\tcacheCreationCost = float64(stats.CacheCreationInputTokens) * pricing.CacheWritePrice5Minute\n\t}\n\n\tcost := (float64(stats.InputTokens)*pricing.InputPrice +\n\t\tfloat64(stats.OutputTokens)*pricing.OutputPrice +\n\t\tfloat64(stats.CacheReadInputTokens)*pricing.CacheReadPrice +\n\t\tcacheCreationCost) / 1_000_000\n\n\treturn math.Round(cost*100) / 100\n}\n\nfunc roundCost(cost float64) float64 {\n\treturn math.Round(cost*100) / 100\n}\n\nfunc normalizeCombinations(combinations []CostCombination) {\n\tfor index := range combinations {\n\t\tif combinations[index].ByUser == nil {\n\t\t\tcombinations[index].ByUser = make(map[string]UsageStats)\n\t\t}\n\t}\n}\n\nfunc addUsageToCombinations(\n\tcombinations *[]CostCombination,\n\tmodel string,\n\tcontextWindow int,\n\tweekStartUnix int64,\n\tmessagesCount int,\n\tinputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64,\n\tuser string,\n) {\n\tvar matchedCombination *CostCombination\n\tfor index := range *combinations {\n\t\tcombination := &(*combinations)[index]\n\t\tif combination.Model == model && combination.ContextWindow == contextWindow && combination.WeekStartUnix == weekStartUnix {\n\t\t\tmatchedCombination = combination\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif matchedCombination == nil {\n\t\tnewCombination := CostCombination{\n\t\t\tModel:         model,\n\t\t\tContextWindow: contextWindow,\n\t\t\tWeekStartUnix: weekStartUnix,\n\t\t\tTotal:         UsageStats{},\n\t\t\tByUser:        make(map[string]UsageStats),\n\t\t}\n\t\t*combinations = append(*combinations, newCombination)\n\t\tmatchedCombination = &(*combinations)[len(*combinations)-1]\n\t}\n\n\tif cacheCreationTokens == 0 {\n\t\tcacheCreationTokens = cacheCreation5MinuteTokens + cacheCreation1HourTokens\n\t}\n\n\tmatchedCombination.Total.RequestCount++\n\tmatchedCombination.Total.MessagesCount += messagesCount\n\tmatchedCombination.Total.InputTokens += inputTokens\n\tmatchedCombination.Total.OutputTokens += outputTokens\n\tmatchedCombination.Total.CacheReadInputTokens += cacheReadTokens\n\tmatchedCombination.Total.CacheCreationInputTokens += cacheCreationTokens\n\tmatchedCombination.Total.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens\n\tmatchedCombination.Total.CacheCreation1HourInputTokens += cacheCreation1HourTokens\n\n\tif user != \"\" {\n\t\tuserStats := matchedCombination.ByUser[user]\n\t\tuserStats.RequestCount++\n\t\tuserStats.MessagesCount += messagesCount\n\t\tuserStats.InputTokens += inputTokens\n\t\tuserStats.OutputTokens += outputTokens\n\t\tuserStats.CacheReadInputTokens += cacheReadTokens\n\t\tuserStats.CacheCreationInputTokens += cacheCreationTokens\n\t\tuserStats.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens\n\t\tuserStats.CacheCreation1HourInputTokens += cacheCreation1HourTokens\n\t\tmatchedCombination.ByUser[user] = userStats\n\t}\n}\n\nfunc buildCombinationJSON(combinations []CostCombination, aggregateUserCosts map[string]float64) ([]CostCombinationJSON, float64) {\n\tresult := make([]CostCombinationJSON, len(combinations))\n\tvar totalCost float64\n\n\tfor index, combination := range combinations {\n\t\tcombinationTotalCost := calculateCost(combination.Total, combination.Model, combination.ContextWindow)\n\t\ttotalCost += combinationTotalCost\n\n\t\tcombinationJSON := CostCombinationJSON{\n\t\t\tModel:         combination.Model,\n\t\t\tContextWindow: combination.ContextWindow,\n\t\t\tWeekStartUnix: combination.WeekStartUnix,\n\t\t\tTotal: UsageStatsJSON{\n\t\t\t\tRequestCount:                    combination.Total.RequestCount,\n\t\t\t\tMessagesCount:                   combination.Total.MessagesCount,\n\t\t\t\tInputTokens:                     combination.Total.InputTokens,\n\t\t\t\tOutputTokens:                    combination.Total.OutputTokens,\n\t\t\t\tCacheReadInputTokens:            combination.Total.CacheReadInputTokens,\n\t\t\t\tCacheCreationInputTokens:        combination.Total.CacheCreationInputTokens,\n\t\t\t\tCacheCreation5MinuteInputTokens: combination.Total.CacheCreation5MinuteInputTokens,\n\t\t\t\tCacheCreation1HourInputTokens:   combination.Total.CacheCreation1HourInputTokens,\n\t\t\t\tCostUSD:                         combinationTotalCost,\n\t\t\t},\n\t\t\tByUser: make(map[string]UsageStatsJSON),\n\t\t}\n\n\t\tfor user, userStats := range combination.ByUser {\n\t\t\tuserCost := calculateCost(userStats, combination.Model, combination.ContextWindow)\n\t\t\tif aggregateUserCosts != nil {\n\t\t\t\taggregateUserCosts[user] += userCost\n\t\t\t}\n\n\t\t\tcombinationJSON.ByUser[user] = UsageStatsJSON{\n\t\t\t\tRequestCount:                    userStats.RequestCount,\n\t\t\t\tMessagesCount:                   userStats.MessagesCount,\n\t\t\t\tInputTokens:                     userStats.InputTokens,\n\t\t\t\tOutputTokens:                    userStats.OutputTokens,\n\t\t\t\tCacheReadInputTokens:            userStats.CacheReadInputTokens,\n\t\t\t\tCacheCreationInputTokens:        userStats.CacheCreationInputTokens,\n\t\t\t\tCacheCreation5MinuteInputTokens: userStats.CacheCreation5MinuteInputTokens,\n\t\t\t\tCacheCreation1HourInputTokens:   userStats.CacheCreation1HourInputTokens,\n\t\t\t\tCostUSD:                         userCost,\n\t\t\t}\n\t\t}\n\n\t\tresult[index] = combinationJSON\n\t}\n\n\treturn result, roundCost(totalCost)\n}\n\nfunc formatUTCOffsetLabel(timestamp time.Time) string {\n\t_, offsetSeconds := timestamp.Zone()\n\tsign := \"+\"\n\tif offsetSeconds < 0 {\n\t\tsign = \"-\"\n\t\toffsetSeconds = -offsetSeconds\n\t}\n\toffsetHours := offsetSeconds / 3600\n\toffsetMinutes := (offsetSeconds % 3600) / 60\n\tif offsetMinutes == 0 {\n\t\treturn fmt.Sprintf(\"UTC%s%d\", sign, offsetHours)\n\t}\n\treturn fmt.Sprintf(\"UTC%s%d:%02d\", sign, offsetHours, offsetMinutes)\n}\n\nfunc formatWeekStartKey(cycleStartAt time.Time) string {\n\tlocalCycleStart := cycleStartAt.In(time.Local)\n\treturn fmt.Sprintf(\"%s %s\", localCycleStart.Format(\"2006-01-02 15:04:05\"), formatUTCOffsetLabel(localCycleStart))\n}\n\nfunc buildByWeekCost(combinations []CostCombination) map[string]float64 {\n\tbyWeek := make(map[string]float64)\n\tfor _, combination := range combinations {\n\t\tif combination.WeekStartUnix <= 0 {\n\t\t\tcontinue\n\t\t}\n\t\tweekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC()\n\t\tweekKey := formatWeekStartKey(weekStartAt)\n\t\tbyWeek[weekKey] += calculateCost(combination.Total, combination.Model, combination.ContextWindow)\n\t}\n\tfor weekKey, weekCost := range byWeek {\n\t\tbyWeek[weekKey] = roundCost(weekCost)\n\t}\n\treturn byWeek\n}\n\nfunc buildByUserAndWeekCost(combinations []CostCombination) map[string]map[string]float64 {\n\tbyUserAndWeek := make(map[string]map[string]float64)\n\tfor _, combination := range combinations {\n\t\tif combination.WeekStartUnix <= 0 {\n\t\t\tcontinue\n\t\t}\n\t\tweekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC()\n\t\tweekKey := formatWeekStartKey(weekStartAt)\n\t\tfor user, userStats := range combination.ByUser {\n\t\t\tuserWeeks, exists := byUserAndWeek[user]\n\t\t\tif !exists {\n\t\t\t\tuserWeeks = make(map[string]float64)\n\t\t\t\tbyUserAndWeek[user] = userWeeks\n\t\t\t}\n\t\t\tuserWeeks[weekKey] += calculateCost(userStats, combination.Model, combination.ContextWindow)\n\t\t}\n\t}\n\tfor _, weekCosts := range byUserAndWeek {\n\t\tfor weekKey, cost := range weekCosts {\n\t\t\tweekCosts[weekKey] = roundCost(cost)\n\t\t}\n\t}\n\treturn byUserAndWeek\n}\n\nfunc deriveWeekStartUnix(cycleHint *WeeklyCycleHint) int64 {\n\tif cycleHint == nil || cycleHint.WindowMinutes <= 0 || cycleHint.ResetAt.IsZero() {\n\t\treturn 0\n\t}\n\twindowDuration := time.Duration(cycleHint.WindowMinutes) * time.Minute\n\treturn cycleHint.ResetAt.UTC().Add(-windowDuration).Unix()\n}\n\nfunc (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {\n\tu.mutex.Lock()\n\tdefer u.mutex.Unlock()\n\n\tresult := &AggregatedUsageJSON{\n\t\tLastUpdated: u.LastUpdated,\n\t\tCosts: CostsSummaryJSON{\n\t\t\tTotalUSD: 0,\n\t\t\tByUser:   make(map[string]float64),\n\t\t\tByWeek:   make(map[string]float64),\n\t\t},\n\t}\n\n\tglobalCombinationsJSON, totalCost := buildCombinationJSON(u.Combinations, result.Costs.ByUser)\n\tresult.Combinations = globalCombinationsJSON\n\tresult.Costs.TotalUSD = totalCost\n\tresult.Costs.ByWeek = buildByWeekCost(u.Combinations)\n\n\tif len(result.Costs.ByWeek) == 0 {\n\t\tresult.Costs.ByWeek = nil\n\t}\n\n\tresult.Costs.ByUserAndWeek = buildByUserAndWeekCost(u.Combinations)\n\tif len(result.Costs.ByUserAndWeek) == 0 {\n\t\tresult.Costs.ByUserAndWeek = nil\n\t}\n\n\tfor user, cost := range result.Costs.ByUser {\n\t\tresult.Costs.ByUser[user] = roundCost(cost)\n\t}\n\n\treturn result\n}\n\nfunc (u *AggregatedUsage) Load() error {\n\tu.mutex.Lock()\n\tdefer u.mutex.Unlock()\n\n\tu.LastUpdated = time.Time{}\n\tu.Combinations = nil\n\n\tdata, err := os.ReadFile(u.filePath)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\n\tvar temp struct {\n\t\tLastUpdated  time.Time         `json:\"last_updated\"`\n\t\tCombinations []CostCombination `json:\"combinations\"`\n\t}\n\n\terr = json.Unmarshal(data, &temp)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tu.LastUpdated = temp.LastUpdated\n\tu.Combinations = temp.Combinations\n\tnormalizeCombinations(u.Combinations)\n\n\treturn nil\n}\n\nfunc (u *AggregatedUsage) Save() error {\n\tjsonData := u.ToJSON()\n\n\tdata, err := json.MarshalIndent(jsonData, \"\", \"  \")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttmpFile := u.filePath + \".tmp\"\n\terr = os.WriteFile(tmpFile, data, 0o644)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.Remove(tmpFile)\n\terr = os.Rename(tmpFile, u.filePath)\n\tif err == nil {\n\t\tu.saveMutex.Lock()\n\t\tu.lastSaveTime = time.Now()\n\t\tu.saveMutex.Unlock()\n\t}\n\treturn err\n}\n\nfunc (u *AggregatedUsage) AddUsage(\n\tmodel string,\n\tcontextWindow int,\n\tmessagesCount int,\n\tinputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64,\n\tuser string,\n) error {\n\treturn u.AddUsageWithCycleHint(model, contextWindow, messagesCount, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens, user, time.Now(), nil)\n}\n\nfunc (u *AggregatedUsage) AddUsageWithCycleHint(\n\tmodel string,\n\tcontextWindow int,\n\tmessagesCount int,\n\tinputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64,\n\tuser string,\n\tobservedAt time.Time,\n\tcycleHint *WeeklyCycleHint,\n) error {\n\tif model == \"\" {\n\t\treturn E.New(\"model cannot be empty\")\n\t}\n\tif contextWindow <= 0 {\n\t\treturn E.New(\"contextWindow must be positive\")\n\t}\n\tif observedAt.IsZero() {\n\t\tobservedAt = time.Now()\n\t}\n\n\tu.mutex.Lock()\n\tdefer u.mutex.Unlock()\n\n\tu.LastUpdated = observedAt\n\tweekStartUnix := deriveWeekStartUnix(cycleHint)\n\n\taddUsageToCombinations(&u.Combinations, model, contextWindow, weekStartUnix, messagesCount, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens, user)\n\n\tgo u.scheduleSave()\n\n\treturn nil\n}\n\nfunc (u *AggregatedUsage) scheduleSave() {\n\tconst saveInterval = time.Minute\n\n\tu.saveMutex.Lock()\n\tdefer u.saveMutex.Unlock()\n\n\ttimeSinceLastSave := time.Since(u.lastSaveTime)\n\n\tif timeSinceLastSave >= saveInterval {\n\t\tgo u.saveAsync()\n\t\treturn\n\t}\n\n\tif u.pendingSave {\n\t\treturn\n\t}\n\n\tu.pendingSave = true\n\tremainingTime := saveInterval - timeSinceLastSave\n\n\tu.saveTimer = time.AfterFunc(remainingTime, func() {\n\t\tu.saveMutex.Lock()\n\t\tu.pendingSave = false\n\t\tu.saveMutex.Unlock()\n\t\tu.saveAsync()\n\t})\n}\n\nfunc (u *AggregatedUsage) saveAsync() {\n\terr := u.Save()\n\tif err != nil {\n\t\tif u.logger != nil {\n\t\t\tu.logger.Error(\"save usage statistics: \", err)\n\t\t}\n\t}\n}\n\nfunc (u *AggregatedUsage) cancelPendingSave() {\n\tu.saveMutex.Lock()\n\tdefer u.saveMutex.Unlock()\n\n\tif u.saveTimer != nil {\n\t\tu.saveTimer.Stop()\n\t\tu.saveTimer = nil\n\t}\n\tu.pendingSave = false\n}\n"
  },
  {
    "path": "service/ccm/service_user.go",
    "content": "package ccm\n\nimport (\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/option\"\n)\n\ntype UserManager struct {\n\taccessMutex sync.RWMutex\n\ttokenMap    map[string]string\n}\n\nfunc (m *UserManager) UpdateUsers(users []option.CCMUser) {\n\tm.accessMutex.Lock()\n\tdefer m.accessMutex.Unlock()\n\ttokenMap := make(map[string]string, len(users))\n\tfor _, user := range users {\n\t\ttokenMap[user.Token] = user.Name\n\t}\n\tm.tokenMap = tokenMap\n}\n\nfunc (m *UserManager) Authenticate(token string) (string, bool) {\n\tm.accessMutex.RLock()\n\tusername, found := m.tokenMap[token]\n\tm.accessMutex.RUnlock()\n\treturn username, found\n}\n"
  },
  {
    "path": "service/derp/service.go",
    "content": "//go:build with_gvisor\n\npackage derp\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\tstdTLS \"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tboxService \"github.com/sagernet/sing-box/adapter/service\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tboxScale \"github.com/sagernet/sing-box/protocol/tailscale\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/filemanager\"\n\t\"github.com/sagernet/tailscale/client/local\"\n\t\"github.com/sagernet/tailscale/derp\"\n\t\"github.com/sagernet/tailscale/derp/derphttp\"\n\t\"github.com/sagernet/tailscale/derp/derpserver\"\n\t\"github.com/sagernet/tailscale/net/netmon\"\n\t\"github.com/sagernet/tailscale/net/stun\"\n\t\"github.com/sagernet/tailscale/net/wsconn\"\n\t\"github.com/sagernet/tailscale/tsweb\"\n\t\"github.com/sagernet/tailscale/types/key\"\n\n\t\"github.com/coder/websocket\"\n\t\"github.com/go-chi/render\"\n\t\"golang.org/x/net/http2\"\n\t\"golang.org/x/net/http2/h2c\"\n)\n\nfunc Register(registry *boxService.Registry) {\n\tboxService.Register[option.DERPServiceOptions](registry, C.TypeDERP, NewService)\n}\n\ntype Service struct {\n\tboxService.Adapter\n\tctx                  context.Context\n\tlogger               logger.ContextLogger\n\tlistener             *listener.Listener\n\tstunListener         *listener.Listener\n\ttlsConfig            tls.ServerConfig\n\tserver               *derpserver.Server\n\tconfigPath           string\n\tverifyClientEndpoint []string\n\tverifyClientURL      []*option.DERPVerifyClientURLOptions\n\thome                 string\n\tmeshKey              string\n\tmeshKeyPath          string\n\tmeshWith             []*option.DERPMeshOptions\n}\n\nfunc NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPServiceOptions) (adapter.Service, error) {\n\tif options.TLS == nil || !options.TLS.Enabled {\n\t\treturn nil, E.New(\"TLS is required for DERP server\")\n\t}\n\ttlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar configPath string\n\tif options.ConfigPath != \"\" {\n\t\tconfigPath = filemanager.BasePath(ctx, os.ExpandEnv(options.ConfigPath))\n\t} else {\n\t\treturn nil, E.New(\"missing config_path\")\n\t}\n\n\tif options.MeshPSK != \"\" {\n\t\terr = checkMeshKey(options.MeshPSK)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"invalid mesh_psk\")\n\t\t}\n\t}\n\n\tvar stunListener *listener.Listener\n\tif options.STUN != nil && options.STUN.Enabled {\n\t\tif options.STUN.Listen == nil {\n\t\t\toptions.STUN.Listen = (*badoption.Addr)(common.Ptr(netip.IPv6Unspecified()))\n\t\t}\n\t\tif options.STUN.ListenPort == 0 {\n\t\t\toptions.STUN.ListenPort = 3478\n\t\t}\n\t\tstunListener = listener.New(listener.Options{\n\t\t\tContext: ctx,\n\t\t\tLogger:  logger,\n\t\t\tNetwork: []string{N.NetworkUDP},\n\t\t\tListen:  options.STUN.ListenOptions,\n\t\t})\n\t}\n\n\treturn &Service{\n\t\tAdapter: boxService.NewAdapter(C.TypeDERP, tag),\n\t\tctx:     ctx,\n\t\tlogger:  logger,\n\t\tlistener: listener.New(listener.Options{\n\t\t\tContext: ctx,\n\t\t\tLogger:  logger,\n\t\t\tNetwork: []string{N.NetworkTCP},\n\t\t\tListen:  options.ListenOptions,\n\t\t}),\n\t\tstunListener:         stunListener,\n\t\ttlsConfig:            tlsConfig,\n\t\tconfigPath:           configPath,\n\t\tverifyClientEndpoint: options.VerifyClientEndpoint,\n\t\tverifyClientURL:      options.VerifyClientURL,\n\t\thome:                 options.Home,\n\t\tmeshKey:              options.MeshPSK,\n\t\tmeshKeyPath:          options.MeshPSKFile,\n\t\tmeshWith:             options.MeshWith,\n\t}, nil\n}\n\nfunc (d *Service) Start(stage adapter.StartStage) error {\n\tswitch stage {\n\tcase adapter.StartStateStart:\n\t\tconfig, err := readDERPConfig(filemanager.BasePath(d.ctx, d.configPath))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tserver := derpserver.New(config.PrivateKey, func(format string, args ...any) {\n\t\t\td.logger.Debug(fmt.Sprintf(format, args...))\n\t\t})\n\n\t\tif len(d.verifyClientURL) > 0 {\n\t\t\tvar httpClients []*http.Client\n\t\t\tvar urls []string\n\t\t\tfor index, options := range d.verifyClientURL {\n\t\t\t\tverifyDialer, createErr := dialer.NewWithOptions(dialer.Options{\n\t\t\t\t\tContext:        d.ctx,\n\t\t\t\t\tOptions:        options.DialerOptions,\n\t\t\t\t\tRemoteIsDomain: options.ServerIsDomain(),\n\t\t\t\t\tNewDialer:      true,\n\t\t\t\t})\n\t\t\t\tif createErr != nil {\n\t\t\t\t\treturn E.Cause(createErr, \"verify_client_url[\", index, \"]\")\n\t\t\t\t}\n\t\t\t\thttpClients = append(httpClients, &http.Client{\n\t\t\t\t\tTransport: &http.Transport{\n\t\t\t\t\t\tForceAttemptHTTP2: true,\n\t\t\t\t\t\tTLSClientConfig: &stdTLS.Config{\n\t\t\t\t\t\t\tRootCAs: adapter.RootPoolFromContext(d.ctx),\n\t\t\t\t\t\t\tTime:    ntp.TimeFuncFromContext(d.ctx),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\t\t\t\treturn verifyDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\turls = append(urls, options.URL)\n\t\t\t}\n\t\t\tserver.SetVerifyClientHTTPClient(httpClients)\n\t\t\tserver.SetVerifyClientURL(urls)\n\t\t}\n\n\t\tif d.meshKey != \"\" {\n\t\t\tserver.SetMeshKey(d.meshKey)\n\t\t} else if d.meshKeyPath != \"\" {\n\t\t\tvar meshKeyContent []byte\n\t\t\tmeshKeyContent, err = os.ReadFile(d.meshKeyPath)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = checkMeshKey(string(meshKeyContent))\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"invalid mesh_psk_path file\")\n\t\t\t}\n\t\t\tserver.SetMeshKey(string(meshKeyContent))\n\t\t}\n\t\td.server = server\n\n\t\tderpMux := http.NewServeMux()\n\t\tderpHandler := derpserver.Handler(server)\n\t\tderpHandler = addWebSocketSupport(server, derpHandler)\n\t\tderpMux.Handle(\"/derp\", derpHandler)\n\n\t\thomeHandler, ok := getHomeHandler(d.home)\n\t\tif !ok {\n\t\t\treturn E.New(\"invalid home value: \", d.home)\n\t\t}\n\n\t\tderpMux.HandleFunc(\"/derp/probe\", derpserver.ProbeHandler)\n\t\tderpMux.HandleFunc(\"/derp/latency-check\", derpserver.ProbeHandler)\n\t\tderpMux.HandleFunc(\"/bootstrap-dns\", tsweb.BrowserHeaderHandlerFunc(handleBootstrapDNS(d.ctx)))\n\t\tderpMux.Handle(\"/\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\ttsweb.AddBrowserHeaders(w)\n\t\t\thomeHandler.ServeHTTP(w, r)\n\t\t}))\n\t\tderpMux.Handle(\"/robots.txt\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\ttsweb.AddBrowserHeaders(w)\n\t\t\tio.WriteString(w, \"User-agent: *\\nDisallow: /\\n\")\n\t\t}))\n\t\tderpMux.Handle(\"/generate_204\", http.HandlerFunc(derpserver.ServeNoContent))\n\n\t\terr = d.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ttcpListener, err := d.listener.ListenTCP()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(d.tlsConfig.NextProtos()) == 0 {\n\t\t\td.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, \"http/1.1\"})\n\t\t} else if !common.Contains(d.tlsConfig.NextProtos(), http2.NextProtoTLS) {\n\t\t\td.tlsConfig.SetNextProtos(append([]string{http2.NextProtoTLS}, d.tlsConfig.NextProtos()...))\n\t\t}\n\t\ttcpListener = aTLS.NewListener(tcpListener, d.tlsConfig)\n\t\thttpServer := &http.Server{\n\t\t\tHandler: h2c.NewHandler(derpMux, &http2.Server{}),\n\t\t}\n\t\tgo httpServer.Serve(tcpListener)\n\n\t\tif d.stunListener != nil {\n\t\t\tstunConn, err := d.stunListener.ListenUDP()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tgo d.loopSTUNPacket(stunConn.(*net.UDPConn))\n\t\t}\n\tcase adapter.StartStatePostStart:\n\t\tif len(d.verifyClientEndpoint) > 0 {\n\t\t\tvar endpoints []*local.Client\n\t\t\tendpointManager := service.FromContext[adapter.EndpointManager](d.ctx)\n\t\t\tfor _, endpointTag := range d.verifyClientEndpoint {\n\t\t\t\tendpoint, loaded := endpointManager.Get(endpointTag)\n\t\t\t\tif !loaded {\n\t\t\t\t\treturn E.New(\"verify_client_endpoint: endpoint not found: \", endpointTag)\n\t\t\t\t}\n\t\t\t\ttsEndpoint, isTailscale := endpoint.(*boxScale.Endpoint)\n\t\t\t\tif !isTailscale {\n\t\t\t\t\treturn E.New(\"verify_client_endpoint: endpoint is not Tailscale: \", endpointTag)\n\t\t\t\t}\n\t\t\t\tlocalClient, err := tsEndpoint.Server().LocalClient()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tendpoints = append(endpoints, localClient)\n\t\t\t}\n\t\t\td.server.SetVerifyClientLocalClient(endpoints)\n\t\t}\n\t\tif len(d.meshWith) > 0 {\n\t\t\tif !d.server.HasMeshKey() {\n\t\t\t\treturn E.New(\"missing mesh psk\")\n\t\t\t}\n\t\t\tfor _, options := range d.meshWith {\n\t\t\t\terr := d.startMeshWithHost(d.server, options)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc checkMeshKey(meshKey string) error {\n\tcheckRegex, err := regexp.Compile(`^[0-9a-f]{64}$`)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !checkRegex.MatchString(meshKey) {\n\t\treturn E.New(\"key must contain exactly 64 hex digits\")\n\t}\n\treturn nil\n}\n\nfunc (d *Service) startMeshWithHost(derpServer *derpserver.Server, server *option.DERPMeshOptions) error {\n\tmeshDialer, err := dialer.NewWithOptions(dialer.Options{\n\t\tContext:        d.ctx,\n\t\tOptions:        server.DialerOptions,\n\t\tRemoteIsDomain: server.ServerIsDomain(),\n\t\tNewDialer:      true,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar hostname string\n\tif server.Host != \"\" {\n\t\thostname = server.Host\n\t} else {\n\t\thostname = server.Server\n\t}\n\tvar stdConfig *tls.STDConfig\n\tif server.TLS != nil && server.TLS.Enabled {\n\t\ttlsConfig, err := tls.NewClient(d.ctx, d.logger, hostname, common.PtrValueOrDefault(server.TLS))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstdConfig, err = tlsConfig.STDConfig()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tlogf := func(format string, args ...any) {\n\t\td.logger.Debug(F.ToString(\"mesh(\", hostname, \"): \", fmt.Sprintf(format, args...)))\n\t}\n\tvar meshHost string\n\tif server.ServerPort == 0 || server.ServerPort == 443 {\n\t\tmeshHost = hostname\n\t} else {\n\t\tmeshHost = M.ParseSocksaddrHostPort(hostname, server.ServerPort).String()\n\t}\n\tvar serverURL string\n\tif stdConfig != nil {\n\t\tserverURL = \"https://\" + meshHost + \"/derp\"\n\t} else {\n\t\tserverURL = \"http://\" + meshHost + \"/derp\"\n\t}\n\tmeshClient, err := derphttp.NewClient(derpServer.PrivateKey(), serverURL, logf, netmon.NewStatic())\n\tif err != nil {\n\t\treturn err\n\t}\n\tmeshClient.TLSConfig = stdConfig\n\tmeshClient.MeshKey = derpServer.MeshKey()\n\tmeshClient.WatchConnectionChanges = true\n\tmeshClient.SetURLDialer(func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\treturn meshDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t})\n\tadd := func(m derp.PeerPresentMessage) { derpServer.AddPacketForwarder(m.Key, meshClient) }\n\tremove := func(m derp.PeerGoneMessage) { derpServer.RemovePacketForwarder(m.Peer, meshClient) }\n\tnotifyError := func(err error) { d.logger.Error(err) }\n\tgo meshClient.RunWatchConnectionLoop(context.Background(), derpServer.PublicKey(), logf, add, remove, notifyError)\n\treturn nil\n}\n\nfunc (d *Service) Close() error {\n\treturn common.Close(\n\t\tcommon.PtrOrNil(d.listener),\n\t\td.tlsConfig,\n\t)\n}\n\nvar homePage = `\n<h1>DERP</h1>\n<p>\n  This is a <a href=\"https://tailscale.com/\">Tailscale</a> DERP server.\n</p>\n\n<p>\n  It provides STUN, interactive connectivity establishment, and relaying of end-to-end encrypted traffic\n  for Tailscale clients.\n</p>\n\n<p>\n  Documentation:\n</p>\n\n<ul>\n\n<li><a href=\"https://tailscale.com/kb/1232/derp-servers\">About DERP</a></li>\n<li><a href=\"https://pkg.go.dev/tailscale.com/derp\">Protocol & Go docs</a></li>\n<li><a href=\"https://github.com/tailscale/tailscale/tree/main/cmd/derper#derp\">How to run a DERP server</a></li>\n\n</body>\n</html>\n`\n\nfunc getHomeHandler(val string) (_ http.Handler, ok bool) {\n\tif val == \"\" {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Header().Set(\"Content-Type\", \"text/html; charset=utf-8\")\n\t\t\tw.WriteHeader(200)\n\t\t\tw.Write([]byte(homePage))\n\t\t}), true\n\t}\n\tif val == \"blank\" {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Header().Set(\"Content-Type\", \"text/html; charset=utf-8\")\n\t\t\tw.WriteHeader(200)\n\t\t}), true\n\t}\n\tif strings.HasPrefix(val, \"http://\") || strings.HasPrefix(val, \"https://\") {\n\t\treturn http.RedirectHandler(val, http.StatusFound), true\n\t}\n\treturn nil, false\n}\n\nfunc addWebSocketSupport(s *derpserver.Server, base http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tup := strings.ToLower(r.Header.Get(\"Upgrade\"))\n\n\t\t// Very early versions of Tailscale set \"Upgrade: WebSocket\" but didn't actually\n\t\t// speak WebSockets (they still assumed DERP's binary framing). So to distinguish\n\t\t// clients that actually want WebSockets, look for an explicit \"derp\" subprotocol.\n\t\tif up != \"websocket\" || !strings.Contains(r.Header.Get(\"Sec-Websocket-Protocol\"), \"derp\") {\n\t\t\tbase.ServeHTTP(w, r)\n\t\t\treturn\n\t\t}\n\n\t\tc, err := websocket.Accept(w, r, &websocket.AcceptOptions{\n\t\t\tSubprotocols:   []string{\"derp\"},\n\t\t\tOriginPatterns: []string{\"*\"},\n\t\t\t// Disable compression because we transmit WireGuard messages that\n\t\t\t// are not compressible.\n\t\t\t// Additionally, Safari has a broken implementation of compression\n\t\t\t// (see https://github.com/nhooyr/websocket/issues/218) that makes\n\t\t\t// enabling it actively harmful.\n\t\t\tCompressionMode: websocket.CompressionDisabled,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tdefer c.Close(websocket.StatusInternalError, \"closing\")\n\t\tif c.Subprotocol() != \"derp\" {\n\t\t\tc.Close(websocket.StatusPolicyViolation, \"client must speak the derp subprotocol\")\n\t\t\treturn\n\t\t}\n\t\twc := wsconn.NetConn(r.Context(), c, websocket.MessageBinary, r.RemoteAddr)\n\t\tbrw := bufio.NewReadWriter(bufio.NewReader(wc), bufio.NewWriter(wc))\n\t\ts.Accept(r.Context(), wc, brw, r.RemoteAddr)\n\t})\n}\n\nfunc handleBootstrapDNS(ctx context.Context) http.HandlerFunc {\n\tdnsRouter := service.FromContext[adapter.DNSRouter](ctx)\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.Header().Set(\"Connection\", \"close\")\n\t\tif queryDomain := r.URL.Query().Get(\"q\"); queryDomain != \"\" {\n\t\t\taddresses, err := dnsRouter.Lookup(ctx, queryDomain, adapter.DNSQueryOptions{})\n\t\t\tif err != nil {\n\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trender.JSON(w, r, render.M{\n\t\t\t\tqueryDomain: addresses,\n\t\t\t})\n\t\t\treturn\n\t\t}\n\t\tw.Write([]byte(\"{}\"))\n\t}\n}\n\ntype derpConfig struct {\n\tPrivateKey key.NodePrivate\n}\n\nfunc readDERPConfig(path string) (*derpConfig, error) {\n\tcontent, err := os.ReadFile(path)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn writeNewDERPConfig(path)\n\t\t}\n\t\treturn nil, err\n\t}\n\tvar config derpConfig\n\terr = json.Unmarshal(content, &config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &config, nil\n}\n\nfunc writeNewDERPConfig(path string) (*derpConfig, error) {\n\tnewKey := key.NewNode()\n\terr := os.MkdirAll(filepath.Dir(path), 0o777)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconfig := derpConfig{\n\t\tPrivateKey: newKey,\n\t}\n\tcontent, err := json.Marshal(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = os.WriteFile(path, content, 0o644)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &config, nil\n}\n\nfunc (d *Service) loopSTUNPacket(packetConn *net.UDPConn) {\n\tbuffer := make([]byte, 65535)\n\toob := make([]byte, 1024)\n\tvar (\n\t\tn        int\n\t\toobN     int\n\t\taddrPort netip.AddrPort\n\t\terr      error\n\t)\n\tfor {\n\t\tn, oobN, _, addrPort, err = packetConn.ReadMsgUDPAddrPort(buffer, oob)\n\t\tif err != nil {\n\t\t\tif E.IsClosedOrCanceled(err) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttime.Sleep(time.Second)\n\t\t\tcontinue\n\t\t}\n\t\tif !stun.Is(buffer[:n]) {\n\t\t\tcontinue\n\t\t}\n\t\ttxid, err := stun.ParseBindingRequest(buffer[:n])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tpacketConn.WriteMsgUDPAddrPort(stun.Response(txid, addrPort), oob[:oobN], addrPort)\n\t}\n}\n"
  },
  {
    "path": "service/ocm/credential.go",
    "content": "package ocm\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"time\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nconst (\n\toauth2ClientID           = \"app_EMoamEEZ73f0CkXaXp7hrann\"\n\toauth2TokenURL           = \"https://auth.openai.com/oauth/token\"\n\topenaiAPIBaseURL         = \"https://api.openai.com\"\n\tchatGPTBackendURL        = \"https://chatgpt.com/backend-api/codex\"\n\ttokenRefreshIntervalDays = 8\n)\n\nfunc getRealUser() (*user.User, error) {\n\tif sudoUser := os.Getenv(\"SUDO_USER\"); sudoUser != \"\" {\n\t\tsudoUserInfo, err := user.Lookup(sudoUser)\n\t\tif err == nil {\n\t\t\treturn sudoUserInfo, nil\n\t\t}\n\t}\n\treturn user.Current()\n}\n\nfunc getDefaultCredentialsPath() (string, error) {\n\tif codexHome := os.Getenv(\"CODEX_HOME\"); codexHome != \"\" {\n\t\treturn filepath.Join(codexHome, \"auth.json\"), nil\n\t}\n\tuserInfo, err := getRealUser()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filepath.Join(userInfo.HomeDir, \".codex\", \"auth.json\"), nil\n}\n\nfunc readCredentialsFromFile(path string) (*oauthCredentials, error) {\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar credentials oauthCredentials\n\terr = json.Unmarshal(data, &credentials)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &credentials, nil\n}\n\nfunc writeCredentialsToFile(credentials *oauthCredentials, path string) error {\n\tdata, err := json.MarshalIndent(credentials, \"\", \"  \")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn os.WriteFile(path, data, 0o600)\n}\n\ntype oauthCredentials struct {\n\tAPIKey      string     `json:\"OPENAI_API_KEY,omitempty\"`\n\tTokens      *tokenData `json:\"tokens,omitempty\"`\n\tLastRefresh *time.Time `json:\"last_refresh,omitempty\"`\n}\n\ntype tokenData struct {\n\tIDToken      string `json:\"id_token,omitempty\"`\n\tAccessToken  string `json:\"access_token\"`\n\tRefreshToken string `json:\"refresh_token\"`\n\tAccountID    string `json:\"account_id,omitempty\"`\n}\n\nfunc (c *oauthCredentials) isAPIKeyMode() bool {\n\treturn c.APIKey != \"\"\n}\n\nfunc (c *oauthCredentials) getAccessToken() string {\n\tif c.APIKey != \"\" {\n\t\treturn c.APIKey\n\t}\n\tif c.Tokens != nil {\n\t\treturn c.Tokens.AccessToken\n\t}\n\treturn \"\"\n}\n\nfunc (c *oauthCredentials) getAccountID() string {\n\tif c.Tokens != nil {\n\t\treturn c.Tokens.AccountID\n\t}\n\treturn \"\"\n}\n\nfunc (c *oauthCredentials) needsRefresh() bool {\n\tif c.APIKey != \"\" {\n\t\treturn false\n\t}\n\tif c.Tokens == nil || c.Tokens.RefreshToken == \"\" {\n\t\treturn false\n\t}\n\tif c.LastRefresh == nil {\n\t\treturn true\n\t}\n\treturn time.Since(*c.LastRefresh) >= time.Duration(tokenRefreshIntervalDays)*24*time.Hour\n}\n\nfunc refreshToken(httpClient *http.Client, credentials *oauthCredentials) (*oauthCredentials, error) {\n\tif credentials.Tokens == nil || credentials.Tokens.RefreshToken == \"\" {\n\t\treturn nil, E.New(\"refresh token is empty\")\n\t}\n\n\trequestBody, err := json.Marshal(map[string]string{\n\t\t\"grant_type\":    \"refresh_token\",\n\t\t\"refresh_token\": credentials.Tokens.RefreshToken,\n\t\t\"client_id\":     oauth2ClientID,\n\t\t\"scope\":         \"openid profile email\",\n\t})\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"marshal request\")\n\t}\n\n\trequest, err := http.NewRequest(\"POST\", oauth2TokenURL, bytes.NewReader(requestBody))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trequest.Header.Set(\"Content-Type\", \"application/json\")\n\trequest.Header.Set(\"Accept\", \"application/json\")\n\n\tresponse, err := httpClient.Do(request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer response.Body.Close()\n\n\tif response.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(response.Body)\n\t\treturn nil, E.New(\"refresh failed: \", response.Status, \" \", string(body))\n\t}\n\n\tvar tokenResponse struct {\n\t\tIDToken      string `json:\"id_token\"`\n\t\tAccessToken  string `json:\"access_token\"`\n\t\tRefreshToken string `json:\"refresh_token\"`\n\t}\n\terr = json.NewDecoder(response.Body).Decode(&tokenResponse)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"decode response\")\n\t}\n\n\tnewCredentials := *credentials\n\tif newCredentials.Tokens == nil {\n\t\tnewCredentials.Tokens = &tokenData{}\n\t}\n\tif tokenResponse.IDToken != \"\" {\n\t\tnewCredentials.Tokens.IDToken = tokenResponse.IDToken\n\t}\n\tif tokenResponse.AccessToken != \"\" {\n\t\tnewCredentials.Tokens.AccessToken = tokenResponse.AccessToken\n\t}\n\tif tokenResponse.RefreshToken != \"\" {\n\t\tnewCredentials.Tokens.RefreshToken = tokenResponse.RefreshToken\n\t}\n\tnow := time.Now()\n\tnewCredentials.LastRefresh = &now\n\n\treturn &newCredentials, nil\n}\n"
  },
  {
    "path": "service/ocm/credential_darwin.go",
    "content": "//go:build darwin\n\npackage ocm\n\nfunc platformReadCredentials(customPath string) (*oauthCredentials, error) {\n\tif customPath == \"\" {\n\t\tvar err error\n\t\tcustomPath, err = getDefaultCredentialsPath()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn readCredentialsFromFile(customPath)\n}\n\nfunc platformWriteCredentials(credentials *oauthCredentials, customPath string) error {\n\tif customPath == \"\" {\n\t\tvar err error\n\t\tcustomPath, err = getDefaultCredentialsPath()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn writeCredentialsToFile(credentials, customPath)\n}\n"
  },
  {
    "path": "service/ocm/credential_other.go",
    "content": "//go:build !darwin\n\npackage ocm\n\nfunc platformReadCredentials(customPath string) (*oauthCredentials, error) {\n\tif customPath == \"\" {\n\t\tvar err error\n\t\tcustomPath, err = getDefaultCredentialsPath()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn readCredentialsFromFile(customPath)\n}\n\nfunc platformWriteCredentials(credentials *oauthCredentials, customPath string) error {\n\tif customPath == \"\" {\n\t\tvar err error\n\t\tcustomPath, err = getDefaultCredentialsPath()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn writeCredentialsToFile(credentials, customPath)\n}\n"
  },
  {
    "path": "service/ocm/service.go",
    "content": "package ocm\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\tstdTLS \"crypto/tls\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"mime\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tboxService \"github.com/sagernet/sing-box/adapter/service\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/openai/openai-go/v3\"\n\t\"github.com/openai/openai-go/v3/responses\"\n\t\"golang.org/x/net/http2\"\n)\n\nfunc RegisterService(registry *boxService.Registry) {\n\tboxService.Register[option.OCMServiceOptions](registry, C.TypeOCM, NewService)\n}\n\ntype errorResponse struct {\n\tError errorDetails `json:\"error\"`\n}\n\ntype errorDetails struct {\n\tType    string `json:\"type\"`\n\tCode    string `json:\"code,omitempty\"`\n\tMessage string `json:\"message\"`\n}\n\nfunc writeJSONError(w http.ResponseWriter, r *http.Request, statusCode int, errorType string, message string) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.WriteHeader(statusCode)\n\n\tjson.NewEncoder(w).Encode(errorResponse{\n\t\tError: errorDetails{\n\t\t\tType:    errorType,\n\t\t\tMessage: message,\n\t\t},\n\t})\n}\n\nfunc isHopByHopHeader(header string) bool {\n\tswitch strings.ToLower(header) {\n\tcase \"connection\", \"keep-alive\", \"proxy-authenticate\", \"proxy-authorization\", \"te\", \"trailers\", \"transfer-encoding\", \"upgrade\", \"host\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc normalizeRateLimitIdentifier(limitIdentifier string) string {\n\ttrimmedIdentifier := strings.TrimSpace(strings.ToLower(limitIdentifier))\n\tif trimmedIdentifier == \"\" {\n\t\treturn \"\"\n\t}\n\treturn strings.ReplaceAll(trimmedIdentifier, \"_\", \"-\")\n}\n\nfunc parseInt64Header(headers http.Header, headerName string) (int64, bool) {\n\theaderValue := strings.TrimSpace(headers.Get(headerName))\n\tif headerValue == \"\" {\n\t\treturn 0, false\n\t}\n\tparsedValue, parseError := strconv.ParseInt(headerValue, 10, 64)\n\tif parseError != nil {\n\t\treturn 0, false\n\t}\n\treturn parsedValue, true\n}\n\nfunc weeklyCycleHintForLimit(headers http.Header, limitIdentifier string) *WeeklyCycleHint {\n\tnormalizedLimitIdentifier := normalizeRateLimitIdentifier(limitIdentifier)\n\tif normalizedLimitIdentifier == \"\" {\n\t\treturn nil\n\t}\n\n\twindowHeader := \"x-\" + normalizedLimitIdentifier + \"-secondary-window-minutes\"\n\tresetHeader := \"x-\" + normalizedLimitIdentifier + \"-secondary-reset-at\"\n\n\twindowMinutes, hasWindowMinutes := parseInt64Header(headers, windowHeader)\n\tresetAtUnix, hasResetAt := parseInt64Header(headers, resetHeader)\n\tif !hasWindowMinutes || !hasResetAt || windowMinutes <= 0 || resetAtUnix <= 0 {\n\t\treturn nil\n\t}\n\n\treturn &WeeklyCycleHint{\n\t\tWindowMinutes: windowMinutes,\n\t\tResetAt:       time.Unix(resetAtUnix, 0).UTC(),\n\t}\n}\n\nfunc extractWeeklyCycleHint(headers http.Header) *WeeklyCycleHint {\n\tactiveLimitIdentifier := normalizeRateLimitIdentifier(headers.Get(\"x-codex-active-limit\"))\n\tif activeLimitIdentifier != \"\" {\n\t\tif activeHint := weeklyCycleHintForLimit(headers, activeLimitIdentifier); activeHint != nil {\n\t\t\treturn activeHint\n\t\t}\n\t}\n\treturn weeklyCycleHintForLimit(headers, \"codex\")\n}\n\ntype Service struct {\n\tboxService.Adapter\n\tctx            context.Context\n\tlogger         log.ContextLogger\n\tcredentialPath string\n\tcredentials    *oauthCredentials\n\tusers          []option.OCMUser\n\tdialer         N.Dialer\n\thttpClient     *http.Client\n\thttpHeaders    http.Header\n\tlistener       *listener.Listener\n\ttlsConfig      tls.ServerConfig\n\thttpServer     *http.Server\n\tuserManager    *UserManager\n\taccessMutex    sync.RWMutex\n\tusageTracker   *AggregatedUsage\n\twebSocketMutex sync.Mutex\n\twebSocketGroup sync.WaitGroup\n\twebSocketConns map[*webSocketSession]struct{}\n\tshuttingDown   bool\n}\n\nfunc NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) {\n\tserviceDialer, err := dialer.NewWithOptions(dialer.Options{\n\t\tContext: ctx,\n\t\tOptions: option.DialerOptions{\n\t\t\tDetour: options.Detour,\n\t\t},\n\t\tRemoteIsDomain: true,\n\t})\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"create dialer\")\n\t}\n\n\thttpClient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tForceAttemptHTTP2: true,\n\t\t\tTLSClientConfig: &stdTLS.Config{\n\t\t\t\tRootCAs: adapter.RootPoolFromContext(ctx),\n\t\t\t\tTime:    ntp.TimeFuncFromContext(ctx),\n\t\t\t},\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\treturn serviceDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t\t\t},\n\t\t},\n\t}\n\n\tuserManager := &UserManager{\n\t\ttokenMap: make(map[string]string),\n\t}\n\n\tvar usageTracker *AggregatedUsage\n\tif options.UsagesPath != \"\" {\n\t\tusageTracker = &AggregatedUsage{\n\t\t\tLastUpdated:  time.Now(),\n\t\t\tCombinations: make([]CostCombination, 0),\n\t\t\tfilePath:     options.UsagesPath,\n\t\t\tlogger:       logger,\n\t\t}\n\t}\n\n\tservice := &Service{\n\t\tAdapter:        boxService.NewAdapter(C.TypeOCM, tag),\n\t\tctx:            ctx,\n\t\tlogger:         logger,\n\t\tcredentialPath: options.CredentialPath,\n\t\tusers:          options.Users,\n\t\tdialer:         serviceDialer,\n\t\thttpClient:     httpClient,\n\t\thttpHeaders:    options.Headers.Build(),\n\t\tlistener: listener.New(listener.Options{\n\t\t\tContext: ctx,\n\t\t\tLogger:  logger,\n\t\t\tNetwork: []string{N.NetworkTCP},\n\t\t\tListen:  options.ListenOptions,\n\t\t}),\n\t\tuserManager:    userManager,\n\t\tusageTracker:   usageTracker,\n\t\twebSocketConns: make(map[*webSocketSession]struct{}),\n\t}\n\n\tif options.TLS != nil {\n\t\ttlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tservice.tlsConfig = tlsConfig\n\t}\n\n\treturn service, nil\n}\n\nfunc (s *Service) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\n\ts.userManager.UpdateUsers(s.users)\n\n\tcredentials, err := platformReadCredentials(s.credentialPath)\n\tif err != nil {\n\t\treturn E.Cause(err, \"read credentials\")\n\t}\n\ts.credentials = credentials\n\n\tif s.usageTracker != nil {\n\t\terr = s.usageTracker.Load()\n\t\tif err != nil {\n\t\t\ts.logger.Warn(\"load usage statistics: \", err)\n\t\t}\n\t}\n\n\trouter := chi.NewRouter()\n\trouter.Mount(\"/\", s)\n\n\ts.httpServer = &http.Server{Handler: router}\n\n\tif s.tlsConfig != nil {\n\t\terr = s.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"create TLS config\")\n\t\t}\n\t}\n\n\ttcpListener, err := s.listener.ListenTCP()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif s.tlsConfig != nil {\n\t\tif !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {\n\t\t\ts.tlsConfig.SetNextProtos(append([]string{\"h2\"}, s.tlsConfig.NextProtos()...))\n\t\t}\n\t\ttcpListener = aTLS.NewListener(tcpListener, s.tlsConfig)\n\t}\n\n\tgo func() {\n\t\tserveErr := s.httpServer.Serve(tcpListener)\n\t\tif serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) {\n\t\t\ts.logger.Error(\"serve error: \", serveErr)\n\t\t}\n\t}()\n\n\treturn nil\n}\n\nfunc (s *Service) getAccessToken() (string, error) {\n\ts.accessMutex.RLock()\n\tif !s.credentials.needsRefresh() {\n\t\ttoken := s.credentials.getAccessToken()\n\t\ts.accessMutex.RUnlock()\n\t\treturn token, nil\n\t}\n\ts.accessMutex.RUnlock()\n\n\ts.accessMutex.Lock()\n\tdefer s.accessMutex.Unlock()\n\n\tif !s.credentials.needsRefresh() {\n\t\treturn s.credentials.getAccessToken(), nil\n\t}\n\n\tnewCredentials, err := refreshToken(s.httpClient, s.credentials)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\ts.credentials = newCredentials\n\n\terr = platformWriteCredentials(newCredentials, s.credentialPath)\n\tif err != nil {\n\t\ts.logger.Warn(\"persist refreshed token: \", err)\n\t}\n\n\treturn newCredentials.getAccessToken(), nil\n}\n\nfunc (s *Service) getAccountID() string {\n\ts.accessMutex.RLock()\n\tdefer s.accessMutex.RUnlock()\n\treturn s.credentials.getAccountID()\n}\n\nfunc (s *Service) isAPIKeyMode() bool {\n\ts.accessMutex.RLock()\n\tdefer s.accessMutex.RUnlock()\n\treturn s.credentials.isAPIKeyMode()\n}\n\nfunc (s *Service) getBaseURL() string {\n\tif s.isAPIKeyMode() {\n\t\treturn openaiAPIBaseURL\n\t}\n\treturn chatGPTBackendURL\n}\n\nfunc (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tpath := r.URL.Path\n\tif !strings.HasPrefix(path, \"/v1/\") {\n\t\twriteJSONError(w, r, http.StatusNotFound, \"invalid_request_error\", \"path must start with /v1/\")\n\t\treturn\n\t}\n\n\tvar proxyPath string\n\tif s.isAPIKeyMode() {\n\t\tproxyPath = path\n\t} else {\n\t\tif path == \"/v1/chat/completions\" {\n\t\t\twriteJSONError(w, r, http.StatusBadRequest, \"invalid_request_error\",\n\t\t\t\t\"chat completions endpoint is only available in API key mode\")\n\t\t\treturn\n\t\t}\n\t\tproxyPath = strings.TrimPrefix(path, \"/v1\")\n\t}\n\n\tvar username string\n\tif len(s.users) > 0 {\n\t\tauthHeader := r.Header.Get(\"Authorization\")\n\t\tif authHeader == \"\" {\n\t\t\ts.logger.Warn(\"authentication failed for request from \", r.RemoteAddr, \": missing Authorization header\")\n\t\t\twriteJSONError(w, r, http.StatusUnauthorized, \"authentication_error\", \"missing api key\")\n\t\t\treturn\n\t\t}\n\t\tclientToken := strings.TrimPrefix(authHeader, \"Bearer \")\n\t\tif clientToken == authHeader {\n\t\t\ts.logger.Warn(\"authentication failed for request from \", r.RemoteAddr, \": invalid Authorization format\")\n\t\t\twriteJSONError(w, r, http.StatusUnauthorized, \"authentication_error\", \"invalid api key format\")\n\t\t\treturn\n\t\t}\n\t\tvar ok bool\n\t\tusername, ok = s.userManager.Authenticate(clientToken)\n\t\tif !ok {\n\t\t\ts.logger.Warn(\"authentication failed for request from \", r.RemoteAddr, \": unknown key: \", clientToken)\n\t\t\twriteJSONError(w, r, http.StatusUnauthorized, \"authentication_error\", \"invalid api key\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tif strings.EqualFold(r.Header.Get(\"Upgrade\"), \"websocket\") && strings.HasPrefix(path, \"/v1/responses\") {\n\t\ts.handleWebSocket(w, r, proxyPath, username)\n\t\treturn\n\t}\n\n\tvar requestModel string\n\n\tif s.usageTracker != nil && r.Body != nil {\n\t\tbodyBytes, err := io.ReadAll(r.Body)\n\t\tif err == nil {\n\t\t\tvar request struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t}\n\t\t\terr := json.Unmarshal(bodyBytes, &request)\n\t\t\tif err == nil {\n\t\t\t\trequestModel = request.Model\n\t\t\t}\n\t\t\tr.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))\n\t\t}\n\t}\n\n\taccessToken, err := s.getAccessToken()\n\tif err != nil {\n\t\ts.logger.Error(\"get access token: \", err)\n\t\twriteJSONError(w, r, http.StatusUnauthorized, \"authentication_error\", \"Authentication failed\")\n\t\treturn\n\t}\n\n\tproxyURL := s.getBaseURL() + proxyPath\n\tif r.URL.RawQuery != \"\" {\n\t\tproxyURL += \"?\" + r.URL.RawQuery\n\t}\n\tproxyRequest, err := http.NewRequestWithContext(r.Context(), r.Method, proxyURL, r.Body)\n\tif err != nil {\n\t\ts.logger.Error(\"create proxy request: \", err)\n\t\twriteJSONError(w, r, http.StatusInternalServerError, \"api_error\", \"Internal server error\")\n\t\treturn\n\t}\n\n\tfor key, values := range r.Header {\n\t\tif !isHopByHopHeader(key) && key != \"Authorization\" {\n\t\t\tproxyRequest.Header[key] = values\n\t\t}\n\t}\n\n\tfor key, values := range s.httpHeaders {\n\t\tproxyRequest.Header.Del(key)\n\t\tproxyRequest.Header[key] = values\n\t}\n\n\tproxyRequest.Header.Set(\"Authorization\", \"Bearer \"+accessToken)\n\n\tif accountID := s.getAccountID(); accountID != \"\" {\n\t\tproxyRequest.Header.Set(\"ChatGPT-Account-Id\", accountID)\n\t}\n\n\tresponse, err := s.httpClient.Do(proxyRequest)\n\tif err != nil {\n\t\twriteJSONError(w, r, http.StatusBadGateway, \"api_error\", err.Error())\n\t\treturn\n\t}\n\tdefer response.Body.Close()\n\n\tfor key, values := range response.Header {\n\t\tif !isHopByHopHeader(key) {\n\t\t\tw.Header()[key] = values\n\t\t}\n\t}\n\tw.WriteHeader(response.StatusCode)\n\n\ttrackUsage := s.usageTracker != nil && response.StatusCode == http.StatusOK &&\n\t\t(path == \"/v1/chat/completions\" || strings.HasPrefix(path, \"/v1/responses\"))\n\tif trackUsage {\n\t\ts.handleResponseWithTracking(w, response, path, requestModel, username)\n\t} else {\n\t\tmediaType, _, err := mime.ParseMediaType(response.Header.Get(\"Content-Type\"))\n\t\tif err == nil && mediaType != \"text/event-stream\" {\n\t\t\t_, _ = io.Copy(w, response.Body)\n\t\t\treturn\n\t\t}\n\t\tflusher, ok := w.(http.Flusher)\n\t\tif !ok {\n\t\t\ts.logger.Error(\"streaming not supported\")\n\t\t\treturn\n\t\t}\n\t\tbuffer := make([]byte, buf.BufferSize)\n\t\tfor {\n\t\t\tn, err := response.Body.Read(buffer)\n\t\t\tif n > 0 {\n\t\t\t\t_, writeError := w.Write(buffer[:n])\n\t\t\t\tif writeError != nil {\n\t\t\t\t\ts.logger.Error(\"write streaming response: \", writeError)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tflusher.Flush()\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, path string, requestModel string, username string) {\n\tisChatCompletions := path == \"/v1/chat/completions\"\n\tweeklyCycleHint := extractWeeklyCycleHint(response.Header)\n\tmediaType, _, err := mime.ParseMediaType(response.Header.Get(\"Content-Type\"))\n\tisStreaming := err == nil && mediaType == \"text/event-stream\"\n\tif !isStreaming && !isChatCompletions && response.Header.Get(\"Content-Type\") == \"\" {\n\t\tisStreaming = true\n\t}\n\tif !isStreaming {\n\t\tbodyBytes, err := io.ReadAll(response.Body)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"read response body: \", err)\n\t\t\treturn\n\t\t}\n\n\t\tvar responseModel, serviceTier string\n\t\tvar inputTokens, outputTokens, cachedTokens int64\n\n\t\tif isChatCompletions {\n\t\t\tvar chatCompletion openai.ChatCompletion\n\t\t\tif json.Unmarshal(bodyBytes, &chatCompletion) == nil {\n\t\t\t\tresponseModel = chatCompletion.Model\n\t\t\t\tserviceTier = string(chatCompletion.ServiceTier)\n\t\t\t\tinputTokens = chatCompletion.Usage.PromptTokens\n\t\t\t\toutputTokens = chatCompletion.Usage.CompletionTokens\n\t\t\t\tcachedTokens = chatCompletion.Usage.PromptTokensDetails.CachedTokens\n\t\t\t}\n\t\t} else {\n\t\t\tvar responsesResponse responses.Response\n\t\t\tif json.Unmarshal(bodyBytes, &responsesResponse) == nil {\n\t\t\t\tresponseModel = string(responsesResponse.Model)\n\t\t\t\tserviceTier = string(responsesResponse.ServiceTier)\n\t\t\t\tinputTokens = responsesResponse.Usage.InputTokens\n\t\t\t\toutputTokens = responsesResponse.Usage.OutputTokens\n\t\t\t\tcachedTokens = responsesResponse.Usage.InputTokensDetails.CachedTokens\n\t\t\t}\n\t\t}\n\n\t\tif inputTokens > 0 || outputTokens > 0 {\n\t\t\tif responseModel == \"\" {\n\t\t\t\tresponseModel = requestModel\n\t\t\t}\n\t\t\tif responseModel != \"\" {\n\t\t\t\tcontextWindow := detectContextWindow(responseModel, serviceTier, inputTokens)\n\t\t\t\ts.usageTracker.AddUsageWithCycleHint(\n\t\t\t\t\tresponseModel,\n\t\t\t\t\tcontextWindow,\n\t\t\t\t\tinputTokens,\n\t\t\t\t\toutputTokens,\n\t\t\t\t\tcachedTokens,\n\t\t\t\t\tserviceTier,\n\t\t\t\t\tusername,\n\t\t\t\t\ttime.Now(),\n\t\t\t\t\tweeklyCycleHint,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t_, _ = writer.Write(bodyBytes)\n\t\treturn\n\t}\n\n\tflusher, ok := writer.(http.Flusher)\n\tif !ok {\n\t\ts.logger.Error(\"streaming not supported\")\n\t\treturn\n\t}\n\n\tvar inputTokens, outputTokens, cachedTokens int64\n\tvar responseModel, serviceTier string\n\tbuffer := make([]byte, buf.BufferSize)\n\tvar leftover []byte\n\n\tfor {\n\t\tn, err := response.Body.Read(buffer)\n\t\tif n > 0 {\n\t\t\tdata := append(leftover, buffer[:n]...)\n\t\t\tlines := bytes.Split(data, []byte(\"\\n\"))\n\n\t\t\tif err == nil {\n\t\t\t\tleftover = lines[len(lines)-1]\n\t\t\t\tlines = lines[:len(lines)-1]\n\t\t\t} else {\n\t\t\t\tleftover = nil\n\t\t\t}\n\n\t\t\tfor _, line := range lines {\n\t\t\t\tline = bytes.TrimSpace(line)\n\t\t\t\tif len(line) == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif bytes.HasPrefix(line, []byte(\"data: \")) {\n\t\t\t\t\teventData := bytes.TrimPrefix(line, []byte(\"data: \"))\n\t\t\t\t\tif bytes.Equal(eventData, []byte(\"[DONE]\")) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif isChatCompletions {\n\t\t\t\t\t\tvar chatChunk openai.ChatCompletionChunk\n\t\t\t\t\t\tif json.Unmarshal(eventData, &chatChunk) == nil {\n\t\t\t\t\t\t\tif chatChunk.Model != \"\" {\n\t\t\t\t\t\t\t\tresponseModel = chatChunk.Model\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif chatChunk.ServiceTier != \"\" {\n\t\t\t\t\t\t\t\tserviceTier = string(chatChunk.ServiceTier)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif chatChunk.Usage.PromptTokens > 0 {\n\t\t\t\t\t\t\t\tinputTokens = chatChunk.Usage.PromptTokens\n\t\t\t\t\t\t\t\tcachedTokens = chatChunk.Usage.PromptTokensDetails.CachedTokens\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif chatChunk.Usage.CompletionTokens > 0 {\n\t\t\t\t\t\t\t\toutputTokens = chatChunk.Usage.CompletionTokens\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvar streamEvent responses.ResponseStreamEventUnion\n\t\t\t\t\t\tif json.Unmarshal(eventData, &streamEvent) == nil {\n\t\t\t\t\t\t\tif streamEvent.Type == \"response.completed\" {\n\t\t\t\t\t\t\t\tcompletedEvent := streamEvent.AsResponseCompleted()\n\t\t\t\t\t\t\t\tif string(completedEvent.Response.Model) != \"\" {\n\t\t\t\t\t\t\t\t\tresponseModel = string(completedEvent.Response.Model)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif completedEvent.Response.ServiceTier != \"\" {\n\t\t\t\t\t\t\t\t\tserviceTier = string(completedEvent.Response.ServiceTier)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif completedEvent.Response.Usage.InputTokens > 0 {\n\t\t\t\t\t\t\t\t\tinputTokens = completedEvent.Response.Usage.InputTokens\n\t\t\t\t\t\t\t\t\tcachedTokens = completedEvent.Response.Usage.InputTokensDetails.CachedTokens\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif completedEvent.Response.Usage.OutputTokens > 0 {\n\t\t\t\t\t\t\t\t\toutputTokens = completedEvent.Response.Usage.OutputTokens\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t_, writeError := writer.Write(buffer[:n])\n\t\t\tif writeError != nil {\n\t\t\t\ts.logger.Error(\"write streaming response: \", writeError)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tflusher.Flush()\n\t\t}\n\n\t\tif err != nil {\n\t\t\tif responseModel == \"\" {\n\t\t\t\tresponseModel = requestModel\n\t\t\t}\n\n\t\t\tif inputTokens > 0 || outputTokens > 0 {\n\t\t\t\tif responseModel != \"\" {\n\t\t\t\t\tcontextWindow := detectContextWindow(responseModel, serviceTier, inputTokens)\n\t\t\t\t\ts.usageTracker.AddUsageWithCycleHint(\n\t\t\t\t\t\tresponseModel,\n\t\t\t\t\t\tcontextWindow,\n\t\t\t\t\t\tinputTokens,\n\t\t\t\t\t\toutputTokens,\n\t\t\t\t\t\tcachedTokens,\n\t\t\t\t\t\tserviceTier,\n\t\t\t\t\t\tusername,\n\t\t\t\t\t\ttime.Now(),\n\t\t\t\t\t\tweeklyCycleHint,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (s *Service) Close() error {\n\twebSocketSessions := s.startWebSocketShutdown()\n\n\terr := common.Close(\n\t\tcommon.PtrOrNil(s.httpServer),\n\t\tcommon.PtrOrNil(s.listener),\n\t\ts.tlsConfig,\n\t)\n\tfor _, session := range webSocketSessions {\n\t\tsession.Close()\n\t}\n\ts.webSocketGroup.Wait()\n\n\tif s.usageTracker != nil {\n\t\ts.usageTracker.cancelPendingSave()\n\t\tsaveErr := s.usageTracker.Save()\n\t\tif saveErr != nil {\n\t\t\ts.logger.Error(\"save usage statistics: \", saveErr)\n\t\t}\n\t}\n\n\treturn err\n}\n\nfunc (s *Service) registerWebSocketSession(session *webSocketSession) bool {\n\ts.webSocketMutex.Lock()\n\tdefer s.webSocketMutex.Unlock()\n\n\tif s.shuttingDown {\n\t\treturn false\n\t}\n\n\ts.webSocketConns[session] = struct{}{}\n\ts.webSocketGroup.Add(1)\n\treturn true\n}\n\nfunc (s *Service) unregisterWebSocketSession(session *webSocketSession) {\n\ts.webSocketMutex.Lock()\n\t_, loaded := s.webSocketConns[session]\n\tif loaded {\n\t\tdelete(s.webSocketConns, session)\n\t}\n\ts.webSocketMutex.Unlock()\n\n\tif loaded {\n\t\ts.webSocketGroup.Done()\n\t}\n}\n\nfunc (s *Service) isShuttingDown() bool {\n\ts.webSocketMutex.Lock()\n\tdefer s.webSocketMutex.Unlock()\n\treturn s.shuttingDown\n}\n\nfunc (s *Service) startWebSocketShutdown() []*webSocketSession {\n\ts.webSocketMutex.Lock()\n\tdefer s.webSocketMutex.Unlock()\n\n\ts.shuttingDown = true\n\n\twebSocketSessions := make([]*webSocketSession, 0, len(s.webSocketConns))\n\tfor session := range s.webSocketConns {\n\t\twebSocketSessions = append(webSocketSessions, session)\n\t}\n\treturn webSocketSessions\n}\n"
  },
  {
    "path": "service/ocm/service_usage.go",
    "content": "package ocm\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype UsageStats struct {\n\tRequestCount int   `json:\"request_count\"`\n\tInputTokens  int64 `json:\"input_tokens\"`\n\tOutputTokens int64 `json:\"output_tokens\"`\n\tCachedTokens int64 `json:\"cached_tokens\"`\n}\n\nfunc (u *UsageStats) UnmarshalJSON(data []byte) error {\n\ttype Alias UsageStats\n\taux := &struct {\n\t\t*Alias\n\t\tPromptTokens     int64 `json:\"prompt_tokens\"`\n\t\tCompletionTokens int64 `json:\"completion_tokens\"`\n\t}{\n\t\tAlias: (*Alias)(u),\n\t}\n\terr := json.Unmarshal(data, aux)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif u.InputTokens == 0 && aux.PromptTokens > 0 {\n\t\tu.InputTokens = aux.PromptTokens\n\t}\n\tif u.OutputTokens == 0 && aux.CompletionTokens > 0 {\n\t\tu.OutputTokens = aux.CompletionTokens\n\t}\n\treturn nil\n}\n\ntype CostCombination struct {\n\tModel         string                `json:\"model\"`\n\tServiceTier   string                `json:\"service_tier,omitempty\"`\n\tContextWindow int                   `json:\"context_window\"`\n\tWeekStartUnix int64                 `json:\"week_start_unix,omitempty\"`\n\tTotal         UsageStats            `json:\"total\"`\n\tByUser        map[string]UsageStats `json:\"by_user\"`\n}\n\ntype AggregatedUsage struct {\n\tLastUpdated  time.Time         `json:\"last_updated\"`\n\tCombinations []CostCombination `json:\"combinations\"`\n\tmutex        sync.Mutex\n\tfilePath     string\n\tlogger       log.ContextLogger\n\tlastSaveTime time.Time\n\tpendingSave  bool\n\tsaveTimer    *time.Timer\n\tsaveMutex    sync.Mutex\n}\n\ntype UsageStatsJSON struct {\n\tRequestCount int     `json:\"request_count\"`\n\tInputTokens  int64   `json:\"input_tokens\"`\n\tOutputTokens int64   `json:\"output_tokens\"`\n\tCachedTokens int64   `json:\"cached_tokens\"`\n\tCostUSD      float64 `json:\"cost_usd\"`\n}\n\ntype CostCombinationJSON struct {\n\tModel         string                    `json:\"model\"`\n\tServiceTier   string                    `json:\"service_tier,omitempty\"`\n\tContextWindow int                       `json:\"context_window\"`\n\tWeekStartUnix int64                     `json:\"week_start_unix,omitempty\"`\n\tTotal         UsageStatsJSON            `json:\"total\"`\n\tByUser        map[string]UsageStatsJSON `json:\"by_user\"`\n}\n\ntype CostsSummaryJSON struct {\n\tTotalUSD      float64                       `json:\"total_usd\"`\n\tByUser        map[string]float64            `json:\"by_user\"`\n\tByWeek        map[string]float64            `json:\"by_week,omitempty\"`\n\tByUserAndWeek map[string]map[string]float64 `json:\"by_user_and_week,omitempty\"`\n}\n\ntype AggregatedUsageJSON struct {\n\tLastUpdated  time.Time             `json:\"last_updated\"`\n\tCosts        CostsSummaryJSON      `json:\"costs\"`\n\tCombinations []CostCombinationJSON `json:\"combinations\"`\n}\n\ntype WeeklyCycleHint struct {\n\tWindowMinutes int64\n\tResetAt       time.Time\n}\n\ntype ModelPricing struct {\n\tInputPrice       float64\n\tOutputPrice      float64\n\tCachedInputPrice float64\n}\n\ntype modelFamily struct {\n\tpattern        *regexp.Regexp\n\tpricing        ModelPricing\n\tpremiumPricing *ModelPricing\n}\n\nconst (\n\tserviceTierAuto     = \"auto\"\n\tserviceTierDefault  = \"default\"\n\tserviceTierFlex     = \"flex\"\n\tserviceTierPriority = \"priority\"\n\tserviceTierScale    = \"scale\"\n)\n\nconst (\n\tcontextWindowStandard   = 272000\n\tcontextWindowPremium    = 1050000\n\tpremiumContextThreshold = 272000\n)\n\nvar (\n\tgpt52Pricing = ModelPricing{\n\t\tInputPrice:       1.75,\n\t\tOutputPrice:      14.0,\n\t\tCachedInputPrice: 0.175,\n\t}\n\n\tgpt5Pricing = ModelPricing{\n\t\tInputPrice:       1.25,\n\t\tOutputPrice:      10.0,\n\t\tCachedInputPrice: 0.125,\n\t}\n\n\tgpt5MiniPricing = ModelPricing{\n\t\tInputPrice:       0.25,\n\t\tOutputPrice:      2.0,\n\t\tCachedInputPrice: 0.025,\n\t}\n\n\tgpt5NanoPricing = ModelPricing{\n\t\tInputPrice:       0.05,\n\t\tOutputPrice:      0.4,\n\t\tCachedInputPrice: 0.005,\n\t}\n\n\tgpt52CodexPricing = ModelPricing{\n\t\tInputPrice:       1.75,\n\t\tOutputPrice:      14.0,\n\t\tCachedInputPrice: 0.175,\n\t}\n\n\tgpt51CodexPricing = ModelPricing{\n\t\tInputPrice:       1.25,\n\t\tOutputPrice:      10.0,\n\t\tCachedInputPrice: 0.125,\n\t}\n\n\tgpt51CodexMiniPricing = ModelPricing{\n\t\tInputPrice:       0.25,\n\t\tOutputPrice:      2.0,\n\t\tCachedInputPrice: 0.025,\n\t}\n\n\tgpt54StandardPricing = ModelPricing{\n\t\tInputPrice:       2.5,\n\t\tOutputPrice:      15.0,\n\t\tCachedInputPrice: 0.25,\n\t}\n\n\tgpt54PremiumPricing = ModelPricing{\n\t\tInputPrice:       5.0,\n\t\tOutputPrice:      22.5,\n\t\tCachedInputPrice: 0.5,\n\t}\n\n\tgpt54ProPricing = ModelPricing{\n\t\tInputPrice:       30.0,\n\t\tOutputPrice:      180.0,\n\t\tCachedInputPrice: 30.0,\n\t}\n\n\tgpt54ProPremiumPricing = ModelPricing{\n\t\tInputPrice:       60.0,\n\t\tOutputPrice:      270.0,\n\t\tCachedInputPrice: 60.0,\n\t}\n\n\tgpt52ProPricing = ModelPricing{\n\t\tInputPrice:       21.0,\n\t\tOutputPrice:      168.0,\n\t\tCachedInputPrice: 21.0,\n\t}\n\n\tgpt5ProPricing = ModelPricing{\n\t\tInputPrice:       15.0,\n\t\tOutputPrice:      120.0,\n\t\tCachedInputPrice: 15.0,\n\t}\n\n\tgpt54FlexPricing = ModelPricing{\n\t\tInputPrice:       1.25,\n\t\tOutputPrice:      7.5,\n\t\tCachedInputPrice: 0.125,\n\t}\n\n\tgpt54PremiumFlexPricing = ModelPricing{\n\t\tInputPrice:       2.5,\n\t\tOutputPrice:      11.25,\n\t\tCachedInputPrice: 0.25,\n\t}\n\n\tgpt54ProFlexPricing = ModelPricing{\n\t\tInputPrice:       15.0,\n\t\tOutputPrice:      90.0,\n\t\tCachedInputPrice: 15.0,\n\t}\n\n\tgpt54ProPremiumFlexPricing = ModelPricing{\n\t\tInputPrice:       30.0,\n\t\tOutputPrice:      135.0,\n\t\tCachedInputPrice: 30.0,\n\t}\n\n\tgpt52FlexPricing = ModelPricing{\n\t\tInputPrice:       0.875,\n\t\tOutputPrice:      7.0,\n\t\tCachedInputPrice: 0.0875,\n\t}\n\n\tgpt5FlexPricing = ModelPricing{\n\t\tInputPrice:       0.625,\n\t\tOutputPrice:      5.0,\n\t\tCachedInputPrice: 0.0625,\n\t}\n\n\tgpt5MiniFlexPricing = ModelPricing{\n\t\tInputPrice:       0.125,\n\t\tOutputPrice:      1.0,\n\t\tCachedInputPrice: 0.0125,\n\t}\n\n\tgpt5NanoFlexPricing = ModelPricing{\n\t\tInputPrice:       0.025,\n\t\tOutputPrice:      0.2,\n\t\tCachedInputPrice: 0.0025,\n\t}\n\n\tgpt54PriorityPricing = ModelPricing{\n\t\tInputPrice:       5.0,\n\t\tOutputPrice:      30.0,\n\t\tCachedInputPrice: 0.5,\n\t}\n\n\tgpt54PremiumPriorityPricing = ModelPricing{\n\t\tInputPrice:       10.0,\n\t\tOutputPrice:      45.0,\n\t\tCachedInputPrice: 1.0,\n\t}\n\n\tgpt52PriorityPricing = ModelPricing{\n\t\tInputPrice:       3.5,\n\t\tOutputPrice:      28.0,\n\t\tCachedInputPrice: 0.35,\n\t}\n\n\tgpt5PriorityPricing = ModelPricing{\n\t\tInputPrice:       2.5,\n\t\tOutputPrice:      20.0,\n\t\tCachedInputPrice: 0.25,\n\t}\n\n\tgpt5MiniPriorityPricing = ModelPricing{\n\t\tInputPrice:       0.45,\n\t\tOutputPrice:      3.6,\n\t\tCachedInputPrice: 0.045,\n\t}\n\n\tgpt52CodexPriorityPricing = ModelPricing{\n\t\tInputPrice:       3.5,\n\t\tOutputPrice:      28.0,\n\t\tCachedInputPrice: 0.35,\n\t}\n\n\tgpt51CodexPriorityPricing = ModelPricing{\n\t\tInputPrice:       2.5,\n\t\tOutputPrice:      20.0,\n\t\tCachedInputPrice: 0.25,\n\t}\n\n\tgpt4oPricing = ModelPricing{\n\t\tInputPrice:       2.5,\n\t\tOutputPrice:      10.0,\n\t\tCachedInputPrice: 1.25,\n\t}\n\n\tgpt4oMiniPricing = ModelPricing{\n\t\tInputPrice:       0.15,\n\t\tOutputPrice:      0.6,\n\t\tCachedInputPrice: 0.075,\n\t}\n\n\tgpt4oAudioPricing = ModelPricing{\n\t\tInputPrice:       2.5,\n\t\tOutputPrice:      10.0,\n\t\tCachedInputPrice: 2.5,\n\t}\n\n\tgpt4oMiniAudioPricing = ModelPricing{\n\t\tInputPrice:       0.15,\n\t\tOutputPrice:      0.6,\n\t\tCachedInputPrice: 0.15,\n\t}\n\n\tgptAudioMiniPricing = ModelPricing{\n\t\tInputPrice:       0.6,\n\t\tOutputPrice:      2.4,\n\t\tCachedInputPrice: 0.6,\n\t}\n\n\to1Pricing = ModelPricing{\n\t\tInputPrice:       15.0,\n\t\tOutputPrice:      60.0,\n\t\tCachedInputPrice: 7.5,\n\t}\n\n\to1ProPricing = ModelPricing{\n\t\tInputPrice:       150.0,\n\t\tOutputPrice:      600.0,\n\t\tCachedInputPrice: 150.0,\n\t}\n\n\to1MiniPricing = ModelPricing{\n\t\tInputPrice:       1.1,\n\t\tOutputPrice:      4.4,\n\t\tCachedInputPrice: 0.55,\n\t}\n\n\to3MiniPricing = ModelPricing{\n\t\tInputPrice:       1.1,\n\t\tOutputPrice:      4.4,\n\t\tCachedInputPrice: 0.55,\n\t}\n\n\to3Pricing = ModelPricing{\n\t\tInputPrice:       2.0,\n\t\tOutputPrice:      8.0,\n\t\tCachedInputPrice: 0.5,\n\t}\n\n\to3ProPricing = ModelPricing{\n\t\tInputPrice:       20.0,\n\t\tOutputPrice:      80.0,\n\t\tCachedInputPrice: 20.0,\n\t}\n\n\to3DeepResearchPricing = ModelPricing{\n\t\tInputPrice:       10.0,\n\t\tOutputPrice:      40.0,\n\t\tCachedInputPrice: 2.5,\n\t}\n\n\to4MiniPricing = ModelPricing{\n\t\tInputPrice:       1.1,\n\t\tOutputPrice:      4.4,\n\t\tCachedInputPrice: 0.275,\n\t}\n\n\to4MiniDeepResearchPricing = ModelPricing{\n\t\tInputPrice:       2.0,\n\t\tOutputPrice:      8.0,\n\t\tCachedInputPrice: 0.5,\n\t}\n\n\to3FlexPricing = ModelPricing{\n\t\tInputPrice:       1.0,\n\t\tOutputPrice:      4.0,\n\t\tCachedInputPrice: 0.25,\n\t}\n\n\to4MiniFlexPricing = ModelPricing{\n\t\tInputPrice:       0.55,\n\t\tOutputPrice:      2.2,\n\t\tCachedInputPrice: 0.138,\n\t}\n\n\to3PriorityPricing = ModelPricing{\n\t\tInputPrice:       3.5,\n\t\tOutputPrice:      14.0,\n\t\tCachedInputPrice: 0.875,\n\t}\n\n\to4MiniPriorityPricing = ModelPricing{\n\t\tInputPrice:       2.0,\n\t\tOutputPrice:      8.0,\n\t\tCachedInputPrice: 0.5,\n\t}\n\n\tgpt41Pricing = ModelPricing{\n\t\tInputPrice:       2.0,\n\t\tOutputPrice:      8.0,\n\t\tCachedInputPrice: 0.5,\n\t}\n\n\tgpt41MiniPricing = ModelPricing{\n\t\tInputPrice:       0.4,\n\t\tOutputPrice:      1.6,\n\t\tCachedInputPrice: 0.1,\n\t}\n\n\tgpt41NanoPricing = ModelPricing{\n\t\tInputPrice:       0.1,\n\t\tOutputPrice:      0.4,\n\t\tCachedInputPrice: 0.025,\n\t}\n\n\tgpt41PriorityPricing = ModelPricing{\n\t\tInputPrice:       3.5,\n\t\tOutputPrice:      14.0,\n\t\tCachedInputPrice: 0.875,\n\t}\n\n\tgpt41MiniPriorityPricing = ModelPricing{\n\t\tInputPrice:       0.7,\n\t\tOutputPrice:      2.8,\n\t\tCachedInputPrice: 0.175,\n\t}\n\n\tgpt41NanoPriorityPricing = ModelPricing{\n\t\tInputPrice:       0.2,\n\t\tOutputPrice:      0.8,\n\t\tCachedInputPrice: 0.05,\n\t}\n\n\tgpt4oPriorityPricing = ModelPricing{\n\t\tInputPrice:       4.25,\n\t\tOutputPrice:      17.0,\n\t\tCachedInputPrice: 2.125,\n\t}\n\n\tgpt4oMiniPriorityPricing = ModelPricing{\n\t\tInputPrice:       0.25,\n\t\tOutputPrice:      1.0,\n\t\tCachedInputPrice: 0.125,\n\t}\n\n\tstandardModelFamilies = []modelFamily{\n\t\t{\n\t\t\tpattern:        regexp.MustCompile(`^gpt-5\\.4-pro(?:$|-)`),\n\t\t\tpricing:        gpt54ProPricing,\n\t\t\tpremiumPricing: &gpt54ProPremiumPricing,\n\t\t},\n\t\t{\n\t\t\tpattern:        regexp.MustCompile(`^gpt-5\\.4(?:$|-)`),\n\t\t\tpricing:        gpt54StandardPricing,\n\t\t\tpremiumPricing: &gpt54PremiumPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.3-codex(?:$|-)`),\n\t\t\tpricing: gpt52CodexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.2-codex(?:$|-)`),\n\t\t\tpricing: gpt52CodexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.1-codex-max(?:$|-)`),\n\t\t\tpricing: gpt51CodexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.1-codex-mini(?:$|-)`),\n\t\t\tpricing: gpt51CodexMiniPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.1-codex(?:$|-)`),\n\t\t\tpricing: gpt51CodexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5-codex-mini(?:$|-)`),\n\t\t\tpricing: gpt51CodexMiniPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`),\n\t\t\tpricing: gpt51CodexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.2-chat-latest$`),\n\t\t\tpricing: gpt52Pricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.1-chat-latest$`),\n\t\t\tpricing: gpt5Pricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5-chat-latest$`),\n\t\t\tpricing: gpt5Pricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.2-pro(?:$|-)`),\n\t\t\tpricing: gpt52ProPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5-pro(?:$|-)`),\n\t\t\tpricing: gpt5ProPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`),\n\t\t\tpricing: gpt5MiniPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5-nano(?:$|-)`),\n\t\t\tpricing: gpt5NanoPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.2(?:$|-)`),\n\t\t\tpricing: gpt52Pricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.1(?:$|-)`),\n\t\t\tpricing: gpt5Pricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5(?:$|-)`),\n\t\t\tpricing: gpt5Pricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o4-mini-deep-research(?:$|-)`),\n\t\t\tpricing: o4MiniDeepResearchPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o4-mini(?:$|-)`),\n\t\t\tpricing: o4MiniPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o3-pro(?:$|-)`),\n\t\t\tpricing: o3ProPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o3-deep-research(?:$|-)`),\n\t\t\tpricing: o3DeepResearchPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o3-mini(?:$|-)`),\n\t\t\tpricing: o3MiniPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o3(?:$|-)`),\n\t\t\tpricing: o3Pricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o1-pro(?:$|-)`),\n\t\t\tpricing: o1ProPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o1-mini(?:$|-)`),\n\t\t\tpricing: o1MiniPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o1(?:$|-)`),\n\t\t\tpricing: o1Pricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-4o-mini-audio(?:$|-)`),\n\t\t\tpricing: gpt4oMiniAudioPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-audio-mini(?:$|-)`),\n\t\t\tpricing: gptAudioMiniPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^(?:gpt-4o-audio|gpt-audio)(?:$|-)`),\n\t\t\tpricing: gpt4oAudioPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-4\\.1-nano(?:$|-)`),\n\t\t\tpricing: gpt41NanoPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-4\\.1-mini(?:$|-)`),\n\t\t\tpricing: gpt41MiniPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-4\\.1(?:$|-)`),\n\t\t\tpricing: gpt41Pricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-4o-mini(?:$|-)`),\n\t\t\tpricing: gpt4oMiniPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-4o(?:$|-)`),\n\t\t\tpricing: gpt4oPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^chatgpt-4o(?:$|-)`),\n\t\t\tpricing: gpt4oPricing,\n\t\t},\n\t}\n\n\tflexModelFamilies = []modelFamily{\n\t\t{\n\t\t\tpattern:        regexp.MustCompile(`^gpt-5\\.4-pro(?:$|-)`),\n\t\t\tpricing:        gpt54ProFlexPricing,\n\t\t\tpremiumPricing: &gpt54ProPremiumFlexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern:        regexp.MustCompile(`^gpt-5\\.4(?:$|-)`),\n\t\t\tpricing:        gpt54FlexPricing,\n\t\t\tpremiumPricing: &gpt54PremiumFlexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`),\n\t\t\tpricing: gpt5MiniFlexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5-nano(?:$|-)`),\n\t\t\tpricing: gpt5NanoFlexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.2(?:$|-)`),\n\t\t\tpricing: gpt52FlexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.1(?:$|-)`),\n\t\t\tpricing: gpt5FlexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5(?:$|-)`),\n\t\t\tpricing: gpt5FlexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o4-mini(?:$|-)`),\n\t\t\tpricing: o4MiniFlexPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o3(?:$|-)`),\n\t\t\tpricing: o3FlexPricing,\n\t\t},\n\t}\n\n\tpriorityModelFamilies = []modelFamily{\n\t\t{\n\t\t\tpattern:        regexp.MustCompile(`^gpt-5\\.4(?:$|-)`),\n\t\t\tpricing:        gpt54PriorityPricing,\n\t\t\tpremiumPricing: &gpt54PremiumPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.3-codex(?:$|-)`),\n\t\t\tpricing: gpt52CodexPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.2-codex(?:$|-)`),\n\t\t\tpricing: gpt52CodexPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.1-codex-max(?:$|-)`),\n\t\t\tpricing: gpt51CodexPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.1-codex(?:$|-)`),\n\t\t\tpricing: gpt51CodexPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5-codex-mini(?:$|-)`),\n\t\t\tpricing: gpt5MiniPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`),\n\t\t\tpricing: gpt51CodexPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`),\n\t\t\tpricing: gpt5MiniPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.2(?:$|-)`),\n\t\t\tpricing: gpt52PriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5\\.1(?:$|-)`),\n\t\t\tpricing: gpt5PriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-5(?:$|-)`),\n\t\t\tpricing: gpt5PriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o4-mini(?:$|-)`),\n\t\t\tpricing: o4MiniPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^o3(?:$|-)`),\n\t\t\tpricing: o3PriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-4\\.1-nano(?:$|-)`),\n\t\t\tpricing: gpt41NanoPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-4\\.1-mini(?:$|-)`),\n\t\t\tpricing: gpt41MiniPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-4\\.1(?:$|-)`),\n\t\t\tpricing: gpt41PriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-4o-mini(?:$|-)`),\n\t\t\tpricing: gpt4oMiniPriorityPricing,\n\t\t},\n\t\t{\n\t\t\tpattern: regexp.MustCompile(`^gpt-4o(?:$|-)`),\n\t\t\tpricing: gpt4oPriorityPricing,\n\t\t},\n\t}\n)\n\nfunc modelFamiliesForTier(serviceTier string) []modelFamily {\n\tswitch serviceTier {\n\tcase serviceTierFlex:\n\t\treturn flexModelFamilies\n\tcase serviceTierPriority:\n\t\treturn priorityModelFamilies\n\tdefault:\n\t\treturn standardModelFamilies\n\t}\n}\n\nfunc findPricingInFamilies(model string, contextWindow int, modelFamilies []modelFamily) (ModelPricing, bool) {\n\tisPremium := contextWindow >= contextWindowPremium\n\tfor _, family := range modelFamilies {\n\t\tif family.pattern.MatchString(model) {\n\t\t\tif isPremium && family.premiumPricing != nil {\n\t\t\t\treturn *family.premiumPricing, true\n\t\t\t}\n\t\t\treturn family.pricing, true\n\t\t}\n\t}\n\treturn ModelPricing{}, false\n}\n\nfunc hasPremiumPricingInFamilies(model string, modelFamilies []modelFamily) bool {\n\tfor _, family := range modelFamilies {\n\t\tif family.pattern.MatchString(model) {\n\t\t\treturn family.premiumPricing != nil\n\t\t}\n\t}\n\treturn false\n}\n\nfunc normalizeServiceTier(serviceTier string) string {\n\tswitch strings.ToLower(strings.TrimSpace(serviceTier)) {\n\tcase \"\", serviceTierAuto, serviceTierDefault:\n\t\treturn serviceTierDefault\n\tcase serviceTierFlex:\n\t\treturn serviceTierFlex\n\tcase serviceTierPriority:\n\t\treturn serviceTierPriority\n\tcase serviceTierScale:\n\t\t// Scale-tier requests are prepaid differently and not listed in this usage file.\n\t\treturn serviceTierDefault\n\tdefault:\n\t\treturn serviceTierDefault\n\t}\n}\n\nfunc getPricing(model string, serviceTier string, contextWindow int) ModelPricing {\n\tnormalizedServiceTier := normalizeServiceTier(serviceTier)\n\tfamilies := modelFamiliesForTier(normalizedServiceTier)\n\n\tif pricing, found := findPricingInFamilies(model, contextWindow, families); found {\n\t\treturn pricing\n\t}\n\n\tnormalizedModel := normalizeGPT5Model(model)\n\tif normalizedModel != model {\n\t\tif pricing, found := findPricingInFamilies(normalizedModel, contextWindow, families); found {\n\t\t\treturn pricing\n\t\t}\n\t}\n\n\tif normalizedServiceTier != serviceTierDefault {\n\t\tif pricing, found := findPricingInFamilies(model, contextWindow, standardModelFamilies); found {\n\t\t\treturn pricing\n\t\t}\n\t\tif normalizedModel != model {\n\t\t\tif pricing, found := findPricingInFamilies(normalizedModel, contextWindow, standardModelFamilies); found {\n\t\t\t\treturn pricing\n\t\t\t}\n\t\t}\n\t}\n\n\treturn gpt4oPricing\n}\n\nfunc detectContextWindow(model string, serviceTier string, inputTokens int64) int {\n\tif inputTokens <= premiumContextThreshold {\n\t\treturn contextWindowStandard\n\t}\n\tnormalizedServiceTier := normalizeServiceTier(serviceTier)\n\tfamilies := modelFamiliesForTier(normalizedServiceTier)\n\tif hasPremiumPricingInFamilies(model, families) {\n\t\treturn contextWindowPremium\n\t}\n\tnormalizedModel := normalizeGPT5Model(model)\n\tif normalizedModel != model && hasPremiumPricingInFamilies(normalizedModel, families) {\n\t\treturn contextWindowPremium\n\t}\n\tif normalizedServiceTier != serviceTierDefault {\n\t\tif hasPremiumPricingInFamilies(model, standardModelFamilies) {\n\t\t\treturn contextWindowPremium\n\t\t}\n\t\tif normalizedModel != model && hasPremiumPricingInFamilies(normalizedModel, standardModelFamilies) {\n\t\t\treturn contextWindowPremium\n\t\t}\n\t}\n\treturn contextWindowStandard\n}\n\nfunc normalizeGPT5Model(model string) string {\n\tif !strings.HasPrefix(model, \"gpt-5.\") {\n\t\treturn model\n\t}\n\n\tswitch {\n\tcase strings.Contains(model, \"-codex-mini\"):\n\t\treturn \"gpt-5.1-codex-mini\"\n\tcase strings.Contains(model, \"-codex-max\"):\n\t\treturn \"gpt-5.1-codex-max\"\n\tcase strings.Contains(model, \"-codex\"):\n\t\treturn \"gpt-5.3-codex\"\n\tcase strings.Contains(model, \"-chat-latest\"):\n\t\treturn \"gpt-5.2-chat-latest\"\n\tcase strings.Contains(model, \"-pro\"):\n\t\treturn \"gpt-5.4-pro\"\n\tcase strings.Contains(model, \"-mini\"):\n\t\treturn \"gpt-5-mini\"\n\tcase strings.Contains(model, \"-nano\"):\n\t\treturn \"gpt-5-nano\"\n\tdefault:\n\t\treturn \"gpt-5.4\"\n\t}\n}\n\nfunc calculateCost(stats UsageStats, model string, serviceTier string, contextWindow int) float64 {\n\tpricing := getPricing(model, serviceTier, contextWindow)\n\n\tregularInputTokens := stats.InputTokens - stats.CachedTokens\n\tif regularInputTokens < 0 {\n\t\tregularInputTokens = 0\n\t}\n\n\tcost := (float64(regularInputTokens)*pricing.InputPrice +\n\t\tfloat64(stats.OutputTokens)*pricing.OutputPrice +\n\t\tfloat64(stats.CachedTokens)*pricing.CachedInputPrice) / 1_000_000\n\n\treturn math.Round(cost*100) / 100\n}\n\nfunc roundCost(cost float64) float64 {\n\treturn math.Round(cost*100) / 100\n}\n\nfunc normalizeCombinations(combinations []CostCombination) {\n\tfor index := range combinations {\n\t\tcombinations[index].ServiceTier = normalizeServiceTier(combinations[index].ServiceTier)\n\t\tif combinations[index].ContextWindow <= 0 {\n\t\t\tcombinations[index].ContextWindow = contextWindowStandard\n\t\t}\n\t\tif combinations[index].ByUser == nil {\n\t\t\tcombinations[index].ByUser = make(map[string]UsageStats)\n\t\t}\n\t}\n}\n\nfunc addUsageToCombinations(combinations *[]CostCombination, model string, serviceTier string, contextWindow int, weekStartUnix int64, user string, inputTokens, outputTokens, cachedTokens int64) {\n\tvar matchedCombination *CostCombination\n\tfor index := range *combinations {\n\t\tcombination := &(*combinations)[index]\n\t\tcombinationServiceTier := normalizeServiceTier(combination.ServiceTier)\n\t\tif combination.ServiceTier != combinationServiceTier {\n\t\t\tcombination.ServiceTier = combinationServiceTier\n\t\t}\n\t\tif combination.Model == model && combinationServiceTier == serviceTier && combination.ContextWindow == contextWindow && combination.WeekStartUnix == weekStartUnix {\n\t\t\tmatchedCombination = combination\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif matchedCombination == nil {\n\t\tnewCombination := CostCombination{\n\t\t\tModel:         model,\n\t\t\tServiceTier:   serviceTier,\n\t\t\tContextWindow: contextWindow,\n\t\t\tWeekStartUnix: weekStartUnix,\n\t\t\tTotal:         UsageStats{},\n\t\t\tByUser:        make(map[string]UsageStats),\n\t\t}\n\t\t*combinations = append(*combinations, newCombination)\n\t\tmatchedCombination = &(*combinations)[len(*combinations)-1]\n\t}\n\n\tmatchedCombination.Total.RequestCount++\n\tmatchedCombination.Total.InputTokens += inputTokens\n\tmatchedCombination.Total.OutputTokens += outputTokens\n\tmatchedCombination.Total.CachedTokens += cachedTokens\n\n\tif user != \"\" {\n\t\tuserStats := matchedCombination.ByUser[user]\n\t\tuserStats.RequestCount++\n\t\tuserStats.InputTokens += inputTokens\n\t\tuserStats.OutputTokens += outputTokens\n\t\tuserStats.CachedTokens += cachedTokens\n\t\tmatchedCombination.ByUser[user] = userStats\n\t}\n}\n\nfunc buildCombinationJSON(combinations []CostCombination, aggregateUserCosts map[string]float64) ([]CostCombinationJSON, float64) {\n\tresult := make([]CostCombinationJSON, len(combinations))\n\tvar totalCost float64\n\n\tfor index, combination := range combinations {\n\t\tcombinationTotalCost := calculateCost(combination.Total, combination.Model, combination.ServiceTier, combination.ContextWindow)\n\t\ttotalCost += combinationTotalCost\n\n\t\tcombinationJSON := CostCombinationJSON{\n\t\t\tModel:         combination.Model,\n\t\t\tServiceTier:   combination.ServiceTier,\n\t\t\tContextWindow: combination.ContextWindow,\n\t\t\tWeekStartUnix: combination.WeekStartUnix,\n\t\t\tTotal: UsageStatsJSON{\n\t\t\t\tRequestCount: combination.Total.RequestCount,\n\t\t\t\tInputTokens:  combination.Total.InputTokens,\n\t\t\t\tOutputTokens: combination.Total.OutputTokens,\n\t\t\t\tCachedTokens: combination.Total.CachedTokens,\n\t\t\t\tCostUSD:      combinationTotalCost,\n\t\t\t},\n\t\t\tByUser: make(map[string]UsageStatsJSON),\n\t\t}\n\n\t\tfor user, userStats := range combination.ByUser {\n\t\t\tuserCost := calculateCost(userStats, combination.Model, combination.ServiceTier, combination.ContextWindow)\n\t\t\tif aggregateUserCosts != nil {\n\t\t\t\taggregateUserCosts[user] += userCost\n\t\t\t}\n\n\t\t\tcombinationJSON.ByUser[user] = UsageStatsJSON{\n\t\t\t\tRequestCount: userStats.RequestCount,\n\t\t\t\tInputTokens:  userStats.InputTokens,\n\t\t\t\tOutputTokens: userStats.OutputTokens,\n\t\t\t\tCachedTokens: userStats.CachedTokens,\n\t\t\t\tCostUSD:      userCost,\n\t\t\t}\n\t\t}\n\n\t\tresult[index] = combinationJSON\n\t}\n\n\treturn result, roundCost(totalCost)\n}\n\nfunc formatUTCOffsetLabel(timestamp time.Time) string {\n\t_, offsetSeconds := timestamp.Zone()\n\tsign := \"+\"\n\tif offsetSeconds < 0 {\n\t\tsign = \"-\"\n\t\toffsetSeconds = -offsetSeconds\n\t}\n\toffsetHours := offsetSeconds / 3600\n\toffsetMinutes := (offsetSeconds % 3600) / 60\n\tif offsetMinutes == 0 {\n\t\treturn fmt.Sprintf(\"UTC%s%d\", sign, offsetHours)\n\t}\n\treturn fmt.Sprintf(\"UTC%s%d:%02d\", sign, offsetHours, offsetMinutes)\n}\n\nfunc formatWeekStartKey(cycleStartAt time.Time) string {\n\tlocalCycleStart := cycleStartAt.In(time.Local)\n\treturn fmt.Sprintf(\"%s %s\", localCycleStart.Format(\"2006-01-02 15:04:05\"), formatUTCOffsetLabel(localCycleStart))\n}\n\nfunc buildByWeekCost(combinations []CostCombination) map[string]float64 {\n\tbyWeek := make(map[string]float64)\n\tfor _, combination := range combinations {\n\t\tif combination.WeekStartUnix <= 0 {\n\t\t\tcontinue\n\t\t}\n\t\tweekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC()\n\t\tweekKey := formatWeekStartKey(weekStartAt)\n\t\tbyWeek[weekKey] += calculateCost(combination.Total, combination.Model, combination.ServiceTier, combination.ContextWindow)\n\t}\n\tfor weekKey, weekCost := range byWeek {\n\t\tbyWeek[weekKey] = roundCost(weekCost)\n\t}\n\treturn byWeek\n}\n\nfunc buildByUserAndWeekCost(combinations []CostCombination) map[string]map[string]float64 {\n\tbyUserAndWeek := make(map[string]map[string]float64)\n\tfor _, combination := range combinations {\n\t\tif combination.WeekStartUnix <= 0 {\n\t\t\tcontinue\n\t\t}\n\t\tweekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC()\n\t\tweekKey := formatWeekStartKey(weekStartAt)\n\t\tfor user, userStats := range combination.ByUser {\n\t\t\tuserWeeks, exists := byUserAndWeek[user]\n\t\t\tif !exists {\n\t\t\t\tuserWeeks = make(map[string]float64)\n\t\t\t\tbyUserAndWeek[user] = userWeeks\n\t\t\t}\n\t\t\tuserWeeks[weekKey] += calculateCost(userStats, combination.Model, combination.ServiceTier, combination.ContextWindow)\n\t\t}\n\t}\n\tfor _, weekCosts := range byUserAndWeek {\n\t\tfor weekKey, cost := range weekCosts {\n\t\t\tweekCosts[weekKey] = roundCost(cost)\n\t\t}\n\t}\n\treturn byUserAndWeek\n}\n\nfunc deriveWeekStartUnix(cycleHint *WeeklyCycleHint) int64 {\n\tif cycleHint == nil || cycleHint.WindowMinutes <= 0 || cycleHint.ResetAt.IsZero() {\n\t\treturn 0\n\t}\n\twindowDuration := time.Duration(cycleHint.WindowMinutes) * time.Minute\n\treturn cycleHint.ResetAt.UTC().Add(-windowDuration).Unix()\n}\n\nfunc (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {\n\tu.mutex.Lock()\n\tdefer u.mutex.Unlock()\n\n\tresult := &AggregatedUsageJSON{\n\t\tLastUpdated: u.LastUpdated,\n\t\tCosts: CostsSummaryJSON{\n\t\t\tTotalUSD: 0,\n\t\t\tByUser:   make(map[string]float64),\n\t\t\tByWeek:   make(map[string]float64),\n\t\t},\n\t}\n\n\tglobalCombinationsJSON, totalCost := buildCombinationJSON(u.Combinations, result.Costs.ByUser)\n\tresult.Combinations = globalCombinationsJSON\n\tresult.Costs.TotalUSD = totalCost\n\tresult.Costs.ByWeek = buildByWeekCost(u.Combinations)\n\n\tif len(result.Costs.ByWeek) == 0 {\n\t\tresult.Costs.ByWeek = nil\n\t}\n\n\tresult.Costs.ByUserAndWeek = buildByUserAndWeekCost(u.Combinations)\n\tif len(result.Costs.ByUserAndWeek) == 0 {\n\t\tresult.Costs.ByUserAndWeek = nil\n\t}\n\n\tfor user, cost := range result.Costs.ByUser {\n\t\tresult.Costs.ByUser[user] = roundCost(cost)\n\t}\n\n\treturn result\n}\n\nfunc (u *AggregatedUsage) Load() error {\n\tu.mutex.Lock()\n\tdefer u.mutex.Unlock()\n\n\tu.LastUpdated = time.Time{}\n\tu.Combinations = nil\n\n\tdata, err := os.ReadFile(u.filePath)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\n\tvar temp struct {\n\t\tLastUpdated  time.Time         `json:\"last_updated\"`\n\t\tCombinations []CostCombination `json:\"combinations\"`\n\t}\n\n\terr = json.Unmarshal(data, &temp)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tu.LastUpdated = temp.LastUpdated\n\tu.Combinations = temp.Combinations\n\tnormalizeCombinations(u.Combinations)\n\n\treturn nil\n}\n\nfunc (u *AggregatedUsage) Save() error {\n\tjsonData := u.ToJSON()\n\n\tdata, err := json.MarshalIndent(jsonData, \"\", \"  \")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttmpFile := u.filePath + \".tmp\"\n\terr = os.WriteFile(tmpFile, data, 0o644)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.Remove(tmpFile)\n\terr = os.Rename(tmpFile, u.filePath)\n\tif err == nil {\n\t\tu.saveMutex.Lock()\n\t\tu.lastSaveTime = time.Now()\n\t\tu.saveMutex.Unlock()\n\t}\n\treturn err\n}\n\nfunc (u *AggregatedUsage) AddUsage(model string, contextWindow int, inputTokens, outputTokens, cachedTokens int64, serviceTier string, user string) error {\n\treturn u.AddUsageWithCycleHint(model, contextWindow, inputTokens, outputTokens, cachedTokens, serviceTier, user, time.Now(), nil)\n}\n\nfunc (u *AggregatedUsage) AddUsageWithCycleHint(model string, contextWindow int, inputTokens, outputTokens, cachedTokens int64, serviceTier string, user string, observedAt time.Time, cycleHint *WeeklyCycleHint) error {\n\tif model == \"\" {\n\t\treturn E.New(\"model cannot be empty\")\n\t}\n\tif contextWindow <= 0 {\n\t\treturn E.New(\"contextWindow must be positive\")\n\t}\n\n\tnormalizedServiceTier := normalizeServiceTier(serviceTier)\n\tif observedAt.IsZero() {\n\t\tobservedAt = time.Now()\n\t}\n\n\tu.mutex.Lock()\n\tdefer u.mutex.Unlock()\n\n\tu.LastUpdated = observedAt\n\tweekStartUnix := deriveWeekStartUnix(cycleHint)\n\n\taddUsageToCombinations(&u.Combinations, model, normalizedServiceTier, contextWindow, weekStartUnix, user, inputTokens, outputTokens, cachedTokens)\n\n\tgo u.scheduleSave()\n\n\treturn nil\n}\n\nfunc (u *AggregatedUsage) scheduleSave() {\n\tconst saveInterval = time.Minute\n\n\tu.saveMutex.Lock()\n\tdefer u.saveMutex.Unlock()\n\n\ttimeSinceLastSave := time.Since(u.lastSaveTime)\n\n\tif timeSinceLastSave >= saveInterval {\n\t\tgo u.saveAsync()\n\t\treturn\n\t}\n\n\tif u.pendingSave {\n\t\treturn\n\t}\n\n\tu.pendingSave = true\n\tremainingTime := saveInterval - timeSinceLastSave\n\n\tu.saveTimer = time.AfterFunc(remainingTime, func() {\n\t\tu.saveMutex.Lock()\n\t\tu.pendingSave = false\n\t\tu.saveMutex.Unlock()\n\t\tu.saveAsync()\n\t})\n}\n\nfunc (u *AggregatedUsage) saveAsync() {\n\terr := u.Save()\n\tif err != nil {\n\t\tif u.logger != nil {\n\t\t\tu.logger.Error(\"save usage statistics: \", err)\n\t\t}\n\t}\n}\n\nfunc (u *AggregatedUsage) cancelPendingSave() {\n\tu.saveMutex.Lock()\n\tdefer u.saveMutex.Unlock()\n\n\tif u.saveTimer != nil {\n\t\tu.saveTimer.Stop()\n\t\tu.saveTimer = nil\n\t}\n\tu.pendingSave = false\n}\n"
  },
  {
    "path": "service/ocm/service_user.go",
    "content": "package ocm\n\nimport (\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/option\"\n)\n\ntype UserManager struct {\n\taccessMutex sync.RWMutex\n\ttokenMap    map[string]string\n}\n\nfunc (m *UserManager) UpdateUsers(users []option.OCMUser) {\n\tm.accessMutex.Lock()\n\tdefer m.accessMutex.Unlock()\n\ttokenMap := make(map[string]string, len(users))\n\tfor _, user := range users {\n\t\ttokenMap[user.Token] = user.Name\n\t}\n\tm.tokenMap = tokenMap\n}\n\nfunc (m *UserManager) Authenticate(token string) (string, bool) {\n\tm.accessMutex.RLock()\n\tusername, found := m.tokenMap[token]\n\tm.accessMutex.RUnlock()\n\treturn username, found\n}\n"
  },
  {
    "path": "service/ocm/service_websocket.go",
    "content": "package ocm\n\nimport (\n\t\"context\"\n\tstdTLS \"crypto/tls\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/ntp\"\n\t\"github.com/sagernet/ws\"\n\t\"github.com/sagernet/ws/wsutil\"\n\n\t\"github.com/openai/openai-go/v3/responses\"\n)\n\ntype webSocketSession struct {\n\tclientConn   net.Conn\n\tupstreamConn net.Conn\n\tcloseOnce    sync.Once\n}\n\nfunc (s *webSocketSession) Close() {\n\ts.closeOnce.Do(func() {\n\t\ts.clientConn.Close()\n\t\ts.upstreamConn.Close()\n\t})\n}\n\nfunc buildUpstreamWebSocketURL(baseURL string, proxyPath string) string {\n\tupstreamURL := baseURL\n\tif strings.HasPrefix(upstreamURL, \"https://\") {\n\t\tupstreamURL = \"wss://\" + upstreamURL[len(\"https://\"):]\n\t} else if strings.HasPrefix(upstreamURL, \"http://\") {\n\t\tupstreamURL = \"ws://\" + upstreamURL[len(\"http://\"):]\n\t}\n\treturn upstreamURL + proxyPath\n}\n\nfunc isForwardableResponseHeader(key string) bool {\n\tlowerKey := strings.ToLower(key)\n\tswitch {\n\tcase strings.HasPrefix(lowerKey, \"x-codex-\"):\n\t\treturn true\n\tcase strings.HasPrefix(lowerKey, \"x-reasoning\"):\n\t\treturn true\n\tcase lowerKey == \"openai-model\":\n\t\treturn true\n\tcase strings.Contains(lowerKey, \"-secondary-\"):\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc isForwardableWebSocketRequestHeader(key string) bool {\n\tif isHopByHopHeader(key) {\n\t\treturn false\n\t}\n\n\tlowerKey := strings.ToLower(key)\n\tswitch {\n\tcase lowerKey == \"authorization\":\n\t\treturn false\n\tcase strings.HasPrefix(lowerKey, \"sec-websocket-\"):\n\t\treturn false\n\tdefault:\n\t\treturn true\n\t}\n}\n\nfunc (s *Service) handleWebSocket(w http.ResponseWriter, r *http.Request, proxyPath string, username string) {\n\taccessToken, err := s.getAccessToken()\n\tif err != nil {\n\t\ts.logger.Error(\"get access token for websocket: \", err)\n\t\twriteJSONError(w, r, http.StatusUnauthorized, \"authentication_error\", \"authentication failed\")\n\t\treturn\n\t}\n\n\tupstreamURL := buildUpstreamWebSocketURL(s.getBaseURL(), proxyPath)\n\tif r.URL.RawQuery != \"\" {\n\t\tupstreamURL += \"?\" + r.URL.RawQuery\n\t}\n\n\tupstreamHeaders := make(http.Header)\n\tfor key, values := range r.Header {\n\t\tif isForwardableWebSocketRequestHeader(key) {\n\t\t\tupstreamHeaders[key] = values\n\t\t}\n\t}\n\tfor key, values := range s.httpHeaders {\n\t\tupstreamHeaders.Del(key)\n\t\tupstreamHeaders[key] = values\n\t}\n\tupstreamHeaders.Set(\"Authorization\", \"Bearer \"+accessToken)\n\tif accountID := s.getAccountID(); accountID != \"\" {\n\t\tupstreamHeaders.Set(\"ChatGPT-Account-Id\", accountID)\n\t}\n\n\tupstreamResponseHeaders := make(http.Header)\n\tupstreamDialer := ws.Dialer{\n\t\tNetDial: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\treturn s.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t\t},\n\t\tTLSConfig: &stdTLS.Config{\n\t\t\tRootCAs: adapter.RootPoolFromContext(s.ctx),\n\t\t\tTime:    ntp.TimeFuncFromContext(s.ctx),\n\t\t},\n\t\tHeader: ws.HandshakeHeaderHTTP(upstreamHeaders),\n\t\tOnHeader: func(key, value []byte) error {\n\t\t\tupstreamResponseHeaders.Add(string(key), string(value))\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tupstreamConn, upstreamBufferedReader, _, err := upstreamDialer.Dial(r.Context(), upstreamURL)\n\tif err != nil {\n\t\ts.logger.Error(\"dial upstream websocket: \", err)\n\t\twriteJSONError(w, r, http.StatusBadGateway, \"api_error\", \"upstream websocket connection failed\")\n\t\treturn\n\t}\n\n\tweeklyCycleHint := extractWeeklyCycleHint(upstreamResponseHeaders)\n\n\tclientResponseHeaders := make(http.Header)\n\tfor key, values := range upstreamResponseHeaders {\n\t\tif isForwardableResponseHeader(key) {\n\t\t\tclientResponseHeaders[key] = values\n\t\t}\n\t}\n\n\tclientUpgrader := ws.HTTPUpgrader{\n\t\tHeader: clientResponseHeaders,\n\t}\n\tif s.isShuttingDown() {\n\t\tupstreamConn.Close()\n\t\twriteJSONError(w, r, http.StatusServiceUnavailable, \"api_error\", \"service is shutting down\")\n\t\treturn\n\t}\n\tclientConn, _, _, err := clientUpgrader.Upgrade(r, w)\n\tif err != nil {\n\t\ts.logger.Error(\"upgrade client websocket: \", err)\n\t\tupstreamConn.Close()\n\t\treturn\n\t}\n\tsession := &webSocketSession{\n\t\tclientConn:   clientConn,\n\t\tupstreamConn: upstreamConn,\n\t}\n\tif !s.registerWebSocketSession(session) {\n\t\tsession.Close()\n\t\treturn\n\t}\n\tdefer s.unregisterWebSocketSession(session)\n\n\tvar upstreamReadWriter io.ReadWriter\n\tif upstreamBufferedReader != nil {\n\t\tupstreamReadWriter = struct {\n\t\t\tio.Reader\n\t\t\tio.Writer\n\t\t}{upstreamBufferedReader, upstreamConn}\n\t} else {\n\t\tupstreamReadWriter = upstreamConn\n\t}\n\n\tmodelChannel := make(chan string, 1)\n\tvar waitGroup sync.WaitGroup\n\n\twaitGroup.Add(2)\n\tgo func() {\n\t\tdefer waitGroup.Done()\n\t\tdefer session.Close()\n\t\ts.proxyWebSocketClientToUpstream(clientConn, upstreamConn, modelChannel)\n\t}()\n\tgo func() {\n\t\tdefer waitGroup.Done()\n\t\tdefer session.Close()\n\t\ts.proxyWebSocketUpstreamToClient(upstreamReadWriter, clientConn, modelChannel, username, weeklyCycleHint)\n\t}()\n\twaitGroup.Wait()\n}\n\nfunc (s *Service) proxyWebSocketClientToUpstream(clientConn net.Conn, upstreamConn net.Conn, modelChannel chan<- string) {\n\tfor {\n\t\tdata, opCode, err := wsutil.ReadClientData(clientConn)\n\t\tif err != nil {\n\t\t\tif !E.IsClosedOrCanceled(err) {\n\t\t\t\ts.logger.Debug(\"read client websocket: \", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif opCode == ws.OpText && s.usageTracker != nil {\n\t\t\tvar request struct {\n\t\t\t\tType  string `json:\"type\"`\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(data, &request) == nil && request.Type == \"response.create\" && request.Model != \"\" {\n\t\t\t\tselect {\n\t\t\t\tcase modelChannel <- request.Model:\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\terr = wsutil.WriteClientMessage(upstreamConn, opCode, data)\n\t\tif err != nil {\n\t\t\tif !E.IsClosedOrCanceled(err) {\n\t\t\t\ts.logger.Debug(\"write upstream websocket: \", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (s *Service) proxyWebSocketUpstreamToClient(upstreamReadWriter io.ReadWriter, clientConn net.Conn, modelChannel <-chan string, username string, weeklyCycleHint *WeeklyCycleHint) {\n\tvar requestModel string\n\tfor {\n\t\tdata, opCode, err := wsutil.ReadServerData(upstreamReadWriter)\n\t\tif err != nil {\n\t\t\tif !E.IsClosedOrCanceled(err) {\n\t\t\t\ts.logger.Debug(\"read upstream websocket: \", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif opCode == ws.OpText && s.usageTracker != nil {\n\t\t\tselect {\n\t\t\tcase model := <-modelChannel:\n\t\t\t\trequestModel = model\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\tvar event struct {\n\t\t\t\tType string `json:\"type\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(data, &event) == nil && event.Type == \"response.completed\" {\n\t\t\t\tvar streamEvent responses.ResponseStreamEventUnion\n\t\t\t\tif json.Unmarshal(data, &streamEvent) == nil {\n\t\t\t\t\tcompletedEvent := streamEvent.AsResponseCompleted()\n\t\t\t\t\tresponseModel := string(completedEvent.Response.Model)\n\t\t\t\t\tserviceTier := string(completedEvent.Response.ServiceTier)\n\t\t\t\t\tinputTokens := completedEvent.Response.Usage.InputTokens\n\t\t\t\t\toutputTokens := completedEvent.Response.Usage.OutputTokens\n\t\t\t\t\tcachedTokens := completedEvent.Response.Usage.InputTokensDetails.CachedTokens\n\n\t\t\t\t\tif inputTokens > 0 || outputTokens > 0 {\n\t\t\t\t\t\tif responseModel == \"\" {\n\t\t\t\t\t\t\tresponseModel = requestModel\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif responseModel != \"\" {\n\t\t\t\t\t\t\tcontextWindow := detectContextWindow(responseModel, serviceTier, inputTokens)\n\t\t\t\t\t\t\ts.usageTracker.AddUsageWithCycleHint(\n\t\t\t\t\t\t\t\tresponseModel,\n\t\t\t\t\t\t\t\tcontextWindow,\n\t\t\t\t\t\t\t\tinputTokens,\n\t\t\t\t\t\t\t\toutputTokens,\n\t\t\t\t\t\t\t\tcachedTokens,\n\t\t\t\t\t\t\t\tserviceTier,\n\t\t\t\t\t\t\t\tusername,\n\t\t\t\t\t\t\t\ttime.Now(),\n\t\t\t\t\t\t\t\tweeklyCycleHint,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\terr = wsutil.WriteServerMessage(clientConn, opCode, data)\n\t\tif err != nil {\n\t\t\tif !E.IsClosedOrCanceled(err) {\n\t\t\t\ts.logger.Debug(\"write client websocket: \", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "service/oomkiller/config.go",
    "content": "package oomkiller\n\nimport (\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc buildTimerConfig(options option.OOMKillerServiceOptions, memoryLimit uint64, useAvailable bool) (timerConfig, error) {\n\tsafetyMargin := uint64(defaultSafetyMargin)\n\tif options.SafetyMargin != nil && options.SafetyMargin.Value() > 0 {\n\t\tsafetyMargin = options.SafetyMargin.Value()\n\t}\n\n\tminInterval := defaultMinInterval\n\tif options.MinInterval != 0 {\n\t\tminInterval = time.Duration(options.MinInterval.Build())\n\t\tif minInterval <= 0 {\n\t\t\treturn timerConfig{}, E.New(\"min_interval must be greater than 0\")\n\t\t}\n\t}\n\n\tmaxInterval := defaultMaxInterval\n\tif options.MaxInterval != 0 {\n\t\tmaxInterval = time.Duration(options.MaxInterval.Build())\n\t\tif maxInterval <= 0 {\n\t\t\treturn timerConfig{}, E.New(\"max_interval must be greater than 0\")\n\t\t}\n\t}\n\tif maxInterval < minInterval {\n\t\treturn timerConfig{}, E.New(\"max_interval must be greater than or equal to min_interval\")\n\t}\n\n\tchecksBeforeLimit := defaultChecksBeforeLimit\n\tif options.ChecksBeforeLimit != 0 {\n\t\tchecksBeforeLimit = options.ChecksBeforeLimit\n\t\tif checksBeforeLimit <= 0 {\n\t\t\treturn timerConfig{}, E.New(\"checks_before_limit must be greater than 0\")\n\t\t}\n\t}\n\n\treturn timerConfig{\n\t\tmemoryLimit:       memoryLimit,\n\t\tsafetyMargin:      safetyMargin,\n\t\tminInterval:       minInterval,\n\t\tmaxInterval:       maxInterval,\n\t\tchecksBeforeLimit: checksBeforeLimit,\n\t\tuseAvailable:      useAvailable,\n\t}, nil\n}\n"
  },
  {
    "path": "service/oomkiller/service.go",
    "content": "//go:build darwin && cgo\n\npackage oomkiller\n\n/*\n#include <dispatch/dispatch.h>\n\nstatic dispatch_source_t memoryPressureSource;\n\nextern void goMemoryPressureCallback(unsigned long status);\n\nstatic void startMemoryPressureMonitor() {\n\tmemoryPressureSource = dispatch_source_create(\n\t\tDISPATCH_SOURCE_TYPE_MEMORYPRESSURE,\n\t\t0,\n\t\tDISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL,\n\t\tdispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)\n\t);\n\tdispatch_source_set_event_handler(memoryPressureSource, ^{\n\t\tunsigned long status = dispatch_source_get_data(memoryPressureSource);\n\t\tgoMemoryPressureCallback(status);\n\t});\n\tdispatch_activate(memoryPressureSource);\n}\n\nstatic void stopMemoryPressureMonitor() {\n\tif (memoryPressureSource) {\n\t\tdispatch_source_cancel(memoryPressureSource);\n\t\tmemoryPressureSource = NULL;\n\t}\n}\n*/\nimport \"C\"\n\nimport (\n\t\"context\"\n\truntimeDebug \"runtime/debug\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tboxService \"github.com/sagernet/sing-box/adapter/service\"\n\tboxConstant \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/memory\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nfunc RegisterService(registry *boxService.Registry) {\n\tboxService.Register[option.OOMKillerServiceOptions](registry, boxConstant.TypeOOMKiller, NewService)\n}\n\nvar (\n\tglobalAccess   sync.Mutex\n\tglobalServices []*Service\n)\n\ntype Service struct {\n\tboxService.Adapter\n\tlogger        log.ContextLogger\n\trouter        adapter.Router\n\tmemoryLimit   uint64\n\thasTimerMode  bool\n\tuseAvailable  bool\n\ttimerConfig   timerConfig\n\tadaptiveTimer *adaptiveTimer\n}\n\nfunc NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) {\n\ts := &Service{\n\t\tAdapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag),\n\t\tlogger:  logger,\n\t\trouter:  service.FromContext[adapter.Router](ctx),\n\t}\n\n\tif options.MemoryLimit != nil {\n\t\ts.memoryLimit = options.MemoryLimit.Value()\n\t\tif s.memoryLimit > 0 {\n\t\t\ts.hasTimerMode = true\n\t\t}\n\t}\n\n\tconfig, err := buildTimerConfig(options, s.memoryLimit, s.useAvailable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ts.timerConfig = config\n\n\treturn s, nil\n}\n\nfunc (s *Service) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\n\tif s.hasTimerMode {\n\t\ts.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)\n\t\tif s.memoryLimit > 0 {\n\t\t\ts.logger.Info(\"started memory monitor with limit: \", s.memoryLimit/(1024*1024), \" MiB\")\n\t\t} else {\n\t\t\ts.logger.Info(\"started memory monitor with available memory detection\")\n\t\t}\n\t} else {\n\t\ts.logger.Info(\"started memory pressure monitor\")\n\t}\n\n\tglobalAccess.Lock()\n\tisFirst := len(globalServices) == 0\n\tglobalServices = append(globalServices, s)\n\tglobalAccess.Unlock()\n\n\tif isFirst {\n\t\tC.startMemoryPressureMonitor()\n\t}\n\treturn nil\n}\n\nfunc (s *Service) Close() error {\n\tif s.adaptiveTimer != nil {\n\t\ts.adaptiveTimer.stop()\n\t}\n\tglobalAccess.Lock()\n\tfor i, svc := range globalServices {\n\t\tif svc == s {\n\t\t\tglobalServices = append(globalServices[:i], globalServices[i+1:]...)\n\t\t\tbreak\n\t\t}\n\t}\n\tisLast := len(globalServices) == 0\n\tglobalAccess.Unlock()\n\tif isLast {\n\t\tC.stopMemoryPressureMonitor()\n\t}\n\treturn nil\n}\n\n//export goMemoryPressureCallback\nfunc goMemoryPressureCallback(status C.ulong) {\n\tglobalAccess.Lock()\n\tservices := make([]*Service, len(globalServices))\n\tcopy(services, globalServices)\n\tglobalAccess.Unlock()\n\tif len(services) == 0 {\n\t\treturn\n\t}\n\tcriticalFlag := C.ulong(C.DISPATCH_MEMORYPRESSURE_CRITICAL)\n\twarnFlag := C.ulong(C.DISPATCH_MEMORYPRESSURE_WARN)\n\tisCritical := status&criticalFlag != 0\n\tisWarning := status&warnFlag != 0\n\tvar level string\n\tswitch {\n\tcase isCritical:\n\t\tlevel = \"critical\"\n\tcase isWarning:\n\t\tlevel = \"warning\"\n\tdefault:\n\t\tlevel = \"normal\"\n\t}\n\tvar freeOSMemory bool\n\tfor _, s := range services {\n\t\tusage := memory.Total()\n\t\tif s.hasTimerMode {\n\t\t\tif isCritical {\n\t\t\t\ts.logger.Warn(\"memory pressure: \", level, \", usage: \", usage/(1024*1024), \" MiB\")\n\t\t\t\tif s.adaptiveTimer != nil {\n\t\t\t\t\ts.adaptiveTimer.startNow()\n\t\t\t\t}\n\t\t\t} else if isWarning {\n\t\t\t\ts.logger.Warn(\"memory pressure: \", level, \", usage: \", usage/(1024*1024), \" MiB\")\n\t\t\t} else {\n\t\t\t\ts.logger.Debug(\"memory pressure: \", level, \", usage: \", usage/(1024*1024), \" MiB\")\n\t\t\t\tif s.adaptiveTimer != nil {\n\t\t\t\t\ts.adaptiveTimer.stop()\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif isCritical {\n\t\t\t\ts.logger.Error(\"memory pressure: \", level, \", usage: \", usage/(1024*1024), \" MiB, resetting network\")\n\t\t\t\ts.router.ResetNetwork()\n\t\t\t\tfreeOSMemory = true\n\t\t\t} else if isWarning {\n\t\t\t\ts.logger.Warn(\"memory pressure: \", level, \", usage: \", usage/(1024*1024), \" MiB\")\n\t\t\t} else {\n\t\t\t\ts.logger.Debug(\"memory pressure: \", level, \", usage: \", usage/(1024*1024), \" MiB\")\n\t\t\t}\n\t\t}\n\t}\n\tif freeOSMemory {\n\t\truntimeDebug.FreeOSMemory()\n\t}\n}\n"
  },
  {
    "path": "service/oomkiller/service_stub.go",
    "content": "//go:build !darwin || !cgo\n\npackage oomkiller\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tboxService \"github.com/sagernet/sing-box/adapter/service\"\n\tboxConstant \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/memory\"\n\t\"github.com/sagernet/sing/service\"\n)\n\nfunc RegisterService(registry *boxService.Registry) {\n\tboxService.Register[option.OOMKillerServiceOptions](registry, boxConstant.TypeOOMKiller, NewService)\n}\n\ntype Service struct {\n\tboxService.Adapter\n\tlogger        log.ContextLogger\n\trouter        adapter.Router\n\tadaptiveTimer *adaptiveTimer\n\ttimerConfig   timerConfig\n\thasTimerMode  bool\n\tuseAvailable  bool\n\tmemoryLimit   uint64\n}\n\nfunc NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) {\n\ts := &Service{\n\t\tAdapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag),\n\t\tlogger:  logger,\n\t\trouter:  service.FromContext[adapter.Router](ctx),\n\t}\n\n\tif options.MemoryLimit != nil {\n\t\ts.memoryLimit = options.MemoryLimit.Value()\n\t}\n\tif s.memoryLimit > 0 {\n\t\ts.hasTimerMode = true\n\t} else if memory.AvailableSupported() {\n\t\ts.useAvailable = true\n\t\ts.hasTimerMode = true\n\t}\n\n\tconfig, err := buildTimerConfig(options, s.memoryLimit, s.useAvailable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ts.timerConfig = config\n\n\treturn s, nil\n}\n\nfunc (s *Service) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\tif !s.hasTimerMode {\n\t\treturn E.New(\"memory pressure monitoring is not available on this platform without memory_limit\")\n\t}\n\ts.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)\n\ts.adaptiveTimer.start(0)\n\tif s.useAvailable {\n\t\ts.logger.Info(\"started memory monitor with available memory detection\")\n\t} else {\n\t\ts.logger.Info(\"started memory monitor with limit: \", s.memoryLimit/(1024*1024), \" MiB\")\n\t}\n\treturn nil\n}\n\nfunc (s *Service) Close() error {\n\tif s.adaptiveTimer != nil {\n\t\ts.adaptiveTimer.stop()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "service/oomkiller/service_timer.go",
    "content": "package oomkiller\n\nimport (\n\truntimeDebug \"runtime/debug\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common/memory\"\n)\n\nconst (\n\tdefaultChecksBeforeLimit = 4\n\tdefaultMinInterval       = 500 * time.Millisecond\n\tdefaultMaxInterval       = 10 * time.Second\n\tdefaultSafetyMargin      = 5 * 1024 * 1024\n)\n\ntype adaptiveTimer struct {\n\tlogger            log.ContextLogger\n\trouter            adapter.Router\n\tmemoryLimit       uint64\n\tsafetyMargin      uint64\n\tminInterval       time.Duration\n\tmaxInterval       time.Duration\n\tchecksBeforeLimit int\n\tuseAvailable      bool\n\n\taccess        sync.Mutex\n\ttimer         *time.Timer\n\tpreviousUsage uint64\n\tlastInterval  time.Duration\n}\n\ntype timerConfig struct {\n\tmemoryLimit       uint64\n\tsafetyMargin      uint64\n\tminInterval       time.Duration\n\tmaxInterval       time.Duration\n\tchecksBeforeLimit int\n\tuseAvailable      bool\n}\n\nfunc newAdaptiveTimer(logger log.ContextLogger, router adapter.Router, config timerConfig) *adaptiveTimer {\n\treturn &adaptiveTimer{\n\t\tlogger:            logger,\n\t\trouter:            router,\n\t\tmemoryLimit:       config.memoryLimit,\n\t\tsafetyMargin:      config.safetyMargin,\n\t\tminInterval:       config.minInterval,\n\t\tmaxInterval:       config.maxInterval,\n\t\tchecksBeforeLimit: config.checksBeforeLimit,\n\t\tuseAvailable:      config.useAvailable,\n\t}\n}\n\nfunc (t *adaptiveTimer) start(_ uint64) {\n\tt.access.Lock()\n\tdefer t.access.Unlock()\n\tt.startLocked()\n}\n\nfunc (t *adaptiveTimer) startNow() {\n\tt.access.Lock()\n\tt.startLocked()\n\tt.access.Unlock()\n\tt.poll()\n}\n\nfunc (t *adaptiveTimer) startLocked() {\n\tif t.timer != nil {\n\t\treturn\n\t}\n\tt.previousUsage = memory.Total()\n\tt.lastInterval = t.minInterval\n\tt.timer = time.AfterFunc(t.minInterval, t.poll)\n}\n\nfunc (t *adaptiveTimer) stop() {\n\tt.access.Lock()\n\tdefer t.access.Unlock()\n\tt.stopLocked()\n}\n\nfunc (t *adaptiveTimer) stopLocked() {\n\tif t.timer != nil {\n\t\tt.timer.Stop()\n\t\tt.timer = nil\n\t}\n}\n\nfunc (t *adaptiveTimer) running() bool {\n\tt.access.Lock()\n\tdefer t.access.Unlock()\n\treturn t.timer != nil\n}\n\nfunc (t *adaptiveTimer) poll() {\n\tt.access.Lock()\n\tdefer t.access.Unlock()\n\tif t.timer == nil {\n\t\treturn\n\t}\n\n\tusage := memory.Total()\n\tdelta := int64(usage) - int64(t.previousUsage)\n\tt.previousUsage = usage\n\n\tvar remaining uint64\n\tvar triggered bool\n\n\tif t.memoryLimit > 0 {\n\t\tif usage >= t.memoryLimit {\n\t\t\tremaining = 0\n\t\t\ttriggered = true\n\t\t} else {\n\t\t\tremaining = t.memoryLimit - usage\n\t\t}\n\t} else if t.useAvailable {\n\t\tavailable := memory.Available()\n\t\tif available <= t.safetyMargin {\n\t\t\tremaining = 0\n\t\t\ttriggered = true\n\t\t} else {\n\t\t\tremaining = available - t.safetyMargin\n\t\t}\n\t} else {\n\t\tremaining = 0\n\t}\n\n\tif triggered {\n\t\tt.logger.Error(\"memory threshold reached, usage: \", usage/(1024*1024), \" MiB, resetting network\")\n\t\tt.router.ResetNetwork()\n\t\truntimeDebug.FreeOSMemory()\n\t}\n\n\tvar interval time.Duration\n\tif triggered {\n\t\tinterval = t.maxInterval\n\t} else if delta <= 0 {\n\t\tinterval = t.maxInterval\n\t} else if t.checksBeforeLimit <= 0 {\n\t\tinterval = t.maxInterval\n\t} else {\n\t\ttimeToLimit := time.Duration(float64(remaining) / float64(delta) * float64(t.lastInterval))\n\t\tinterval = timeToLimit / time.Duration(t.checksBeforeLimit)\n\t\tif interval < t.minInterval {\n\t\t\tinterval = t.minInterval\n\t\t}\n\t\tif interval > t.maxInterval {\n\t\t\tinterval = t.maxInterval\n\t\t}\n\t}\n\n\tt.lastInterval = interval\n\tt.timer.Reset(interval)\n}\n"
  },
  {
    "path": "service/resolved/resolve1.go",
    "content": "//go:build linux\n\npackage resolved\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\n\t\"github.com/godbus/dbus/v5\"\n\tmDNS \"github.com/miekg/dns\"\n)\n\ntype resolve1Manager Service\n\ntype Address struct {\n\tIfIndex int32\n\tFamily  int32\n\tAddress []byte\n}\n\ntype Name struct {\n\tIfIndex  int32\n\tHostname string\n}\n\ntype ResourceRecord struct {\n\tIfIndex int32\n\tType    uint16\n\tClass   uint16\n\tData    []byte\n}\n\ntype SRVRecord struct {\n\tPriority  uint16\n\tWeight    uint16\n\tPort      uint16\n\tHostname  string\n\tAddresses []Address\n\tCNAME     string\n}\n\ntype TXTRecord []byte\n\ntype LinkDNS struct {\n\tFamily  int32\n\tAddress []byte\n}\n\ntype LinkDNSEx struct {\n\tFamily  int32\n\tAddress []byte\n\tPort    uint16\n\tName    string\n}\n\ntype LinkDomain struct {\n\tDomain      string\n\tRoutingOnly bool\n}\n\nfunc (t *resolve1Manager) getLink(ifIndex int32) (*TransportLink, *dbus.Error) {\n\tlink, loaded := t.links[ifIndex]\n\tif !loaded {\n\t\tlink = &TransportLink{}\n\t\tt.links[ifIndex] = link\n\t\tiif, err := t.network.InterfaceFinder().ByIndex(int(ifIndex))\n\t\tif err != nil {\n\t\t\treturn nil, wrapError(err)\n\t\t}\n\t\tlink.iif = iif\n\t}\n\treturn link, nil\n}\n\nfunc (t *resolve1Manager) getSenderProcess(sender dbus.Sender) (int32, error) {\n\tvar senderPid int32\n\tdbusObject := t.systemBus.Object(\"org.freedesktop.DBus\", \"/org/freedesktop/DBus\")\n\tif dbusObject == nil {\n\t\treturn 0, E.New(\"missing dbus object\")\n\t}\n\terr := dbusObject.Call(\"org.freedesktop.DBus.GetConnectionUnixProcessID\", 0, string(sender)).Store(&senderPid)\n\tif err != nil {\n\t\treturn 0, E.Cause(err, \"GetConnectionUnixProcessID\")\n\t}\n\treturn senderPid, nil\n}\n\nfunc (t *resolve1Manager) createMetadata(sender dbus.Sender) adapter.InboundContext {\n\tvar metadata adapter.InboundContext\n\tmetadata.Inbound = t.Tag()\n\tmetadata.InboundType = C.TypeResolved\n\tsenderPid, err := t.getSenderProcess(sender)\n\tif err != nil {\n\t\treturn metadata\n\t}\n\tvar processInfo adapter.ConnectionOwner\n\tmetadata.ProcessInfo = &processInfo\n\tprocessInfo.ProcessID = uint32(senderPid)\n\n\tprocessPath, err := os.Readlink(F.ToString(\"/proc/\", senderPid, \"/exe\"))\n\tif err == nil {\n\t\tprocessInfo.ProcessPath = processPath\n\t} else {\n\t\tprocessPath, err = os.Readlink(F.ToString(\"/proc/\", senderPid, \"/comm\"))\n\t\tif err == nil {\n\t\t\tprocessInfo.ProcessPath = processPath\n\t\t}\n\t}\n\n\tvar uidFound bool\n\tstatusContent, err := os.ReadFile(F.ToString(\"/proc/\", senderPid, \"/status\"))\n\tif err == nil {\n\t\tfor _, line := range strings.Split(string(statusContent), \"\\n\") {\n\t\t\tline = strings.TrimSpace(line)\n\t\t\tif strings.HasPrefix(line, \"Uid:\") {\n\t\t\t\tfields := strings.Fields(line)\n\t\t\t\tif len(fields) >= 2 {\n\t\t\t\t\tuid, parseErr := strconv.ParseUint(fields[1], 10, 32)\n\t\t\t\t\tif parseErr != nil {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tprocessInfo.UserId = int32(uid)\n\t\t\t\t\tuidFound = true\n\t\t\t\t\tif osUser, _ := user.LookupId(F.ToString(uid)); osUser != nil {\n\t\t\t\t\t\tprocessInfo.UserName = osUser.Username\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif !uidFound {\n\t\tmetadata.ProcessInfo.UserId = -1\n\t}\n\treturn metadata\n}\n\nfunc (t *resolve1Manager) log(sender dbus.Sender, message ...any) {\n\tmetadata := t.createMetadata(sender)\n\tif metadata.ProcessInfo != nil {\n\t\tvar prefix string\n\t\tif metadata.ProcessInfo.ProcessPath != \"\" {\n\t\t\tprefix = filepath.Base(metadata.ProcessInfo.ProcessPath)\n\t\t} else if metadata.ProcessInfo.UserName != \"\" {\n\t\t\tprefix = F.ToString(\"user:\", metadata.ProcessInfo.UserName)\n\t\t} else if metadata.ProcessInfo.UserId != 0 {\n\t\t\tprefix = F.ToString(\"uid:\", metadata.ProcessInfo.UserId)\n\t\t}\n\t\tt.logger.Info(\"(\", prefix, \") \", F.ToString(message...))\n\t} else {\n\t\tt.logger.Info(F.ToString(message...))\n\t}\n}\n\nfunc (t *resolve1Manager) logRequest(sender dbus.Sender, message ...any) context.Context {\n\tctx := log.ContextWithNewID(t.ctx)\n\tmetadata := t.createMetadata(sender)\n\tif metadata.ProcessInfo != nil {\n\t\tvar prefix string\n\t\tif metadata.ProcessInfo.ProcessPath != \"\" {\n\t\t\tprefix = filepath.Base(metadata.ProcessInfo.ProcessPath)\n\t\t} else if metadata.ProcessInfo.UserName != \"\" {\n\t\t\tprefix = F.ToString(\"user:\", metadata.ProcessInfo.UserName)\n\t\t} else if metadata.ProcessInfo.UserId != 0 {\n\t\t\tprefix = F.ToString(\"uid:\", metadata.ProcessInfo.UserId)\n\t\t}\n\t\tt.logger.InfoContext(ctx, \"(\", prefix, \") \", strings.Join(F.MapToString(message), \" \"))\n\t} else {\n\t\tt.logger.InfoContext(ctx, strings.Join(F.MapToString(message), \" \"))\n\t}\n\treturn adapter.WithContext(ctx, &metadata)\n}\n\nfunc familyToString(family int32) string {\n\tswitch family {\n\tcase syscall.AF_UNSPEC:\n\t\treturn \"AF_UNSPEC\"\n\tcase syscall.AF_INET:\n\t\treturn \"AF_INET\"\n\tcase syscall.AF_INET6:\n\t\treturn \"AF_INET6\"\n\tdefault:\n\t\treturn F.ToString(family)\n\t}\n}\n\nfunc (t *resolve1Manager) ResolveHostname(sender dbus.Sender, ifIndex int32, hostname string, family int32, flags uint64) (addresses []Address, canonical string, outflags uint64, err *dbus.Error) {\n\tt.linkAccess.Lock()\n\tlink, err := t.getLink(ifIndex)\n\tif err != nil {\n\t\treturn\n\t}\n\tt.linkAccess.Unlock()\n\tvar strategy C.DomainStrategy\n\tswitch family {\n\tcase syscall.AF_UNSPEC:\n\t\tstrategy = C.DomainStrategyAsIS\n\tcase syscall.AF_INET:\n\t\tstrategy = C.DomainStrategyIPv4Only\n\tcase syscall.AF_INET6:\n\t\tstrategy = C.DomainStrategyIPv6Only\n\t}\n\tctx := t.logRequest(sender, \"ResolveHostname \", link.iif.Name, \" \", hostname, \" \", familyToString(family), \" \", flags)\n\tresponseAddresses, lookupErr := t.dnsRouter.Lookup(ctx, hostname, adapter.DNSQueryOptions{\n\t\tLookupStrategy: strategy,\n\t})\n\tif lookupErr != nil {\n\t\terr = wrapError(err)\n\t\treturn\n\t}\n\taddresses = common.Map(responseAddresses, func(it netip.Addr) Address {\n\t\tvar addrFamily int32\n\t\tif it.Is4() {\n\t\t\taddrFamily = syscall.AF_INET\n\t\t} else {\n\t\t\taddrFamily = syscall.AF_INET6\n\t\t}\n\t\treturn Address{\n\t\t\tIfIndex: ifIndex,\n\t\t\tFamily:  addrFamily,\n\t\t\tAddress: it.AsSlice(),\n\t\t}\n\t})\n\tcanonical = mDNS.CanonicalName(hostname)\n\treturn\n}\n\nfunc (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, family int32, address []byte, flags uint64) (names []Name, outflags uint64, err *dbus.Error) {\n\tt.linkAccess.Lock()\n\tlink, err := t.getLink(ifIndex)\n\tif err != nil {\n\t\treturn\n\t}\n\tt.linkAccess.Unlock()\n\taddr, ok := netip.AddrFromSlice(address)\n\tif !ok {\n\t\terr = wrapError(E.New(\"invalid address\"))\n\t\treturn\n\t}\n\tvar nibbles []string\n\tfor i := len(address) - 1; i >= 0; i-- {\n\t\tb := address[i]\n\t\tnibbles = append(nibbles, fmt.Sprintf(\"%x\", b&0x0F))\n\t\tnibbles = append(nibbles, fmt.Sprintf(\"%x\", b>>4))\n\t}\n\tvar ptrDomain string\n\tif addr.Is4() {\n\t\tptrDomain = strings.Join(nibbles, \".\") + \".in-addr.arpa.\"\n\t} else {\n\t\tptrDomain = strings.Join(nibbles, \".\") + \".ip6.arpa.\"\n\t}\n\trequest := &mDNS.Msg{\n\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\tRecursionDesired: true,\n\t\t},\n\t\tQuestion: []mDNS.Question{\n\t\t\t{\n\t\t\t\tName:   mDNS.Fqdn(ptrDomain),\n\t\t\t\tQtype:  mDNS.TypePTR,\n\t\t\t\tQclass: mDNS.ClassINET,\n\t\t\t},\n\t\t},\n\t}\n\tctx := t.logRequest(sender, \"ResolveAddress \", link.iif.Name, familyToString(family), addr, flags)\n\tvar metadata adapter.InboundContext\n\tmetadata.InboundType = t.Type()\n\tmetadata.Inbound = t.Tag()\n\tresponse, lookupErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{})\n\tif lookupErr != nil {\n\t\terr = wrapError(err)\n\t\treturn\n\t}\n\tif response.Rcode != mDNS.RcodeSuccess {\n\t\terr = rcodeError(response.Rcode)\n\t\treturn\n\t}\n\tfor _, rawRR := range response.Answer {\n\t\tswitch rr := rawRR.(type) {\n\t\tcase *mDNS.PTR:\n\t\t\tnames = append(names, Name{\n\t\t\t\tIfIndex:  ifIndex,\n\t\t\t\tHostname: rr.Ptr,\n\t\t\t})\n\t\t}\n\t}\n\treturn\n}\n\nfunc (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, hostname string, qClass uint16, qType uint16, flags uint64) (records []ResourceRecord, outflags uint64, err *dbus.Error) {\n\tt.linkAccess.Lock()\n\tlink, err := t.getLink(ifIndex)\n\tif err != nil {\n\t\treturn\n\t}\n\tt.linkAccess.Unlock()\n\trequest := &mDNS.Msg{\n\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\tRecursionDesired: true,\n\t\t},\n\t\tQuestion: []mDNS.Question{\n\t\t\t{\n\t\t\t\tName:   mDNS.Fqdn(hostname),\n\t\t\t\tQtype:  qType,\n\t\t\t\tQclass: qClass,\n\t\t\t},\n\t\t},\n\t}\n\tctx := t.logRequest(sender, \"ResolveRecord\", link.iif.Name, hostname, mDNS.Class(qClass), mDNS.Type(qType), flags)\n\tvar metadata adapter.InboundContext\n\tmetadata.InboundType = t.Type()\n\tmetadata.Inbound = t.Tag()\n\tresponse, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{})\n\tif exchangeErr != nil {\n\t\terr = wrapError(exchangeErr)\n\t\treturn\n\t}\n\tif response.Rcode != mDNS.RcodeSuccess {\n\t\terr = rcodeError(response.Rcode)\n\t\treturn\n\t}\n\tfor _, rr := range response.Answer {\n\t\tvar record ResourceRecord\n\t\trecord.IfIndex = ifIndex\n\t\trecord.Type = rr.Header().Rrtype\n\t\trecord.Class = rr.Header().Class\n\t\tdata := make([]byte, mDNS.Len(rr))\n\t\t_, unpackErr := mDNS.PackRR(rr, data, 0, nil, false)\n\t\tif unpackErr != nil {\n\t\t\terr = wrapError(unpackErr)\n\t\t}\n\t\trecord.Data = data\n\t\trecords = append(records, record)\n\t}\n\treturn\n}\n\nfunc (t *resolve1Manager) ResolveService(sender dbus.Sender, ifIndex int32, hostname string, sType string, domain string, family int32, flags uint64) (srvData []SRVRecord, txtData []TXTRecord, canonicalName string, canonicalType string, canonicalDomain string, outflags uint64, err *dbus.Error) {\n\tt.linkAccess.Lock()\n\tlink, err := t.getLink(ifIndex)\n\tif err != nil {\n\t\treturn\n\t}\n\tt.linkAccess.Unlock()\n\n\tserviceName := hostname\n\tif hostname != \"\" && !strings.HasSuffix(hostname, \".\") {\n\t\tserviceName += \".\"\n\t}\n\tserviceName += sType\n\tif !strings.HasSuffix(serviceName, \".\") {\n\t\tserviceName += \".\"\n\t}\n\tserviceName += domain\n\tif !strings.HasSuffix(serviceName, \".\") {\n\t\tserviceName += \".\"\n\t}\n\n\tctx := t.logRequest(sender, \"ResolveService \", link.iif.Name, \" \", hostname, \" \", sType, \" \", domain, \" \", familyToString(family), \" \", flags)\n\n\tsrvRequest := &mDNS.Msg{\n\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\tRecursionDesired: true,\n\t\t},\n\t\tQuestion: []mDNS.Question{\n\t\t\t{\n\t\t\t\tName:   serviceName,\n\t\t\t\tQtype:  mDNS.TypeSRV,\n\t\t\t\tQclass: mDNS.ClassINET,\n\t\t\t},\n\t\t},\n\t}\n\tvar metadata adapter.InboundContext\n\tmetadata.InboundType = t.Type()\n\tmetadata.Inbound = t.Tag()\n\tsrvResponse, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), srvRequest, adapter.DNSQueryOptions{})\n\tif exchangeErr != nil {\n\t\terr = wrapError(exchangeErr)\n\t\treturn\n\t}\n\tif srvResponse.Rcode != mDNS.RcodeSuccess {\n\t\terr = rcodeError(srvResponse.Rcode)\n\t\treturn\n\t}\n\n\ttxtRequest := &mDNS.Msg{\n\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\tRecursionDesired: true,\n\t\t},\n\t\tQuestion: []mDNS.Question{\n\t\t\t{\n\t\t\t\tName:   serviceName,\n\t\t\t\tQtype:  mDNS.TypeTXT,\n\t\t\t\tQclass: mDNS.ClassINET,\n\t\t\t},\n\t\t},\n\t}\n\n\ttxtResponse, exchangeErr := t.dnsRouter.Exchange(ctx, txtRequest, adapter.DNSQueryOptions{})\n\tif exchangeErr != nil {\n\t\terr = wrapError(exchangeErr)\n\t\treturn\n\t}\n\n\tfor _, rawRR := range srvResponse.Answer {\n\t\tswitch rr := rawRR.(type) {\n\t\tcase *mDNS.SRV:\n\t\t\tvar srvRecord SRVRecord\n\t\t\tsrvRecord.Priority = rr.Priority\n\t\t\tsrvRecord.Weight = rr.Weight\n\t\t\tsrvRecord.Port = rr.Port\n\t\t\tsrvRecord.Hostname = rr.Target\n\n\t\t\tvar strategy C.DomainStrategy\n\t\t\tswitch family {\n\t\t\tcase syscall.AF_UNSPEC:\n\t\t\t\tstrategy = C.DomainStrategyAsIS\n\t\t\tcase syscall.AF_INET:\n\t\t\t\tstrategy = C.DomainStrategyIPv4Only\n\t\t\tcase syscall.AF_INET6:\n\t\t\t\tstrategy = C.DomainStrategyIPv6Only\n\t\t\t}\n\n\t\t\taddrs, lookupErr := t.dnsRouter.Lookup(ctx, rr.Target, adapter.DNSQueryOptions{\n\t\t\t\tLookupStrategy: strategy,\n\t\t\t})\n\t\t\tif lookupErr == nil {\n\t\t\t\tsrvRecord.Addresses = common.Map(addrs, func(it netip.Addr) Address {\n\t\t\t\t\tvar addrFamily int32\n\t\t\t\t\tif it.Is4() {\n\t\t\t\t\t\taddrFamily = syscall.AF_INET\n\t\t\t\t\t} else {\n\t\t\t\t\t\taddrFamily = syscall.AF_INET6\n\t\t\t\t\t}\n\t\t\t\t\treturn Address{\n\t\t\t\t\t\tIfIndex: ifIndex,\n\t\t\t\t\t\tFamily:  addrFamily,\n\t\t\t\t\t\tAddress: it.AsSlice(),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\tfor _, a := range srvResponse.Answer {\n\t\t\t\tif cname, ok := a.(*mDNS.CNAME); ok && cname.Header().Name == rr.Target {\n\t\t\t\t\tsrvRecord.CNAME = cname.Target\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tsrvData = append(srvData, srvRecord)\n\t\t}\n\t}\n\tfor _, rawRR := range txtResponse.Answer {\n\t\tswitch rr := rawRR.(type) {\n\t\tcase *mDNS.TXT:\n\t\t\tdata := make([]byte, mDNS.Len(rr))\n\t\t\t_, packErr := mDNS.PackRR(rr, data, 0, nil, false)\n\t\t\tif packErr == nil {\n\t\t\t\ttxtData = append(txtData, data)\n\t\t\t}\n\t\t}\n\t}\n\tcanonicalName = mDNS.CanonicalName(hostname)\n\tcanonicalType = mDNS.CanonicalName(sType)\n\tcanonicalDomain = mDNS.CanonicalName(domain)\n\treturn\n}\n\nfunc (t *resolve1Manager) SetLinkDNS(sender dbus.Sender, ifIndex int32, addresses []LinkDNS) *dbus.Error {\n\tt.linkAccess.Lock()\n\tdefer t.linkAccess.Unlock()\n\tlink, err := t.getLink(ifIndex)\n\tif err != nil {\n\t\treturn wrapError(err)\n\t}\n\tlink.address = addresses\n\tif len(addresses) > 0 {\n\t\tt.log(sender, \"SetLinkDNS \", link.iif.Name, \" \", strings.Join(common.Map(addresses, func(it LinkDNS) string {\n\t\t\treturn M.AddrFromIP(it.Address).String()\n\t\t}), \", \"))\n\t} else {\n\t\tt.log(sender, \"SetLinkDNS \", link.iif.Name, \" (empty)\")\n\t}\n\treturn t.postUpdate(link)\n}\n\nfunc (t *resolve1Manager) SetLinkDNSEx(sender dbus.Sender, ifIndex int32, addresses []LinkDNSEx) *dbus.Error {\n\tt.linkAccess.Lock()\n\tdefer t.linkAccess.Unlock()\n\tlink, err := t.getLink(ifIndex)\n\tif err != nil {\n\t\treturn wrapError(err)\n\t}\n\tlink.addressEx = addresses\n\tif len(addresses) > 0 {\n\t\tt.log(sender, \"SetLinkDNSEx \", link.iif.Name, \" \", strings.Join(common.Map(addresses, func(it LinkDNSEx) string {\n\t\t\treturn M.SocksaddrFrom(M.AddrFromIP(it.Address), it.Port).String()\n\t\t}), \", \"))\n\t} else {\n\t\tt.log(sender, \"SetLinkDNSEx \", link.iif.Name, \" (empty)\")\n\t}\n\treturn t.postUpdate(link)\n}\n\nfunc (t *resolve1Manager) SetLinkDomains(sender dbus.Sender, ifIndex int32, domains []LinkDomain) *dbus.Error {\n\tt.linkAccess.Lock()\n\tdefer t.linkAccess.Unlock()\n\tlink, err := t.getLink(ifIndex)\n\tif err != nil {\n\t\treturn wrapError(err)\n\t}\n\tlink.domain = domains\n\tif len(domains) > 0 {\n\t\tt.log(sender, \"SetLinkDomains \", link.iif.Name, \" \", strings.Join(common.Map(domains, func(domain LinkDomain) string {\n\t\t\tif !domain.RoutingOnly {\n\t\t\t\treturn domain.Domain\n\t\t\t} else {\n\t\t\t\treturn \"~\" + domain.Domain\n\t\t\t}\n\t\t}), \", \"))\n\t} else {\n\t\tt.log(sender, \"SetLinkDomains \", link.iif.Name, \" (empty)\")\n\t}\n\treturn t.postUpdate(link)\n}\n\nfunc (t *resolve1Manager) SetLinkDefaultRoute(sender dbus.Sender, ifIndex int32, defaultRoute bool) *dbus.Error {\n\tt.linkAccess.Lock()\n\tdefer t.linkAccess.Unlock()\n\tlink, err := t.getLink(ifIndex)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlink.defaultRoute = defaultRoute\n\tif defaultRoute {\n\t\tt.defaultRouteSequence = append(common.Filter(t.defaultRouteSequence, func(it int32) bool { return it != ifIndex }), ifIndex)\n\t} else {\n\t\tt.defaultRouteSequence = common.Filter(t.defaultRouteSequence, func(it int32) bool { return it != ifIndex })\n\t}\n\tvar defaultRouteString string\n\tif defaultRoute {\n\t\tdefaultRouteString = \"yes\"\n\t} else {\n\t\tdefaultRouteString = \"no\"\n\t}\n\tt.log(sender, \"SetLinkDefaultRoute \", link.iif.Name, \" \", defaultRouteString)\n\treturn t.postUpdate(link)\n}\n\nfunc (t *resolve1Manager) SetLinkLLMNR(ifIndex int32, llmnrMode string) *dbus.Error {\n\treturn nil\n}\n\nfunc (t *resolve1Manager) SetLinkMulticastDNS(ifIndex int32, mdnsMode string) *dbus.Error {\n\treturn nil\n}\n\nfunc (t *resolve1Manager) SetLinkDNSOverTLS(sender dbus.Sender, ifIndex int32, dotMode string) *dbus.Error {\n\tt.linkAccess.Lock()\n\tdefer t.linkAccess.Unlock()\n\tlink, err := t.getLink(ifIndex)\n\tif err != nil {\n\t\treturn wrapError(err)\n\t}\n\tswitch dotMode {\n\tcase \"yes\":\n\t\tlink.dnsOverTLS = true\n\tcase \"\":\n\t\tdotMode = \"no\"\n\t\tfallthrough\n\tcase \"opportunistic\", \"no\":\n\t\tlink.dnsOverTLS = false\n\t}\n\tt.log(sender, \"SetLinkDNSOverTLS \", link.iif.Name, \" \", dotMode)\n\treturn t.postUpdate(link)\n}\n\nfunc (t *resolve1Manager) SetLinkDNSSEC(ifIndex int32, dnssecMode string) *dbus.Error {\n\treturn nil\n}\n\nfunc (t *resolve1Manager) SetLinkDNSSECNegativeTrustAnchors(ifIndex int32, domains []string) *dbus.Error {\n\treturn nil\n}\n\nfunc (t *resolve1Manager) RevertLink(sender dbus.Sender, ifIndex int32) *dbus.Error {\n\tt.linkAccess.Lock()\n\tdefer t.linkAccess.Unlock()\n\tlink, err := t.getLink(ifIndex)\n\tif err != nil {\n\t\treturn wrapError(err)\n\t}\n\tdelete(t.links, ifIndex)\n\tt.log(sender, \"RevertLink \", link.iif.Name)\n\treturn t.postUpdate(link)\n}\n\n// TODO: implement RegisterService, UnregisterService\n\nfunc (t *resolve1Manager) RegisterService(sender dbus.Sender, identifier string, nameTemplate string, serviceType string, port uint16, priority uint16, weight uint16, txtRecords []TXTRecord) (objectPath dbus.ObjectPath, dbusErr *dbus.Error) {\n\treturn \"\", wrapError(E.New(\"not implemented\"))\n}\n\nfunc (t *resolve1Manager) UnregisterService(sender dbus.Sender, servicePath dbus.ObjectPath) error {\n\treturn wrapError(E.New(\"not implemented\"))\n}\n\nfunc (t *resolve1Manager) ResetStatistics() *dbus.Error {\n\treturn nil\n}\n\nfunc (t *resolve1Manager) FlushCaches(sender dbus.Sender) *dbus.Error {\n\tt.dnsRouter.ClearCache()\n\tt.log(sender, \"FlushCaches\")\n\treturn nil\n}\n\nfunc (t *resolve1Manager) ResetServerFeatures() *dbus.Error {\n\treturn nil\n}\n\nfunc (t *resolve1Manager) postUpdate(link *TransportLink) *dbus.Error {\n\tif t.updateCallback != nil {\n\t\treturn wrapError(t.updateCallback(link))\n\t}\n\treturn nil\n}\n\nfunc rcodeError(rcode int) *dbus.Error {\n\treturn dbus.NewError(\"org.freedesktop.resolve1.DnsError.\"+mDNS.RcodeToString[rcode], []any{mDNS.RcodeToString[rcode]})\n}\n\nfunc wrapError(err error) *dbus.Error {\n\tif err == nil {\n\t\treturn nil\n\t}\n\tvar rcode dns.RcodeError\n\tif errors.As(err, &rcode) {\n\t\treturn rcodeError(int(rcode))\n\t}\n\treturn dbus.MakeFailedError(err)\n}\n"
  },
  {
    "path": "service/resolved/service.go",
    "content": "//go:build linux\n\npackage resolved\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tboxService \"github.com/sagernet/sing-box/adapter/service\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tdnsOutbound \"github.com/sagernet/sing-box/protocol/dns\"\n\ttun \"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/control\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"github.com/godbus/dbus/v5\"\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc RegisterService(registry *boxService.Registry) {\n\tboxService.Register[option.ResolvedServiceOptions](registry, C.TypeResolved, NewService)\n}\n\ntype Service struct {\n\tboxService.Adapter\n\tctx                   context.Context\n\tlogger                log.ContextLogger\n\tnetwork               adapter.NetworkManager\n\tdnsRouter             adapter.DNSRouter\n\tlistener              *listener.Listener\n\tsystemBus             *dbus.Conn\n\tlinkAccess            sync.RWMutex\n\tlinks                 map[int32]*TransportLink\n\tdefaultRouteSequence  []int32\n\tnetworkUpdateCallback *list.Element[tun.NetworkUpdateCallback]\n\tupdateCallback        func(*TransportLink) error\n\tdeleteCallback        func(*TransportLink)\n}\n\ntype TransportLink struct {\n\tiif          *control.Interface\n\taddress      []LinkDNS\n\taddressEx    []LinkDNSEx\n\tdomain       []LinkDomain\n\tdefaultRoute bool\n\tdnsOverTLS   bool\n\t// dnsOverTLSFallback bool\n}\n\nfunc NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.ResolvedServiceOptions) (adapter.Service, error) {\n\tinbound := &Service{\n\t\tAdapter:   boxService.NewAdapter(C.TypeResolved, tag),\n\t\tctx:       ctx,\n\t\tlogger:    logger,\n\t\tnetwork:   service.FromContext[adapter.NetworkManager](ctx),\n\t\tdnsRouter: service.FromContext[adapter.DNSRouter](ctx),\n\t\tlinks:     make(map[int32]*TransportLink),\n\t}\n\tinbound.listener = listener.New(listener.Options{\n\t\tContext:                  ctx,\n\t\tLogger:                   logger,\n\t\tNetwork:                  []string{N.NetworkTCP, N.NetworkUDP},\n\t\tListen:                   options.ListenOptions,\n\t\tConnectionHandler:        inbound,\n\t\tOOBPacketHandler:         inbound,\n\t\tThreadUnsafePacketWriter: true,\n\t})\n\treturn inbound, nil\n}\n\nfunc (i *Service) Start(stage adapter.StartStage) error {\n\tswitch stage {\n\tcase adapter.StartStateInitialize:\n\t\tinboundManager := service.FromContext[adapter.ServiceManager](i.ctx)\n\t\tfor _, transport := range inboundManager.Services() {\n\t\t\tif transport.Type() == C.TypeResolved && transport != i {\n\t\t\t\treturn E.New(\"multiple resolved service are not supported\")\n\t\t\t}\n\t\t}\n\t\tsystemBus, err := dbus.SystemBus()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ti.systemBus = systemBus\n\t\terr = systemBus.Export((*resolve1Manager)(i), \"/org/freedesktop/resolve1\", \"org.freedesktop.resolve1.Manager\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treply, err := systemBus.RequestName(\"org.freedesktop.resolve1\", dbus.NameFlagDoNotQueue)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tswitch reply {\n\t\tcase dbus.RequestNameReplyPrimaryOwner:\n\t\tcase dbus.RequestNameReplyExists:\n\t\t\treturn E.New(\"D-Bus object already exists, maybe real resolved is running\")\n\t\tdefault:\n\t\t\treturn E.New(\"unknown request name reply: \", reply)\n\t\t}\n\t\ti.networkUpdateCallback = i.network.NetworkMonitor().RegisterCallback(i.onNetworkUpdate)\n\tcase adapter.StartStateStart:\n\t\terr := i.listener.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (i *Service) Close() error {\n\tif i.networkUpdateCallback != nil {\n\t\ti.network.NetworkMonitor().UnregisterCallback(i.networkUpdateCallback)\n\t}\n\tif i.systemBus != nil {\n\t\ti.systemBus.ReleaseName(\"org.freedesktop.resolve1\")\n\t\ti.systemBus.Close()\n\t}\n\treturn i.listener.Close()\n}\n\nfunc (i *Service) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {\n\tmetadata.Inbound = i.Tag()\n\tmetadata.InboundType = i.Type()\n\tmetadata.Destination = M.Socksaddr{}\n\tfor {\n\t\tconn.SetReadDeadline(time.Now().Add(C.DNSTimeout))\n\t\terr := dnsOutbound.HandleStreamDNSRequest(ctx, i.dnsRouter, conn, metadata)\n\t\tif err != nil {\n\t\t\tN.CloseOnHandshakeFailure(conn, onClose, err)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (i *Service) NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr) {\n\tgo i.exchangePacket(buffer, oob, source)\n}\n\nfunc (i *Service) exchangePacket(buffer *buf.Buffer, oob []byte, source M.Socksaddr) {\n\tctx := log.ContextWithNewID(i.ctx)\n\terr := i.exchangePacket0(ctx, buffer, oob, source)\n\tif err != nil {\n\t\ti.logger.ErrorContext(ctx, \"process DNS packet: \", err)\n\t}\n}\n\nfunc (i *Service) exchangePacket0(ctx context.Context, buffer *buf.Buffer, oob []byte, source M.Socksaddr) error {\n\tvar message mDNS.Msg\n\terr := message.Unpack(buffer.Bytes())\n\tbuffer.Release()\n\tif err != nil {\n\t\treturn E.Cause(err, \"unpack request\")\n\t}\n\tvar metadata adapter.InboundContext\n\tmetadata.Source = source\n\tmetadata.InboundType = i.Type()\n\tmetadata.Inbound = i.Tag()\n\tresponse, err := i.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tresponseBuffer, err := dns.TruncateDNSMessage(&message, response, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer responseBuffer.Release()\n\t_, _, err = i.listener.UDPConn().WriteMsgUDPAddrPort(responseBuffer.Bytes(), oob, source.AddrPort())\n\treturn err\n}\n\nfunc (i *Service) onNetworkUpdate() {\n\ti.linkAccess.Lock()\n\tdefer i.linkAccess.Unlock()\n\tvar deleteIfIndex []int\n\tfor ifIndex, link := range i.links {\n\t\tiif, err := i.network.InterfaceFinder().ByIndex(int(ifIndex))\n\t\tif err != nil || iif != link.iif {\n\t\t\tdeleteIfIndex = append(deleteIfIndex, int(ifIndex))\n\t\t}\n\t\ti.defaultRouteSequence = common.Filter(i.defaultRouteSequence, func(it int32) bool {\n\t\t\treturn it != ifIndex\n\t\t})\n\t\tif i.deleteCallback != nil {\n\t\t\ti.deleteCallback(link)\n\t\t}\n\t}\n\tfor _, ifIndex := range deleteIfIndex {\n\t\tdelete(i.links, int32(ifIndex))\n\t}\n}\n\nfunc (conf *TransportLink) nameList(ndots int, name string) []string {\n\tsearch := common.Map(common.Filter(conf.domain, func(it LinkDomain) bool {\n\t\treturn !it.RoutingOnly\n\t}), func(it LinkDomain) string {\n\t\treturn it.Domain\n\t})\n\n\tl := len(name)\n\trooted := l > 0 && name[l-1] == '.'\n\tif l > 254 || l == 254 && !rooted {\n\t\treturn nil\n\t}\n\n\tif rooted {\n\t\tif avoidDNS(name) {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{name}\n\t}\n\n\thasNdots := strings.Count(name, \".\") >= ndots\n\tname += \".\"\n\t// l++\n\n\tnames := make([]string, 0, 1+len(search))\n\tif hasNdots && !avoidDNS(name) {\n\t\tnames = append(names, name)\n\t}\n\tfor _, suffix := range search {\n\t\tfqdn := name + suffix\n\t\tif !avoidDNS(fqdn) && len(fqdn) <= 254 {\n\t\t\tnames = append(names, fqdn)\n\t\t}\n\t}\n\tif !hasNdots && !avoidDNS(name) {\n\t\tnames = append(names, name)\n\t}\n\treturn names\n}\n\nfunc avoidDNS(name string) bool {\n\tif name == \"\" {\n\t\treturn true\n\t}\n\tif name[len(name)-1] == '.' {\n\t\tname = name[:len(name)-1]\n\t}\n\treturn strings.HasSuffix(name, \".onion\")\n}\n"
  },
  {
    "path": "service/resolved/stub.go",
    "content": "//go:build !linux\n\npackage resolved\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tboxService \"github.com/sagernet/sing-box/adapter/service\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc RegisterService(registry *boxService.Registry) {\n\tboxService.Register[option.ResolvedServiceOptions](registry, C.TypeResolved, func(ctx context.Context, logger log.ContextLogger, tag string, options option.ResolvedServiceOptions) (adapter.Service, error) {\n\t\treturn nil, E.New(\"resolved service is only supported on Linux\")\n\t})\n}\n\nfunc RegisterTransport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.ResolvedDNSServerOptions](registry, C.TypeResolved, func(ctx context.Context, logger log.ContextLogger, tag string, options option.ResolvedDNSServerOptions) (adapter.DNSTransport, error) {\n\t\treturn nil, E.New(\"resolved DNS server is only supported on Linux\")\n\t})\n}\n"
  },
  {
    "path": "service/resolved/transport.go",
    "content": "//go:build linux\n\npackage resolved\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/service\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nfunc RegisterTransport(registry *dns.TransportRegistry) {\n\tdns.RegisterTransport[option.ResolvedDNSServerOptions](registry, C.TypeResolved, NewTransport)\n}\n\nvar _ adapter.DNSTransport = (*Transport)(nil)\n\ntype Transport struct {\n\tdns.TransportAdapter\n\tctx                    context.Context\n\tlogger                 logger.ContextLogger\n\tserviceTag             string\n\tacceptDefaultResolvers bool\n\tndots                  int\n\ttimeout                time.Duration\n\tattempts               int\n\trotate                 bool\n\tservice                *Service\n\tlinkAccess             sync.RWMutex\n\tlinkServers            map[*TransportLink]*LinkServers\n}\n\ntype LinkServers struct {\n\tLink         *TransportLink\n\tServers      []adapter.DNSTransport\n\tserverOffset uint32\n}\n\nfunc (c *LinkServers) ServerOffset(rotate bool) uint32 {\n\tif rotate {\n\t\treturn atomic.AddUint32(&c.serverOffset, 1) - 1\n\t}\n\treturn 0\n}\n\nfunc NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.ResolvedDNSServerOptions) (adapter.DNSTransport, error) {\n\treturn &Transport{\n\t\tTransportAdapter:       dns.NewTransportAdapter(C.DNSTypeDHCP, tag, nil),\n\t\tctx:                    ctx,\n\t\tlogger:                 logger,\n\t\tserviceTag:             options.Service,\n\t\tacceptDefaultResolvers: options.AcceptDefaultResolvers,\n\t\t// ndots:                  options.NDots,\n\t\t// timeout:                time.Duration(options.Timeout),\n\t\t// attempts:               options.Attempts,\n\t\t// rotate:                 options.Rotate,\n\t\tndots:       1,\n\t\ttimeout:     5 * time.Second,\n\t\tattempts:    2,\n\t\tlinkServers: make(map[*TransportLink]*LinkServers),\n\t}, nil\n}\n\nfunc (t *Transport) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateInitialize {\n\t\treturn nil\n\t}\n\tserviceManager := service.FromContext[adapter.ServiceManager](t.ctx)\n\tservice, loaded := serviceManager.Get(t.serviceTag)\n\tif !loaded {\n\t\treturn E.New(\"service not found: \", t.serviceTag)\n\t}\n\tresolvedInbound, isResolved := service.(*Service)\n\tif !isResolved {\n\t\treturn E.New(\"service is not resolved: \", t.serviceTag)\n\t}\n\tresolvedInbound.updateCallback = t.updateTransports\n\tresolvedInbound.deleteCallback = t.deleteTransport\n\tt.service = resolvedInbound\n\treturn nil\n}\n\nfunc (t *Transport) Close() error {\n\tt.linkAccess.RLock()\n\tdefer t.linkAccess.RUnlock()\n\tfor _, servers := range t.linkServers {\n\t\tfor _, server := range servers.Servers {\n\t\t\tserver.Close()\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (t *Transport) Reset() {\n\tt.linkAccess.RLock()\n\tdefer t.linkAccess.RUnlock()\n\tfor _, servers := range t.linkServers {\n\t\tfor _, server := range servers.Servers {\n\t\t\tserver.Reset()\n\t\t}\n\t}\n}\n\nfunc (t *Transport) updateTransports(link *TransportLink) error {\n\tt.linkAccess.Lock()\n\tdefer t.linkAccess.Unlock()\n\tif servers, loaded := t.linkServers[link]; loaded {\n\t\tfor _, server := range servers.Servers {\n\t\t\tserver.Close()\n\t\t}\n\t}\n\tserverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{\n\t\tBindInterface:      link.iif.Name,\n\t\tUDPFragmentDefault: true,\n\t}))\n\tvar transports []adapter.DNSTransport\n\tfor _, address := range link.address {\n\t\tserverAddr, ok := netip.AddrFromSlice(address.Address)\n\t\tif !ok {\n\t\t\treturn os.ErrInvalid\n\t\t}\n\t\tif link.dnsOverTLS {\n\t\t\ttlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.String(), option.OutboundTLSOptions{\n\t\t\t\tEnabled:    true,\n\t\t\t\tServerName: serverAddr.String(),\n\t\t\t}))\n\t\t\ttransports = append(transports, transport.NewTLSRaw(t.logger, t.TransportAdapter, serverDialer, M.SocksaddrFrom(serverAddr, 53), tlsConfig))\n\n\t\t} else {\n\t\t\ttransports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, M.SocksaddrFrom(serverAddr, 53)))\n\t\t}\n\t}\n\tfor _, address := range link.addressEx {\n\t\tserverAddr, ok := netip.AddrFromSlice(address.Address)\n\t\tif !ok {\n\t\t\treturn os.ErrInvalid\n\t\t}\n\t\tif link.dnsOverTLS {\n\t\t\tvar serverName string\n\t\t\tif address.Name != \"\" {\n\t\t\t\tserverName = address.Name\n\t\t\t} else {\n\t\t\t\tserverName = serverAddr.String()\n\t\t\t}\n\t\t\ttlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.String(), option.OutboundTLSOptions{\n\t\t\t\tEnabled:    true,\n\t\t\t\tServerName: serverName,\n\t\t\t}))\n\t\t\ttransports = append(transports, transport.NewTLSRaw(t.logger, t.TransportAdapter, serverDialer, M.SocksaddrFrom(serverAddr, address.Port), tlsConfig))\n\n\t\t} else {\n\t\t\ttransports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, M.SocksaddrFrom(serverAddr, address.Port)))\n\t\t}\n\t}\n\tt.linkServers[link] = &LinkServers{\n\t\tLink:    link,\n\t\tServers: transports,\n\t}\n\treturn nil\n}\n\nfunc (t *Transport) deleteTransport(link *TransportLink) {\n\tt.linkAccess.Lock()\n\tdefer t.linkAccess.Unlock()\n\tservers, loaded := t.linkServers[link]\n\tif !loaded {\n\t\treturn\n\t}\n\tfor _, server := range servers.Servers {\n\t\tserver.Close()\n\t}\n\tdelete(t.linkServers, link)\n}\n\nfunc (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tquestion := message.Question[0]\n\tvar selectedLink *TransportLink\n\tt.service.linkAccess.RLock()\n\tfor _, link := range t.service.links {\n\t\tfor _, domain := range link.domain {\n\t\t\tif domain.Domain == \".\" && domain.RoutingOnly && !t.acceptDefaultResolvers {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif strings.HasSuffix(question.Name, domain.Domain) {\n\t\t\t\tselectedLink = link\n\t\t\t}\n\t\t}\n\t}\n\tif selectedLink == nil && t.acceptDefaultResolvers {\n\t\tfor l := len(t.service.defaultRouteSequence); l > 0; l-- {\n\t\t\tselectedLink = t.service.links[t.service.defaultRouteSequence[l-1]]\n\t\t\tif len(selectedLink.address) > 0 || len(selectedLink.addressEx) > 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tt.service.linkAccess.RUnlock()\n\tif selectedLink == nil {\n\t\treturn dns.FixedResponseStatus(message, mDNS.RcodeNameError), nil\n\t}\n\tt.linkAccess.RLock()\n\tservers := t.linkServers[selectedLink]\n\tt.linkAccess.RUnlock()\n\tif len(servers.Servers) == 0 {\n\t\treturn dns.FixedResponseStatus(message, mDNS.RcodeNameError), nil\n\t}\n\tif question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {\n\t\treturn t.exchangeParallel(ctx, servers, message)\n\t} else {\n\t\treturn t.exchangeSingleRequest(ctx, servers, message)\n\t}\n}\n\nfunc (t *Transport) exchangeSingleRequest(ctx context.Context, servers *LinkServers, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tvar lastErr error\n\tfor _, fqdn := range servers.Link.nameList(t.ndots, message.Question[0].Name) {\n\t\tresponse, err := t.tryOneName(ctx, servers, message, fqdn)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\t\treturn response, nil\n\t}\n\treturn nil, lastErr\n}\n\nfunc (t *Transport) tryOneName(ctx context.Context, servers *LinkServers, message *mDNS.Msg, fqdn string) (*mDNS.Msg, error) {\n\tserverOffset := servers.ServerOffset(t.rotate)\n\tsLen := uint32(len(servers.Servers))\n\tvar lastErr error\n\tfor i := 0; i < t.attempts; i++ {\n\t\tfor j := uint32(0); j < sLen; j++ {\n\t\t\tserver := servers.Servers[(serverOffset+j)%sLen]\n\t\t\tquestion := message.Question[0]\n\t\t\tquestion.Name = fqdn\n\t\t\texchangeMessage := *message\n\t\t\texchangeMessage.Question = []mDNS.Question{question}\n\t\t\texchangeCtx, cancel := context.WithTimeout(ctx, t.timeout)\n\t\t\tresponse, err := server.Exchange(exchangeCtx, &exchangeMessage)\n\t\t\tcancel()\n\t\t\tif err != nil {\n\t\t\t\tlastErr = err\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn response, nil\n\t\t}\n\t}\n\treturn nil, E.Cause(lastErr, fqdn)\n}\n\nfunc (t *Transport) exchangeParallel(ctx context.Context, servers *LinkServers, message *mDNS.Msg) (*mDNS.Msg, error) {\n\treturned := make(chan struct{})\n\tdefer close(returned)\n\ttype queryResult struct {\n\t\tresponse *mDNS.Msg\n\t\terr      error\n\t}\n\tresults := make(chan queryResult)\n\tstartRacer := func(ctx context.Context, fqdn string) {\n\t\tresponse, err := t.tryOneName(ctx, servers, message, fqdn)\n\t\tselect {\n\t\tcase results <- queryResult{response, err}:\n\t\tcase <-returned:\n\t\t}\n\t}\n\tqueryCtx, queryCancel := context.WithCancel(ctx)\n\tdefer queryCancel()\n\tvar nameCount int\n\tfor _, fqdn := range servers.Link.nameList(t.ndots, message.Question[0].Name) {\n\t\tnameCount++\n\t\tgo startRacer(queryCtx, fqdn)\n\t}\n\tvar errors []error\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, ctx.Err()\n\t\tcase result := <-results:\n\t\t\tif result.err == nil {\n\t\t\t\treturn result.response, nil\n\t\t\t}\n\t\t\terrors = append(errors, result.err)\n\t\t\tif len(errors) == nameCount {\n\t\t\t\treturn nil, E.Errors(errors...)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "service/ssmapi/api.go",
    "content": "package ssmapi\n\nimport (\n\t\"net/http\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tsHTTP \"github.com/sagernet/sing/protocol/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/render\"\n)\n\ntype APIServer struct {\n\tlogger  logger.Logger\n\ttraffic *TrafficManager\n\tuser    *UserManager\n}\n\nfunc NewAPIServer(logger logger.Logger, traffic *TrafficManager, user *UserManager) *APIServer {\n\treturn &APIServer{\n\t\tlogger:  logger,\n\t\ttraffic: traffic,\n\t\tuser:    user,\n\t}\n}\n\nfunc (s *APIServer) Route(r chi.Router) {\n\tr.Route(\"/server/v1\", func(r chi.Router) {\n\t\tr.Use(func(handler http.Handler) http.Handler {\n\t\t\treturn http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {\n\t\t\t\ts.logger.Debug(request.Method, \" \", request.RequestURI, \" \", sHTTP.SourceAddress(request))\n\t\t\t\thandler.ServeHTTP(writer, request)\n\t\t\t})\n\t\t})\n\t\tr.Get(\"/\", s.getServerInfo)\n\t\tr.Get(\"/users\", s.listUser)\n\t\tr.Post(\"/users\", s.addUser)\n\t\tr.Get(\"/users/{username}\", s.getUser)\n\t\tr.Put(\"/users/{username}\", s.updateUser)\n\t\tr.Delete(\"/users/{username}\", s.deleteUser)\n\t\tr.Get(\"/stats\", s.getStats)\n\t})\n}\n\nfunc (s *APIServer) getServerInfo(writer http.ResponseWriter, request *http.Request) {\n\trender.JSON(writer, request, render.M{\n\t\t\"server\":     \"sing-box \" + C.Version,\n\t\t\"apiVersion\": \"v1\",\n\t})\n}\n\ntype UserObject struct {\n\tUserName        string `json:\"username\"`\n\tPassword        string `json:\"uPSK,omitempty\"`\n\tDownlinkBytes   int64  `json:\"downlinkBytes\"`\n\tUplinkBytes     int64  `json:\"uplinkBytes\"`\n\tDownlinkPackets int64  `json:\"downlinkPackets\"`\n\tUplinkPackets   int64  `json:\"uplinkPackets\"`\n\tTCPSessions     int64  `json:\"tcpSessions\"`\n\tUDPSessions     int64  `json:\"udpSessions\"`\n}\n\nfunc (s *APIServer) listUser(writer http.ResponseWriter, request *http.Request) {\n\trender.JSON(writer, request, render.M{\n\t\t\"users\": s.user.List(),\n\t})\n}\n\nfunc (s *APIServer) addUser(writer http.ResponseWriter, request *http.Request) {\n\tvar addRequest struct {\n\t\tUserName string `json:\"username\"`\n\t\tPassword string `json:\"uPSK\"`\n\t}\n\terr := render.DecodeJSON(request.Body, &addRequest)\n\tif err != nil {\n\t\trender.Status(request, http.StatusBadRequest)\n\t\trender.PlainText(writer, request, err.Error())\n\t\treturn\n\t}\n\terr = s.user.Add(addRequest.UserName, addRequest.Password)\n\tif err != nil {\n\t\trender.Status(request, http.StatusBadRequest)\n\t\trender.PlainText(writer, request, err.Error())\n\t\treturn\n\t}\n\twriter.WriteHeader(http.StatusCreated)\n}\n\nfunc (s *APIServer) getUser(writer http.ResponseWriter, request *http.Request) {\n\tuserName := chi.URLParam(request, \"username\")\n\tif userName == \"\" {\n\t\twriter.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\tuPSK, loaded := s.user.Get(userName)\n\tif !loaded {\n\t\twriter.WriteHeader(http.StatusNotFound)\n\t\treturn\n\t}\n\tuser := UserObject{\n\t\tUserName: userName,\n\t\tPassword: uPSK,\n\t}\n\ts.traffic.ReadUser(&user)\n\trender.JSON(writer, request, user)\n}\n\nfunc (s *APIServer) updateUser(writer http.ResponseWriter, request *http.Request) {\n\tuserName := chi.URLParam(request, \"username\")\n\tif userName == \"\" {\n\t\twriter.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\tvar updateRequest struct {\n\t\tPassword string `json:\"uPSK\"`\n\t}\n\terr := render.DecodeJSON(request.Body, &updateRequest)\n\tif err != nil {\n\t\trender.Status(request, http.StatusBadRequest)\n\t\trender.PlainText(writer, request, err.Error())\n\t\treturn\n\t}\n\t_, loaded := s.user.Get(userName)\n\tif !loaded {\n\t\twriter.WriteHeader(http.StatusNotFound)\n\t\treturn\n\t}\n\terr = s.user.Update(userName, updateRequest.Password)\n\tif err != nil {\n\t\trender.Status(request, http.StatusBadRequest)\n\t\trender.PlainText(writer, request, err.Error())\n\t\treturn\n\t}\n\twriter.WriteHeader(http.StatusNoContent)\n}\n\nfunc (s *APIServer) deleteUser(writer http.ResponseWriter, request *http.Request) {\n\tuserName := chi.URLParam(request, \"username\")\n\tif userName == \"\" {\n\t\twriter.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\t_, loaded := s.user.Get(userName)\n\tif !loaded {\n\t\twriter.WriteHeader(http.StatusNotFound)\n\t\treturn\n\t}\n\terr := s.user.Delete(userName)\n\tif err != nil {\n\t\trender.Status(request, http.StatusBadRequest)\n\t\trender.PlainText(writer, request, err.Error())\n\t\treturn\n\t}\n\twriter.WriteHeader(http.StatusNoContent)\n}\n\nfunc (s *APIServer) getStats(writer http.ResponseWriter, request *http.Request) {\n\trequireClear := request.URL.Query().Get(\"clear\") == \"true\"\n\n\tusers := s.user.List()\n\ts.traffic.ReadUsers(users, requireClear)\n\tfor i := range users {\n\t\tusers[i].Password = \"\"\n\t}\n\tuplinkBytes, downlinkBytes, uplinkPackets, downlinkPackets, tcpSessions, udpSessions := s.traffic.ReadGlobal(requireClear)\n\n\trender.JSON(writer, request, render.M{\n\t\t\"uplinkBytes\":     uplinkBytes,\n\t\t\"downlinkBytes\":   downlinkBytes,\n\t\t\"uplinkPackets\":   uplinkPackets,\n\t\t\"downlinkPackets\": downlinkPackets,\n\t\t\"tcpSessions\":     tcpSessions,\n\t\t\"udpSessions\":     udpSessions,\n\t\t\"users\":           users,\n\t})\n}\n"
  },
  {
    "path": "service/ssmapi/cache.go",
    "content": "package ssmapi\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"sync/atomic\"\n\n\t\"github.com/sagernet/sing/common/json\"\n\t\"github.com/sagernet/sing/common/json/badjson\"\n\t\"github.com/sagernet/sing/service/filemanager\"\n)\n\ntype Cache struct {\n\tEndpoints *badjson.TypedMap[string, *EndpointCache] `json:\"endpoints\"`\n}\n\ntype EndpointCache struct {\n\tGlobalUplink          int64                             `json:\"global_uplink\"`\n\tGlobalDownlink        int64                             `json:\"global_downlink\"`\n\tGlobalUplinkPackets   int64                             `json:\"global_uplink_packets\"`\n\tGlobalDownlinkPackets int64                             `json:\"global_downlink_packets\"`\n\tGlobalTCPSessions     int64                             `json:\"global_tcp_sessions\"`\n\tGlobalUDPSessions     int64                             `json:\"global_udp_sessions\"`\n\tUserUplink            *badjson.TypedMap[string, int64]  `json:\"user_uplink\"`\n\tUserDownlink          *badjson.TypedMap[string, int64]  `json:\"user_downlink\"`\n\tUserUplinkPackets     *badjson.TypedMap[string, int64]  `json:\"user_uplink_packets\"`\n\tUserDownlinkPackets   *badjson.TypedMap[string, int64]  `json:\"user_downlink_packets\"`\n\tUserTCPSessions       *badjson.TypedMap[string, int64]  `json:\"user_tcp_sessions\"`\n\tUserUDPSessions       *badjson.TypedMap[string, int64]  `json:\"user_udp_sessions\"`\n\tUsers                 *badjson.TypedMap[string, string] `json:\"users\"`\n}\n\nfunc (s *Service) loadCache() error {\n\tif s.cachePath == \"\" {\n\t\treturn nil\n\t}\n\tbasePath := filemanager.BasePath(s.ctx, s.cachePath)\n\tcacheBinary, err := os.ReadFile(basePath)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\terr = s.decodeCache(cacheBinary)\n\tif err != nil {\n\t\tos.RemoveAll(basePath)\n\t\treturn err\n\t}\n\ts.cacheMutex.Lock()\n\ts.lastSavedCache = cacheBinary\n\ts.cacheMutex.Unlock()\n\treturn nil\n}\n\nfunc (s *Service) saveCache() error {\n\tif s.cachePath == \"\" {\n\t\treturn nil\n\t}\n\tcacheBinary, err := s.encodeCache()\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.cacheMutex.Lock()\n\tdefer s.cacheMutex.Unlock()\n\tif bytes.Equal(s.lastSavedCache, cacheBinary) {\n\t\treturn nil\n\t}\n\treturn s.writeCache(cacheBinary)\n}\n\nfunc (s *Service) writeCache(cacheBinary []byte) error {\n\tbasePath := filemanager.BasePath(s.ctx, s.cachePath)\n\terr := os.MkdirAll(filepath.Dir(basePath), 0o777)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.WriteFile(basePath, cacheBinary, 0o644)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.lastSavedCache = cacheBinary\n\treturn nil\n}\n\nfunc (s *Service) decodeCache(cacheBinary []byte) error {\n\tif len(cacheBinary) == 0 {\n\t\treturn nil\n\t}\n\tcache, err := json.UnmarshalExtended[*Cache](cacheBinary)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif cache.Endpoints == nil || cache.Endpoints.Size() == 0 {\n\t\treturn nil\n\t}\n\tfor _, entry := range cache.Endpoints.Entries() {\n\t\ttrafficManager, loaded := s.traffics[entry.Key]\n\t\tif !loaded {\n\t\t\tcontinue\n\t\t}\n\t\ttrafficManager.globalUplink.Store(entry.Value.GlobalUplink)\n\t\ttrafficManager.globalDownlink.Store(entry.Value.GlobalDownlink)\n\t\ttrafficManager.globalUplinkPackets.Store(entry.Value.GlobalUplinkPackets)\n\t\ttrafficManager.globalDownlinkPackets.Store(entry.Value.GlobalDownlinkPackets)\n\t\ttrafficManager.globalTCPSessions.Store(entry.Value.GlobalTCPSessions)\n\t\ttrafficManager.globalUDPSessions.Store(entry.Value.GlobalUDPSessions)\n\t\ttrafficManager.userUplink = typedAtomicInt64Map(entry.Value.UserUplink)\n\t\ttrafficManager.userDownlink = typedAtomicInt64Map(entry.Value.UserDownlink)\n\t\ttrafficManager.userUplinkPackets = typedAtomicInt64Map(entry.Value.UserUplinkPackets)\n\t\ttrafficManager.userDownlinkPackets = typedAtomicInt64Map(entry.Value.UserDownlinkPackets)\n\t\ttrafficManager.userTCPSessions = typedAtomicInt64Map(entry.Value.UserTCPSessions)\n\t\ttrafficManager.userUDPSessions = typedAtomicInt64Map(entry.Value.UserUDPSessions)\n\t\tuserManager, loaded := s.users[entry.Key]\n\t\tif !loaded {\n\t\t\tcontinue\n\t\t}\n\t\tuserManager.usersMap = typedMap(entry.Value.Users)\n\t\t_ = userManager.postUpdate(false)\n\t}\n\treturn nil\n}\n\nfunc (s *Service) encodeCache() ([]byte, error) {\n\tendpoints := new(badjson.TypedMap[string, *EndpointCache])\n\tfor tag, traffic := range s.traffics {\n\t\tvar (\n\t\t\tuserUplink          = new(badjson.TypedMap[string, int64])\n\t\t\tuserDownlink        = new(badjson.TypedMap[string, int64])\n\t\t\tuserUplinkPackets   = new(badjson.TypedMap[string, int64])\n\t\t\tuserDownlinkPackets = new(badjson.TypedMap[string, int64])\n\t\t\tuserTCPSessions     = new(badjson.TypedMap[string, int64])\n\t\t\tuserUDPSessions     = new(badjson.TypedMap[string, int64])\n\t\t\tuserMap             = new(badjson.TypedMap[string, string])\n\t\t)\n\t\tfor user, uplink := range traffic.userUplink {\n\t\t\tif uplink.Load() > 0 {\n\t\t\t\tuserUplink.Put(user, uplink.Load())\n\t\t\t}\n\t\t}\n\t\tfor user, downlink := range traffic.userDownlink {\n\t\t\tif downlink.Load() > 0 {\n\t\t\t\tuserDownlink.Put(user, downlink.Load())\n\t\t\t}\n\t\t}\n\t\tfor user, uplinkPackets := range traffic.userUplinkPackets {\n\t\t\tif uplinkPackets.Load() > 0 {\n\t\t\t\tuserUplinkPackets.Put(user, uplinkPackets.Load())\n\t\t\t}\n\t\t}\n\t\tfor user, downlinkPackets := range traffic.userDownlinkPackets {\n\t\t\tif downlinkPackets.Load() > 0 {\n\t\t\t\tuserDownlinkPackets.Put(user, downlinkPackets.Load())\n\t\t\t}\n\t\t}\n\t\tfor user, tcpSessions := range traffic.userTCPSessions {\n\t\t\tif tcpSessions.Load() > 0 {\n\t\t\t\tuserTCPSessions.Put(user, tcpSessions.Load())\n\t\t\t}\n\t\t}\n\t\tfor user, udpSessions := range traffic.userUDPSessions {\n\t\t\tif udpSessions.Load() > 0 {\n\t\t\t\tuserUDPSessions.Put(user, udpSessions.Load())\n\t\t\t}\n\t\t}\n\t\tuserManager := s.users[tag]\n\t\tif userManager != nil && len(userManager.usersMap) > 0 {\n\t\t\tuserMap = new(badjson.TypedMap[string, string])\n\t\t\tfor username, password := range userManager.usersMap {\n\t\t\t\tif username != \"\" && password != \"\" {\n\t\t\t\t\tuserMap.Put(username, password)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tendpoints.Put(tag, &EndpointCache{\n\t\t\tGlobalUplink:          traffic.globalUplink.Load(),\n\t\t\tGlobalDownlink:        traffic.globalDownlink.Load(),\n\t\t\tGlobalUplinkPackets:   traffic.globalUplinkPackets.Load(),\n\t\t\tGlobalDownlinkPackets: traffic.globalDownlinkPackets.Load(),\n\t\t\tGlobalTCPSessions:     traffic.globalTCPSessions.Load(),\n\t\t\tGlobalUDPSessions:     traffic.globalUDPSessions.Load(),\n\t\t\tUserUplink:            sortTypedMap(userUplink),\n\t\t\tUserDownlink:          sortTypedMap(userDownlink),\n\t\t\tUserUplinkPackets:     sortTypedMap(userUplinkPackets),\n\t\t\tUserDownlinkPackets:   sortTypedMap(userDownlinkPackets),\n\t\t\tUserTCPSessions:       sortTypedMap(userTCPSessions),\n\t\t\tUserUDPSessions:       sortTypedMap(userUDPSessions),\n\t\t\tUsers:                 sortTypedMap(userMap),\n\t\t})\n\t}\n\tvar buffer bytes.Buffer\n\tencoder := json.NewEncoder(&buffer)\n\tencoder.SetIndent(\"\", \"  \")\n\terr := encoder.Encode(&Cache{\n\t\tEndpoints: sortTypedMap(endpoints),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn buffer.Bytes(), nil\n}\n\nfunc sortTypedMap[T comparable](trafficMap *badjson.TypedMap[string, T]) *badjson.TypedMap[string, T] {\n\tif trafficMap == nil {\n\t\treturn nil\n\t}\n\tkeys := trafficMap.Keys()\n\tsort.Strings(keys)\n\tsortedMap := new(badjson.TypedMap[string, T])\n\tfor _, key := range keys {\n\t\tvalue, _ := trafficMap.Get(key)\n\t\tsortedMap.Put(key, value)\n\t}\n\treturn sortedMap\n}\n\nfunc typedAtomicInt64Map(trafficMap *badjson.TypedMap[string, int64]) map[string]*atomic.Int64 {\n\tresult := make(map[string]*atomic.Int64)\n\tif trafficMap != nil {\n\t\tfor _, entry := range trafficMap.Entries() {\n\t\t\tcounter := new(atomic.Int64)\n\t\t\tcounter.Store(entry.Value)\n\t\t\tresult[entry.Key] = counter\n\t\t}\n\t}\n\treturn result\n}\n\nfunc typedMap[T comparable](trafficMap *badjson.TypedMap[string, T]) map[string]T {\n\tresult := make(map[string]T)\n\tif trafficMap != nil {\n\t\tfor _, entry := range trafficMap.Entries() {\n\t\t\tresult[entry.Key] = entry.Value\n\t\t}\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "service/ssmapi/server.go",
    "content": "package ssmapi\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tboxService \"github.com/sagernet/sing-box/adapter/service\"\n\t\"github.com/sagernet/sing-box/common/listener\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tN \"github.com/sagernet/sing/common/network\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\t\"github.com/sagernet/sing/service\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"golang.org/x/net/http2\"\n)\n\nfunc RegisterService(registry *boxService.Registry) {\n\tboxService.Register[option.SSMAPIServiceOptions](registry, C.TypeSSMAPI, NewService)\n}\n\ntype Service struct {\n\tboxService.Adapter\n\tctx            context.Context\n\tcancel         context.CancelFunc\n\tlogger         log.ContextLogger\n\tlistener       *listener.Listener\n\ttlsConfig      tls.ServerConfig\n\thttpServer     *http.Server\n\ttraffics       map[string]*TrafficManager\n\tusers          map[string]*UserManager\n\tcachePath      string\n\tsaveTicker     *time.Ticker\n\tlastSavedCache []byte\n\tcacheMutex     sync.Mutex\n}\n\nfunc NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.SSMAPIServiceOptions) (adapter.Service, error) {\n\tctx, cancel := context.WithCancel(ctx)\n\tchiRouter := chi.NewRouter()\n\ts := &Service{\n\t\tAdapter: boxService.NewAdapter(C.TypeSSMAPI, tag),\n\t\tctx:     ctx,\n\t\tcancel:  cancel,\n\t\tlogger:  logger,\n\t\tlistener: listener.New(listener.Options{\n\t\t\tContext: ctx,\n\t\t\tLogger:  logger,\n\t\t\tNetwork: []string{N.NetworkTCP},\n\t\t\tListen:  options.ListenOptions,\n\t\t}),\n\t\thttpServer: &http.Server{\n\t\t\tHandler: chiRouter,\n\t\t},\n\t\ttraffics:  make(map[string]*TrafficManager),\n\t\tusers:     make(map[string]*UserManager),\n\t\tcachePath: options.CachePath,\n\t}\n\tinboundManager := service.FromContext[adapter.InboundManager](ctx)\n\tif options.Servers.Size() == 0 {\n\t\treturn nil, E.New(\"missing servers\")\n\t}\n\tfor i, entry := range options.Servers.Entries() {\n\t\tinbound, loaded := inboundManager.Get(entry.Value)\n\t\tif !loaded {\n\t\t\treturn nil, E.New(\"parse SSM server[\", i, \"]: inbound \", entry.Value, \" not found\")\n\t\t}\n\t\tmanagedServer, isManaged := inbound.(adapter.ManagedSSMServer)\n\t\tif !isManaged {\n\t\t\treturn nil, E.New(\"parse SSM server[\", i, \"]: inbound/\", inbound.Type(), \"[\", inbound.Tag(), \"] is not a SSM server\")\n\t\t}\n\t\ttraffic := NewTrafficManager()\n\t\tmanagedServer.SetTracker(traffic)\n\t\tuser := NewUserManager(managedServer, traffic)\n\t\tchiRouter.Route(entry.Key, NewAPIServer(logger, traffic, user).Route)\n\t\ts.traffics[entry.Key] = traffic\n\t\ts.users[entry.Key] = user\n\t}\n\tif options.TLS != nil {\n\t\ttlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ts.tlsConfig = tlsConfig\n\t}\n\treturn s, nil\n}\n\nfunc (s *Service) Start(stage adapter.StartStage) error {\n\tif stage != adapter.StartStateStart {\n\t\treturn nil\n\t}\n\terr := s.loadCache()\n\tif err != nil {\n\t\ts.logger.Error(E.Cause(err, \"load cache\"))\n\t}\n\ts.saveTicker = time.NewTicker(1 * time.Minute)\n\tgo s.loopSaveCache()\n\tif s.tlsConfig != nil {\n\t\terr = s.tlsConfig.Start()\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"create TLS config\")\n\t\t}\n\t}\n\ttcpListener, err := s.listener.ListenTCP()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif s.tlsConfig != nil {\n\t\tif !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {\n\t\t\ts.tlsConfig.SetNextProtos(append([]string{\"h2\"}, s.tlsConfig.NextProtos()...))\n\t\t}\n\t\ttcpListener = aTLS.NewListener(tcpListener, s.tlsConfig)\n\t}\n\tgo func() {\n\t\terr = s.httpServer.Serve(tcpListener)\n\t\tif err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\t\ts.logger.Error(\"serve error: \", err)\n\t\t}\n\t}()\n\treturn nil\n}\n\nfunc (s *Service) loopSaveCache() {\n\tfor {\n\t\tselect {\n\t\tcase <-s.ctx.Done():\n\t\t\treturn\n\t\tcase <-s.saveTicker.C:\n\t\t\terr := s.saveCache()\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Error(E.Cause(err, \"save cache\"))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *Service) Close() error {\n\tif s.cancel != nil {\n\t\ts.cancel()\n\t}\n\tif s.saveTicker != nil {\n\t\ts.saveTicker.Stop()\n\t}\n\terr := s.saveCache()\n\tif err != nil {\n\t\ts.logger.Error(E.Cause(err, \"save cache\"))\n\t}\n\treturn common.Close(\n\t\tcommon.PtrOrNil(s.httpServer),\n\t\tcommon.PtrOrNil(s.listener),\n\t\ts.tlsConfig,\n\t)\n}\n"
  },
  {
    "path": "service/ssmapi/traffic.go",
    "content": "package ssmapi\n\nimport (\n\t\"net\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nvar _ adapter.SSMTracker = (*TrafficManager)(nil)\n\ntype TrafficManager struct {\n\tglobalUplink          atomic.Int64\n\tglobalDownlink        atomic.Int64\n\tglobalUplinkPackets   atomic.Int64\n\tglobalDownlinkPackets atomic.Int64\n\tglobalTCPSessions     atomic.Int64\n\tglobalUDPSessions     atomic.Int64\n\tuserAccess            sync.Mutex\n\tuserUplink            map[string]*atomic.Int64\n\tuserDownlink          map[string]*atomic.Int64\n\tuserUplinkPackets     map[string]*atomic.Int64\n\tuserDownlinkPackets   map[string]*atomic.Int64\n\tuserTCPSessions       map[string]*atomic.Int64\n\tuserUDPSessions       map[string]*atomic.Int64\n}\n\nfunc NewTrafficManager() *TrafficManager {\n\tmanager := &TrafficManager{\n\t\tuserUplink:          make(map[string]*atomic.Int64),\n\t\tuserDownlink:        make(map[string]*atomic.Int64),\n\t\tuserUplinkPackets:   make(map[string]*atomic.Int64),\n\t\tuserDownlinkPackets: make(map[string]*atomic.Int64),\n\t\tuserTCPSessions:     make(map[string]*atomic.Int64),\n\t\tuserUDPSessions:     make(map[string]*atomic.Int64),\n\t}\n\treturn manager\n}\n\nfunc (s *TrafficManager) UpdateUsers(users []string) {\n\ts.userAccess.Lock()\n\tdefer s.userAccess.Unlock()\n\tnewUserUplink := make(map[string]*atomic.Int64)\n\tnewUserDownlink := make(map[string]*atomic.Int64)\n\tnewUserUplinkPackets := make(map[string]*atomic.Int64)\n\tnewUserDownlinkPackets := make(map[string]*atomic.Int64)\n\tnewUserTCPSessions := make(map[string]*atomic.Int64)\n\tnewUserUDPSessions := make(map[string]*atomic.Int64)\n\tfor _, user := range users {\n\t\tif counter, loaded := s.userUplink[user]; loaded {\n\t\t\tnewUserUplink[user] = counter\n\t\t}\n\t\tif counter, loaded := s.userDownlink[user]; loaded {\n\t\t\tnewUserDownlink[user] = counter\n\t\t}\n\t\tif counter, loaded := s.userUplinkPackets[user]; loaded {\n\t\t\tnewUserUplinkPackets[user] = counter\n\t\t}\n\t\tif counter, loaded := s.userDownlinkPackets[user]; loaded {\n\t\t\tnewUserDownlinkPackets[user] = counter\n\t\t}\n\t\tif counter, loaded := s.userTCPSessions[user]; loaded {\n\t\t\tnewUserTCPSessions[user] = counter\n\t\t}\n\t\tif counter, loaded := s.userUDPSessions[user]; loaded {\n\t\t\tnewUserUDPSessions[user] = counter\n\t\t}\n\t}\n\ts.userUplink = newUserUplink\n\ts.userDownlink = newUserDownlink\n\ts.userUplinkPackets = newUserUplinkPackets\n\ts.userDownlinkPackets = newUserDownlinkPackets\n\ts.userTCPSessions = newUserTCPSessions\n\ts.userUDPSessions = newUserUDPSessions\n}\n\nfunc (s *TrafficManager) userCounter(user string) (*atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64) {\n\ts.userAccess.Lock()\n\tdefer s.userAccess.Unlock()\n\tupCounter, loaded := s.userUplink[user]\n\tif !loaded {\n\t\tupCounter = new(atomic.Int64)\n\t\ts.userUplink[user] = upCounter\n\t}\n\tdownCounter, loaded := s.userDownlink[user]\n\tif !loaded {\n\t\tdownCounter = new(atomic.Int64)\n\t\ts.userDownlink[user] = downCounter\n\t}\n\tupPacketsCounter, loaded := s.userUplinkPackets[user]\n\tif !loaded {\n\t\tupPacketsCounter = new(atomic.Int64)\n\t\ts.userUplinkPackets[user] = upPacketsCounter\n\t}\n\tdownPacketsCounter, loaded := s.userDownlinkPackets[user]\n\tif !loaded {\n\t\tdownPacketsCounter = new(atomic.Int64)\n\t\ts.userDownlinkPackets[user] = downPacketsCounter\n\t}\n\ttcpSessionsCounter, loaded := s.userTCPSessions[user]\n\tif !loaded {\n\t\ttcpSessionsCounter = new(atomic.Int64)\n\t\ts.userTCPSessions[user] = tcpSessionsCounter\n\t}\n\tudpSessionsCounter, loaded := s.userUDPSessions[user]\n\tif !loaded {\n\t\tudpSessionsCounter = new(atomic.Int64)\n\t\ts.userUDPSessions[user] = udpSessionsCounter\n\t}\n\treturn upCounter, downCounter, upPacketsCounter, downPacketsCounter, tcpSessionsCounter, udpSessionsCounter\n}\n\nfunc (s *TrafficManager) TrackConnection(conn net.Conn, metadata adapter.InboundContext) net.Conn {\n\ts.globalTCPSessions.Add(1)\n\tvar readCounter []*atomic.Int64\n\tvar writeCounter []*atomic.Int64\n\treadCounter = append(readCounter, &s.globalUplink)\n\twriteCounter = append(writeCounter, &s.globalDownlink)\n\tupCounter, downCounter, _, _, tcpSessionCounter, _ := s.userCounter(metadata.User)\n\treadCounter = append(readCounter, upCounter)\n\twriteCounter = append(writeCounter, downCounter)\n\ttcpSessionCounter.Add(1)\n\treturn bufio.NewInt64CounterConn(conn, readCounter, writeCounter)\n}\n\nfunc (s *TrafficManager) TrackPacketConnection(conn N.PacketConn, metadata adapter.InboundContext) N.PacketConn {\n\ts.globalUDPSessions.Add(1)\n\tvar readCounter []*atomic.Int64\n\tvar readPacketCounter []*atomic.Int64\n\tvar writeCounter []*atomic.Int64\n\tvar writePacketCounter []*atomic.Int64\n\treadCounter = append(readCounter, &s.globalUplink)\n\twriteCounter = append(writeCounter, &s.globalDownlink)\n\treadPacketCounter = append(readPacketCounter, &s.globalUplinkPackets)\n\twritePacketCounter = append(writePacketCounter, &s.globalDownlinkPackets)\n\tupCounter, downCounter, upPacketsCounter, downPacketsCounter, _, udpSessionCounter := s.userCounter(metadata.User)\n\treadCounter = append(readCounter, upCounter)\n\twriteCounter = append(writeCounter, downCounter)\n\treadPacketCounter = append(readPacketCounter, upPacketsCounter)\n\twritePacketCounter = append(writePacketCounter, downPacketsCounter)\n\tudpSessionCounter.Add(1)\n\treturn bufio.NewInt64CounterPacketConn(conn, readCounter, readPacketCounter, writeCounter, writePacketCounter)\n}\n\nfunc (s *TrafficManager) ReadUser(user *UserObject) {\n\ts.userAccess.Lock()\n\tdefer s.userAccess.Unlock()\n\ts.readUser(user, false)\n}\n\nfunc (s *TrafficManager) readUser(user *UserObject, swap bool) {\n\tif counter, loaded := s.userUplink[user.UserName]; loaded {\n\t\tif swap {\n\t\t\tuser.UplinkBytes = counter.Swap(0)\n\t\t} else {\n\t\t\tuser.UplinkBytes = counter.Load()\n\t\t}\n\t}\n\tif counter, loaded := s.userDownlink[user.UserName]; loaded {\n\t\tif swap {\n\t\t\tuser.DownlinkBytes = counter.Swap(0)\n\t\t} else {\n\t\t\tuser.DownlinkBytes = counter.Load()\n\t\t}\n\t}\n\tif counter, loaded := s.userUplinkPackets[user.UserName]; loaded {\n\t\tif swap {\n\t\t\tuser.UplinkPackets = counter.Swap(0)\n\t\t} else {\n\t\t\tuser.UplinkPackets = counter.Load()\n\t\t}\n\t}\n\tif counter, loaded := s.userDownlinkPackets[user.UserName]; loaded {\n\t\tif swap {\n\t\t\tuser.DownlinkPackets = counter.Swap(0)\n\t\t} else {\n\t\t\tuser.DownlinkPackets = counter.Load()\n\t\t}\n\t}\n\tif counter, loaded := s.userTCPSessions[user.UserName]; loaded {\n\t\tif swap {\n\t\t\tuser.TCPSessions = counter.Swap(0)\n\t\t} else {\n\t\t\tuser.TCPSessions = counter.Load()\n\t\t}\n\t}\n\tif counter, loaded := s.userUDPSessions[user.UserName]; loaded {\n\t\tif swap {\n\t\t\tuser.UDPSessions = counter.Swap(0)\n\t\t} else {\n\t\t\tuser.UDPSessions = counter.Load()\n\t\t}\n\t}\n}\n\nfunc (s *TrafficManager) ReadUsers(users []*UserObject, swap bool) {\n\ts.userAccess.Lock()\n\tdefer s.userAccess.Unlock()\n\tfor _, user := range users {\n\t\ts.readUser(user, swap)\n\t}\n}\n\nfunc (s *TrafficManager) ReadGlobal(swap bool) (uplinkBytes int64, downlinkBytes int64, uplinkPackets int64, downlinkPackets int64, tcpSessions int64, udpSessions int64) {\n\tif swap {\n\t\treturn s.globalUplink.Swap(0),\n\t\t\ts.globalDownlink.Swap(0),\n\t\t\ts.globalUplinkPackets.Swap(0),\n\t\t\ts.globalDownlinkPackets.Swap(0),\n\t\t\ts.globalTCPSessions.Swap(0),\n\t\t\ts.globalUDPSessions.Swap(0)\n\t} else {\n\t\treturn s.globalUplink.Load(),\n\t\t\ts.globalDownlink.Load(),\n\t\t\ts.globalUplinkPackets.Load(),\n\t\t\ts.globalDownlinkPackets.Load(),\n\t\t\ts.globalTCPSessions.Load(),\n\t\t\ts.globalUDPSessions.Load()\n\t}\n}\n"
  },
  {
    "path": "service/ssmapi/user.go",
    "content": "package ssmapi\n\nimport (\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype UserManager struct {\n\taccess         sync.Mutex\n\tusersMap       map[string]string\n\tserver         adapter.ManagedSSMServer\n\ttrafficManager *TrafficManager\n}\n\nfunc NewUserManager(inbound adapter.ManagedSSMServer, trafficManager *TrafficManager) *UserManager {\n\treturn &UserManager{\n\t\tusersMap:       make(map[string]string),\n\t\tserver:         inbound,\n\t\ttrafficManager: trafficManager,\n\t}\n}\n\nfunc (m *UserManager) postUpdate(updated bool) error {\n\tusers := make([]string, 0, len(m.usersMap))\n\tuPSKs := make([]string, 0, len(m.usersMap))\n\tfor username, password := range m.usersMap {\n\t\tusers = append(users, username)\n\t\tuPSKs = append(uPSKs, password)\n\t}\n\terr := m.server.UpdateUsers(users, uPSKs)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif updated {\n\t\tm.trafficManager.UpdateUsers(users)\n\t}\n\treturn nil\n}\n\nfunc (m *UserManager) List() []*UserObject {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tusers := make([]*UserObject, 0, len(m.usersMap))\n\tfor username, password := range m.usersMap {\n\t\tusers = append(users, &UserObject{\n\t\t\tUserName: username,\n\t\t\tPassword: password,\n\t\t})\n\t}\n\treturn users\n}\n\nfunc (m *UserManager) Add(username string, password string) error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tif _, found := m.usersMap[username]; found {\n\t\treturn E.New(\"user \", username, \" already exists\")\n\t}\n\tm.usersMap[username] = password\n\treturn m.postUpdate(true)\n}\n\nfunc (m *UserManager) Get(username string) (string, bool) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tif password, found := m.usersMap[username]; found {\n\t\treturn password, true\n\t}\n\treturn \"\", false\n}\n\nfunc (m *UserManager) Update(username string, password string) error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tm.usersMap[username] = password\n\treturn m.postUpdate(true)\n}\n\nfunc (m *UserManager) Delete(username string) error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tdelete(m.usersMap, username)\n\treturn m.postUpdate(true)\n}\n"
  },
  {
    "path": "test/box_test.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/sagernet/quic-go\"\n\t\"github.com/sagernet/quic-go/http3\"\n\t\"github.com/sagernet/sing-box\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/include\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\t\"github.com/sagernet/sing/common/debug\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/protocol/socks\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/goleak\"\n)\n\nfunc TestMain(m *testing.M) {\n\tgoleak.VerifyTestMain(m)\n}\n\nvar globalCtx context.Context\n\nfunc init() {\n\tglobalCtx = include.Context(context.Background())\n}\n\nfunc startInstance(t *testing.T, options option.Options) *box.Box {\n\tif debug.Enabled {\n\t\toptions.Log = &option.LogOptions{\n\t\t\tLevel: \"trace\",\n\t\t}\n\t} else {\n\t\toptions.Log = &option.LogOptions{\n\t\t\tLevel: \"warning\",\n\t\t}\n\t}\n\tctx, cancel := context.WithCancel(globalCtx)\n\tvar instance *box.Box\n\tvar err error\n\tfor retry := 0; retry < 3; retry++ {\n\t\tinstance, err = box.New(box.Options{\n\t\t\tContext: ctx,\n\t\t\tOptions: options,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\terr = instance.Start()\n\t\tif err != nil {\n\t\t\ttime.Sleep(time.Second)\n\t\t\tcontinue\n\t\t}\n\t\tbreak\n\t}\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\tinstance.Close()\n\t\tcancel()\n\t})\n\treturn instance\n}\n\nfunc testSuit(t *testing.T, clientPort uint16, testPort uint16) {\n\tdialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort(\"127.0.0.1\", clientPort), socks.Version5, \"\", \"\")\n\tdialTCP := func() (net.Conn, error) {\n\t\treturn dialer.DialContext(context.Background(), \"tcp\", M.ParseSocksaddrHostPort(\"127.0.0.1\", testPort))\n\t}\n\tdialUDP := func() (net.PacketConn, error) {\n\t\treturn dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort(\"127.0.0.1\", testPort))\n\t}\n\trequire.NoError(t, testPingPongWithConn(t, testPort, dialTCP))\n\trequire.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))\n\trequire.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))\n\trequire.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))\n\n\t// require.NoError(t, testPacketConnTimeout(t, dialUDP))\n}\n\nfunc testQUIC(t *testing.T, clientPort uint16) {\n\tdialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort(\"127.0.0.1\", clientPort), socks.Version5, \"\", \"\")\n\tclient := &http.Client{\n\t\tTransport: &http3.Transport{\n\t\t\tDial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {\n\t\t\t\tdestination := M.ParseSocksaddr(addr)\n\t\t\t\tudpConn, err := dialer.DialContext(ctx, N.NetworkUDP, destination)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treturn quic.DialEarly(ctx, udpConn.(net.PacketConn), destination, tlsCfg, cfg)\n\t\t\t},\n\t\t},\n\t}\n\tresponse, err := client.Get(\"https://cloudflare.com/cdn-cgi/trace\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, response.StatusCode)\n\tcontent, err := io.ReadAll(response.Body)\n\trequire.NoError(t, err)\n\tprintln(string(content))\n}\n\nfunc testSuitLargeUDP(t *testing.T, clientPort uint16, testPort uint16) {\n\tdialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort(\"127.0.0.1\", clientPort), socks.Version5, \"\", \"\")\n\tdialTCP := func() (net.Conn, error) {\n\t\treturn dialer.DialContext(context.Background(), \"tcp\", M.ParseSocksaddrHostPort(\"127.0.0.1\", testPort))\n\t}\n\tdialUDP := func() (net.PacketConn, error) {\n\t\treturn dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort(\"127.0.0.1\", testPort))\n\t}\n\trequire.NoError(t, testPingPongWithConn(t, testPort, dialTCP))\n\trequire.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))\n\trequire.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))\n\trequire.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))\n\trequire.NoError(t, testLargeDataWithPacketConnSize(t, testPort, 4096, dialUDP))\n}\n\nfunc testTCP(t *testing.T, clientPort uint16, testPort uint16) {\n\tdialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort(\"127.0.0.1\", clientPort), socks.Version5, \"\", \"\")\n\tdialTCP := func() (net.Conn, error) {\n\t\treturn dialer.DialContext(context.Background(), \"tcp\", M.ParseSocksaddrHostPort(\"127.0.0.1\", testPort))\n\t}\n\trequire.NoError(t, testPingPongWithConn(t, testPort, dialTCP))\n\trequire.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))\n}\n\nfunc testSuitSimple(t *testing.T, clientPort uint16, testPort uint16) {\n\tdialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort(\"127.0.0.1\", clientPort), socks.Version5, \"\", \"\")\n\tdialTCP := func() (net.Conn, error) {\n\t\treturn dialer.DialContext(context.Background(), \"tcp\", M.ParseSocksaddrHostPort(\"127.0.0.1\", testPort))\n\t}\n\tdialUDP := func() (net.PacketConn, error) {\n\t\treturn dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort(\"127.0.0.1\", testPort))\n\t}\n\trequire.NoError(t, testPingPongWithConn(t, testPort, dialTCP))\n\trequire.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))\n\trequire.NoError(t, testPingPongWithConn(t, testPort, dialTCP))\n\trequire.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))\n}\n\nfunc testSuitSimple1(t *testing.T, clientPort uint16, testPort uint16) {\n\tdialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort(\"127.0.0.1\", clientPort), socks.Version5, \"\", \"\")\n\tdialTCP := func() (net.Conn, error) {\n\t\treturn dialer.DialContext(context.Background(), \"tcp\", M.ParseSocksaddrHostPort(\"127.0.0.1\", testPort))\n\t}\n\tdialUDP := func() (net.PacketConn, error) {\n\t\treturn dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort(\"127.0.0.1\", testPort))\n\t}\n\trequire.NoError(t, testPingPongWithConn(t, testPort, dialTCP))\n\tif !C.IsDarwin {\n\t\trequire.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))\n\t}\n\trequire.NoError(t, testPingPongWithConn(t, testPort, dialTCP))\n\tif !C.IsDarwin {\n\t\trequire.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))\n\t}\n}\n\nfunc testSuitWg(t *testing.T, clientPort uint16, testPort uint16) {\n\tdialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort(\"127.0.0.1\", clientPort), socks.Version5, \"\", \"\")\n\tdialTCP := func() (net.Conn, error) {\n\t\treturn dialer.DialContext(context.Background(), \"tcp\", M.ParseSocksaddrHostPort(\"10.0.0.1\", testPort))\n\t}\n\tdialUDP := func() (net.PacketConn, error) {\n\t\tconn, err := dialer.DialContext(context.Background(), \"udp\", M.ParseSocksaddrHostPort(\"10.0.0.1\", testPort))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn bufio.NewUnbindPacketConn(conn), nil\n\t}\n\trequire.NoError(t, testPingPongWithConn(t, testPort, dialTCP))\n\trequire.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))\n\trequire.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))\n\trequire.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))\n}\n"
  },
  {
    "path": "test/brutal_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/gofrs/uuid/v5\"\n)\n\nfunc TestBrutalShadowsocks(t *testing.T) {\n\tmethod := shadowaead_2022.List[0]\n\tpassword := mkBase64(t, 16)\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t\tMultiplex: &option.InboundMultiplexOptions{\n\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\tBrutal: &option.BrutalOptions{\n\t\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\t\tUpMbps:   100,\n\t\t\t\t\t\t\tDownMbps: 100,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t\tMultiplex: &option.OutboundMultiplexOptions{\n\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\tProtocol: \"smux\",\n\t\t\t\t\t\tPadding:  true,\n\t\t\t\t\t\tBrutal: &option.BrutalOptions{\n\t\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\t\tUpMbps:   100,\n\t\t\t\t\t\t\tDownMbps: 100,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestBrutalTrojan(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tpassword := mkBase64(t, 16)\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tOptions: &option.TrojanInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TrojanUser{{Password: password}},\n\t\t\t\t\tMultiplex: &option.InboundMultiplexOptions{\n\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\tBrutal: &option.BrutalOptions{\n\t\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\t\tUpMbps:   100,\n\t\t\t\t\t\t\tDownMbps: 100,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.TrojanOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tPassword: password,\n\t\t\t\t\tMultiplex: &option.OutboundMultiplexOptions{\n\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\tProtocol: \"yamux\",\n\t\t\t\t\t\tPadding:  true,\n\t\t\t\t\t\tBrutal: &option.BrutalOptions{\n\t\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\t\tUpMbps:   100,\n\t\t\t\t\t\t\tDownMbps: 100,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestBrutalVMess(t *testing.T) {\n\tuser, _ := uuid.NewV4()\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VMessUser{{UUID: user.String()}},\n\t\t\t\t\tMultiplex: &option.InboundMultiplexOptions{\n\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\tBrutal: &option.BrutalOptions{\n\t\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\t\tUpMbps:   100,\n\t\t\t\t\t\t\tDownMbps: 100,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.VMessOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID: user.String(),\n\t\t\t\t\tMultiplex: &option.OutboundMultiplexOptions{\n\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\tProtocol: \"h2mux\",\n\t\t\t\t\t\tPadding:  true,\n\t\t\t\t\t\tBrutal: &option.BrutalOptions{\n\t\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\t\tUpMbps:   100,\n\t\t\t\t\t\t\tDownMbps: 100,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestBrutalVLESS(t *testing.T) {\n\tuser, _ := uuid.NewV4()\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVLESS,\n\t\t\t\tOptions: &option.VLESSInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VLESSUser{{UUID: user.String()}},\n\t\t\t\t\tMultiplex: &option.InboundMultiplexOptions{\n\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\tBrutal: &option.BrutalOptions{\n\t\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\t\tUpMbps:   100,\n\t\t\t\t\t\t\tDownMbps: 100,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:    true,\n\t\t\t\t\t\t\tServerName: \"google.com\",\n\t\t\t\t\t\t\tReality: &option.InboundRealityOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tHandshake: option.InboundRealityHandshakeOptions{\n\t\t\t\t\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\t\t\t\t\tServer:     \"google.com\",\n\t\t\t\t\t\t\t\t\t\tServerPort: 443,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tShortID:    []string{\"0123456789abcdef\"},\n\t\t\t\t\t\t\t\tPrivateKey: \"UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVLESS,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.VLESSOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID: user.String(),\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:    true,\n\t\t\t\t\t\t\tServerName: \"google.com\",\n\t\t\t\t\t\t\tReality: &option.OutboundRealityOptions{\n\t\t\t\t\t\t\t\tEnabled:   true,\n\t\t\t\t\t\t\t\tShortID:   \"0123456789abcdef\",\n\t\t\t\t\t\t\t\tPublicKey: \"jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tUTLS: &option.OutboundUTLSOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tMultiplex: &option.OutboundMultiplexOptions{\n\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\tProtocol: \"h2mux\",\n\t\t\t\t\t\tPadding:  true,\n\t\t\t\t\t\tBrutal: &option.BrutalOptions{\n\t\t\t\t\t\t\tEnabled:  true,\n\t\t\t\t\t\t\tUpMbps:   100,\n\t\t\t\t\t\t\tDownMbps: 100,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/clash_darwin_test.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"syscall\"\n\n\t\"golang.org/x/net/route\"\n)\n\nfunc defaultRouteIP() (netip.Addr, error) {\n\tidx, err := defaultRouteInterfaceIndex()\n\tif err != nil {\n\t\treturn netip.Addr{}, err\n\t}\n\tiface, err := net.InterfaceByIndex(idx)\n\tif err != nil {\n\t\treturn netip.Addr{}, err\n\t}\n\taddrs, err := iface.Addrs()\n\tif err != nil {\n\t\treturn netip.Addr{}, err\n\t}\n\tfor _, addr := range addrs {\n\t\tip := addr.(*net.IPNet).IP\n\t\tif ip.To4() != nil {\n\t\t\treturn netip.AddrFrom4([4]byte(ip)), nil\n\t\t}\n\t}\n\n\treturn netip.Addr{}, errors.New(\"no ipv4 addr\")\n}\n\nfunc defaultRouteInterfaceIndex() (int, error) {\n\trib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"route.FetchRIB: %w\", err)\n\t}\n\tmsgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"route.ParseRIB: %w\", err)\n\t}\n\tfor _, message := range msgs {\n\t\trouteMessage := message.(*route.RouteMessage)\n\t\tif routeMessage.Flags&(syscall.RTF_UP|syscall.RTF_GATEWAY|syscall.RTF_STATIC) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\taddresses := routeMessage.Addrs\n\n\t\tdestination, ok := addresses[0].(*route.Inet4Addr)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif destination.IP != [4]byte{0, 0, 0, 0} {\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch addresses[1].(type) {\n\t\tcase *route.Inet4Addr:\n\t\t\treturn routeMessage.Index, nil\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t}\n\n\treturn 0, fmt.Errorf(\"ambiguous gateway interfaces found\")\n}\n"
  },
  {
    "path": "test/clash_other_test.go",
    "content": "//go:build !darwin\n\npackage main\n\nimport (\n\t\"errors\"\n\t\"net/netip\"\n)\n\nfunc defaultRouteIP() (netip.Addr, error) {\n\treturn netip.Addr{}, errors.New(\"not supported\")\n}\n"
  },
  {
    "path": "test/clash_test.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/md5\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t_ \"net/http/pprof\"\n\t\"net/netip\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common/control\"\n\tF \"github.com/sagernet/sing/common/format\"\n\n\t\"github.com/docker/docker/api/types/image\"\n\t\"github.com/docker/docker/client\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// kanged from clash\n\nconst (\n\tImageShadowsocksRustServer = \"ghcr.io/shadowsocks/ssserver-rust:latest\"\n\tImageShadowsocksRustClient = \"ghcr.io/shadowsocks/sslocal-rust:latest\"\n\tImageV2RayCore             = \"v2fly/v2fly-core:latest\"\n\tImageTrojan                = \"trojangfw/trojan:latest\"\n\tImageNaive                 = \"pocat/naiveproxy:client\"\n\tImageBoringTun             = \"ghcr.io/ntkme/boringtun:edge\"\n\tImageHysteria              = \"tobyxdd/hysteria:v1.3.5\"\n\tImageHysteria2             = \"tobyxdd/hysteria:v2\"\n\tImageNginx                 = \"nginx:stable\"\n\tImageShadowTLS             = \"ghcr.io/ihciah/shadow-tls:latest\"\n\tImageXRayCore              = \"teddysun/xray:latest\"\n\tImageShadowsocksLegacy     = \"mritd/shadowsocks:latest\"\n\tImageTUICServer            = \"kilvn/tuic-server:latest\"\n\tImageTUICClient            = \"kilvn/tuic-client:latest\"\n)\n\nvar allImages = []string{\n\tImageShadowsocksRustServer,\n\tImageShadowsocksRustClient,\n\tImageV2RayCore,\n\tImageTrojan,\n\tImageNaive,\n\tImageBoringTun,\n\tImageHysteria,\n\tImageHysteria2,\n\tImageNginx,\n\tImageShadowTLS,\n\tImageXRayCore,\n\tImageShadowsocksLegacy,\n\tImageTUICServer,\n\tImageTUICClient,\n}\n\nvar localIP = netip.MustParseAddr(\"127.0.0.1\")\n\nfunc init() {\n\tdockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer dockerClient.Close()\n\n\tlist, err := dockerClient.ImageList(context.Background(), image.ListOptions{All: true})\n\tif err != nil {\n\t\tlog.Warn(err)\n\t\treturn\n\t}\n\n\timageExist := func(image string) bool {\n\t\tfor _, item := range list {\n\t\t\tfor _, tag := range item.RepoTags {\n\t\t\t\tif image == tag {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\tfor _, i := range allImages {\n\t\tif imageExist(i) {\n\t\t\tcontinue\n\t\t}\n\n\t\tlog.Info(\"pulling image: \", i)\n\t\timageStream, err := dockerClient.ImagePull(context.Background(), i, image.PullOptions{})\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tio.Copy(io.Discard, imageStream)\n\t}\n}\n\nfunc newPingPongPair() (chan []byte, chan []byte, func(t *testing.T) error) {\n\tpingCh := make(chan []byte)\n\tpongCh := make(chan []byte)\n\ttest := func(t *testing.T) error {\n\t\tdefer close(pingCh)\n\t\tdefer close(pongCh)\n\t\tpingOpen := false\n\t\tpongOpen := false\n\t\tvar recv []byte\n\n\t\tfor {\n\t\t\tif pingOpen && pongOpen {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase recv, pingOpen = <-pingCh:\n\t\t\t\tassert.True(t, pingOpen)\n\t\t\t\tassert.Equal(t, []byte(\"ping\"), recv)\n\t\t\tcase recv, pongOpen = <-pongCh:\n\t\t\t\tassert.True(t, pongOpen)\n\t\t\t\tassert.Equal(t, []byte(\"pong\"), recv)\n\t\t\tcase <-time.After(10 * time.Second):\n\t\t\t\treturn errors.New(\"timeout\")\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\treturn pingCh, pongCh, test\n}\n\nfunc newLargeDataPair() (chan hashPair, chan hashPair, func(t *testing.T) error) {\n\tpingCh := make(chan hashPair)\n\tpongCh := make(chan hashPair)\n\ttest := func(t *testing.T) error {\n\t\tdefer close(pingCh)\n\t\tdefer close(pongCh)\n\t\tpingOpen := false\n\t\tpongOpen := false\n\t\tvar serverPair hashPair\n\t\tvar clientPair hashPair\n\n\t\tfor {\n\t\t\tif pingOpen && pongOpen {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase serverPair, pingOpen = <-pingCh:\n\t\t\t\tassert.True(t, pingOpen)\n\t\t\tcase clientPair, pongOpen = <-pongCh:\n\t\t\t\tassert.True(t, pongOpen)\n\t\t\tcase <-time.After(10 * time.Second):\n\t\t\t\treturn errors.New(\"timeout\")\n\t\t\t}\n\t\t}\n\n\t\tassert.Equal(t, serverPair.recvHash, clientPair.sendHash)\n\t\tassert.Equal(t, serverPair.sendHash, clientPair.recvHash)\n\n\t\treturn nil\n\t}\n\n\treturn pingCh, pongCh, test\n}\n\nfunc testPingPongWithConn(t *testing.T, port uint16, cc func() (net.Conn, error)) error {\n\tl, err := listen(\"tcp\", \":\"+F.ToString(port))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer l.Close()\n\n\tc, err := cc()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer c.Close()\n\n\tpingCh, pongCh, test := newPingPongPair()\n\tgo func() {\n\t\tc, err := l.Accept()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tbuf := make([]byte, 4)\n\t\tif _, err := io.ReadFull(c, buf); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tpingCh <- buf\n\t\tif _, err := c.Write([]byte(\"pong\")); err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tif _, err := c.Write([]byte(\"ping\")); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tbuf := make([]byte, 4)\n\t\tif _, err := io.ReadFull(c, buf); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tpongCh <- buf\n\t}()\n\n\treturn test(t)\n}\n\nfunc testPingPongWithPacketConn(t *testing.T, port uint16, pcc func() (net.PacketConn, error)) error {\n\tl, err := listenPacket(\"udp\", \":\"+F.ToString(port))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer l.Close()\n\n\trAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: int(port)}\n\n\tpingCh, pongCh, test := newPingPongPair()\n\tgo func() {\n\t\tbuf := make([]byte, 1024)\n\t\tn, rAddr, err := l.ReadFrom(buf)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tpingCh <- buf[:n]\n\t\tif _, err := l.WriteTo([]byte(\"pong\"), rAddr); err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\tpc, err := pcc()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer pc.Close()\n\n\tgo func() {\n\t\tif _, err := pc.WriteTo([]byte(\"ping\"), rAddr); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tbuf := make([]byte, 1024)\n\t\tn, _, err := pc.ReadFrom(buf)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tpongCh <- buf[:n]\n\t}()\n\n\treturn test(t)\n}\n\ntype hashPair struct {\n\tsendHash map[int][]byte\n\trecvHash map[int][]byte\n}\n\nfunc testLargeDataWithConn(t *testing.T, port uint16, cc func() (net.Conn, error)) error {\n\tl, err := listen(\"tcp\", \":\"+F.ToString(port))\n\trequire.NoError(t, err)\n\tdefer l.Close()\n\n\ttimes := 100\n\tchunkSize := int64(64 * 1024)\n\n\tpingCh, pongCh, test := newLargeDataPair()\n\twriteRandData := func(conn net.Conn) (map[int][]byte, error) {\n\t\tbuf := make([]byte, chunkSize)\n\t\thashMap := map[int][]byte{}\n\t\tfor i := 0; i < times; i++ {\n\t\t\tif _, err := rand.Read(buf[1:]); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tbuf[0] = byte(i)\n\n\t\t\thash := md5.Sum(buf)\n\t\t\thashMap[i] = hash[:]\n\n\t\t\tif _, err := conn.Write(buf); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\treturn hashMap, nil\n\t}\n\n\tc, err := cc()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer c.Close()\n\n\tgo func() {\n\t\tc, err := l.Accept()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tdefer c.Close()\n\n\t\thashMap := map[int][]byte{}\n\t\tbuf := make([]byte, chunkSize)\n\n\t\tfor i := 0; i < times; i++ {\n\t\t\t_, err := io.ReadFull(c, buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Log(err.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\thash := md5.Sum(buf)\n\t\t\thashMap[int(buf[0])] = hash[:]\n\t\t}\n\n\t\tsendHash, err := writeRandData(c)\n\t\tif err != nil {\n\t\t\tt.Log(err.Error())\n\t\t\treturn\n\t\t}\n\n\t\tpingCh <- hashPair{\n\t\t\tsendHash: sendHash,\n\t\t\trecvHash: hashMap,\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tsendHash, err := writeRandData(c)\n\t\tif err != nil {\n\t\t\tt.Log(err.Error())\n\t\t\treturn\n\t\t}\n\n\t\thashMap := map[int][]byte{}\n\t\tbuf := make([]byte, chunkSize)\n\n\t\tfor i := 0; i < times; i++ {\n\t\t\t_, err := io.ReadFull(c, buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Log(err.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\thash := md5.Sum(buf)\n\t\t\thashMap[int(buf[0])] = hash[:]\n\t\t}\n\n\t\tpongCh <- hashPair{\n\t\t\tsendHash: sendHash,\n\t\t\trecvHash: hashMap,\n\t\t}\n\t}()\n\n\treturn test(t)\n}\n\nfunc testLargeDataWithPacketConn(t *testing.T, port uint16, pcc func() (net.PacketConn, error)) error {\n\treturn testLargeDataWithPacketConnSize(t, port, 1500, pcc)\n}\n\nfunc testLargeDataWithPacketConnSize(t *testing.T, port uint16, chunkSize int, pcc func() (net.PacketConn, error)) error {\n\tl, err := listenPacket(\"udp\", \":\"+F.ToString(port))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer l.Close()\n\n\trAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: int(port)}\n\n\ttimes := 50\n\n\tpingCh, pongCh, test := newLargeDataPair()\n\twriteRandData := func(pc net.PacketConn, addr net.Addr) (map[int][]byte, error) {\n\t\thashMap := map[int][]byte{}\n\t\tmux := sync.Mutex{}\n\t\tfor i := 0; i < times; i++ {\n\t\t\tbuf := make([]byte, chunkSize)\n\t\t\tif _, err := rand.Read(buf[1:]); err != nil {\n\t\t\t\tt.Log(err.Error())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbuf[0] = byte(i)\n\n\t\t\thash := md5.Sum(buf)\n\t\t\tmux.Lock()\n\t\t\thashMap[i] = hash[:]\n\t\t\tmux.Unlock()\n\n\t\t\tif _, err := pc.WriteTo(buf, addr); err != nil {\n\t\t\t\tt.Log(err.Error())\n\t\t\t}\n\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t}\n\n\t\treturn hashMap, nil\n\t}\n\n\tgo func() {\n\t\tvar rAddr net.Addr\n\t\thashMap := map[int][]byte{}\n\t\tbuf := make([]byte, 64*1024)\n\n\t\tfor i := 0; i < times; i++ {\n\t\t\t_, rAddr, err = l.ReadFrom(buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Log(err.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t\thash := md5.Sum(buf[:chunkSize])\n\t\t\thashMap[int(buf[0])] = hash[:]\n\t\t}\n\t\tsendHash, err := writeRandData(l, rAddr)\n\t\tif err != nil {\n\t\t\tt.Log(err.Error())\n\t\t\treturn\n\t\t}\n\n\t\tpingCh <- hashPair{\n\t\t\tsendHash: sendHash,\n\t\t\trecvHash: hashMap,\n\t\t}\n\t}()\n\n\tpc, err := pcc()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer pc.Close()\n\n\tgo func() {\n\t\tsendHash, err := writeRandData(pc, rAddr)\n\t\tif err != nil {\n\t\t\tt.Log(err.Error())\n\t\t\treturn\n\t\t}\n\n\t\thashMap := map[int][]byte{}\n\t\tbuf := make([]byte, 64*1024)\n\n\t\tfor i := 0; i < times; i++ {\n\t\t\t_, _, err := pc.ReadFrom(buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Log(err.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\thash := md5.Sum(buf[:chunkSize])\n\t\t\thashMap[int(buf[0])] = hash[:]\n\t\t}\n\n\t\tpongCh <- hashPair{\n\t\t\tsendHash: sendHash,\n\t\t\trecvHash: hashMap,\n\t\t}\n\t}()\n\n\treturn test(t)\n}\n\nfunc testPacketConnTimeout(t *testing.T, pcc func() (net.PacketConn, error)) error {\n\tpc, err := pcc()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = pc.SetReadDeadline(time.Now().Add(time.Millisecond * 300))\n\trequire.NoError(t, err)\n\n\terrCh := make(chan error, 1)\n\tgo func() {\n\t\tbuf := make([]byte, 1024)\n\t\t_, _, err := pc.ReadFrom(buf)\n\t\terrCh <- err\n\t}()\n\n\tselect {\n\tcase <-errCh:\n\t\treturn nil\n\tcase <-time.After(time.Second * 10):\n\t\treturn errors.New(\"timeout\")\n\t}\n}\n\nfunc listen(network, address string) (net.Listener, error) {\n\tvar lc net.ListenConfig\n\tlc.Control = control.ReuseAddr()\n\tvar lastErr error\n\tfor i := 0; i < 5; i++ {\n\t\tl, err := lc.Listen(context.Background(), network, address)\n\t\tif err == nil {\n\t\t\treturn l, nil\n\t\t}\n\n\t\tlastErr = err\n\t\ttime.Sleep(5 * time.Millisecond)\n\t}\n\treturn nil, lastErr\n}\n\nfunc listenPacket(network, address string) (net.PacketConn, error) {\n\tvar lc net.ListenConfig\n\tlc.Control = control.ReuseAddr()\n\tvar lastErr error\n\tfor i := 0; i < 5; i++ {\n\t\tl, err := lc.ListenPacket(context.Background(), network, address)\n\t\tif err == nil {\n\t\t\treturn l, nil\n\t\t}\n\n\t\tlastErr = err\n\t\ttime.Sleep(5 * time.Millisecond)\n\t}\n\treturn nil, lastErr\n}\n"
  },
  {
    "path": "test/config/hysteria-client.json",
    "content": "{\n  \"server\": \"127.0.0.1:10000\",\n  \"auth_str\": \"password\",\n  \"obfs\": \"fuck me till the daylight\",\n  \"up_mbps\": 100,\n  \"down_mbps\": 100,\n  \"socks5\": {\n    \"listen\": \"127.0.0.1:10001\"\n  },\n  \"server_name\": \"example.org\",\n  \"ca\": \"/etc/hysteria/ca.pem\"\n}"
  },
  {
    "path": "test/config/hysteria-server.json",
    "content": "{\n  \"listen\": \":10000\",\n  \"cert\": \"/etc/hysteria/cert.pem\",\n  \"key\": \"/etc/hysteria/key.pem\",\n  \"auth_str\": \"password\",\n  \"obfs\": \"fuck me till the daylight\",\n  \"up_mbps\": 100,\n  \"down_mbps\": 100\n}"
  },
  {
    "path": "test/config/hysteria2-client.yml",
    "content": "server: 127.0.0.1:10000\nauth: password\nsocks5:\n  listen: 127.0.0.1:10001\ntls:\n  sni: example.org\n  ca: /etc/hysteria/ca.pem\nobfs:\n  type: salamander\n  salamander:\n    password: cry_me_a_r1ver"
  },
  {
    "path": "test/config/hysteria2-server.yml",
    "content": "listen: 127.0.0.1:10000\nauth:\n  type: password\n  password: password\ntls:\n  sni: example.org\n  cert: /etc/hysteria/cert.pem\n  key: /etc/hysteria/key.pem\nobfs:\n  type: salamander\n  salamander:\n    password: cry_me_a_r1ver"
  },
  {
    "path": "test/config/naive-nginx.conf",
    "content": "stream {\n    server {\n        listen 10000 ssl;\n        listen [::]:10000 ssl;\n\n        ssl_certificate /etc/nginx/cert.pem;\n        ssl_certificate_key /etc/nginx/key.pem;\n        ssl_session_timeout 1d;\n        ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions\n        ssl_session_tickets off;\n\n        # modern configuration\n        ssl_protocols TLSv1.3;\n        ssl_prefer_server_ciphers off;\n\n        proxy_pass 127.0.0.1:10003;\n    }\n}"
  },
  {
    "path": "test/config/naive-quic.json",
    "content": "{\n  \"listen\": \"socks://127.0.0.1:10001\",\n  \"proxy\": \"quic://sekai:password@example.org:10000\",\n  \"host-resolver-rules\": \"MAP example.org 127.0.0.1\",\n  \"log\": \"\"\n}"
  },
  {
    "path": "test/config/naive.json",
    "content": "{\n  \"listen\": \"socks://127.0.0.1:10001\",\n  \"proxy\": \"https://sekai:password@example.org:10000\",\n  \"host-resolver-rules\": \"MAP example.org 127.0.0.1\",\n  \"log\": \"\"\n}"
  },
  {
    "path": "test/config/nginx.conf",
    "content": "user  nginx;\nworker_processes  auto;\n\nerror_log  /var/log/nginx/error.log notice;\npid        /var/run/nginx.pid;\n\nevents {\n    worker_connections  1024;\n}\n\nhttp {\n    include       /etc/nginx/mime.types;\n    default_type  application/octet-stream;\n\n    log_format  main  '$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n\n    access_log  /var/log/nginx/access.log  main;\n\n    sendfile        on;\n    #tcp_nopush     on;\n\n    keepalive_timeout  65;\n\n    #gzip  on;\n}\n\ninclude /etc/nginx/conf.d/naive.conf;"
  },
  {
    "path": "test/config/shadowsocksr.json",
    "content": "{\n  \"server\": \"0.0.0.0\",\n  \"server_ipv6\": \"::\",\n  \"server_port\": 10000,\n  \"local_address\": \"127.0.0.1\",\n  \"local_port\": 1080,\n  \"password\": \"password0\",\n  \"timeout\": 120,\n  \"method\": \"aes-256-cfb\",\n  \"protocol\": \"origin\",\n  \"protocol_param\": \"\",\n  \"obfs\": \"plain\",\n  \"obfs_param\": \"\",\n  \"redirect\": \"\",\n  \"dns_ipv6\": false,\n  \"fast_open\": true,\n  \"workers\": 1,\n  \"forbidden_ip\": \"\"\n}"
  },
  {
    "path": "test/config/trojan.json",
    "content": "{\n    \"run_type\": \"server\",\n    \"local_addr\": \"0.0.0.0\",\n    \"local_port\": 10000,\n    \"password\": [\n        \"password\"\n    ],\n    \"log_level\": 1,\n    \"ssl\": {\n        \"cert\": \"/path/to/certificate.crt\",\n        \"key\": \"/path/to/private.key\",\n        \"key_password\": \"\",\n        \"cipher\": \"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384\",\n        \"cipher_tls13\": \"TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384\",\n        \"prefer_server_cipher\": true,\n        \"alpn\": [\n            \"http/1.1\"\n        ],\n        \"alpn_port_override\": {\n            \"h2\": 81\n        },\n        \"reuse_session\": true,\n        \"session_ticket\": false,\n        \"session_timeout\": 600,\n        \"plain_http_response\": \"\",\n        \"curves\": \"\",\n        \"dhparam\": \"\"\n    },\n    \"tcp\": {\n        \"prefer_ipv4\": false,\n        \"no_delay\": true,\n        \"keep_alive\": true,\n        \"reuse_port\": false,\n        \"fast_open\": false,\n        \"fast_open_qlen\": 20\n    },\n    \"mysql\": {\n        \"enabled\": false\n    }\n}"
  },
  {
    "path": "test/config/tuic-client.json",
    "content": "{\n  \"relay\": {\n    \"server\": \"example.org:10000\",\n    \"ip\": \"127.0.0.1\",\n    \"uuid\": \"FE35D05B-8803-45C4-BAE6-723AD2CD5D3D\",\n    \"password\": \"tuic\",\n    \"certificates\": [\n      \"/etc/tuic/ca.pem\"\n    ]\n  },\n  \"local\": {\n    \"server\": \"127.0.0.1:10001\",\n    \"max_packet_size\": 65535\n  },\n  \"log_level\": \"debug\"\n}"
  },
  {
    "path": "test/config/tuic-server.json",
    "content": "{\n    \"server\": \"[::]:10000\",\n    \"users\": {\n        \"FE35D05B-8803-45C4-BAE6-723AD2CD5D3D\": \"tuic\"\n    },\n    \"certificate\":  \"/etc/tuic/cert.pem\",\n    \"private_key\": \"/etc/tuic/key.pem\",\n    \"max_external_packet_size\": 65535,\n    \"log_level\": \"debug\"\n}"
  },
  {
    "path": "test/config/vless-server.json",
    "content": "{\n  \"log\": {\n    \"loglevel\": \"debug\"\n  },\n  \"inbounds\": [\n    {\n      \"listen\": \"0.0.0.0\",\n      \"port\": 1234,\n      \"protocol\": \"vless\",\n      \"settings\": {\n        \"decryption\": \"none\",\n        \"clients\": [\n          {\n            \"id\": \"b831381d-6324-4d53-ad4f-8cda48b30811\"\n          }\n        ]\n      }\n    }\n  ],\n  \"outbounds\": [\n    {\n      \"protocol\": \"freedom\"\n    }\n  ]\n}"
  },
  {
    "path": "test/config/vless-tls-client.json",
    "content": "{\n  \"log\": {\n    \"loglevel\": \"debug\"\n  },\n  \"inbounds\": [\n    {\n      \"listen\": \"0.0.0.0\",\n      \"port\": \"1080\",\n      \"protocol\": \"socks\",\n      \"settings\": {\n        \"auth\": \"noauth\",\n        \"udp\": true,\n        \"ip\": \"127.0.0.1\"\n      }\n    }\n  ],\n  \"outbounds\": [\n    {\n      \"protocol\": \"vless\",\n      \"settings\": {\n        \"vnext\": [\n          {\n            \"address\": \"host.docker.internal\",\n            \"port\": 1234,\n            \"users\": [\n              {\n                \"id\": \"\",\n                \"encryption\": \"none\",\n                \"flow\": \"\"\n              }\n            ]\n          }\n        ]\n      },\n      \"streamSettings\": {\n        \"network\": \"tcp\",\n        \"security\": \"tls\",\n        \"tlsSettings\": {\n          \"serverName\": \"example.org\",\n          \"certificates\": [\n            {\n              \"certificateFile\": \"/path/to/certificate.crt\",\n              \"keyFile\": \"/path/to/private.key\"\n            }\n          ],\n          \"fingerprint\": \"chrome\"\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "test/config/vless-tls-server.json",
    "content": "{\n  \"log\": {\n    \"loglevel\": \"debug\"\n  },\n  \"inbounds\": [\n    {\n      \"listen\": \"0.0.0.0\",\n      \"port\": 1234,\n      \"protocol\": \"vless\",\n      \"settings\": {\n        \"decryption\": \"none\",\n        \"clients\": [\n          {\n            \"id\": \"b831381d-6324-4d53-ad4f-8cda48b30811\",\n            \"flow\": \"\"\n          }\n        ]\n      },\n      \"streamSettings\": {\n        \"network\": \"tcp\",\n        \"security\": \"tls\",\n        \"tlsSettings\": {\n          \"serverName\": \"example.org\",\n          \"certificates\": [\n            {\n              \"certificateFile\": \"/path/to/certificate.crt\",\n              \"keyFile\": \"/path/to/private.key\"\n            }\n          ]\n        }\n      }\n    }\n  ],\n  \"outbounds\": [\n    {\n      \"protocol\": \"freedom\"\n    }\n  ]\n}"
  },
  {
    "path": "test/config/vmess-client.json",
    "content": "{\n  \"log\": {\n    \"loglevel\": \"debug\"\n  },\n  \"inbounds\": [\n    {\n      \"listen\": \"127.0.0.1\",\n      \"port\": \"1080\",\n      \"protocol\": \"socks\",\n      \"settings\": {\n        \"auth\": \"noauth\",\n        \"udp\": true,\n        \"ip\": \"127.0.0.1\"\n      }\n    }\n  ],\n  \"outbounds\": [\n    {\n      \"protocol\": \"vmess\",\n      \"settings\": {\n        \"vnext\": [\n          {\n            \"address\": \"127.0.0.1\",\n            \"port\": 1234,\n            \"users\": [\n              {\n                \"id\": \"\",\n                \"alterId\": 0,\n                \"security\": \"none\",\n                \"experiments\": \"\"\n              }\n            ]\n          }\n        ]\n      }\n    }\n  ]\n}"
  },
  {
    "path": "test/config/vmess-grpc-client.json",
    "content": "{\n  \"log\": {\n    \"loglevel\": \"debug\"\n  },\n  \"inbounds\": [\n    {\n      \"listen\": \"127.0.0.1\",\n      \"port\": \"1080\",\n      \"protocol\": \"socks\",\n      \"settings\": {\n        \"auth\": \"noauth\",\n        \"udp\": true,\n        \"ip\": \"127.0.0.1\"\n      }\n    }\n  ],\n  \"outbounds\": [\n    {\n      \"protocol\": \"vmess\",\n      \"settings\": {\n        \"vnext\": [\n          {\n            \"address\": \"127.0.0.1\",\n            \"port\": 1234,\n            \"users\": [\n              {\n                \"id\": \"\"\n              }\n            ]\n          }\n        ]\n      },\n      \"streamSettings\": {\n        \"network\": \"gun\",\n        \"security\": \"tls\",\n        \"tlsSettings\": {\n          \"serverName\": \"example.org\",\n          \"certificates\": [\n            {\n              \"certificateFile\": \"/path/to/certificate.crt\",\n              \"keyFile\": \"/path/to/private.key\"\n            }\n          ]\n        },\n        \"grpcSettings\": {\n          \"serviceName\": \"TunService\"\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "test/config/vmess-grpc-server.json",
    "content": "{\n  \"log\": {\n    \"loglevel\": \"debug\"\n  },\n  \"inbounds\": [\n    {\n      \"listen\": \"0.0.0.0\",\n      \"port\": 1234,\n      \"protocol\": \"vmess\",\n      \"settings\": {\n        \"clients\": [\n          {\n            \"id\": \"b831381d-6324-4d53-ad4f-8cda48b30811\"\n          }\n        ]\n      },\n      \"streamSettings\": {\n        \"network\": \"gun\",\n        \"security\": \"tls\",\n        \"tlsSettings\": {\n          \"serverName\": \"example.org\",\n          \"certificates\": [\n            {\n              \"certificateFile\": \"/path/to/certificate.crt\",\n              \"keyFile\": \"/path/to/private.key\"\n            }\n          ]\n        },\n        \"grpcSettings\": {\n          \"serviceName\": \"TunService\"\n        }\n      }\n    }\n  ],\n  \"outbounds\": [\n    {\n      \"protocol\": \"freedom\"\n    }\n  ]\n}"
  },
  {
    "path": "test/config/vmess-mux-client.json",
    "content": "{\n  \"log\": {\n    \"loglevel\": \"debug\"\n  },\n  \"inbounds\": [\n    {\n      \"listen\": \"127.0.0.1\",\n      \"port\": \"1080\",\n      \"protocol\": \"socks\",\n      \"settings\": {\n        \"auth\": \"noauth\",\n        \"udp\": true,\n        \"ip\": \"127.0.0.1\"\n      }\n    }\n  ],\n  \"outbounds\": [\n    {\n      \"protocol\": \"vmess\",\n      \"settings\": {\n        \"vnext\": [\n          {\n            \"address\": \"127.0.0.1\",\n            \"port\": 1234,\n            \"users\": [\n              {\n                \"id\": \"\",\n                \"alterId\": 0,\n                \"security\": \"none\",\n                \"experiments\": \"\"\n              }\n            ]\n          }\n        ]\n      },\n      \"mux\": {\n        \"enabled\": true\n      }\n    }\n  ]\n}"
  },
  {
    "path": "test/config/vmess-server.json",
    "content": "{\n  \"log\": {\n    \"loglevel\": \"debug\"\n  },\n  \"inbounds\": [\n    {\n      \"listen\": \"0.0.0.0\",\n      \"port\": 1234,\n      \"protocol\": \"vmess\",\n      \"settings\": {\n        \"clients\": [\n          {\n            \"id\": \"b831381d-6324-4d53-ad4f-8cda48b30811\",\n            \"alterId\": 0\n          }\n        ]\n      }\n    }\n  ],\n  \"outbounds\": [\n    {\n      \"protocol\": \"freedom\"\n    }\n  ]\n}"
  },
  {
    "path": "test/config/vmess-ws-client.json",
    "content": "{\n  \"log\": {\n    \"loglevel\": \"debug\"\n  },\n  \"inbounds\": [\n    {\n      \"listen\": \"127.0.0.1\",\n      \"port\": \"1080\",\n      \"protocol\": \"socks\",\n      \"settings\": {\n        \"auth\": \"noauth\",\n        \"udp\": true,\n        \"ip\": \"127.0.0.1\"\n      }\n    }\n  ],\n  \"outbounds\": [\n    {\n      \"protocol\": \"vmess\",\n      \"settings\": {\n        \"vnext\": [\n          {\n            \"address\": \"127.0.0.1\",\n            \"port\": 1234,\n            \"users\": [\n              {\n                \"id\": \"\"\n              }\n            ]\n          }\n        ]\n      },\n      \"streamSettings\": {\n        \"network\": \"ws\",\n        \"security\": \"tls\",\n        \"tlsSettings\": {\n          \"serverName\": \"example.org\",\n          \"certificates\": [\n            {\n              \"certificateFile\": \"/path/to/certificate.crt\",\n              \"keyFile\": \"/path/to/private.key\"\n            }\n          ]\n        },\n        \"wsSettings\": {\n          \"maxEarlyData\": 2048,\n          \"earlyDataHeaderName\": \"\"\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "test/config/vmess-ws-server.json",
    "content": "{\n  \"log\": {\n    \"loglevel\": \"debug\"\n  },\n  \"inbounds\": [\n    {\n      \"listen\": \"0.0.0.0\",\n      \"port\": 1234,\n      \"protocol\": \"vmess\",\n      \"settings\": {\n        \"clients\": [\n          {\n            \"id\": \"b831381d-6324-4d53-ad4f-8cda48b30811\"\n          }\n        ]\n      },\n      \"streamSettings\": {\n        \"network\": \"ws\",\n        \"security\": \"tls\",\n        \"tlsSettings\": {\n          \"serverName\": \"example.org\",\n          \"certificates\": [\n            {\n              \"certificateFile\": \"/path/to/certificate.crt\",\n              \"keyFile\": \"/path/to/private.key\"\n            }\n          ]\n        },\n        \"wsSettings\": {\n          \"maxEarlyData\": 2048,\n          \"earlyDataHeaderName\": \"\"\n        }\n      }\n    }\n  ],\n  \"outbounds\": [\n    {\n      \"protocol\": \"freedom\"\n    }\n  ]\n}"
  },
  {
    "path": "test/config/wireguard.conf",
    "content": "[Interface]\nPrivateKey = gHWUGzTh5YCEV6k8dneVP537XhVtoQJPIlFNs2zsxlE=\nAddress = 10.0.0.1/32\nListenPort = 10000\n\n[Peer]\nPublicKey = LV2xr9tzxwbs0ZLUlFN9k/0Or9QWqIInvxc/Cu7/2hA=\nAllowedIPs = 10.0.0.2/32"
  },
  {
    "path": "test/direct_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\n// Since this is a feature one-off added by outsiders, I won't address these anymore.\nfunc _TestProxyProtocol(t *testing.T) {\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t\tOptions: &option.DirectInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:        common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort:    serverPort,\n\t\t\t\t\t\tProxyProtocol: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t\tTag:  \"proxy-out\",\n\t\t\t\tOptions: &option.DirectOutboundOptions{\n\t\t\t\t\tOverrideAddress: \"127.0.0.1\",\n\t\t\t\t\tOverridePort:    serverPort,\n\t\t\t\t\tProxyProtocol:   2,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"proxy-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/docker_test.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common/debug\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/rw\"\n\n\t\"github.com/docker/docker/api/types/container\"\n\t\"github.com/docker/docker/client\"\n\t\"github.com/docker/docker/pkg/stdcopy\"\n\t\"github.com/docker/go-connections/nat\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype DockerOptions struct {\n\tImage      string\n\tEntryPoint string\n\tPorts      []uint16\n\tCmd        []string\n\tEnv        []string\n\tBind       map[string]string\n\tStdin      []byte\n\tCap        []string\n}\n\nfunc startDockerContainer(t *testing.T, options DockerOptions) {\n\tdockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())\n\trequire.NoError(t, err)\n\tdefer dockerClient.Close()\n\n\twriteStdin := len(options.Stdin) > 0\n\n\tvar containerOptions container.Config\n\n\tif writeStdin {\n\t\tcontainerOptions.OpenStdin = true\n\t\tcontainerOptions.StdinOnce = true\n\t}\n\n\tcontainerOptions.Image = options.Image\n\tif options.EntryPoint != \"\" {\n\t\tcontainerOptions.Entrypoint = []string{options.EntryPoint}\n\t}\n\tcontainerOptions.Cmd = options.Cmd\n\tcontainerOptions.Env = options.Env\n\tcontainerOptions.ExposedPorts = make(nat.PortSet)\n\n\tvar hostOptions container.HostConfig\n\thostOptions.NetworkMode = \"host\"\n\thostOptions.CapAdd = options.Cap\n\thostOptions.PortBindings = make(nat.PortMap)\n\n\tfor _, port := range options.Ports {\n\t\tcontainerOptions.ExposedPorts[nat.Port(F.ToString(port, \"/tcp\"))] = struct{}{}\n\t\tcontainerOptions.ExposedPorts[nat.Port(F.ToString(port, \"/udp\"))] = struct{}{}\n\t\thostOptions.PortBindings[nat.Port(F.ToString(port, \"/tcp\"))] = []nat.PortBinding{\n\t\t\t{HostPort: F.ToString(port), HostIP: \"0.0.0.0\"},\n\t\t}\n\t\thostOptions.PortBindings[nat.Port(F.ToString(port, \"/udp\"))] = []nat.PortBinding{\n\t\t\t{HostPort: F.ToString(port), HostIP: \"0.0.0.0\"},\n\t\t}\n\t}\n\n\tif len(options.Bind) > 0 {\n\t\thostOptions.Binds = []string{}\n\t\tfor path, internalPath := range options.Bind {\n\t\t\tif !rw.FileExists(path) {\n\t\t\t\tpath = filepath.Join(\"config\", path)\n\t\t\t}\n\t\t\tpath, _ = filepath.Abs(path)\n\t\t\thostOptions.Binds = append(hostOptions.Binds, path+\":\"+internalPath)\n\t\t}\n\t}\n\n\tdockerContainer, err := dockerClient.ContainerCreate(context.Background(), &containerOptions, &hostOptions, nil, nil, \"\")\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\tcleanContainer(dockerContainer.ID)\n\t})\n\n\trequire.NoError(t, dockerClient.ContainerStart(context.Background(), dockerContainer.ID, container.StartOptions{}))\n\n\tif writeStdin {\n\t\tstdinAttach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, container.AttachOptions{\n\t\t\tStdin:  writeStdin,\n\t\t\tStream: true,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\t_, err = stdinAttach.Conn.Write(options.Stdin)\n\t\trequire.NoError(t, err)\n\t\tstdinAttach.Close()\n\t}\n\tif debug.Enabled {\n\t\tattach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, container.AttachOptions{\n\t\t\tStdout: true,\n\t\t\tStderr: true,\n\t\t\tLogs:   true,\n\t\t\tStream: true,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tgo func() {\n\t\t\tstdcopy.StdCopy(os.Stderr, os.Stderr, attach.Reader)\n\t\t}()\n\t}\n\ttime.Sleep(time.Second)\n}\n\nfunc cleanContainer(id string) error {\n\tdockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer dockerClient.Close()\n\treturn dockerClient.ContainerRemove(context.Background(), id, container.RemoveOptions{Force: true})\n}\n"
  },
  {
    "path": "test/domain_inbound_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/gofrs/uuid/v5\"\n)\n\nfunc TestTUICDomainUDP(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTUIC,\n\t\t\t\tOptions: &option.TUICInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TUICUser{{\n\t\t\t\t\t\tUUID: uuid.Nil.String(),\n\t\t\t\t\t}},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTUIC,\n\t\t\t\tTag:  \"tuic-out\",\n\t\t\t\tOptions: &option.TUICOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID: uuid.Nil.String(),\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"tuic-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestQUIC(t, clientPort)\n}\n"
  },
  {
    "path": "test/ech_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/gofrs/uuid/v5\"\n)\n\nfunc TestECH(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\techConfig, echKey := common.Must2(tls.ECHKeygenDefault(\"not.example.org\"))\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tOptions: &option.TrojanInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TrojanUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t\tECH: &option.InboundECHOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tKey:     []string{echKey},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tTag:  \"trojan-out\",\n\t\t\t\tOptions: &option.TrojanOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tECH: &option.OutboundECHOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tConfig:  []string{echConfig},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"trojan-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestECHQUIC(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\techConfig, echKey := common.Must2(tls.ECHKeygenDefault(\"not.example.org\"))\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTUIC,\n\t\t\t\tOptions: &option.TUICInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TUICUser{{\n\t\t\t\t\t\tUUID: uuid.Nil.String(),\n\t\t\t\t\t}},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t\tECH: &option.InboundECHOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tKey:     []string{echKey},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTUIC,\n\t\t\t\tTag:  \"tuic-out\",\n\t\t\t\tOptions: &option.TUICOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID: uuid.Nil.String(),\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tECH: &option.OutboundECHOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tConfig:  []string{echConfig},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"tuic-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitLargeUDP(t, clientPort, testPort)\n}\n\nfunc TestECHHysteria2(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\techConfig, echKey := common.Must2(tls.ECHKeygenDefault(\"not.example.org\"))\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeHysteria2,\n\t\t\t\tOptions: &option.Hysteria2InboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.Hysteria2User{{\n\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t}},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t\tECH: &option.InboundECHOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tKey:     []string{echKey},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeHysteria2,\n\t\t\t\tTag:  \"hy2-out\",\n\t\t\t\tOptions: &option.Hysteria2OutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tECH: &option.OutboundECHOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tConfig:  []string{echConfig},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"hy2-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitLargeUDP(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/go.mod",
    "content": "module test\n\ngo 1.24.7\n\nrequire github.com/sagernet/sing-box v0.0.0\n\nreplace github.com/sagernet/sing-box => ../\n\nrequire (\n\tgithub.com/docker/docker v27.3.1+incompatible\n\tgithub.com/docker/go-connections v0.5.0\n\tgithub.com/gofrs/uuid/v5 v5.4.0\n\tgithub.com/sagernet/quic-go v0.59.0-sing-box-mod.2\n\tgithub.com/sagernet/sing v0.8.0-beta.16\n\tgithub.com/sagernet/sing-quic v0.6.0-beta.11\n\tgithub.com/sagernet/sing-shadowsocks v0.2.8\n\tgithub.com/sagernet/sing-shadowsocks2 v0.2.1\n\tgithub.com/spyzhov/ajson v0.9.4\n\tgithub.com/stretchr/testify v1.11.1\n\tgo.uber.org/goleak v1.3.0\n\tgolang.org/x/net v0.48.0\n)\n\nrequire (\n\tfilippo.io/edwards25519 v1.1.0 // indirect\n\tgithub.com/Microsoft/go-winio v0.6.1 // indirect\n\tgithub.com/ajg/form v1.5.1 // indirect\n\tgithub.com/akutz/memconn v0.1.0 // indirect\n\tgithub.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect\n\tgithub.com/andybalholm/brotli v1.1.0 // indirect\n\tgithub.com/anthropics/anthropic-sdk-go v1.19.0 // indirect\n\tgithub.com/anytls/sing-anytls v0.0.11 // indirect\n\tgithub.com/caddyserver/certmagic v0.25.0 // indirect\n\tgithub.com/caddyserver/zerossl v0.1.3 // indirect\n\tgithub.com/coder/websocket v1.8.14 // indirect\n\tgithub.com/containerd/log v0.1.0 // indirect\n\tgithub.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect\n\tgithub.com/cretz/bine v0.2.0 // indirect\n\tgithub.com/database64128/netx-go v0.1.1 // indirect\n\tgithub.com/database64128/tfo-go/v2 v2.3.1 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect\n\tgithub.com/distribution/reference v0.5.0 // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/ebitengine/purego v0.9.1 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/florianl/go-nfqueue/v2 v2.0.2 // indirect\n\tgithub.com/fsnotify/fsnotify v1.7.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.7.0 // indirect\n\tgithub.com/gaissmai/bart v0.18.0 // indirect\n\tgithub.com/go-chi/chi/v5 v5.2.3 // indirect\n\tgithub.com/go-chi/render v1.0.3 // indirect\n\tgithub.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-ole/go-ole v1.3.0 // indirect\n\tgithub.com/gobwas/httphead v0.1.0 // indirect\n\tgithub.com/gobwas/pool v0.2.1 // indirect\n\tgithub.com/godbus/dbus/v5 v5.2.1 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/hashicorp/yamux v0.1.2 // indirect\n\tgithub.com/hdevalence/ed25519consensus v0.2.0 // indirect\n\tgithub.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 // indirect\n\tgithub.com/jsimonetti/rtnetlink v1.4.0 // indirect\n\tgithub.com/keybase/go-keychain v0.0.1 // indirect\n\tgithub.com/klauspost/compress v1.18.0 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.3.0 // indirect\n\tgithub.com/libdns/acmedns v0.5.0 // indirect\n\tgithub.com/libdns/alidns v1.0.6-beta.3 // indirect\n\tgithub.com/libdns/cloudflare v0.2.2 // indirect\n\tgithub.com/libdns/libdns v1.1.1 // indirect\n\tgithub.com/logrusorgru/aurora v2.0.3+incompatible // indirect\n\tgithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect\n\tgithub.com/mdlayher/socket v0.5.1 // indirect\n\tgithub.com/metacubex/utls v1.8.4 // indirect\n\tgithub.com/mholt/acmez/v3 v3.1.4 // indirect\n\tgithub.com/miekg/dns v1.1.69 // indirect\n\tgithub.com/mitchellh/go-ps v1.0.0 // indirect\n\tgithub.com/moby/docker-image-spec v1.3.1 // indirect\n\tgithub.com/moby/term v0.5.0 // indirect\n\tgithub.com/morikuni/aec v1.0.0 // indirect\n\tgithub.com/openai/openai-go/v3 v3.15.0 // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.0 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.21 // indirect\n\tgithub.com/pires/go-proxyproto v0.8.1 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/prometheus-community/pro-bing v0.4.0 // indirect\n\tgithub.com/quic-go/qpack v0.6.0 // indirect\n\tgithub.com/safchain/ethtool v0.3.0 // indirect\n\tgithub.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect\n\tgithub.com/sagernet/cors v1.2.1 // indirect\n\tgithub.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 // indirect\n\tgithub.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 // indirect\n\tgithub.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f // indirect\n\tgithub.com/sagernet/fswatch v0.1.1 // indirect\n\tgithub.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 // indirect\n\tgithub.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect\n\tgithub.com/sagernet/nftables v0.3.0-beta.4 // indirect\n\tgithub.com/sagernet/sing-mux v0.3.4 // indirect\n\tgithub.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect\n\tgithub.com/sagernet/sing-tun v0.8.0-beta.17 // indirect\n\tgithub.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 // indirect\n\tgithub.com/sagernet/smux v1.5.50-sing-box-mod.1 // indirect\n\tgithub.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 // indirect\n\tgithub.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 // indirect\n\tgithub.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect\n\tgithub.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect\n\tgithub.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect\n\tgithub.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect\n\tgithub.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect\n\tgithub.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect\n\tgithub.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect\n\tgithub.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect\n\tgithub.com/tidwall/gjson v1.18.0 // indirect\n\tgithub.com/tidwall/match v1.1.1 // indirect\n\tgithub.com/tidwall/pretty v1.2.1 // indirect\n\tgithub.com/tidwall/sjson v1.2.5 // indirect\n\tgithub.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect\n\tgithub.com/vishvananda/netns v0.0.5 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgithub.com/zeebo/blake3 v0.2.4 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect\n\tgo.opentelemetry.io/otel v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.38.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.1 // indirect\n\tgo.uber.org/zap/exp v0.3.0 // indirect\n\tgo4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect\n\tgo4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect\n\tgolang.org/x/crypto v0.46.0 // indirect\n\tgolang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect\n\tgolang.org/x/mod v0.31.0 // indirect\n\tgolang.org/x/oauth2 v0.32.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.39.0 // indirect\n\tgolang.org/x/term v0.38.0 // indirect\n\tgolang.org/x/text v0.32.0 // indirect\n\tgolang.org/x/time v0.11.0 // indirect\n\tgolang.org/x/tools v0.40.0 // indirect\n\tgolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect\n\tgolang.zx2c4.com/wireguard/windows v0.5.3 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect\n\tgoogle.golang.org/grpc v1.77.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.11 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tgotest.tools/v3 v3.5.1 // indirect\n\tlukechampine.com/blake3 v1.3.0 // indirect\n)\n"
  },
  {
    "path": "test/go.sum",
    "content": "filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngithub.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=\ngithub.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=\ngithub.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=\ngithub.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=\ngithub.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=\ngithub.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=\ngithub.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=\ngithub.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=\ngithub.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=\ngithub.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=\ngithub.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=\ngithub.com/anthropics/anthropic-sdk-go v1.19.0 h1:mO6E+ffSzLRvR/YUH9KJC0uGw0uV8GjISIuzem//3KE=\ngithub.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=\ngithub.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=\ngithub.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=\ngithub.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=\ngithub.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=\ngithub.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=\ngithub.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=\ngithub.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=\ngithub.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=\ngithub.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=\ngithub.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=\ngithub.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=\ngithub.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=\ngithub.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=\ngithub.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=\ngithub.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=\ngithub.com/database64128/netx-go v0.1.1 h1:dT5LG7Gs7zFZBthFBbzWE6K8wAHjSNAaK7wCYZT7NzM=\ngithub.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc=\ngithub.com/database64128/tfo-go/v2 v2.3.1 h1:EGE+ELd5/AQ0X6YBlQ9RgKs8+kciNhgN3d8lRvfEJQw=\ngithub.com/database64128/tfo-go/v2 v2.3.1/go.mod h1:k9wcpg/8i5zenspBkc9jUEYehpZZccBnCElzOJB++bU=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=\ngithub.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=\ngithub.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=\ngithub.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=\ngithub.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=\ngithub.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=\ngithub.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=\ngithub.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE=\ngithub.com/florianl/go-nfqueue/v2 v2.0.2/go.mod h1:VA09+iPOT43OMoCKNfXHyzujQUty2xmzyCRkBOlmabc=\ngithub.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=\ngithub.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=\ngithub.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=\ngithub.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=\ngithub.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo=\ngithub.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY=\ngithub.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=\ngithub.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=\ngithub.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=\ngithub.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=\ngithub.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=\ngithub.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=\ngithub.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I=\ngithub.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=\ngithub.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=\ngithub.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=\ngithub.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=\ngithub.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=\ngithub.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=\ngithub.com/godbus/dbus/v5 v5.2.1 h1:I4wwMdWSkmI57ewd+elNGwLRf2/dtSaFz1DujfWYvOk=\ngithub.com/godbus/dbus/v5 v5.2.1/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=\ngithub.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=\ngithub.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=\ngithub.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=\ngithub.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=\ngithub.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=\ngithub.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=\ngithub.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=\ngithub.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=\ngithub.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 h1:MEufgJohwIjFi2n3eJv4c/8UdRLQVUwPwSWQPoER+eU=\ngithub.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo=\ngithub.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=\ngithub.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=\ngithub.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=\ngithub.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=\ngithub.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/libdns/acmedns v0.5.0 h1:5pRtmUj4Lb/QkNJSl1xgOGBUJTWW7RjpNaIhjpDXjPE=\ngithub.com/libdns/acmedns v0.5.0/go.mod h1:X7UAFP1Ep9NpTwWpVlrZzJLR7epynAy0wrIxSPFgKjQ=\ngithub.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8=\ngithub.com/libdns/alidns v1.0.6-beta.3/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec=\ngithub.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI=\ngithub.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=\ngithub.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=\ngithub.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=\ngithub.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=\ngithub.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=\ngithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=\ngithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=\ngithub.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=\ngithub.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=\ngithub.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=\ngithub.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=\ngithub.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ=\ngithub.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=\ngithub.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=\ngithub.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=\ngithub.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=\ngithub.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=\ngithub.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=\ngithub.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=\ngithub.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=\ngithub.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=\ngithub.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=\ngithub.com/openai/openai-go/v3 v3.15.0 h1:hk99rM7YPz+M99/5B/zOQcVwFRLLMdprVGx1vaZ8XMo=\ngithub.com/openai/openai-go/v3 v3.15.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=\ngithub.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=\ngithub.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=\ngithub.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=\ngithub.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=\ngithub.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=\ngithub.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=\ngithub.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=\ngithub.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=\ngithub.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=\ngithub.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=\ngithub.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=\ngithub.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=\ngithub.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=\ngithub.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 h1:0BYNmr0ptjsII948U0oBFmrbo4qEaCFcrE2JPRg3Zlk=\ngithub.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=\ngithub.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 h1:ghxhYSBQpzkakqWqJDvXr/Zmxe0WjTjKuALEGbjGiGY=\ngithub.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:M+4ZjPhLJXIvoxcQsbDofmc19Wrig59hZ+hLvj6S3To=\ngithub.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f h1:8jZbZ4KBTdcXDFLwUBNQt5Xci6ZuAKh255S8TwuBCaM=\ngithub.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=\ngithub.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f h1:tG0hCx+0u5zca7qQ7AMkcv4DCrBG/DKW1ggs/P+BRRI=\ngithub.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=\ngithub.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f h1:ZXp5hKJIA7iJ52ZShJCKMQEPLpp/7dDIVZmPGV9Il40=\ngithub.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=\ngithub.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f h1:gL7H8HS8s38adz4/HZtRHh79qMwsbLTRRPz4GQ9LcWI=\ngithub.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=\ngithub.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f h1:Dchgc0pAY5Jwb5lzUlE+1nhHIzqLx+YOurXLHgvWd/0=\ngithub.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=\ngithub.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f h1:+MOLSQoduuKDxF410i1LcSPaQGaiP0eZb0INvMlmjM4=\ngithub.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=\ngithub.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:lIZna05Vn6n8k21p8OpSUnhwGm+E57PrMjiI4ZUfMSg=\ngithub.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=\ngithub.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f h1:B2aFQ5CRHI20t8YsEizvtguS5W2QfK7D5XV/NzTIxPE=\ngithub.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=\ngithub.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:qpSwJ1rFGYCfJDenNCZoWYjoG7N+xEa6ke+E7/JO1i4=\ngithub.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=\ngithub.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f h1:cx7Ipg0tSvTDjS4maMEYz4vuzz93BMPAysmZ1YLrz80=\ngithub.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=\ngithub.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f h1:4jOHuUiBxD8pJEpBBVQfJqyLmxjpd3t4MLRzU7YLFyg=\ngithub.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=\ngithub.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f h1:OpXBa2WlRU+Mam9oRe9Nn4/zf7gQ+qiBTNK8A5RwbfQ=\ngithub.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=\ngithub.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f h1:nJpGFi+6hI85tl4zoyNFEnFEQ5+xEV5gyvsUoMvd8g0=\ngithub.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=\ngithub.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f h1:SEy2rpmgOJgrqcEryJI/RSnqUWIsEsp0cfYoA8y21jc=\ngithub.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=\ngithub.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f h1:EW2TuFMLm0iBGqRZtuGwIZdeYmDtDsDmRcRRJQOMxUo=\ngithub.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=\ngithub.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f h1:3U5woxrNCkzfv1+UX+mVoWh1228AE1qAiMG02F9oFbY=\ngithub.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=\ngithub.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f h1:YwFTfuWG3mmctroeDYtFZ6LHjGsedVO+5wInYbbUuUY=\ngithub.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=\ngithub.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:r4V0ddPCRLgGu0VdgR3aUsO9NjpmyjAf+h+3oTD9D6E=\ngithub.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=\ngithub.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f h1:B8yf4gFvEYUnwWmtVK9sdwUsflYZ387MhYmlOP2ohFQ=\ngithub.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=\ngithub.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:9YyaMg4rO1/jIgrxmNb0LKH+X7frSYWfX2pFgW5JUVM=\ngithub.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=\ngithub.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f h1:B0fnGu0sh9yT/9JDN5u/GqThGoOzNN/daOAuGWFLXEk=\ngithub.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=\ngithub.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f h1:lxPcIXKSSI5JDhc7rx/6yufISWM4vtBS2FY9PavWQTs=\ngithub.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=\ngithub.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=\ngithub.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=\ngithub.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU=\ngithub.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=\ngithub.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=\ngithub.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=\ngithub.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=\ngithub.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=\ngithub.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw=\ngithub.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=\ngithub.com/sagernet/sing v0.8.0-beta.16 h1:Fe+6E9VHYky9Mx4cf0ugbZPWDcXRflpAu7JQ5bWXvaA=\ngithub.com/sagernet/sing v0.8.0-beta.16/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=\ngithub.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=\ngithub.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=\ngithub.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4=\ngithub.com/sagernet/sing-quic v0.6.0-beta.11/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=\ngithub.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=\ngithub.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=\ngithub.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=\ngithub.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=\ngithub.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=\ngithub.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=\ngithub.com/sagernet/sing-tun v0.8.0-beta.17 h1:6DdbNXeTFYj8Tb4FCh8Mp2boA3rVY6VNqzTOObj7Xis=\ngithub.com/sagernet/sing-tun v0.8.0-beta.17/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8=\ngithub.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=\ngithub.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=\ngithub.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=\ngithub.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=\ngithub.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 h1:eYz/OpMqWCvO2++iw3dEuzrlfC2xv78GdlGvprIM6O8=\ngithub.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc=\ngithub.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA=\ngithub.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=\ngithub.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=\ngithub.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/spyzhov/ajson v0.9.4 h1:MVibcTCgO7DY4IlskdqIlCmDOsUOZ9P7oKj8ifdcf84=\ngithub.com/spyzhov/ajson v0.9.4/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=\ngithub.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=\ngithub.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=\ngithub.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=\ngithub.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=\ngithub.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=\ngithub.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=\ngithub.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=\ngithub.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=\ngithub.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=\ngithub.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=\ngithub.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=\ngithub.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14=\ngithub.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=\ngithub.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=\ngithub.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=\ngithub.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=\ngithub.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=\ngithub.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=\ngithub.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=\ngithub.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=\ngithub.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=\ngithub.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=\ngithub.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=\ngithub.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=\ngithub.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=\ngithub.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=\ngithub.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=\ngithub.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=\ngithub.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=\ngo.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=\ngo.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4=\ngo.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=\ngo.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=\ngo.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=\ngo.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=\ngo.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=\ngo.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=\ngo.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=\ngo.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=\ngo.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=\ngo.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=\ngo4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=\ngo4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=\ngo4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=\ngo4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=\ngolang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=\ngolang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=\ngolang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=\ngolang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=\ngolang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=\ngolang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=\ngolang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=\ngolang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=\ngolang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=\ngolang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=\ngolang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=\ngolang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=\ngolang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=\ngolang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=\ngolang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=\ngolang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=\ngolang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=\ngolang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=\ngoogle.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=\ngotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=\nlukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=\nlukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=\nsoftware.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=\nsoftware.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=\n"
  },
  {
    "path": "test/http_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nfunc TestHTTPSelf(t *testing.T) {\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeHTTP,\n\t\t\t\tTag:  \"http-out\",\n\t\t\t\tOptions: &option.HTTPOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"http-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestTCP(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/hysteria2_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-quic/hysteria2\"\n\t\"github.com/sagernet/sing/common\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nfunc TestHysteria2Self(t *testing.T) {\n\tt.Run(\"self\", func(t *testing.T) {\n\t\ttestHysteria2Self(t, \"\", false)\n\t})\n\tt.Run(\"self-salamander\", func(t *testing.T) {\n\t\ttestHysteria2Self(t, \"password\", false)\n\t})\n\tt.Run(\"self-hop\", func(t *testing.T) {\n\t\ttestHysteria2Self(t, \"\", true)\n\t})\n\tt.Run(\"self-hop-salamander\", func(t *testing.T) {\n\t\ttestHysteria2Self(t, \"password\", true)\n\t})\n}\n\nfunc TestHysteria2Hop(t *testing.T) {\n\ttestHysteria2Self(t, \"password\", true)\n}\n\nfunc testHysteria2Self(t *testing.T, salamanderPassword string, portHop bool) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tvar obfs *option.Hysteria2Obfs\n\tif salamanderPassword != \"\" {\n\t\tobfs = &option.Hysteria2Obfs{\n\t\t\tType:     hysteria2.ObfsTypeSalamander,\n\t\t\tPassword: salamanderPassword,\n\t\t}\n\t}\n\tvar (\n\t\tserverPorts []string\n\t\thopInterval time.Duration\n\t)\n\tif portHop {\n\t\tserverPorts = []string{F.ToString(serverPort, \":\", serverPort)}\n\t\thopInterval = 5 * time.Second\n\t}\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeHysteria2,\n\t\t\t\tOptions: &option.Hysteria2InboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUpMbps:   100,\n\t\t\t\t\tDownMbps: 100,\n\t\t\t\t\tObfs:     obfs,\n\t\t\t\t\tUsers: []option.Hysteria2User{{\n\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t}},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeHysteria2,\n\t\t\t\tTag:  \"hy2-out\",\n\t\t\t\tOptions: &option.Hysteria2OutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tServerPorts: serverPorts,\n\t\t\t\t\tHopInterval: badoption.Duration(hopInterval),\n\t\t\t\t\tUpMbps:      100,\n\t\t\t\t\tDownMbps:    100,\n\t\t\t\t\tObfs:        obfs,\n\t\t\t\t\tPassword:    \"password\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"hy2-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitLargeUDP(t, clientPort, testPort)\n\tif portHop {\n\t\ttime.Sleep(5 * time.Second)\n\t\ttestSuitLargeUDP(t, clientPort, testPort)\n\t}\n}\n\nfunc TestHysteria2Inbound(t *testing.T) {\n\tcaPem, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeHysteria2,\n\t\t\t\tOptions: &option.Hysteria2InboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tObfs: &option.Hysteria2Obfs{\n\t\t\t\t\t\tType:     hysteria2.ObfsTypeSalamander,\n\t\t\t\t\t\tPassword: \"cry_me_a_r1ver\",\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.Hysteria2User{{\n\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t}},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageHysteria2,\n\t\tPorts: []uint16{serverPort, clientPort},\n\t\tCmd:   []string{\"client\", \"-c\", \"/etc/hysteria/config.yml\", \"--disable-update-check\", \"--log-level\", \"debug\"},\n\t\tBind: map[string]string{\n\t\t\t\"hysteria2-client.yml\": \"/etc/hysteria/config.yml\",\n\t\t\tcaPem:                  \"/etc/hysteria/ca.pem\",\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestHysteria2Outbound(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageHysteria2,\n\t\tPorts: []uint16{testPort},\n\t\tCmd:   []string{\"server\", \"-c\", \"/etc/hysteria/config.yml\", \"--disable-update-check\", \"--log-level\", \"debug\"},\n\t\tBind: map[string]string{\n\t\t\t\"hysteria2-server.yml\": \"/etc/hysteria/config.yml\",\n\t\t\tcertPem:                \"/etc/hysteria/cert.pem\",\n\t\t\tkeyPem:                 \"/etc/hysteria/key.pem\",\n\t\t},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeHysteria2,\n\t\t\t\tOptions: &option.Hysteria2OutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tObfs: &option.Hysteria2Obfs{\n\t\t\t\t\t\tType:     hysteria2.ObfsTypeSalamander,\n\t\t\t\t\t\tPassword: \"cry_me_a_r1ver\",\n\t\t\t\t\t},\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitSimple1(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/hysteria_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nfunc TestHysteriaSelf(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeHysteria,\n\t\t\t\tOptions: &option.HysteriaInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUpMbps:   100,\n\t\t\t\t\tDownMbps: 100,\n\t\t\t\t\tUsers: []option.HysteriaUser{{\n\t\t\t\t\t\tAuthString: \"password\",\n\t\t\t\t\t}},\n\t\t\t\t\tObfs: \"fuck me till the daylight\",\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeHysteria,\n\t\t\t\tTag:  \"hy-out\",\n\t\t\t\tOptions: &option.HysteriaOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUpMbps:     100,\n\t\t\t\t\tDownMbps:   100,\n\t\t\t\t\tAuthString: \"password\",\n\t\t\t\t\tObfs:       \"fuck me till the daylight\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"hy-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestHysteriaInbound(t *testing.T) {\n\tcaPem, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeHysteria,\n\t\t\t\tOptions: &option.HysteriaInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUpMbps:   100,\n\t\t\t\t\tDownMbps: 100,\n\t\t\t\t\tUsers: []option.HysteriaUser{{\n\t\t\t\t\t\tAuthString: \"password\",\n\t\t\t\t\t}},\n\t\t\t\t\tObfs: \"fuck me till the daylight\",\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageHysteria,\n\t\tPorts: []uint16{serverPort, clientPort},\n\t\tCmd:   []string{\"-c\", \"/etc/hysteria/config.json\", \"client\"},\n\t\tBind: map[string]string{\n\t\t\t\"hysteria-client.json\": \"/etc/hysteria/config.json\",\n\t\t\tcaPem:                  \"/etc/hysteria/ca.pem\",\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestHysteriaOutbound(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageHysteria,\n\t\tPorts: []uint16{testPort},\n\t\tCmd:   []string{\"-c\", \"/etc/hysteria/config.json\", \"server\"},\n\t\tBind: map[string]string{\n\t\t\t\"hysteria-server.json\": \"/etc/hysteria/config.json\",\n\t\t\tcertPem:                \"/etc/hysteria/cert.pem\",\n\t\t\tkeyPem:                 \"/etc/hysteria/key.pem\",\n\t\t},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeHysteria,\n\t\t\t\tOptions: &option.HysteriaOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUpMbps:     100,\n\t\t\t\t\tDownMbps:   100,\n\t\t\t\t\tAuthString: \"password\",\n\t\t\t\t\tObfs:       \"fuck me till the daylight\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitSimple1(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/inbound_detour_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nfunc TestChainedInbound(t *testing.T) {\n\tmethod := shadowaead_2022.List[0]\n\tpassword := mkBase64(t, 16)\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t\tDetour:     \"detour\",\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"detour\",\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: otherPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t\tDialerOptions: option.DialerOptions{\n\t\t\t\t\t\tDetour: \"detour-out\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"detour-out\",\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestTCP(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/ktls_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/gofrs/uuid/v5\"\n)\n\nfunc TestKTLS(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tOptions: &option.TrojanInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TrojanUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t\t// KernelTx:        true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tTag:  \"trojan-out\",\n\t\t\t\tOptions: &option.TrojanOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKernelTx:        true,\n\t\t\t\t\t\t\tKernelRx:        true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"trojan-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestKTLSECH(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\techConfig, echKey := common.Must2(tls.ECHKeygenDefault(\"not.example.org\"))\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tOptions: &option.TrojanInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TrojanUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t\tKernelTx:        true,\n\t\t\t\t\t\t\tECH: &option.InboundECHOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tKey:     []string{echKey},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tTag:  \"trojan-out\",\n\t\t\t\tOptions: &option.TrojanOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKernelTx:        true,\n\t\t\t\t\t\t\tKernelRx:        true,\n\t\t\t\t\t\t\tECH: &option.OutboundECHOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tConfig:  []string{echConfig},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"trojan-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestKTLSReality(t *testing.T) {\n\tuser, _ := uuid.NewV4()\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVLESS,\n\t\t\t\tOptions: &option.VLESSInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VLESSUser{{UUID: user.String()}},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:    true,\n\t\t\t\t\t\t\tServerName: \"google.com\",\n\t\t\t\t\t\t\tKernelTx:   true,\n\t\t\t\t\t\t\tReality: &option.InboundRealityOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tHandshake: option.InboundRealityHandshakeOptions{\n\t\t\t\t\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\t\t\t\t\tServer:     \"google.com\",\n\t\t\t\t\t\t\t\t\t\tServerPort: 443,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tShortID:    []string{\"0123456789abcdef\"},\n\t\t\t\t\t\t\t\tPrivateKey: \"UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVLESS,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.VLESSOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID: user.String(),\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:    true,\n\t\t\t\t\t\t\tServerName: \"google.com\",\n\t\t\t\t\t\t\tKernelTx:   true,\n\t\t\t\t\t\t\tKernelRx:   true,\n\t\t\t\t\t\t\tReality: &option.OutboundRealityOptions{\n\t\t\t\t\t\t\t\tEnabled:   true,\n\t\t\t\t\t\t\t\tShortID:   \"0123456789abcdef\",\n\t\t\t\t\t\t\t\tPublicKey: \"jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tUTLS: &option.OutboundUTLSOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/mkcert.go",
    "content": "package main\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/sha1\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/asn1\"\n\t\"encoding/pem\"\n\t\"math/big\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common/rw\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc createSelfSignedCertificate(t *testing.T, domain string) (caPem, certPem, keyPem string) {\n\tconst userAndHostname = \"sekai@nekohasekai.local\"\n\ttempDir, err := os.MkdirTemp(\"\", \"sing-box-test\")\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\tos.RemoveAll(tempDir)\n\t})\n\tcaKey, err := rsa.GenerateKey(rand.Reader, 3072)\n\trequire.NoError(t, err)\n\tspkiASN1, err := x509.MarshalPKIXPublicKey(caKey.Public())\n\tvar spki struct {\n\t\tAlgorithm        pkix.AlgorithmIdentifier\n\t\tSubjectPublicKey asn1.BitString\n\t}\n\t_, err = asn1.Unmarshal(spkiASN1, &spki)\n\trequire.NoError(t, err)\n\tskid := sha1.Sum(spki.SubjectPublicKey.Bytes)\n\tcaTpl := &x509.Certificate{\n\t\tSerialNumber: randomSerialNumber(t),\n\t\tSubject: pkix.Name{\n\t\t\tOrganization:       []string{\"sing-box test CA\"},\n\t\t\tOrganizationalUnit: []string{userAndHostname},\n\t\t\tCommonName:         \"sing-box \" + userAndHostname,\n\t\t},\n\t\tSubjectKeyId:          skid[:],\n\t\tNotAfter:              time.Now().AddDate(10, 0, 0),\n\t\tNotBefore:             time.Now(),\n\t\tKeyUsage:              x509.KeyUsageCertSign,\n\t\tBasicConstraintsValid: true,\n\t\tIsCA:                  true,\n\t\tMaxPathLenZero:        true,\n\t}\n\tcaCert, err := x509.CreateCertificate(rand.Reader, caTpl, caTpl, caKey.Public(), caKey)\n\trequire.NoError(t, err)\n\terr = rw.WriteFile(filepath.Join(tempDir, \"ca.pem\"), pem.EncodeToMemory(&pem.Block{Type: \"CERTIFICATE\", Bytes: caCert}))\n\trequire.NoError(t, err)\n\tkey, err := rsa.GenerateKey(rand.Reader, 2048)\n\tdomainTpl := &x509.Certificate{\n\t\tSerialNumber: randomSerialNumber(t),\n\t\tSubject: pkix.Name{\n\t\t\tOrganization:       []string{\"sing-box test certificate\"},\n\t\t\tOrganizationalUnit: []string{\"sing-box \" + userAndHostname},\n\t\t},\n\t\tNotBefore: time.Now(), NotAfter: time.Now().AddDate(0, 0, 30),\n\t\tKeyUsage:    x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t}\n\tdomainTpl.DNSNames = append(domainTpl.DNSNames, domain)\n\tcert, err := x509.CreateCertificate(rand.Reader, domainTpl, caTpl, key.Public(), caKey)\n\trequire.NoError(t, err)\n\tcertPEM := pem.EncodeToMemory(&pem.Block{Type: \"CERTIFICATE\", Bytes: cert})\n\tprivDER, err := x509.MarshalPKCS8PrivateKey(key)\n\trequire.NoError(t, err)\n\tprivPEM := pem.EncodeToMemory(&pem.Block{Type: \"PRIVATE KEY\", Bytes: privDER})\n\terr = rw.WriteFile(filepath.Join(tempDir, domain+\".pem\"), certPEM)\n\trequire.NoError(t, err)\n\terr = rw.WriteFile(filepath.Join(tempDir, domain+\".key.pem\"), privPEM)\n\trequire.NoError(t, err)\n\treturn filepath.Join(tempDir, \"ca.pem\"), filepath.Join(tempDir, domain+\".pem\"), filepath.Join(tempDir, domain+\".key.pem\")\n}\n\nfunc randomSerialNumber(t *testing.T) *big.Int {\n\tserialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)\n\tserialNumber, err := rand.Int(rand.Reader, serialNumberLimit)\n\trequire.NoError(t, err)\n\treturn serialNumber\n}\n"
  },
  {
    "path": "test/mux_cool_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"os\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/spyzhov/ajson\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMuxCoolServer(t *testing.T) {\n\tuserId := newUUID()\n\tcontent, err := os.ReadFile(\"config/vmess-mux-client.json\")\n\trequire.NoError(t, err)\n\tconfig, err := ajson.Unmarshal(content)\n\trequire.NoError(t, err)\n\n\tconfig.MustKey(\"inbounds\").MustIndex(0).MustKey(\"port\").SetNumeric(float64(clientPort))\n\toutbound := config.MustKey(\"outbounds\").MustIndex(0).MustKey(\"settings\").MustKey(\"vnext\").MustIndex(0)\n\toutbound.MustKey(\"port\").SetNumeric(float64(serverPort))\n\tuser := outbound.MustKey(\"users\").MustIndex(0)\n\tuser.MustKey(\"id\").SetString(userId.String())\n\n\tcontent, err = ajson.Marshal(config)\n\trequire.NoError(t, err)\n\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageV2RayCore,\n\t\tPorts:      []uint16{serverPort, testPort},\n\t\tEntryPoint: \"v2ray\",\n\t\tCmd:        []string{\"run\"},\n\t\tStdin:      content,\n\t})\n\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VMessUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"sekai\",\n\t\t\t\t\t\t\tUUID: userId.String(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\ttestSuitSimple(t, clientPort, testPort)\n}\n\nfunc TestMuxCoolClient(t *testing.T) {\n\tuser := newUUID()\n\tcontent, err := os.ReadFile(\"config/vmess-server.json\")\n\trequire.NoError(t, err)\n\tconfig, err := ajson.Unmarshal(content)\n\trequire.NoError(t, err)\n\n\tinbound := config.MustKey(\"inbounds\").MustIndex(0)\n\tinbound.MustKey(\"port\").SetNumeric(float64(serverPort))\n\tinbound.MustKey(\"settings\").MustKey(\"clients\").MustIndex(0).MustKey(\"id\").SetString(user.String())\n\n\tcontent, err = ajson.Marshal(config)\n\trequire.NoError(t, err)\n\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageXRayCore,\n\t\tPorts:      []uint16{serverPort, testPort},\n\t\tEntryPoint: \"xray\",\n\t\tStdin:      content,\n\t})\n\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID:           user.String(),\n\t\t\t\t\tPacketEncoding: \"xudp\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitSimple(t, clientPort, testPort)\n}\n\nfunc TestMuxCoolSelf(t *testing.T) {\n\tuser := newUUID()\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VMessUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"sekai\",\n\t\t\t\t\t\t\tUUID: user.String(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tTag:  \"vmess-out\",\n\t\t\t\tOptions: &option.VMessOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID:           user.String(),\n\t\t\t\t\tPacketEncoding: \"xudp\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"vmess-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitSimple(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/mux_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/gofrs/uuid/v5\"\n)\n\nvar muxProtocols = []string{\n\t\"h2mux\",\n\t\"smux\",\n\t\"yamux\",\n}\n\nfunc TestVMessSMux(t *testing.T) {\n\ttestVMessMux(t, option.OutboundMultiplexOptions{\n\t\tEnabled:  true,\n\t\tProtocol: \"smux\",\n\t})\n}\n\nfunc TestShadowsocksMux(t *testing.T) {\n\tfor _, protocol := range muxProtocols {\n\t\tt.Run(protocol, func(t *testing.T) {\n\t\t\ttestShadowsocksMux(t, option.OutboundMultiplexOptions{\n\t\t\t\tEnabled:  true,\n\t\t\t\tProtocol: protocol,\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestShadowsockH2Mux(t *testing.T) {\n\ttestShadowsocksMux(t, option.OutboundMultiplexOptions{\n\t\tEnabled:  true,\n\t\tProtocol: \"h2mux\",\n\t\tPadding:  true,\n\t})\n}\n\nfunc TestShadowsockSMuxPadding(t *testing.T) {\n\ttestShadowsocksMux(t, option.OutboundMultiplexOptions{\n\t\tEnabled:  true,\n\t\tProtocol: \"smux\",\n\t\tPadding:  true,\n\t})\n}\n\nfunc testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) {\n\tmethod := shadowaead_2022.List[0]\n\tpassword := mkBase64(t, 16)\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t\tMultiplex: &option.InboundMultiplexOptions{\n\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:    method,\n\t\t\t\t\tPassword:  password,\n\t\t\t\t\tMultiplex: &options,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) {\n\tuser, _ := uuid.NewV4()\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VMessUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUUID: user.String(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tMultiplex: &option.InboundMultiplexOptions{\n\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tTag:  \"vmess-out\",\n\t\t\t\tOptions: &option.VMessOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tSecurity:  \"auto\",\n\t\t\t\t\tUUID:      user.String(),\n\t\t\t\t\tMultiplex: &options,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"vmess-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/naive_self_test.go",
    "content": "//go:build with_naive_outbound\n\npackage main\n\nimport (\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/protocol/naive\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\t\"github.com/sagernet/sing/common/network\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNaiveSelf(t *testing.T) {\n\tcaPem, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tcaPemContent, err := os.ReadFile(caPem)\n\trequire.NoError(t, err)\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeNaive,\n\t\t\t\tTag:  \"naive-in\",\n\t\t\t\tOptions: &option.NaiveInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []auth.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUsername: \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tNetwork: network.NetworkTCP,\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeNaive,\n\t\t\t\tTag:  \"naive-out\",\n\t\t\t\tOptions: &option.NaiveOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsername: \"sekai\",\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:     true,\n\t\t\t\t\t\t\tServerName:  \"example.org\",\n\t\t\t\t\t\t\tCertificate: []string{string(caPemContent)},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"naive-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestTCP(t, clientPort, testPort)\n}\n\nfunc TestNaiveSelfECH(t *testing.T) {\n\tcaPem, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tcaPemContent, err := os.ReadFile(caPem)\n\trequire.NoError(t, err)\n\techConfig, echKey := common.Must2(tls.ECHKeygenDefault(\"not.example.org\"))\n\tinstance := startInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeNaive,\n\t\t\t\tTag:  \"naive-in\",\n\t\t\t\tOptions: &option.NaiveInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []auth.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUsername: \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tNetwork: network.NetworkTCP,\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t\tECH: &option.InboundECHOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tKey:     []string{echKey},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeNaive,\n\t\t\t\tTag:  \"naive-out\",\n\t\t\t\tOptions: &option.NaiveOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsername: \"sekai\",\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:     true,\n\t\t\t\t\t\t\tServerName:  \"example.org\",\n\t\t\t\t\t\t\tCertificate: []string{string(caPemContent)},\n\t\t\t\t\t\t\tECH: &option.OutboundECHOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tConfig:  []string{echConfig},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"naive-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tnaiveOut, ok := instance.Outbound().Outbound(\"naive-out\")\n\trequire.True(t, ok)\n\tnaiveOutbound := naiveOut.(*naive.Outbound)\n\n\tnetLogPath := \"/tmp/naive_ech_netlog.json\"\n\trequire.True(t, naiveOutbound.Client().Engine().StartNetLogToFile(netLogPath, true))\n\tdefer naiveOutbound.Client().Engine().StopNetLog()\n\n\ttestTCP(t, clientPort, testPort)\n\n\tnaiveOutbound.Client().Engine().StopNetLog()\n\n\tlogContent, err := os.ReadFile(netLogPath)\n\trequire.NoError(t, err)\n\tlogStr := string(logContent)\n\n\trequire.True(t, strings.Contains(logStr, `\"encrypted_client_hello\":true`),\n\t\t\"ECH should be accepted in TLS handshake. NetLog saved to: %s\", netLogPath)\n}\n\nfunc TestNaiveSelfInsecureConcurrency(t *testing.T) {\n\tcaPem, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tcaPemContent, err := os.ReadFile(caPem)\n\trequire.NoError(t, err)\n\n\tinstance := startInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeNaive,\n\t\t\t\tTag:  \"naive-in\",\n\t\t\t\tOptions: &option.NaiveInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []auth.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUsername: \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tNetwork: network.NetworkTCP,\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeNaive,\n\t\t\t\tTag:  \"naive-out\",\n\t\t\t\tOptions: &option.NaiveOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsername:            \"sekai\",\n\t\t\t\t\tPassword:            \"password\",\n\t\t\t\t\tInsecureConcurrency: 3,\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:     true,\n\t\t\t\t\t\t\tServerName:  \"example.org\",\n\t\t\t\t\t\t\tCertificate: []string{string(caPemContent)},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"naive-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tnaiveOut, ok := instance.Outbound().Outbound(\"naive-out\")\n\trequire.True(t, ok)\n\tnaiveOutbound := naiveOut.(*naive.Outbound)\n\n\tnetLogPath := \"/tmp/naive_concurrency_netlog.json\"\n\trequire.True(t, naiveOutbound.Client().Engine().StartNetLogToFile(netLogPath, true))\n\tdefer naiveOutbound.Client().Engine().StopNetLog()\n\n\t// Send multiple sequential connections to trigger round-robin\n\t// With insecure_concurrency=3, connections will be distributed to 3 pools\n\tfor i := 0; i < 6; i++ {\n\t\ttestTCP(t, clientPort, testPort)\n\t}\n\n\tnaiveOutbound.Client().Engine().StopNetLog()\n\n\t// Verify NetLog contains multiple independent HTTP/2 sessions\n\tlogContent, err := os.ReadFile(netLogPath)\n\trequire.NoError(t, err)\n\tlogStr := string(logContent)\n\n\t// Count HTTP2_SESSION_INITIALIZED events to verify connection pool isolation\n\t// NetLog stores event types as numeric IDs, HTTP2_SESSION_INITIALIZED = 249\n\tsessionCount := strings.Count(logStr, `\"type\":249`)\n\trequire.GreaterOrEqual(t, sessionCount, 3,\n\t\t\"Expected at least 3 HTTP/2 sessions with insecure_concurrency=3. NetLog: %s\", netLogPath)\n}\n\nfunc TestNaiveSelfQUIC(t *testing.T) {\n\tcaPem, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tcaPemContent, err := os.ReadFile(caPem)\n\trequire.NoError(t, err)\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeNaive,\n\t\t\t\tTag:  \"naive-in\",\n\t\t\t\tOptions: &option.NaiveInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []auth.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUsername: \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tNetwork: network.NetworkUDP,\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeNaive,\n\t\t\t\tTag:  \"naive-out\",\n\t\t\t\tOptions: &option.NaiveOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsername: \"sekai\",\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\tQUIC:     true,\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:     true,\n\t\t\t\t\t\t\tServerName:  \"example.org\",\n\t\t\t\t\t\t\tCertificate: []string{string(caPemContent)},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"naive-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestTCP(t, clientPort, testPort)\n}\n\nfunc TestNaiveSelfQUICCongestionControl(t *testing.T) {\n\ttestCases := []struct {\n\t\tname              string\n\t\tcongestionControl string\n\t}{\n\t\t{\"BBR\", \"bbr\"},\n\t\t{\"BBR2\", \"bbr2\"},\n\t\t{\"Cubic\", \"cubic\"},\n\t\t{\"Reno\", \"reno\"},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tcaPem, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\t\t\tcaPemContent, err := os.ReadFile(caPem)\n\t\t\trequire.NoError(t, err)\n\t\t\tstartInstance(t, option.Options{\n\t\t\t\tInbounds: []option.Inbound{\n\t\t\t\t\t{\n\t\t\t\t\t\tType: C.TypeMixed,\n\t\t\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType: C.TypeNaive,\n\t\t\t\t\t\tTag:  \"naive-in\",\n\t\t\t\t\t\tOptions: &option.NaiveInboundOptions{\n\t\t\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tUsers: []auth.User{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tUsername: \"sekai\",\n\t\t\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tNetwork:               network.NetworkUDP,\n\t\t\t\t\t\t\tQUICCongestionControl: tc.congestionControl,\n\t\t\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tOutbounds: []option.Outbound{\n\t\t\t\t\t{\n\t\t\t\t\t\tType: C.TypeDirect,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType: C.TypeNaive,\n\t\t\t\t\t\tTag:  \"naive-out\",\n\t\t\t\t\t\tOptions: &option.NaiveOutboundOptions{\n\t\t\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tUsername:              \"sekai\",\n\t\t\t\t\t\t\tPassword:              \"password\",\n\t\t\t\t\t\t\tQUIC:                  true,\n\t\t\t\t\t\t\tQUICCongestionControl: tc.congestionControl,\n\t\t\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\t\t\tEnabled:     true,\n\t\t\t\t\t\t\t\t\tServerName:  \"example.org\",\n\t\t\t\t\t\t\t\t\tCertificate: []string{string(caPemContent)},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tRoute: &option.RouteOptions{\n\t\t\t\t\tRules: []option.Rule{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\t\t\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\t\t\tOutbound: \"naive-out\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\ttestTCP(t, clientPort, testPort)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "test/naive_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/auth\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\t\"github.com/sagernet/sing/common/network\"\n)\n\nfunc TestNaiveInboundWithNginx(t *testing.T) {\n\tcaPem, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeNaive,\n\t\t\t\tOptions: &option.NaiveInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: otherPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []auth.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUsername: \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tNetwork: network.NetworkTCP,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageNginx,\n\t\tPorts: []uint16{serverPort, otherPort},\n\t\tBind: map[string]string{\n\t\t\t\"nginx.conf\":       \"/etc/nginx/nginx.conf\",\n\t\t\t\"naive-nginx.conf\": \"/etc/nginx/conf.d/naive.conf\",\n\t\t\tcertPem:            \"/etc/nginx/cert.pem\",\n\t\t\tkeyPem:             \"/etc/nginx/key.pem\",\n\t\t},\n\t})\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageNaive,\n\t\tPorts: []uint16{serverPort, clientPort},\n\t\tBind: map[string]string{\n\t\t\t\"naive.json\": \"/etc/naiveproxy/config.json\",\n\t\t\tcaPem:        \"/etc/naiveproxy/ca.pem\",\n\t\t},\n\t\tEnv: []string{\n\t\t\t\"SSL_CERT_FILE=/etc/naiveproxy/ca.pem\",\n\t\t},\n\t})\n\ttestTCP(t, clientPort, testPort)\n}\n\nfunc TestNaiveInbound(t *testing.T) {\n\tcaPem, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeNaive,\n\t\t\t\tOptions: &option.NaiveInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []auth.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUsername: \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tNetwork: network.NetworkTCP,\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageNaive,\n\t\tPorts: []uint16{serverPort, clientPort},\n\t\tBind: map[string]string{\n\t\t\t\"naive.json\": \"/etc/naiveproxy/config.json\",\n\t\t\tcaPem:        \"/etc/naiveproxy/ca.pem\",\n\t\t},\n\t\tEnv: []string{\n\t\t\t\"SSL_CERT_FILE=/etc/naiveproxy/ca.pem\",\n\t\t},\n\t})\n\ttestTCP(t, clientPort, testPort)\n}\n\nfunc TestNaiveHTTP3Inbound(t *testing.T) {\n\tcaPem, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeNaive,\n\t\t\t\tOptions: &option.NaiveInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []auth.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUsername: \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tNetwork: network.NetworkUDP,\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageNaive,\n\t\tPorts: []uint16{serverPort, clientPort},\n\t\tBind: map[string]string{\n\t\t\t\"naive-quic.json\": \"/etc/naiveproxy/config.json\",\n\t\t\tcaPem:             \"/etc/naiveproxy/ca.pem\",\n\t\t},\n\t\tEnv: []string{\n\t\t\t\"SSL_CERT_FILE=/etc/naiveproxy/ca.pem\",\n\t\t},\n\t})\n\ttestTCP(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/reality_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/gofrs/uuid/v5\"\n)\n\nfunc TestReality(t *testing.T) {\n\tuser, _ := uuid.NewV4()\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVLESS,\n\t\t\t\tOptions: &option.VLESSInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VLESSUser{{UUID: user.String()}},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:    true,\n\t\t\t\t\t\t\tServerName: \"google.com\",\n\t\t\t\t\t\t\tReality: &option.InboundRealityOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t\tHandshake: option.InboundRealityHandshakeOptions{\n\t\t\t\t\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\t\t\t\t\tServer:     \"google.com\",\n\t\t\t\t\t\t\t\t\t\tServerPort: 443,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tShortID:    []string{\"0123456789abcdef\"},\n\t\t\t\t\t\t\t\tPrivateKey: \"UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVLESS,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.VLESSOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID: user.String(),\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:    true,\n\t\t\t\t\t\t\tServerName: \"google.com\",\n\t\t\t\t\t\t\tReality: &option.OutboundRealityOptions{\n\t\t\t\t\t\t\t\tEnabled:   true,\n\t\t\t\t\t\t\t\tShortID:   \"0123456789abcdef\",\n\t\t\t\t\t\t\t\tPublicKey: \"jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tUTLS: &option.OutboundUTLSOptions{\n\t\t\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/shadowsocks_legacy_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowsocks2/shadowstream\"\n\t\"github.com/sagernet/sing/common\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nfunc TestShadowsocksLegacy(t *testing.T) {\n\ttestShadowsocksLegacy(t, shadowstream.MethodList[0])\n}\n\nfunc testShadowsocksLegacy(t *testing.T, method string) {\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageShadowsocksLegacy,\n\t\tPorts: []uint16{serverPort},\n\t\tEnv: []string{\n\t\t\t\"SS_MODULE=ss-server\",\n\t\t\tF.ToString(\"SS_CONFIG=-s 0.0.0.0 -u -p 10000 -m \", method, \" -k FzcLbKs2dY9mhL\"),\n\t\t},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: \"FzcLbKs2dY9mhL\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitSimple(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/shadowsocks_test.go",
    "content": "package main\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\t\"github.com/sagernet/sing/common\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\tserverPort uint16 = 10000 + iota\n\tclientPort\n\ttestPort\n\totherPort\n\totherClientPort\n)\n\nfunc TestShadowsocks(t *testing.T) {\n\tfor _, method := range []string{\n\t\t\"aes-128-gcm\",\n\t\t\"aes-256-gcm\",\n\t\t\"chacha20-ietf-poly1305\",\n\t} {\n\t\tt.Run(method+\"-inbound\", func(t *testing.T) {\n\t\t\ttestShadowsocksInboundWithShadowsocksRust(t, method, mkBase64(t, 16))\n\t\t})\n\t\tt.Run(method+\"-outbound\", func(t *testing.T) {\n\t\t\ttestShadowsocksOutboundWithShadowsocksRust(t, method, mkBase64(t, 16))\n\t\t})\n\t\tt.Run(method+\"-self\", func(t *testing.T) {\n\t\t\ttestShadowsocksSelf(t, method, mkBase64(t, 16))\n\t\t})\n\t}\n}\n\nfunc TestShadowsocksNone(t *testing.T) {\n\ttestShadowsocksSelf(t, \"none\", \"\")\n}\n\nfunc TestShadowsocks2022(t *testing.T) {\n\tfor _, method16 := range []string{\n\t\t\"2022-blake3-aes-128-gcm\",\n\t} {\n\t\tt.Run(method16+\"-inbound\", func(t *testing.T) {\n\t\t\ttestShadowsocksInboundWithShadowsocksRust(t, method16, mkBase64(t, 16))\n\t\t})\n\t\tt.Run(method16+\"-outbound\", func(t *testing.T) {\n\t\t\ttestShadowsocksOutboundWithShadowsocksRust(t, method16, mkBase64(t, 16))\n\t\t})\n\t\tt.Run(method16+\"-self\", func(t *testing.T) {\n\t\t\ttestShadowsocksSelf(t, method16, mkBase64(t, 16))\n\t\t})\n\t}\n\tfor _, method32 := range []string{\n\t\t\"2022-blake3-aes-256-gcm\",\n\t\t\"2022-blake3-chacha20-poly1305\",\n\t} {\n\t\tt.Run(method32+\"-inbound\", func(t *testing.T) {\n\t\t\ttestShadowsocksInboundWithShadowsocksRust(t, method32, mkBase64(t, 32))\n\t\t})\n\t\tt.Run(method32+\"-outbound\", func(t *testing.T) {\n\t\t\ttestShadowsocksOutboundWithShadowsocksRust(t, method32, mkBase64(t, 32))\n\t\t})\n\t\tt.Run(method32+\"-self\", func(t *testing.T) {\n\t\t\ttestShadowsocksSelf(t, method32, mkBase64(t, 32))\n\t\t})\n\t}\n}\n\nfunc TestShadowsocks2022EIH(t *testing.T) {\n\tfor _, method16 := range []string{\n\t\t\"2022-blake3-aes-128-gcm\",\n\t} {\n\t\tt.Run(method16, func(t *testing.T) {\n\t\t\ttestShadowsocks2022EIH(t, method16, mkBase64(t, 16))\n\t\t})\n\t}\n\tfor _, method32 := range []string{\n\t\t\"2022-blake3-aes-256-gcm\",\n\t} {\n\t\tt.Run(method32, func(t *testing.T) {\n\t\t\ttestShadowsocks2022EIH(t, method32, mkBase64(t, 32))\n\t\t})\n\t}\n}\n\nfunc testShadowsocksInboundWithShadowsocksRust(t *testing.T, method string, password string) {\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageShadowsocksRustClient,\n\t\tEntryPoint: \"sslocal\",\n\t\tPorts:      []uint16{serverPort, clientPort},\n\t\tCmd:        []string{\"-s\", F.ToString(\"127.0.0.1:\", serverPort), \"-b\", F.ToString(\"0.0.0.0:\", clientPort), \"-m\", method, \"-k\", password, \"-U\"},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, password string) {\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageShadowsocksRustServer,\n\t\tEntryPoint: \"ssserver\",\n\t\tPorts:      []uint16{serverPort, testPort},\n\t\tCmd:        []string{\"-s\", F.ToString(\"0.0.0.0:\", serverPort), \"-m\", method, \"-k\", password, \"-U\"},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc testShadowsocksSelf(t *testing.T, method string, password string) {\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestShadowsocksUoT(t *testing.T) {\n\tmethod := shadowaead_2022.List[0]\n\tpassword := mkBase64(t, 16)\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t\tUDPOverTCP: &option.UDPOverTCPOptions{\n\t\t\t\t\t\tEnabled: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc testShadowsocks2022EIH(t *testing.T, method string, password string) {\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t\tUsers: []option.ShadowsocksUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPassword: password,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password + \":\" + password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc mkBase64(t *testing.T, length int) string {\n\tpsk := make([]byte, length)\n\t_, err := rand.Read(psk)\n\trequire.NoError(t, err)\n\treturn base64.StdEncoding.EncodeToString(psk)\n}\n"
  },
  {
    "path": "test/shadowtls_test.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\t\"github.com/sagernet/sing/common\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestShadowTLS(t *testing.T) {\n\tt.Run(\"v1\", func(t *testing.T) {\n\t\ttestShadowTLS(t, 1, \"\", false, option.ShadowTLSWildcardSNIOff)\n\t})\n\tt.Run(\"v2\", func(t *testing.T) {\n\t\ttestShadowTLS(t, 2, \"hello\", false, option.ShadowTLSWildcardSNIOff)\n\t})\n\tt.Run(\"v3\", func(t *testing.T) {\n\t\ttestShadowTLS(t, 3, \"hello\", false, option.ShadowTLSWildcardSNIOff)\n\t})\n\tt.Run(\"v2-utls\", func(t *testing.T) {\n\t\ttestShadowTLS(t, 2, \"hello\", true, option.ShadowTLSWildcardSNIOff)\n\t})\n\tt.Run(\"v3-utls\", func(t *testing.T) {\n\t\ttestShadowTLS(t, 3, \"hello\", true, option.ShadowTLSWildcardSNIOff)\n\t})\n\tt.Run(\"v3-wildcard-sni-authed\", func(t *testing.T) {\n\t\ttestShadowTLS(t, 3, \"hello\", false, option.ShadowTLSWildcardSNIAuthed)\n\t})\n\tt.Run(\"v3-wildcard-sni-all\", func(t *testing.T) {\n\t\ttestShadowTLS(t, 3, \"hello\", false, option.ShadowTLSWildcardSNIAll)\n\t})\n\tt.Run(\"v3-wildcard-sni-authed-utls\", func(t *testing.T) {\n\t\ttestShadowTLS(t, 3, \"hello\", true, option.ShadowTLSWildcardSNIAll)\n\t})\n\tt.Run(\"v3-wildcard-sni-all-utls\", func(t *testing.T) {\n\t\ttestShadowTLS(t, 3, \"hello\", true, option.ShadowTLSWildcardSNIAll)\n\t})\n}\n\nfunc testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool, wildcardSNI option.WildcardSNI) {\n\tmethod := shadowaead_2022.List[0]\n\tssPassword := mkBase64(t, 16)\n\tvar clientServerName string\n\tif wildcardSNI != option.ShadowTLSWildcardSNIOff {\n\t\tclientServerName = \"cloudflare.com\"\n\t} else {\n\t\tclientServerName = \"google.com\"\n\t}\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowTLS,\n\t\t\t\tTag:  \"in\",\n\t\t\t\tOptions: &option.ShadowTLSInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t\tDetour:     \"detour\",\n\t\t\t\t\t},\n\t\t\t\t\tHandshake: option.ShadowTLSHandshakeOptions{\n\t\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\t\tServer:     \"google.com\",\n\t\t\t\t\t\t\tServerPort: 443,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tVersion:     version,\n\t\t\t\t\tPassword:    password,\n\t\t\t\t\tUsers:       []option.ShadowTLSUser{{Password: password}},\n\t\t\t\t\tWildcardSNI: wildcardSNI,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"detour\",\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: otherPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: ssPassword,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: ssPassword,\n\t\t\t\t\tDialerOptions: option.DialerOptions{\n\t\t\t\t\t\tDetour: \"detour\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowTLS,\n\t\t\t\tTag:  \"detour\",\n\t\t\t\tOptions: &option.ShadowTLSOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:    true,\n\t\t\t\t\t\t\tServerName: clientServerName,\n\t\t\t\t\t\t\tUTLS: &option.OutboundUTLSOptions{\n\t\t\t\t\t\t\t\tEnabled: utlsEanbled,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tVersion:  version,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t\tTag:  \"direct\",\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"detour\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"direct\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestTCP(t, clientPort, testPort)\n}\n\nfunc TestShadowTLSFallback(t *testing.T) {\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeShadowTLS,\n\t\t\t\tOptions: &option.ShadowTLSInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tHandshake: option.ShadowTLSHandshakeOptions{\n\t\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\t\tServer:     \"bing.com\",\n\t\t\t\t\t\t\tServerPort: 443,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tVersion: 3,\n\t\t\t\t\tUsers: []option.ShadowTLSUser{\n\t\t\t\t\t\t{Password: \"hello\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\tvar d net.Dialer\n\t\t\t\treturn d.DialContext(ctx, network, \"127.0.0.1:\"+F.ToString(serverPort))\n\t\t\t},\n\t\t},\n\t}\n\tresponse, err := client.Get(\"https://bing.com\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, response.StatusCode, 200)\n\tresponse.Body.Close()\n\tclient.CloseIdleConnections()\n}\n\nfunc TestShadowTLSFallbackWildcardAll(t *testing.T) {\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeShadowTLS,\n\t\t\t\tOptions: &option.ShadowTLSInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tVersion: 3,\n\t\t\t\t\tUsers: []option.ShadowTLSUser{\n\t\t\t\t\t\t{Password: \"hello\"},\n\t\t\t\t\t},\n\t\t\t\t\tWildcardSNI: option.ShadowTLSWildcardSNIAll,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\tvar d net.Dialer\n\t\t\t\treturn d.DialContext(ctx, network, \"127.0.0.1:\"+F.ToString(serverPort))\n\t\t\t},\n\t\t},\n\t}\n\tresponse, err := client.Get(\"https://www.bing.com\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, response.StatusCode, 200)\n\tresponse.Body.Close()\n\tclient.CloseIdleConnections()\n}\n\nfunc TestShadowTLSFallbackWildcardAuthedFail(t *testing.T) {\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeShadowTLS,\n\t\t\t\tOptions: &option.ShadowTLSInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tHandshake: option.ShadowTLSHandshakeOptions{\n\t\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\t\tServer:     \"bing.com\",\n\t\t\t\t\t\t\tServerPort: 443,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tVersion: 3,\n\t\t\t\t\tUsers: []option.ShadowTLSUser{\n\t\t\t\t\t\t{Password: \"hello\"},\n\t\t\t\t\t},\n\t\t\t\t\tWildcardSNI: option.ShadowTLSWildcardSNIAuthed,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\tvar d net.Dialer\n\t\t\t\treturn d.DialContext(ctx, network, \"127.0.0.1:\"+F.ToString(serverPort))\n\t\t\t},\n\t\t},\n\t}\n\t_, err := client.Get(\"https://baidu.com\")\n\texpected := &tls.CertificateVerificationError{}\n\trequire.ErrorAs(t, err, &expected)\n\tclient.CloseIdleConnections()\n}\n\nfunc TestShadowTLSFallbackWildcardOffFail(t *testing.T) {\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeShadowTLS,\n\t\t\t\tOptions: &option.ShadowTLSInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tHandshake: option.ShadowTLSHandshakeOptions{\n\t\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\t\tServer:     \"bing.com\",\n\t\t\t\t\t\t\tServerPort: 443,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tVersion: 3,\n\t\t\t\t\tUsers: []option.ShadowTLSUser{\n\t\t\t\t\t\t{Password: \"hello\"},\n\t\t\t\t\t},\n\t\t\t\t\tWildcardSNI: option.ShadowTLSWildcardSNIOff,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\tvar d net.Dialer\n\t\t\t\treturn d.DialContext(ctx, network, \"127.0.0.1:\"+F.ToString(serverPort))\n\t\t\t},\n\t\t},\n\t}\n\t_, err := client.Get(\"https://baidu.com\")\n\texpected := &tls.CertificateVerificationError{}\n\trequire.ErrorAs(t, err, &expected)\n\tclient.CloseIdleConnections()\n}\n\nfunc TestShadowTLSInbound(t *testing.T) {\n\tmethod := shadowaead_2022.List[0]\n\tpassword := mkBase64(t, 16)\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageShadowTLS,\n\t\tPorts:      []uint16{serverPort, otherPort},\n\t\tEntryPoint: \"shadow-tls\",\n\t\tCmd:        []string{\"--v3\", \"--threads\", \"1\", \"client\", \"--listen\", \"0.0.0.0:\" + F.ToString(otherPort), \"--server\", \"127.0.0.1:\" + F.ToString(serverPort), \"--sni\", \"google.com\", \"--password\", password},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowTLS,\n\t\t\t\tOptions: &option.ShadowTLSInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t\tDetour:     \"detour\",\n\t\t\t\t\t},\n\t\t\t\t\tHandshake: option.ShadowTLSHandshakeOptions{\n\t\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\t\tServer:     \"google.com\",\n\t\t\t\t\t\t\tServerPort: 443,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tVersion: 3,\n\t\t\t\t\tUsers: []option.ShadowTLSUser{\n\t\t\t\t\t\t{Password: password},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"detour\",\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"out\",\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: otherPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestTCP(t, clientPort, testPort)\n}\n\nfunc TestShadowTLSOutbound(t *testing.T) {\n\tmethod := shadowaead_2022.List[0]\n\tpassword := mkBase64(t, 16)\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageShadowTLS,\n\t\tPorts:      []uint16{serverPort, otherPort},\n\t\tEntryPoint: \"shadow-tls\",\n\t\tCmd:        []string{\"--v3\", \"--threads\", \"1\", \"server\", \"--listen\", \"0.0.0.0:\" + F.ToString(serverPort), \"--server\", \"127.0.0.1:\" + F.ToString(otherPort), \"--tls\", \"google.com:443\", \"--password\", \"hello\"},\n\t\tEnv:        []string{\"RUST_LOG=trace\"},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"detour\",\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: otherPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t\tDialerOptions: option.DialerOptions{\n\t\t\t\t\t\tDetour: \"detour\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowTLS,\n\t\t\t\tTag:  \"detour\",\n\t\t\t\tOptions: &option.ShadowTLSOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:    true,\n\t\t\t\t\t\t\tServerName: \"google.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tVersion:  3,\n\t\t\t\t\tPassword: \"hello\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t\tTag:  \"direct\",\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"detour\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"direct\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestTCP(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/socks_test.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tF \"github.com/sagernet/sing/common/format\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/protocol/socks\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSOCKSUDPTimeout(t *testing.T) {\n\tconst testTimeout = 2 * time.Second\n\tudpTimeout := option.UDPTimeoutCompat(testTimeout)\n\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeSOCKS,\n\t\t\t\tTag:  \"socks-in\",\n\t\t\t\tOptions: &option.SocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t\tUDPTimeout: udpTimeout,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t},\n\t})\n\n\ttestUDPSessionIdleTimeout(t, clientPort, testPort, testTimeout)\n}\n\nfunc TestMixedUDPTimeout(t *testing.T) {\n\tconst testTimeout = 2 * time.Second\n\tudpTimeout := option.UDPTimeoutCompat(testTimeout)\n\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t\tUDPTimeout: udpTimeout,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t},\n\t})\n\n\ttestUDPSessionIdleTimeout(t, clientPort, testPort, testTimeout)\n}\n\nfunc testUDPSessionIdleTimeout(t *testing.T, proxyPort uint16, echoPort uint16, expectedTimeout time.Duration) {\n\techoServer, err := listenPacket(\"udp\", \":\"+F.ToString(echoPort))\n\trequire.NoError(t, err)\n\tdefer echoServer.Close()\n\n\tgo func() {\n\t\tbuffer := make([]byte, 1024)\n\t\tfor {\n\t\t\tn, address, err := echoServer.ReadFrom(buffer)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_, _ = echoServer.WriteTo(buffer[:n], address)\n\t\t}\n\t}()\n\n\tdialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort(\"127.0.0.1\", proxyPort), socks.Version5, \"\", \"\")\n\n\tpacketConn, err := dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort(\"127.0.0.1\", echoPort))\n\trequire.NoError(t, err)\n\tdefer packetConn.Close()\n\n\tremoteAddress := &net.UDPAddr{IP: net.ParseIP(\"127.0.0.1\"), Port: int(echoPort)}\n\n\t_, err = packetConn.WriteTo([]byte(\"hello\"), remoteAddress)\n\trequire.NoError(t, err)\n\n\tbuffer := make([]byte, 1024)\n\tpacketConn.SetReadDeadline(time.Now().Add(5 * time.Second))\n\tn, _, err := packetConn.ReadFrom(buffer)\n\trequire.NoError(t, err, \"failed to receive echo response\")\n\trequire.Equal(t, \"hello\", string(buffer[:n]))\n\tt.Log(\"UDP echo successful, session established\")\n\n\tpacketConn.SetReadDeadline(time.Time{})\n\n\twaitTime := expectedTimeout + time.Second\n\tt.Logf(\"Waiting %v for UDP session to timeout...\", waitTime)\n\ttime.Sleep(waitTime)\n\n\t_, err = packetConn.WriteTo([]byte(\"after-timeout\"), remoteAddress)\n\tif err != nil {\n\t\tt.Logf(\"Write after timeout correctly failed: %v\", err)\n\t\treturn\n\t}\n\n\tpacketConn.SetReadDeadline(time.Now().Add(3 * time.Second))\n\tn, _, err = packetConn.ReadFrom(buffer)\n\tif err != nil {\n\t\tt.Logf(\"Read after timeout correctly failed: %v\", err)\n\t\treturn\n\t}\n\n\tt.Fatalf(\"UDP session should have timed out after %v, but received response: %s\",\n\t\texpectedTimeout, string(buffer[:n]))\n}\n"
  },
  {
    "path": "test/ss_plugin_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nfunc TestShadowsocksObfs(t *testing.T) {\n\tfor _, mode := range []string{\n\t\t\"http\", \"tls\",\n\t} {\n\t\tt.Run(\"obfs-local \"+mode, func(t *testing.T) {\n\t\t\ttestShadowsocksPlugin(t, \"obfs-local\", \"obfs=\"+mode, \"--plugin obfs-server --plugin-opts obfs=\"+mode)\n\t\t})\n\t}\n}\n\n// Since I can't test this on m1 mac (rosetta error: bss_size overflow), I don't care about it\nfunc _TestShadowsocksV2RayPlugin(t *testing.T) {\n\ttestShadowsocksPlugin(t, \"v2ray-plugin\", \"\", \"--plugin v2ray-plugin --plugin-opts=server\")\n}\n\nfunc testShadowsocksPlugin(t *testing.T, name string, opts string, args string) {\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageShadowsocksLegacy,\n\t\tPorts: []uint16{serverPort, testPort},\n\t\tEnv: []string{\n\t\t\t\"SS_MODULE=ss-server\",\n\t\t\t\"SS_CONFIG=-s 0.0.0.0 -u -p 10000 -m chacha20-ietf-poly1305 -k FzcLbKs2dY9mhL \" + args,\n\t\t},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:        \"chacha20-ietf-poly1305\",\n\t\t\t\t\tPassword:      \"FzcLbKs2dY9mhL\",\n\t\t\t\t\tPlugin:        name,\n\t\t\t\t\tPluginOptions: opts,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitSimple(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/tfo_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nfunc TestTCPSlowOpen(t *testing.T) {\n\tmethod := shadowaead.List[0]\n\tpassword := mkBase64(t, 16)\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tOptions: &option.ShadowsocksInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:      common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort:  serverPort,\n\t\t\t\t\t\tTCPFastOpen: true,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeShadowsocks,\n\t\t\t\tTag:  \"ss-out\",\n\t\t\t\tOptions: &option.ShadowsocksOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tDialerOptions: option.DialerOptions{\n\t\t\t\t\t\tTCPFastOpen: true,\n\t\t\t\t\t},\n\t\t\t\t\tMethod:   method,\n\t\t\t\t\tPassword: password,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"ss-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/tls_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nfunc TestUTLS(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tOptions: &option.TrojanInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TrojanUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tTag:  \"trojan-out\",\n\t\t\t\tOptions: &option.TrojanOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tUTLS: &option.OutboundUTLSOptions{\n\t\t\t\t\t\t\t\tEnabled:     true,\n\t\t\t\t\t\t\t\tFingerprint: \"chrome\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"trojan-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/trojan_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n)\n\nfunc TestTrojanOutbound(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageTrojan,\n\t\tPorts: []uint16{serverPort, testPort},\n\t\tBind: map[string]string{\n\t\t\t\"trojan.json\": \"/config/config.json\",\n\t\t\tcertPem:       \"/path/to/certificate.crt\",\n\t\t\tkeyPem:        \"/path/to/private.key\",\n\t\t},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tOptions: &option.TrojanOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestTrojanSelf(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tOptions: &option.TrojanInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TrojanUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tTag:  \"trojan-out\",\n\t\t\t\tOptions: &option.TrojanOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"trojan-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestTrojanPlainSelf(t *testing.T) {\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tOptions: &option.TrojanInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TrojanUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"sekai\",\n\t\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tTag:  \"trojan-out\",\n\t\t\t\tOptions: &option.TrojanOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tPassword: \"password\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"trojan-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/tuic_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/gofrs/uuid/v5\"\n)\n\nfunc TestTUICSelf(t *testing.T) {\n\tt.Run(\"self\", func(t *testing.T) {\n\t\ttestTUICSelf(t, false, false)\n\t})\n\tt.Run(\"self-udp-stream\", func(t *testing.T) {\n\t\ttestTUICSelf(t, true, false)\n\t})\n\tt.Run(\"self-early\", func(t *testing.T) {\n\t\ttestTUICSelf(t, false, true)\n\t})\n}\n\nfunc testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tvar udpRelayMode string\n\tif udpStream {\n\t\tudpRelayMode = \"quic\"\n\t}\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTUIC,\n\t\t\t\tOptions: &option.TUICInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TUICUser{{\n\t\t\t\t\t\tUUID: uuid.Nil.String(),\n\t\t\t\t\t}},\n\t\t\t\t\tZeroRTTHandshake: zeroRTTHandshake,\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTUIC,\n\t\t\t\tTag:  \"tuic-out\",\n\t\t\t\tOptions: &option.TUICOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID:             uuid.Nil.String(),\n\t\t\t\t\tUDPRelayMode:     udpRelayMode,\n\t\t\t\t\tZeroRTTHandshake: zeroRTTHandshake,\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"tuic-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitLargeUDP(t, clientPort, testPort)\n}\n\nfunc TestTUICInbound(t *testing.T) {\n\tcaPem, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeTUIC,\n\t\t\t\tOptions: &option.TUICInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TUICUser{{\n\t\t\t\t\t\tUUID:     \"FE35D05B-8803-45C4-BAE6-723AD2CD5D3D\",\n\t\t\t\t\t\tPassword: \"tuic\",\n\t\t\t\t\t}},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageTUICClient,\n\t\tPorts: []uint16{serverPort, clientPort},\n\t\tBind: map[string]string{\n\t\t\t\"tuic-client.json\": \"/etc/tuic/config.json\",\n\t\t\tcaPem:              \"/etc/tuic/ca.pem\",\n\t\t},\n\t})\n\ttestSuitLargeUDP(t, clientPort, testPort)\n}\n\nfunc TestTUICOutbound(t *testing.T) {\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage: ImageTUICServer,\n\t\tPorts: []uint16{testPort},\n\t\tBind: map[string]string{\n\t\t\t\"tuic-server.json\": \"/etc/tuic/config.json\",\n\t\t\tcertPem:            \"/etc/tuic/cert.pem\",\n\t\t\tkeyPem:             \"/etc/tuic/key.pem\",\n\t\t},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeTUIC,\n\t\t\t\tOptions: &option.TUICOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID:     \"FE35D05B-8803-45C4-BAE6-723AD2CD5D3D\",\n\t\t\t\t\tPassword: \"tuic\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitLargeUDP(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/v2ray_api_test.go",
    "content": "package main\n\n/*\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/experimental/v2rayapi\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestV2RayAPI(t *testing.T) {\n\ti := startInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t\tTag:  \"out\",\n\t\t\t},\n\t\t},\n\t\tExperimental: &option.ExperimentalOptions{\n\t\t\tV2RayAPI: &option.V2RayAPIOptions{\n\t\t\t\tListen: \"127.0.0.1:8080\",\n\t\t\t\tStats: &option.V2RayStatsServiceOptions{\n\t\t\t\t\tEnabled:   true,\n\t\t\t\t\tInbounds:  []string{\"in\"},\n\t\t\t\t\tOutbounds: []string{\"out\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n\tstatsService := i.Router().V2RayServer().StatsService()\n\trequire.NotNil(t, statsService)\n\tresponse, err := statsService.(v2rayapi.StatsServiceServer).QueryStats(context.Background(), &v2rayapi.QueryStatsRequest{Regexp: true, Patterns: []string{\".*\"}})\n\trequire.NoError(t, err)\n\tcount := response.Stat[0].Value\n\trequire.Equal(t, len(response.Stat), 4)\n\tfor _, stat := range response.Stat {\n\t\trequire.Equal(t, count, stat.Value)\n\t}\n}\n*/\n"
  },
  {
    "path": "test/v2ray_grpc_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"os\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/gofrs/uuid/v5\"\n\t\"github.com/spyzhov/ajson\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestV2RayGRPCInbound(t *testing.T) {\n\tt.Run(\"origin\", func(t *testing.T) {\n\t\ttestV2RayGRPCInbound(t, false)\n\t})\n\tt.Run(\"lite\", func(t *testing.T) {\n\t\ttestV2RayGRPCInbound(t, true)\n\t})\n}\n\nfunc testV2RayGRPCInbound(t *testing.T, forceLite bool) {\n\tuserId, err := uuid.DefaultGenerator.NewV4()\n\trequire.NoError(t, err)\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VMessUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"sekai\",\n\t\t\t\t\t\t\tUUID: userId.String(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTransport: &option.V2RayTransportOptions{\n\t\t\t\t\t\tType: C.V2RayTransportTypeGRPC,\n\t\t\t\t\t\tGRPCOptions: option.V2RayGRPCOptions{\n\t\t\t\t\t\t\tServiceName: \"TunService\",\n\t\t\t\t\t\t\tForceLite:   forceLite,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tcontent, err := os.ReadFile(\"config/vmess-grpc-client.json\")\n\trequire.NoError(t, err)\n\tconfig, err := ajson.Unmarshal(content)\n\trequire.NoError(t, err)\n\n\tconfig.MustKey(\"inbounds\").MustIndex(0).MustKey(\"port\").SetNumeric(float64(clientPort))\n\toutbound := config.MustKey(\"outbounds\").MustIndex(0).MustKey(\"settings\").MustKey(\"vnext\").MustIndex(0)\n\toutbound.MustKey(\"port\").SetNumeric(float64(serverPort))\n\tuser := outbound.MustKey(\"users\").MustIndex(0)\n\tuser.MustKey(\"id\").SetString(userId.String())\n\tcontent, err = ajson.Marshal(config)\n\trequire.NoError(t, err)\n\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageV2RayCore,\n\t\tPorts:      []uint16{serverPort, testPort},\n\t\tEntryPoint: \"v2ray\",\n\t\tCmd:        []string{\"run\"},\n\t\tStdin:      content,\n\t\tBind: map[string]string{\n\t\t\tcertPem: \"/path/to/certificate.crt\",\n\t\t\tkeyPem:  \"/path/to/private.key\",\n\t\t},\n\t})\n\n\ttestSuitSimple(t, clientPort, testPort)\n}\n\nfunc TestV2RayGRPCOutbound(t *testing.T) {\n\tt.Run(\"origin\", func(t *testing.T) {\n\t\ttestV2RayGRPCOutbound(t, false)\n\t})\n\tt.Run(\"lite\", func(t *testing.T) {\n\t\ttestV2RayGRPCOutbound(t, true)\n\t})\n}\n\nfunc testV2RayGRPCOutbound(t *testing.T, forceLite bool) {\n\tuserId, err := uuid.DefaultGenerator.NewV4()\n\trequire.NoError(t, err)\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\n\tcontent, err := os.ReadFile(\"config/vmess-grpc-server.json\")\n\trequire.NoError(t, err)\n\tconfig, err := ajson.Unmarshal(content)\n\trequire.NoError(t, err)\n\n\tinbound := config.MustKey(\"inbounds\").MustIndex(0)\n\tinbound.MustKey(\"port\").SetNumeric(float64(serverPort))\n\tinbound.MustKey(\"settings\").MustKey(\"clients\").MustIndex(0).MustKey(\"id\").SetString(userId.String())\n\tcontent, err = ajson.Marshal(config)\n\trequire.NoError(t, err)\n\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageV2RayCore,\n\t\tPorts:      []uint16{serverPort, testPort},\n\t\tEntryPoint: \"v2ray\",\n\t\tCmd:        []string{\"run\"},\n\t\tStdin:      content,\n\t\tEnv:        []string{\"V2RAY_VMESS_AEAD_FORCED=false\"},\n\t\tBind: map[string]string{\n\t\t\tcertPem: \"/path/to/certificate.crt\",\n\t\t\tkeyPem:  \"/path/to/private.key\",\n\t\t},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tTag:  \"vmess-out\",\n\t\t\t\tOptions: &option.VMessOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID:     userId.String(),\n\t\t\t\t\tSecurity: \"zero\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTransport: &option.V2RayTransportOptions{\n\t\t\t\t\t\tType: C.V2RayTransportTypeGRPC,\n\t\t\t\t\t\tGRPCOptions: option.V2RayGRPCOptions{\n\t\t\t\t\t\t\tServiceName: \"TunService\",\n\t\t\t\t\t\t\tForceLite:   forceLite,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestV2RayGRPCLite(t *testing.T) {\n\tt.Run(\"server\", func(t *testing.T) {\n\t\ttestV2RayTransportSelfWith(t, &option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeGRPC,\n\t\t\tGRPCOptions: option.V2RayGRPCOptions{\n\t\t\t\tServiceName: \"TunService\",\n\t\t\t\tForceLite:   true,\n\t\t\t},\n\t\t}, &option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeGRPC,\n\t\t\tGRPCOptions: option.V2RayGRPCOptions{\n\t\t\t\tServiceName: \"TunService\",\n\t\t\t},\n\t\t})\n\t})\n\tt.Run(\"client\", func(t *testing.T) {\n\t\ttestV2RayTransportSelfWith(t, &option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeGRPC,\n\t\t\tGRPCOptions: option.V2RayGRPCOptions{\n\t\t\t\tServiceName: \"TunService\",\n\t\t\t},\n\t\t}, &option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeGRPC,\n\t\t\tGRPCOptions: option.V2RayGRPCOptions{\n\t\t\t\tServiceName: \"TunService\",\n\t\t\t\tForceLite:   true,\n\t\t\t},\n\t\t})\n\t})\n\tt.Run(\"self\", func(t *testing.T) {\n\t\ttestV2RayTransportSelfWith(t, &option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeGRPC,\n\t\t\tGRPCOptions: option.V2RayGRPCOptions{\n\t\t\t\tServiceName: \"TunService\",\n\t\t\t\tForceLite:   true,\n\t\t\t},\n\t\t}, &option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeGRPC,\n\t\t\tGRPCOptions: option.V2RayGRPCOptions{\n\t\t\t\tServiceName: \"TunService\",\n\t\t\t\tForceLite:   true,\n\t\t\t},\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "test/v2ray_httpupgrade_test.go",
    "content": "package main\n\nimport (\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n)\n\nfunc TestV2RayHTTPUpgrade(t *testing.T) {\n\tt.Run(\"self\", func(t *testing.T) {\n\t\ttestV2RayTransportSelf(t, &option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeHTTPUpgrade,\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "test/v2ray_transport_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/gofrs/uuid/v5\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestV2RayHTTPSelf(t *testing.T) {\n\ttestV2RayTransportSelf(t, &option.V2RayTransportOptions{\n\t\tType: C.V2RayTransportTypeHTTP,\n\t\tHTTPOptions: option.V2RayHTTPOptions{\n\t\t\tMethod: \"POST\",\n\t\t},\n\t})\n}\n\nfunc TestV2RayHTTPPlainSelf(t *testing.T) {\n\ttestV2RayTransportNOTLSSelf(t, &option.V2RayTransportOptions{\n\t\tType: C.V2RayTransportTypeHTTP,\n\t})\n}\n\nfunc testV2RayTransportSelf(t *testing.T, transport *option.V2RayTransportOptions) {\n\ttestV2RayTransportSelfWith(t, transport, transport)\n}\n\nfunc testV2RayTransportSelfWith(t *testing.T, server, client *option.V2RayTransportOptions) {\n\tt.Run(\"vmess\", func(t *testing.T) {\n\t\ttestVMessTransportSelf(t, server, client)\n\t})\n\tt.Run(\"trojan\", func(t *testing.T) {\n\t\ttestTrojanTransportSelf(t, server, client)\n\t})\n}\n\nfunc testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, client *option.V2RayTransportOptions) {\n\tuser, err := uuid.DefaultGenerator.NewV4()\n\trequire.NoError(t, err)\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VMessUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"sekai\",\n\t\t\t\t\t\t\tUUID: user.String(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTransport: server,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tTag:  \"vmess-out\",\n\t\t\t\tOptions: &option.VMessOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID:     user.String(),\n\t\t\t\t\tSecurity: \"zero\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTransport: client,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"vmess-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, client *option.V2RayTransportOptions) {\n\tuser, err := uuid.DefaultGenerator.NewV4()\n\trequire.NoError(t, err)\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tOptions: &option.TrojanInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.TrojanUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"sekai\",\n\t\t\t\t\t\t\tPassword: user.String(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTransport: server,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeTrojan,\n\t\t\t\tTag:  \"vmess-out\",\n\t\t\t\tOptions: &option.TrojanOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tPassword: user.String(),\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTransport: client,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"vmess-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc TestVMessQUICSelf(t *testing.T) {\n\ttransport := &option.V2RayTransportOptions{\n\t\tType: C.V2RayTransportTypeQUIC,\n\t}\n\tuser, err := uuid.DefaultGenerator.NewV4()\n\trequire.NoError(t, err)\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VMessUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"sekai\",\n\t\t\t\t\t\t\tUUID: user.String(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTransport: transport,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tTag:  \"vmess-out\",\n\t\t\t\tOptions: &option.VMessOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID:     user.String(),\n\t\t\t\t\tSecurity: \"zero\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTransport: transport,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"vmess-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuitSimple1(t, clientPort, testPort)\n}\n\nfunc testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportOptions) {\n\tuser, err := uuid.DefaultGenerator.NewV4()\n\trequire.NoError(t, err)\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VMessUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"sekai\",\n\t\t\t\t\t\t\tUUID: user.String(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTransport: transport,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tTag:  \"vmess-out\",\n\t\t\t\tOptions: &option.VMessOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID:      user.String(),\n\t\t\t\t\tSecurity:  \"zero\",\n\t\t\t\t\tTransport: transport,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"vmess-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/v2ray_ws_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"os\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/gofrs/uuid/v5\"\n\t\"github.com/spyzhov/ajson\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestV2RayWebsocket(t *testing.T) {\n\tt.Run(\"self\", func(t *testing.T) {\n\t\ttestV2RayTransportSelf(t, &option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeWebsocket,\n\t\t})\n\t})\n\tt.Run(\"self-early-data\", func(t *testing.T) {\n\t\ttestV2RayTransportSelf(t, &option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeWebsocket,\n\t\t\tWebsocketOptions: option.V2RayWebsocketOptions{\n\t\t\t\tMaxEarlyData: 2048,\n\t\t\t},\n\t\t})\n\t})\n\tt.Run(\"self-xray-early-data\", func(t *testing.T) {\n\t\ttestV2RayTransportSelf(t, &option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeWebsocket,\n\t\t\tWebsocketOptions: option.V2RayWebsocketOptions{\n\t\t\t\tMaxEarlyData:        2048,\n\t\t\t\tEarlyDataHeaderName: \"Sec-WebSocket-Protocol\",\n\t\t\t},\n\t\t})\n\t})\n\tt.Run(\"inbound\", func(t *testing.T) {\n\t\ttestV2RayWebsocketInbound(t, 0, \"\")\n\t})\n\tt.Run(\"inbound-early-data\", func(t *testing.T) {\n\t\ttestV2RayWebsocketInbound(t, 2048, \"\")\n\t})\n\tt.Run(\"inbound-xray-early-data\", func(t *testing.T) {\n\t\ttestV2RayWebsocketInbound(t, 2048, \"Sec-WebSocket-Protocol\")\n\t})\n\tt.Run(\"outbound\", func(t *testing.T) {\n\t\ttestV2RayWebsocketOutbound(t, 0, \"\")\n\t})\n\tt.Run(\"outbound-early-data\", func(t *testing.T) {\n\t\ttestV2RayWebsocketOutbound(t, 2048, \"\")\n\t})\n\tt.Run(\"outbound-xray-early-data\", func(t *testing.T) {\n\t\ttestV2RayWebsocketOutbound(t, 2048, \"Sec-WebSocket-Protocol\")\n\t})\n}\n\nfunc testV2RayWebsocketInbound(t *testing.T, maxEarlyData uint32, earlyDataHeaderName string) {\n\tuserId, err := uuid.DefaultGenerator.NewV4()\n\trequire.NoError(t, err)\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VMessUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"sekai\",\n\t\t\t\t\t\t\tUUID: userId.String(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t\tKeyPath:         keyPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTransport: &option.V2RayTransportOptions{\n\t\t\t\t\t\tType: C.V2RayTransportTypeWebsocket,\n\t\t\t\t\t\tWebsocketOptions: option.V2RayWebsocketOptions{\n\t\t\t\t\t\t\tMaxEarlyData:        maxEarlyData,\n\t\t\t\t\t\t\tEarlyDataHeaderName: earlyDataHeaderName,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tcontent, err := os.ReadFile(\"config/vmess-ws-client.json\")\n\trequire.NoError(t, err)\n\tconfig, err := ajson.Unmarshal(content)\n\trequire.NoError(t, err)\n\n\tconfig.MustKey(\"inbounds\").MustIndex(0).MustKey(\"port\").SetNumeric(float64(clientPort))\n\toutbound := config.MustKey(\"outbounds\").MustIndex(0)\n\tsettings := outbound.MustKey(\"settings\").MustKey(\"vnext\").MustIndex(0)\n\tsettings.MustKey(\"port\").SetNumeric(float64(serverPort))\n\tuser := settings.MustKey(\"users\").MustIndex(0)\n\tuser.MustKey(\"id\").SetString(userId.String())\n\twsSettings := outbound.MustKey(\"streamSettings\").MustKey(\"wsSettings\")\n\twsSettings.MustKey(\"maxEarlyData\").SetNumeric(float64(maxEarlyData))\n\twsSettings.MustKey(\"earlyDataHeaderName\").SetString(earlyDataHeaderName)\n\tcontent, err = ajson.Marshal(config)\n\trequire.NoError(t, err)\n\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageV2RayCore,\n\t\tPorts:      []uint16{serverPort, testPort},\n\t\tEntryPoint: \"v2ray\",\n\t\tCmd:        []string{\"run\"},\n\t\tStdin:      content,\n\t\tBind: map[string]string{\n\t\t\tcertPem: \"/path/to/certificate.crt\",\n\t\t\tkeyPem:  \"/path/to/private.key\",\n\t\t},\n\t})\n\n\ttestSuitSimple(t, clientPort, testPort)\n}\n\nfunc testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHeaderName string) {\n\tuserId, err := uuid.DefaultGenerator.NewV4()\n\trequire.NoError(t, err)\n\t_, certPem, keyPem := createSelfSignedCertificate(t, \"example.org\")\n\n\tcontent, err := os.ReadFile(\"config/vmess-ws-server.json\")\n\trequire.NoError(t, err)\n\tconfig, err := ajson.Unmarshal(content)\n\trequire.NoError(t, err)\n\n\tinbound := config.MustKey(\"inbounds\").MustIndex(0)\n\tinbound.MustKey(\"port\").SetNumeric(float64(serverPort))\n\tinbound.MustKey(\"settings\").MustKey(\"clients\").MustIndex(0).MustKey(\"id\").SetString(userId.String())\n\twsSettings := inbound.MustKey(\"streamSettings\").MustKey(\"wsSettings\")\n\twsSettings.MustKey(\"maxEarlyData\").SetNumeric(float64(maxEarlyData))\n\twsSettings.MustKey(\"earlyDataHeaderName\").SetString(earlyDataHeaderName)\n\tcontent, err = ajson.Marshal(config)\n\trequire.NoError(t, err)\n\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageV2RayCore,\n\t\tPorts:      []uint16{serverPort, testPort},\n\t\tEntryPoint: \"v2ray\",\n\t\tCmd:        []string{\"run\"},\n\t\tStdin:      content,\n\t\tEnv:        []string{\"V2RAY_VMESS_AEAD_FORCED=false\"},\n\t\tBind: map[string]string{\n\t\t\tcertPem: \"/path/to/certificate.crt\",\n\t\t\tkeyPem:  \"/path/to/private.key\",\n\t\t},\n\t})\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tTag:  \"vmess-out\",\n\t\t\t\tOptions: &option.VMessOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUUID:     userId.String(),\n\t\t\t\t\tSecurity: \"zero\",\n\t\t\t\t\tOutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{\n\t\t\t\t\t\tTLS: &option.OutboundTLSOptions{\n\t\t\t\t\t\t\tEnabled:         true,\n\t\t\t\t\t\t\tServerName:      \"example.org\",\n\t\t\t\t\t\t\tCertificatePath: certPem,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTransport: &option.V2RayTransportOptions{\n\t\t\t\t\t\tType: C.V2RayTransportTypeWebsocket,\n\t\t\t\t\t\tWebsocketOptions: option.V2RayWebsocketOptions{\n\t\t\t\t\t\t\tMaxEarlyData:        maxEarlyData,\n\t\t\t\t\t\t\tEarlyDataHeaderName: earlyDataHeaderName,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/vmess_test.go",
    "content": "package main\n\nimport (\n\t\"net/netip\"\n\t\"os\"\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\n\t\"github.com/gofrs/uuid/v5\"\n\t\"github.com/spyzhov/ajson\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc newUUID() uuid.UUID {\n\tuser, _ := uuid.DefaultGenerator.NewV4()\n\treturn user\n}\n\nfunc TestVMessAuto(t *testing.T) {\n\tsecurity := \"auto\"\n\tt.Run(\"self\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 0, false, false, false)\n\t})\n\tt.Run(\"packetaddr\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 0, false, false, true)\n\t})\n\tt.Run(\"inbound\", func(t *testing.T) {\n\t\ttestVMessInboundWithV2Ray(t, security, 0, false)\n\t})\n\tt.Run(\"outbound\", func(t *testing.T) {\n\t\ttestVMessOutboundWithV2Ray(t, security, false, false, 0)\n\t})\n}\n\nfunc TestVMess(t *testing.T) {\n\tfor _, security := range []string{\n\t\t\"zero\",\n\t} {\n\t\tt.Run(security, func(t *testing.T) {\n\t\t\ttestVMess0(t, security)\n\t\t})\n\t}\n\tfor _, security := range []string{\n\t\t\"none\",\n\t} {\n\t\tt.Run(security, func(t *testing.T) {\n\t\t\ttestVMess1(t, security)\n\t\t})\n\t}\n\tfor _, security := range []string{\n\t\t\"aes-128-gcm\", \"chacha20-poly1305\", \"aes-128-cfb\",\n\t} {\n\t\tt.Run(security, func(t *testing.T) {\n\t\t\ttestVMess2(t, security)\n\t\t})\n\t}\n}\n\nfunc testVMess0(t *testing.T, security string) {\n\tt.Run(\"self\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 0, false, false, false)\n\t})\n\tt.Run(\"self-legacy\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 1, false, false, false)\n\t})\n\tt.Run(\"packetaddr\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 0, false, false, true)\n\t})\n\tt.Run(\"outbound\", func(t *testing.T) {\n\t\ttestVMessOutboundWithV2Ray(t, security, false, false, 0)\n\t})\n\tt.Run(\"outbound-legacy\", func(t *testing.T) {\n\t\ttestVMessOutboundWithV2Ray(t, security, false, false, 1)\n\t})\n}\n\nfunc testVMess1(t *testing.T, security string) {\n\tt.Run(\"self\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 0, false, false, false)\n\t})\n\tt.Run(\"self-legacy\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 1, false, false, false)\n\t})\n\tt.Run(\"packetaddr\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 0, false, false, true)\n\t})\n\tt.Run(\"inbound\", func(t *testing.T) {\n\t\ttestVMessInboundWithV2Ray(t, security, 0, false)\n\t})\n\tt.Run(\"outbound\", func(t *testing.T) {\n\t\ttestVMessOutboundWithV2Ray(t, security, false, false, 0)\n\t})\n\tt.Run(\"outbound-legacy\", func(t *testing.T) {\n\t\ttestVMessOutboundWithV2Ray(t, security, false, false, 1)\n\t})\n}\n\nfunc testVMess2(t *testing.T, security string) {\n\tt.Run(\"self\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 0, false, false, false)\n\t})\n\tt.Run(\"self-padding\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 0, true, false, false)\n\t})\n\tt.Run(\"self-authid\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 0, false, true, false)\n\t})\n\tt.Run(\"self-padding-authid\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 0, true, true, false)\n\t})\n\tt.Run(\"self-legacy\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 1, false, false, false)\n\t})\n\tt.Run(\"self-legacy-padding\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 1, true, false, false)\n\t})\n\tt.Run(\"packetaddr\", func(t *testing.T) {\n\t\ttestVMessSelf(t, security, 0, false, false, true)\n\t})\n\tt.Run(\"inbound\", func(t *testing.T) {\n\t\ttestVMessInboundWithV2Ray(t, security, 0, false)\n\t})\n\tt.Run(\"inbound-authid\", func(t *testing.T) {\n\t\ttestVMessInboundWithV2Ray(t, security, 0, true)\n\t})\n\tt.Run(\"inbound-legacy\", func(t *testing.T) {\n\t\ttestVMessInboundWithV2Ray(t, security, 64, false)\n\t})\n\tt.Run(\"outbound\", func(t *testing.T) {\n\t\ttestVMessOutboundWithV2Ray(t, security, false, false, 0)\n\t})\n\tt.Run(\"outbound-padding\", func(t *testing.T) {\n\t\ttestVMessOutboundWithV2Ray(t, security, true, false, 0)\n\t})\n\tt.Run(\"outbound-authid\", func(t *testing.T) {\n\t\ttestVMessOutboundWithV2Ray(t, security, false, true, 0)\n\t})\n\tt.Run(\"outbound-padding-authid\", func(t *testing.T) {\n\t\ttestVMessOutboundWithV2Ray(t, security, true, true, 0)\n\t})\n\tt.Run(\"outbound-legacy\", func(t *testing.T) {\n\t\ttestVMessOutboundWithV2Ray(t, security, false, false, 1)\n\t})\n\tt.Run(\"outbound-legacy-padding\", func(t *testing.T) {\n\t\ttestVMessOutboundWithV2Ray(t, security, true, false, 1)\n\t})\n}\n\nfunc testVMessInboundWithV2Ray(t *testing.T, security string, alterId int, authenticatedLength bool) {\n\tuserId := newUUID()\n\tcontent, err := os.ReadFile(\"config/vmess-client.json\")\n\trequire.NoError(t, err)\n\tconfig, err := ajson.Unmarshal(content)\n\trequire.NoError(t, err)\n\n\tconfig.MustKey(\"inbounds\").MustIndex(0).MustKey(\"port\").SetNumeric(float64(clientPort))\n\toutbound := config.MustKey(\"outbounds\").MustIndex(0).MustKey(\"settings\").MustKey(\"vnext\").MustIndex(0)\n\toutbound.MustKey(\"port\").SetNumeric(float64(serverPort))\n\tuser := outbound.MustKey(\"users\").MustIndex(0)\n\tuser.MustKey(\"id\").SetString(userId.String())\n\tuser.MustKey(\"alterId\").SetNumeric(float64(alterId))\n\tuser.MustKey(\"security\").SetString(security)\n\tvar experiments string\n\tif authenticatedLength {\n\t\texperiments += \"AuthenticatedLength\"\n\t}\n\tuser.MustKey(\"experiments\").SetString(experiments)\n\n\tcontent, err = ajson.Marshal(config)\n\trequire.NoError(t, err)\n\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageV2RayCore,\n\t\tPorts:      []uint16{serverPort, testPort},\n\t\tEntryPoint: \"v2ray\",\n\t\tCmd:        []string{\"run\"},\n\t\tStdin:      content,\n\t\tEnv:        []string{\"V2RAY_VMESS_AEAD_FORCED=false\"},\n\t})\n\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VMessUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:    \"sekai\",\n\t\t\t\t\t\t\tUUID:    userId.String(),\n\t\t\t\t\t\t\tAlterId: alterId,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\ttestSuitSimple(t, clientPort, testPort)\n}\n\nfunc testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding bool, authenticatedLength bool, alterId int) {\n\tuser := newUUID()\n\tcontent, err := os.ReadFile(\"config/vmess-server.json\")\n\trequire.NoError(t, err)\n\tconfig, err := ajson.Unmarshal(content)\n\trequire.NoError(t, err)\n\n\tinbound := config.MustKey(\"inbounds\").MustIndex(0)\n\tinbound.MustKey(\"port\").SetNumeric(float64(serverPort))\n\tinbound.MustKey(\"settings\").MustKey(\"clients\").MustIndex(0).MustKey(\"id\").SetString(user.String())\n\tinbound.MustKey(\"settings\").MustKey(\"clients\").MustIndex(0).MustKey(\"alterId\").SetNumeric(float64(alterId))\n\n\tcontent, err = ajson.Marshal(config)\n\trequire.NoError(t, err)\n\n\tstartDockerContainer(t, DockerOptions{\n\t\tImage:      ImageV2RayCore,\n\t\tPorts:      []uint16{serverPort, testPort},\n\t\tEntryPoint: \"v2ray\",\n\t\tCmd:        []string{\"run\"},\n\t\tStdin:      content,\n\t\tEnv:        []string{\"V2RAY_VMESS_AEAD_FORCED=false\"},\n\t})\n\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tSecurity:            security,\n\t\t\t\t\tUUID:                user.String(),\n\t\t\t\t\tGlobalPadding:       globalPadding,\n\t\t\t\t\tAuthenticatedLength: authenticatedLength,\n\t\t\t\t\tAlterId:             alterId,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n\nfunc testVMessSelf(t *testing.T, security string, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) {\n\tuser := newUUID()\n\tstartInstance(t, option.Options{\n\t\tInbounds: []option.Inbound{\n\t\t\t{\n\t\t\t\tType: C.TypeMixed,\n\t\t\t\tTag:  \"mixed-in\",\n\t\t\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: clientPort,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tOptions: &option.VMessInboundOptions{\n\t\t\t\t\tListenOptions: option.ListenOptions{\n\t\t\t\t\t\tListen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),\n\t\t\t\t\t\tListenPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tUsers: []option.VMessUser{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:    \"sekai\",\n\t\t\t\t\t\t\tUUID:    user.String(),\n\t\t\t\t\t\t\tAlterId: alterId,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutbounds: []option.Outbound{\n\t\t\t{\n\t\t\t\tType: C.TypeDirect,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: C.TypeVMess,\n\t\t\t\tTag:  \"vmess-out\",\n\t\t\t\tOptions: &option.VMessOutboundOptions{\n\t\t\t\t\tServerOptions: option.ServerOptions{\n\t\t\t\t\t\tServer:     \"127.0.0.1\",\n\t\t\t\t\t\tServerPort: serverPort,\n\t\t\t\t\t},\n\t\t\t\t\tSecurity:            security,\n\t\t\t\t\tUUID:                user.String(),\n\t\t\t\t\tAlterId:             alterId,\n\t\t\t\t\tGlobalPadding:       globalPadding,\n\t\t\t\t\tAuthenticatedLength: authenticatedLength,\n\t\t\t\t\tPacketEncoding:      \"packetaddr\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoute: &option.RouteOptions{\n\t\t\tRules: []option.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: C.RuleTypeDefault,\n\t\t\t\t\tDefaultOptions: option.DefaultRule{\n\t\t\t\t\t\tRawDefaultRule: option.RawDefaultRule{\n\t\t\t\t\t\t\tInbound: []string{\"mixed-in\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRuleAction: option.RuleAction{\n\t\t\t\t\t\t\tAction: C.RuleActionTypeRoute,\n\t\t\t\t\t\t\tRouteOptions: option.RouteActionOptions{\n\t\t\t\t\t\t\t\tOutbound: \"vmess-out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ttestSuit(t, clientPort, testPort)\n}\n"
  },
  {
    "path": "test/wrapper_test.go",
    "content": "package main\n\nimport (\n\t\"testing\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOptionsWrapper(t *testing.T) {\n\tinbound := option.Inbound{\n\t\tType: C.TypeHTTP,\n\t\tOptions: &option.HTTPMixedInboundOptions{\n\t\t\tInboundTLSOptionsContainer: option.InboundTLSOptionsContainer{\n\t\t\t\tTLS: &option.InboundTLSOptions{\n\t\t\t\t\tEnabled: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\ttlsOptionsWrapper, loaded := inbound.Options.(option.InboundTLSOptionsWrapper)\n\trequire.True(t, loaded, \"find inbound tls options\")\n\ttlsOptions := tlsOptionsWrapper.TakeInboundTLSOptions()\n\trequire.NotNil(t, tlsOptions, \"find inbound tls options\")\n\ttlsOptions.Enabled = false\n\ttlsOptionsWrapper.ReplaceInboundTLSOptions(tlsOptions)\n\trequire.False(t, inbound.Options.(*option.HTTPMixedInboundOptions).TLS.Enabled, \"replace tls enabled\")\n}\n"
  },
  {
    "path": "transport/simple-obfs/README.md",
    "content": "# simple-obfs\n\nmod from https://github.com/Dreamacro/clash/transport/simple-obfs\nversion: 1.11.8"
  },
  {
    "path": "transport/simple-obfs/http.go",
    "content": "package obfs\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\n\tB \"github.com/sagernet/sing/common/buf\"\n)\n\n// HTTPObfs is shadowsocks http simple-obfs implementation\ntype HTTPObfs struct {\n\tnet.Conn\n\thost          string\n\tport          string\n\tbuf           []byte\n\toffset        int\n\tfirstRequest  bool\n\tfirstResponse bool\n}\n\nfunc (ho *HTTPObfs) Read(b []byte) (int, error) {\n\tif ho.buf != nil {\n\t\tn := copy(b, ho.buf[ho.offset:])\n\t\tho.offset += n\n\t\tif ho.offset == len(ho.buf) {\n\t\t\tB.Put(ho.buf)\n\t\t\tho.buf = nil\n\t\t}\n\t\treturn n, nil\n\t}\n\n\tif ho.firstResponse {\n\t\tbuf := B.Get(B.BufferSize)\n\t\tn, err := ho.Conn.Read(buf)\n\t\tif err != nil {\n\t\t\tB.Put(buf)\n\t\t\treturn 0, err\n\t\t}\n\t\tidx := bytes.Index(buf[:n], []byte(\"\\r\\n\\r\\n\"))\n\t\tif idx == -1 {\n\t\t\tB.Put(buf)\n\t\t\treturn 0, io.EOF\n\t\t}\n\t\tho.firstResponse = false\n\t\tlength := n - (idx + 4)\n\t\tn = copy(b, buf[idx+4:n])\n\t\tif length > n {\n\t\t\tho.buf = buf[:idx+4+length]\n\t\t\tho.offset = idx + 4 + n\n\t\t} else {\n\t\t\tB.Put(buf)\n\t\t}\n\t\treturn n, nil\n\t}\n\treturn ho.Conn.Read(b)\n}\n\nfunc (ho *HTTPObfs) Write(b []byte) (int, error) {\n\tif ho.firstRequest {\n\t\trandBytes := make([]byte, 16)\n\t\trand.Read(randBytes)\n\t\treq, _ := http.NewRequest(\"GET\", fmt.Sprintf(\"http://%s/\", ho.host), bytes.NewBuffer(b[:]))\n\t\treq.Header.Set(\"User-Agent\", fmt.Sprintf(\"curl/7.%d.%d\", rand.Int()%54, rand.Int()%2))\n\t\treq.Header.Set(\"Upgrade\", \"websocket\")\n\t\treq.Header.Set(\"Connection\", \"Upgrade\")\n\t\treq.Host = ho.host\n\t\tif ho.port != \"80\" {\n\t\t\treq.Host = fmt.Sprintf(\"%s:%s\", ho.host, ho.port)\n\t\t}\n\t\treq.Header.Set(\"Sec-WebSocket-Key\", base64.URLEncoding.EncodeToString(randBytes))\n\t\treq.ContentLength = int64(len(b))\n\t\terr := req.Write(ho.Conn)\n\t\tho.firstRequest = false\n\t\treturn len(b), err\n\t}\n\n\treturn ho.Conn.Write(b)\n}\n\nfunc (ho *HTTPObfs) Upstream() any {\n\treturn ho.Conn\n}\n\n// NewHTTPObfs return a HTTPObfs\nfunc NewHTTPObfs(conn net.Conn, host string, port string) net.Conn {\n\treturn &HTTPObfs{\n\t\tConn:          conn,\n\t\tfirstRequest:  true,\n\t\tfirstResponse: true,\n\t\thost:          host,\n\t\tport:          port,\n\t}\n}\n"
  },
  {
    "path": "transport/simple-obfs/tls.go",
    "content": "package obfs\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"time\"\n\n\tB \"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/random\"\n)\n\nfunc init() {\n\trandom.InitializeSeed()\n}\n\nconst (\n\tchunkSize = 1 << 14 // 2 ** 14 == 16 * 1024\n)\n\n// TLSObfs is shadowsocks tls simple-obfs implementation\ntype TLSObfs struct {\n\tnet.Conn\n\tserver        string\n\tremain        int\n\tfirstRequest  bool\n\tfirstResponse bool\n}\n\nfunc (to *TLSObfs) read(b []byte, discardN int) (int, error) {\n\tbuf := B.Get(discardN)\n\t_, err := io.ReadFull(to.Conn, buf)\n\tB.Put(buf)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tsizeBuf := make([]byte, 2)\n\t_, err = io.ReadFull(to.Conn, sizeBuf)\n\tif err != nil {\n\t\treturn 0, nil\n\t}\n\n\tlength := int(binary.BigEndian.Uint16(sizeBuf))\n\tif length > len(b) {\n\t\tn, err := to.Conn.Read(b)\n\t\tif err != nil {\n\t\t\treturn n, err\n\t\t}\n\t\tto.remain = length - n\n\t\treturn n, nil\n\t}\n\n\treturn io.ReadFull(to.Conn, b[:length])\n}\n\nfunc (to *TLSObfs) Read(b []byte) (int, error) {\n\tif to.remain > 0 {\n\t\tlength := to.remain\n\t\tif length > len(b) {\n\t\t\tlength = len(b)\n\t\t}\n\n\t\tn, err := io.ReadFull(to.Conn, b[:length])\n\t\tto.remain -= n\n\t\treturn n, err\n\t}\n\n\tif to.firstResponse {\n\t\t// type + ver + lensize + 91 = 96\n\t\t// type + ver + lensize + 1 = 6\n\t\t// type + ver = 3\n\t\tto.firstResponse = false\n\t\treturn to.read(b, 105)\n\t}\n\n\t// type + ver = 3\n\treturn to.read(b, 3)\n}\n\nfunc (to *TLSObfs) Write(b []byte) (int, error) {\n\tlength := len(b)\n\tfor i := 0; i < length; i += chunkSize {\n\t\tend := i + chunkSize\n\t\tif end > length {\n\t\t\tend = length\n\t\t}\n\n\t\tn, err := to.write(b[i:end])\n\t\tif err != nil {\n\t\t\treturn n, err\n\t\t}\n\t}\n\treturn length, nil\n}\n\nfunc (to *TLSObfs) write(b []byte) (int, error) {\n\tif to.firstRequest {\n\t\thelloMsg := makeClientHelloMsg(b, to.server)\n\t\t_, err := to.Conn.Write(helloMsg)\n\t\tto.firstRequest = false\n\t\treturn len(b), err\n\t}\n\n\tbuf := B.NewSize(5 + len(b))\n\tdefer buf.Release()\n\tbuf.Write([]byte{0x17, 0x03, 0x03})\n\tbinary.Write(buf, binary.BigEndian, uint16(len(b)))\n\tbuf.Write(b)\n\t_, err := to.Conn.Write(buf.Bytes())\n\treturn len(b), err\n}\n\nfunc (to *TLSObfs) Upstream() any {\n\treturn to.Conn\n}\n\n// NewTLSObfs return a SimpleObfs\nfunc NewTLSObfs(conn net.Conn, server string) net.Conn {\n\treturn &TLSObfs{\n\t\tConn:          conn,\n\t\tserver:        server,\n\t\tfirstRequest:  true,\n\t\tfirstResponse: true,\n\t}\n}\n\nfunc makeClientHelloMsg(data []byte, server string) []byte {\n\trandom := make([]byte, 28)\n\tsessionID := make([]byte, 32)\n\trand.Read(random)\n\trand.Read(sessionID)\n\n\tbuf := &bytes.Buffer{}\n\n\t// handshake, TLS 1.0 version, length\n\tbuf.WriteByte(22)\n\tbuf.Write([]byte{0x03, 0x01})\n\tlength := uint16(212 + len(data) + len(server))\n\tbuf.WriteByte(byte(length >> 8))\n\tbuf.WriteByte(byte(length & 0xff))\n\n\t// clientHello, length, TLS 1.2 version\n\tbuf.WriteByte(1)\n\tbuf.WriteByte(0)\n\tbinary.Write(buf, binary.BigEndian, uint16(208+len(data)+len(server)))\n\tbuf.Write([]byte{0x03, 0x03})\n\n\t// random with timestamp, sid len, sid\n\tbinary.Write(buf, binary.BigEndian, uint32(time.Now().Unix()))\n\tbuf.Write(random)\n\tbuf.WriteByte(32)\n\tbuf.Write(sessionID)\n\n\t// cipher suites\n\tbuf.Write([]byte{0x00, 0x38})\n\tbuf.Write([]byte{\n\t\t0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f,\n\t\t0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a,\n\t\t0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d,\n\t\t0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff,\n\t})\n\n\t// compression\n\tbuf.Write([]byte{0x01, 0x00})\n\n\t// extension length\n\tbinary.Write(buf, binary.BigEndian, uint16(79+len(data)+len(server)))\n\n\t// session ticket\n\tbuf.Write([]byte{0x00, 0x23})\n\tbinary.Write(buf, binary.BigEndian, uint16(len(data)))\n\tbuf.Write(data)\n\n\t// server name\n\tbuf.Write([]byte{0x00, 0x00})\n\tbinary.Write(buf, binary.BigEndian, uint16(len(server)+5))\n\tbinary.Write(buf, binary.BigEndian, uint16(len(server)+3))\n\tbuf.WriteByte(0)\n\tbinary.Write(buf, binary.BigEndian, uint16(len(server)))\n\tbuf.Write([]byte(server))\n\n\t// ec_point\n\tbuf.Write([]byte{0x00, 0x0b, 0x00, 0x04, 0x03, 0x01, 0x00, 0x02})\n\n\t// groups\n\tbuf.Write([]byte{0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18})\n\n\t// signature\n\tbuf.Write([]byte{\n\t\t0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05,\n\t\t0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01,\n\t\t0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03,\n\t})\n\n\t// encrypt then mac\n\tbuf.Write([]byte{0x00, 0x16, 0x00, 0x00})\n\n\t// extended master secret\n\tbuf.Write([]byte{0x00, 0x17, 0x00, 0x00})\n\n\treturn buf.Bytes()\n}\n"
  },
  {
    "path": "transport/sip003/args.go",
    "content": "package sip003\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n)\n\n// mod from https://github.com/shadowsocks/v2ray-plugin/blob/master/args.go\n\n// Args maps a string key to a list of values. It is similar to url.Values.\ntype Args map[string][]string\n\n// Get the first value associated with the given key. If there are any values\n// associated with the key, the value return has the value and ok is set to\n// true. If there are no values for the given key, value is \"\" and ok is false.\n// If you need access to multiple values, use the map directly.\nfunc (args Args) Get(key string) (value string, ok bool) {\n\tif args == nil {\n\t\treturn \"\", false\n\t}\n\tvals, ok := args[key]\n\tif !ok || len(vals) == 0 {\n\t\treturn \"\", false\n\t}\n\treturn vals[0], true\n}\n\n// Add Append value to the list of values for key.\nfunc (args Args) Add(key, value string) {\n\targs[key] = append(args[key], value)\n}\n\n// Return the index of the next unescaped byte in s that is in the term set, or\n// else the length of the string if no terminators appear. Additionally return\n// the unescaped string up to the returned index.\nfunc indexUnescaped(s string, term []byte) (int, string, error) {\n\tvar i int\n\tunesc := make([]byte, 0)\n\tfor i = 0; i < len(s); i++ {\n\t\tb := s[i]\n\t\t// A terminator byte?\n\t\tif bytes.IndexByte(term, b) != -1 {\n\t\t\tbreak\n\t\t}\n\t\tif b == '\\\\' {\n\t\t\ti++\n\t\t\tif i >= len(s) {\n\t\t\t\treturn 0, \"\", fmt.Errorf(\"nothing following final escape in %q\", s)\n\t\t\t}\n\t\t\tb = s[i]\n\t\t}\n\t\tunesc = append(unesc, b)\n\t}\n\treturn i, string(unesc), nil\n}\n\n// ParsePluginOptions Parse a name–value mapping as from SS_PLUGIN_OPTIONS.\n//\n// \"<value> is a k=v string value with options that are to be passed to the\n// transport. semicolons, equal signs and backslashes must be escaped\n// with a backslash.\"\n// Example: secret=nou;cache=/tmp/cache;secret=yes\nfunc ParsePluginOptions(s string) (opts Args, err error) {\n\topts = make(Args)\n\tif len(s) == 0 {\n\t\treturn\n\t}\n\ti := 0\n\tfor {\n\t\tvar key, value string\n\t\tvar offset, begin int\n\n\t\tif i >= len(s) {\n\t\t\tbreak\n\t\t}\n\t\tbegin = i\n\t\t// Read the key.\n\t\toffset, key, err = indexUnescaped(s[i:], []byte{'=', ';'})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif len(key) == 0 {\n\t\t\terr = fmt.Errorf(\"empty key in %q\", s[begin:i])\n\t\t\treturn\n\t\t}\n\t\ti += offset\n\t\t// End of string or no equals sign?\n\t\tif i >= len(s) || s[i] != '=' {\n\t\t\topts.Add(key, \"1\")\n\t\t\t// Skip the semicolon.\n\t\t\ti++\n\t\t\tcontinue\n\t\t}\n\t\t// Skip the equals sign.\n\t\ti++\n\t\t// Read the value.\n\t\toffset, value, err = indexUnescaped(s[i:], []byte{';'})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\ti += offset\n\t\topts.Add(key, value)\n\t\t// Skip the semicolon.\n\t\ti++\n\t}\n\treturn opts, nil\n}\n\n// Escape backslashes and all the bytes that are in set.\nfunc backslashEscape(s string, set []byte) string {\n\tvar buf bytes.Buffer\n\tfor _, b := range []byte(s) {\n\t\tif b == '\\\\' || bytes.IndexByte(set, b) != -1 {\n\t\t\tbuf.WriteByte('\\\\')\n\t\t}\n\t\tbuf.WriteByte(b)\n\t}\n\treturn buf.String()\n}\n"
  },
  {
    "path": "transport/sip003/obfs.go",
    "content": "package sip003\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/transport/simple-obfs\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nvar _ Plugin = (*ObfsLocal)(nil)\n\nfunc init() {\n\tRegisterPlugin(\"obfs-local\", newObfsLocal)\n}\n\nfunc newObfsLocal(ctx context.Context, pluginOpts Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {\n\tplugin := &ObfsLocal{\n\t\tdialer:     dialer,\n\t\tserverAddr: serverAddr,\n\t}\n\tmode := \"http\"\n\tif obfsMode, loaded := pluginOpts.Get(\"obfs\"); loaded {\n\t\tmode = obfsMode\n\t}\n\tif obfsHost, loaded := pluginOpts.Get(\"obfs-host\"); loaded {\n\t\tplugin.host = obfsHost\n\t}\n\tswitch mode {\n\tcase \"http\":\n\tcase \"tls\":\n\t\tplugin.tls = true\n\tdefault:\n\t\treturn nil, E.New(\"unknown obfs mode \", mode)\n\t}\n\tplugin.port = F.ToString(serverAddr.Port)\n\treturn plugin, nil\n}\n\ntype ObfsLocal struct {\n\tdialer     N.Dialer\n\tserverAddr M.Socksaddr\n\ttls        bool\n\thost       string\n\tport       string\n}\n\nfunc (o *ObfsLocal) DialContext(ctx context.Context) (net.Conn, error) {\n\tconn, err := o.dialer.DialContext(ctx, N.NetworkTCP, o.serverAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !o.tls {\n\t\treturn obfs.NewHTTPObfs(conn, o.host, o.port), nil\n\t} else {\n\t\treturn obfs.NewTLSObfs(conn, o.host), nil\n\t}\n}\n"
  },
  {
    "path": "transport/sip003/plugin.go",
    "content": "package sip003\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype PluginConstructor func(ctx context.Context, pluginArgs Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error)\n\ntype Plugin interface {\n\tDialContext(ctx context.Context) (net.Conn, error)\n}\n\nvar plugins map[string]PluginConstructor\n\nfunc RegisterPlugin(name string, constructor PluginConstructor) {\n\tif plugins == nil {\n\t\tplugins = make(map[string]PluginConstructor)\n\t}\n\tplugins[name] = constructor\n}\n\nfunc CreatePlugin(ctx context.Context, name string, pluginArgs string, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {\n\tpluginOptions, err := ParsePluginOptions(pluginArgs)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"parse plugin_opts\")\n\t}\n\tconstructor, loaded := plugins[name]\n\tif !loaded {\n\t\treturn nil, E.New(\"plugin not found: \", name)\n\t}\n\treturn constructor(ctx, pluginOptions, router, dialer, serverAddr)\n}\n"
  },
  {
    "path": "transport/sip003/v2ray.go",
    "content": "package sip003\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"strconv\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2ray\"\n\t\"github.com/sagernet/sing-vmess\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/json/badoption\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc init() {\n\tRegisterPlugin(\"v2ray-plugin\", newV2RayPlugin)\n}\n\nfunc newV2RayPlugin(ctx context.Context, pluginOpts Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {\n\tvar tlsOptions option.OutboundTLSOptions\n\tif _, loaded := pluginOpts.Get(\"tls\"); loaded {\n\t\ttlsOptions.Enabled = true\n\t}\n\tif certPath, certLoaded := pluginOpts.Get(\"cert\"); certLoaded {\n\t\ttlsOptions.CertificatePath = certPath\n\t}\n\tif certRaw, certLoaded := pluginOpts.Get(\"certRaw\"); certLoaded {\n\t\tcertHead := \"-----BEGIN CERTIFICATE-----\"\n\t\tcertTail := \"-----END CERTIFICATE-----\"\n\t\tfixedCert := certHead + \"\\n\" + certRaw + \"\\n\" + certTail\n\t\ttlsOptions.Certificate = []string{fixedCert}\n\t}\n\n\tmode := \"websocket\"\n\tif modeOpt, loaded := pluginOpts.Get(\"mode\"); loaded {\n\t\tmode = modeOpt\n\t}\n\n\thost := \"cloudfront.com\"\n\tpath := \"/\"\n\n\tif hostOpt, loaded := pluginOpts.Get(\"host\"); loaded {\n\t\thost = hostOpt\n\t\ttlsOptions.ServerName = hostOpt\n\t}\n\tif pathOpt, loaded := pluginOpts.Get(\"path\"); loaded {\n\t\tpath = pathOpt\n\t}\n\n\tvar tlsClient tls.Config\n\tvar err error\n\tif tlsOptions.Enabled {\n\t\ttlsClient, err = tls.NewClient(ctx, logger.NOP(), serverAddr.AddrString(), tlsOptions)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar mux int\n\tvar transportOptions option.V2RayTransportOptions\n\tswitch mode {\n\tcase \"websocket\":\n\t\ttransportOptions = option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeWebsocket,\n\t\t\tWebsocketOptions: option.V2RayWebsocketOptions{\n\t\t\t\tHeaders: map[string]badoption.Listable[string]{\n\t\t\t\t\t\"Host\": []string{host},\n\t\t\t\t},\n\t\t\t\tPath: path,\n\t\t\t},\n\t\t}\n\t\tif muxOpt, loaded := pluginOpts.Get(\"mux\"); loaded {\n\t\t\tmuxVal, err := strconv.Atoi(muxOpt)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, \"parse mux value\")\n\t\t\t}\n\t\t\tmux = muxVal\n\t\t} else {\n\t\t\tmux = 1\n\t\t}\n\tcase \"quic\":\n\t\ttransportOptions = option.V2RayTransportOptions{\n\t\t\tType: C.V2RayTransportTypeQUIC,\n\t\t}\n\tdefault:\n\t\treturn nil, E.New(\"v2ray-plugin: unknown mode: \" + mode)\n\t}\n\n\ttransport, err := v2ray.NewClientTransport(context.Background(), dialer, serverAddr, transportOptions, tlsClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif mux > 0 {\n\t\treturn &v2rayMuxWrapper{transport}, nil\n\t}\n\n\treturn transport, nil\n}\n\nvar _ Plugin = (*v2rayMuxWrapper)(nil)\n\ntype v2rayMuxWrapper struct {\n\tadapter.V2RayClientTransport\n}\n\nfunc (w *v2rayMuxWrapper) DialContext(ctx context.Context) (net.Conn, error) {\n\tconn, err := w.V2RayClientTransport.DialContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn vmess.NewMuxConnWrapper(conn, vmess.MuxDestination), nil\n}\n"
  },
  {
    "path": "transport/trojan/mux.go",
    "content": "package trojan\n\nimport (\n\tstd_bufio \"bufio\"\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/task\"\n\t\"github.com/sagernet/smux\"\n)\n\nfunc HandleMuxConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler, logger logger.ContextLogger, onClose N.CloseHandlerFunc) error {\n\tsession, err := smux.Server(conn, smuxConfig())\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar group task.Group\n\tgroup.Append0(func(_ context.Context) error {\n\t\tvar stream net.Conn\n\t\tfor {\n\t\t\tstream, err = session.AcceptStream()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tgo newMuxConnection(ctx, stream, source, handler, logger)\n\t\t}\n\t})\n\tgroup.Cleanup(func() {\n\t\tsession.Close()\n\t\tif onClose != nil {\n\t\t\tonClose(os.ErrClosed)\n\t\t}\n\t})\n\treturn group.Run(ctx)\n}\n\nfunc newMuxConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler, logger logger.ContextLogger) {\n\terr := newMuxConnection0(ctx, conn, source, handler)\n\tif err != nil {\n\t\tlogger.ErrorContext(ctx, E.Cause(err, \"process trojan-go multiplex connection\"))\n\t}\n}\n\nfunc newMuxConnection0(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler) error {\n\treader := std_bufio.NewReader(conn)\n\tcommand, err := reader.ReadByte()\n\tif err != nil {\n\t\treturn E.Cause(err, \"read command\")\n\t}\n\tdestination, err := M.SocksaddrSerializer.ReadAddrPort(reader)\n\tif err != nil {\n\t\treturn E.Cause(err, \"read destination\")\n\t}\n\tif reader.Buffered() > 0 {\n\t\tbuffer := buf.NewSize(reader.Buffered())\n\t\t_, err = buffer.ReadFullFrom(reader, buffer.Len())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tconn = bufio.NewCachedConn(conn, buffer)\n\t}\n\tswitch command {\n\tcase CommandTCP:\n\t\thandler.NewConnectionEx(ctx, conn, source, destination, nil)\n\tcase CommandUDP:\n\t\thandler.NewPacketConnectionEx(ctx, &PacketConn{Conn: conn}, source, destination, nil)\n\tdefault:\n\t\treturn E.New(\"unknown command \", command)\n\t}\n\treturn nil\n}\n\nfunc smuxConfig() *smux.Config {\n\tconfig := smux.DefaultConfig()\n\tconfig.KeepAliveDisabled = true\n\treturn config\n}\n"
  },
  {
    "path": "transport/trojan/protocol.go",
    "content": "package trojan\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"net\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/rw\"\n)\n\nconst (\n\tKeyLength  = 56\n\tCommandTCP = 1\n\tCommandUDP = 3\n\tCommandMux = 0x7f\n)\n\nvar CRLF = []byte{'\\r', '\\n'}\n\nvar _ N.EarlyWriter = (*ClientConn)(nil)\n\ntype ClientConn struct {\n\tN.ExtendedConn\n\tkey           [KeyLength]byte\n\tdestination   M.Socksaddr\n\theaderWritten bool\n}\n\nfunc NewClientConn(conn net.Conn, key [KeyLength]byte, destination M.Socksaddr) *ClientConn {\n\treturn &ClientConn{\n\t\tExtendedConn: bufio.NewExtendedConn(conn),\n\t\tkey:          key,\n\t\tdestination:  destination,\n\t}\n}\n\nfunc (c *ClientConn) NeedHandshakeForWrite() bool {\n\treturn !c.headerWritten\n}\n\nfunc (c *ClientConn) Write(p []byte) (n int, err error) {\n\tif c.headerWritten {\n\t\treturn c.ExtendedConn.Write(p)\n\t}\n\terr = ClientHandshake(c.ExtendedConn, c.key, c.destination, p)\n\tif err != nil {\n\t\treturn\n\t}\n\tn = len(p)\n\tc.headerWritten = true\n\treturn\n}\n\nfunc (c *ClientConn) WriteBuffer(buffer *buf.Buffer) error {\n\tif c.headerWritten {\n\t\treturn c.ExtendedConn.WriteBuffer(buffer)\n\t}\n\terr := ClientHandshakeBuffer(c.ExtendedConn, c.key, c.destination, buffer)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.headerWritten = true\n\treturn nil\n}\n\nfunc (c *ClientConn) FrontHeadroom() int {\n\tif !c.headerWritten {\n\t\treturn KeyLength + 5 + M.MaxSocksaddrLength\n\t}\n\treturn 0\n}\n\nfunc (c *ClientConn) Upstream() any {\n\treturn c.ExtendedConn\n}\n\nfunc (c *ClientConn) ReaderReplaceable() bool {\n\treturn c.headerWritten\n}\n\nfunc (c *ClientConn) WriterReplaceable() bool {\n\treturn c.headerWritten\n}\n\ntype ClientPacketConn struct {\n\tnet.Conn\n\taccess          sync.Mutex\n\tkey             [KeyLength]byte\n\theaderWritten   bool\n\treadWaitOptions N.ReadWaitOptions\n}\n\nfunc NewClientPacketConn(conn net.Conn, key [KeyLength]byte) *ClientPacketConn {\n\treturn &ClientPacketConn{\n\t\tConn: conn,\n\t\tkey:  key,\n\t}\n}\n\nfunc (c *ClientPacketConn) NeedHandshake() bool {\n\treturn !c.headerWritten\n}\n\nfunc (c *ClientPacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {\n\treturn ReadPacket(c.Conn, buffer)\n}\n\nfunc (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {\n\tif !c.headerWritten {\n\t\tc.access.Lock()\n\t\tif c.headerWritten {\n\t\t\tc.access.Unlock()\n\t\t} else {\n\t\t\terr := ClientHandshakePacket(c.Conn, c.key, destination, buffer)\n\t\t\tc.headerWritten = true\n\t\t\tc.access.Unlock()\n\t\t\treturn err\n\t\t}\n\t}\n\treturn WritePacket(c.Conn, buffer, destination)\n}\n\nfunc (c *ClientPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tbuffer := buf.With(p)\n\tdestination, err := c.ReadPacket(buffer)\n\tif err != nil {\n\t\treturn\n\t}\n\tn = buffer.Len()\n\tif destination.IsDomain() {\n\t\taddr = destination\n\t} else {\n\t\taddr = destination.UDPAddr()\n\t}\n\treturn\n}\n\nfunc (c *ClientPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\treturn bufio.WritePacket(c, p, addr)\n}\n\nfunc (c *ClientPacketConn) Read(p []byte) (n int, err error) {\n\tn, _, err = c.ReadFrom(p)\n\treturn\n}\n\nfunc (c *ClientPacketConn) Write(p []byte) (n int, err error) {\n\treturn 0, os.ErrInvalid\n}\n\nfunc (c *ClientPacketConn) FrontHeadroom() int {\n\tif !c.headerWritten {\n\t\treturn KeyLength + 2*M.MaxSocksaddrLength + 9\n\t}\n\treturn M.MaxSocksaddrLength + 4\n}\n\nfunc (c *ClientPacketConn) Upstream() any {\n\treturn c.Conn\n}\n\nfunc Key(password string) [KeyLength]byte {\n\tvar key [KeyLength]byte\n\thash := sha256.New224()\n\tcommon.Must1(hash.Write([]byte(password)))\n\thex.Encode(key[:], hash.Sum(nil))\n\treturn key\n}\n\nfunc ClientHandshakeRaw(conn net.Conn, key [KeyLength]byte, command byte, destination M.Socksaddr, payload []byte) error {\n\t_, err := conn.Write(key[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = conn.Write(CRLF)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = conn.Write([]byte{command})\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = M.SocksaddrSerializer.WriteAddrPort(conn, destination)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = conn.Write(CRLF)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(payload) > 0 {\n\t\t_, err = conn.Write(payload)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc ClientHandshake(conn net.Conn, key [KeyLength]byte, destination M.Socksaddr, payload []byte) error {\n\theaderLen := KeyLength + M.SocksaddrSerializer.AddrPortLen(destination) + 5\n\theader := buf.NewSize(headerLen + len(payload))\n\tdefer header.Release()\n\tcommon.Must1(header.Write(key[:]))\n\tcommon.Must1(header.Write(CRLF))\n\tcommon.Must(header.WriteByte(CommandTCP))\n\terr := M.SocksaddrSerializer.WriteAddrPort(header, destination)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcommon.Must1(header.Write(CRLF))\n\tcommon.Must1(header.Write(payload))\n\t_, err = conn.Write(header.Bytes())\n\tif err != nil {\n\t\treturn E.Cause(err, \"write request\")\n\t}\n\treturn nil\n}\n\nfunc ClientHandshakeBuffer(conn net.Conn, key [KeyLength]byte, destination M.Socksaddr, payload *buf.Buffer) error {\n\theader := buf.With(payload.ExtendHeader(KeyLength + M.SocksaddrSerializer.AddrPortLen(destination) + 5))\n\tcommon.Must1(header.Write(key[:]))\n\tcommon.Must1(header.Write(CRLF))\n\tcommon.Must(header.WriteByte(CommandTCP))\n\terr := M.SocksaddrSerializer.WriteAddrPort(header, destination)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcommon.Must1(header.Write(CRLF))\n\n\t_, err = conn.Write(payload.Bytes())\n\tif err != nil {\n\t\treturn E.Cause(err, \"write request\")\n\t}\n\treturn nil\n}\n\nfunc ClientHandshakePacket(conn net.Conn, key [KeyLength]byte, destination M.Socksaddr, payload *buf.Buffer) error {\n\theaderLen := KeyLength + 2*M.SocksaddrSerializer.AddrPortLen(destination) + 9\n\tpayloadLen := payload.Len()\n\tvar header *buf.Buffer\n\tvar writeHeader bool\n\tif payload.Start() >= headerLen {\n\t\theader = buf.With(payload.ExtendHeader(headerLen))\n\t} else {\n\t\theader = buf.NewSize(headerLen)\n\t\tdefer header.Release()\n\t\twriteHeader = true\n\t}\n\tcommon.Must1(header.Write(key[:]))\n\tcommon.Must1(header.Write(CRLF))\n\tcommon.Must(header.WriteByte(CommandUDP))\n\terr := M.SocksaddrSerializer.WriteAddrPort(header, destination)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcommon.Must1(header.Write(CRLF))\n\tcommon.Must(M.SocksaddrSerializer.WriteAddrPort(header, destination))\n\tcommon.Must(binary.Write(header, binary.BigEndian, uint16(payloadLen)))\n\tcommon.Must1(header.Write(CRLF))\n\n\tif writeHeader {\n\t\t_, err := conn.Write(header.Bytes())\n\t\tif err != nil {\n\t\t\treturn E.Cause(err, \"write request\")\n\t\t}\n\t}\n\n\t_, err = conn.Write(payload.Bytes())\n\tif err != nil {\n\t\treturn E.Cause(err, \"write payload\")\n\t}\n\treturn nil\n}\n\nfunc ReadPacket(conn net.Conn, buffer *buf.Buffer) (M.Socksaddr, error) {\n\tdestination, err := M.SocksaddrSerializer.ReadAddrPort(conn)\n\tif err != nil {\n\t\treturn M.Socksaddr{}, E.Cause(err, \"read destination\")\n\t}\n\n\tvar length uint16\n\terr = binary.Read(conn, binary.BigEndian, &length)\n\tif err != nil {\n\t\treturn M.Socksaddr{}, E.Cause(err, \"read chunk length\")\n\t}\n\n\terr = rw.SkipN(conn, 2)\n\tif err != nil {\n\t\treturn M.Socksaddr{}, E.Cause(err, \"skip crlf\")\n\t}\n\n\t_, err = buffer.ReadFullFrom(conn, int(length))\n\treturn destination, err\n}\n\nfunc WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error {\n\tdefer buffer.Release()\n\tbufferLen := buffer.Len()\n\theader := buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination) + 4))\n\terr := M.SocksaddrSerializer.WriteAddrPort(header, destination)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcommon.Must(binary.Write(header, binary.BigEndian, uint16(bufferLen)))\n\tcommon.Must1(header.Write(CRLF))\n\t_, err = conn.Write(buffer.Bytes())\n\tif err != nil {\n\t\treturn E.Cause(err, \"write packet\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "transport/trojan/protocol_wait.go",
    "content": "package trojan\n\nimport (\n\t\"encoding/binary\"\n\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/rw\"\n)\n\nvar _ N.PacketReadWaiter = (*ClientPacketConn)(nil)\n\nfunc (c *ClientPacketConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {\n\tc.readWaitOptions = options\n\treturn false\n}\n\nfunc (c *ClientPacketConn) WaitReadPacket() (buffer *buf.Buffer, destination M.Socksaddr, err error) {\n\tdestination, err = M.SocksaddrSerializer.ReadAddrPort(c.Conn)\n\tif err != nil {\n\t\treturn nil, M.Socksaddr{}, E.Cause(err, \"read destination\")\n\t}\n\n\tvar length uint16\n\terr = binary.Read(c.Conn, binary.BigEndian, &length)\n\tif err != nil {\n\t\treturn nil, M.Socksaddr{}, E.Cause(err, \"read chunk length\")\n\t}\n\n\terr = rw.SkipN(c.Conn, 2)\n\tif err != nil {\n\t\treturn nil, M.Socksaddr{}, E.Cause(err, \"skip crlf\")\n\t}\n\n\tbuffer = c.readWaitOptions.NewPacketBuffer()\n\t_, err = buffer.ReadFullFrom(c.Conn, int(length))\n\tif err != nil {\n\t\tbuffer.Release()\n\t\treturn\n\t}\n\tc.readWaitOptions.PostReturn(buffer)\n\treturn\n}\n"
  },
  {
    "path": "transport/trojan/service.go",
    "content": "package trojan\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"net\"\n\n\t\"github.com/sagernet/sing/common/auth\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/rw\"\n)\n\ntype Handler interface {\n\tN.TCPConnectionHandlerEx\n\tN.UDPConnectionHandlerEx\n}\n\ntype Service[K comparable] struct {\n\tusers           map[K][56]byte\n\tkeys            map[[56]byte]K\n\thandler         Handler\n\tfallbackHandler N.TCPConnectionHandlerEx\n\tlogger          logger.ContextLogger\n}\n\nfunc NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandlerEx, logger logger.ContextLogger) *Service[K] {\n\treturn &Service[K]{\n\t\tusers:           make(map[K][56]byte),\n\t\tkeys:            make(map[[56]byte]K),\n\t\thandler:         handler,\n\t\tfallbackHandler: fallbackHandler,\n\t\tlogger:          logger,\n\t}\n}\n\nvar ErrUserExists = E.New(\"user already exists\")\n\nfunc (s *Service[K]) UpdateUsers(userList []K, passwordList []string) error {\n\tusers := make(map[K][56]byte)\n\tkeys := make(map[[56]byte]K)\n\tfor i, user := range userList {\n\t\tif _, loaded := users[user]; loaded {\n\t\t\treturn ErrUserExists\n\t\t}\n\t\tkey := Key(passwordList[i])\n\t\tif oldUser, loaded := keys[key]; loaded {\n\t\t\treturn E.Extend(ErrUserExists, \"password used by \", oldUser)\n\t\t}\n\t\tusers[user] = key\n\t\tkeys[key] = user\n\t}\n\ts.users = users\n\ts.keys = keys\n\treturn nil\n}\n\nfunc (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, onClose N.CloseHandlerFunc) error {\n\tvar key [KeyLength]byte\n\tn, err := conn.Read(key[:])\n\tif err != nil {\n\t\treturn err\n\t} else if n != KeyLength {\n\t\treturn s.fallback(ctx, conn, source, key[:n], E.New(\"bad request size\"), onClose)\n\t}\n\n\tif user, loaded := s.keys[key]; loaded {\n\t\tctx = auth.ContextWithUser(ctx, user)\n\t} else {\n\t\treturn s.fallback(ctx, conn, source, key[:], E.New(\"bad request\"), onClose)\n\t}\n\n\terr = rw.SkipN(conn, 2)\n\tif err != nil {\n\t\treturn E.Cause(err, \"skip crlf\")\n\t}\n\n\tvar command byte\n\terr = binary.Read(conn, binary.BigEndian, &command)\n\tif err != nil {\n\t\treturn E.Cause(err, \"read command\")\n\t}\n\n\tswitch command {\n\tcase CommandTCP, CommandUDP, CommandMux:\n\tdefault:\n\t\treturn E.New(\"unknown command \", command)\n\t}\n\n\t// var destination M.Socksaddr\n\tdestination, err := M.SocksaddrSerializer.ReadAddrPort(conn)\n\tif err != nil {\n\t\treturn E.Cause(err, \"read destination\")\n\t}\n\n\terr = rw.SkipN(conn, 2)\n\tif err != nil {\n\t\treturn E.Cause(err, \"skip crlf\")\n\t}\n\n\tswitch command {\n\tcase CommandTCP:\n\t\ts.handler.NewConnectionEx(ctx, conn, source, destination, onClose)\n\tcase CommandUDP:\n\t\ts.handler.NewPacketConnectionEx(ctx, &PacketConn{Conn: conn}, source, destination, onClose)\n\t// case CommandMux:\n\tdefault:\n\t\treturn HandleMuxConnection(ctx, conn, source, s.handler, s.logger, onClose)\n\t}\n\treturn nil\n}\n\nfunc (s *Service[K]) fallback(ctx context.Context, conn net.Conn, source M.Socksaddr, header []byte, err error, onClose N.CloseHandlerFunc) error {\n\tif s.fallbackHandler == nil {\n\t\treturn E.Extend(err, \"fallback disabled\")\n\t}\n\tconn = bufio.NewCachedConn(conn, buf.As(header).ToOwned())\n\ts.fallbackHandler.NewConnectionEx(ctx, conn, source, M.Socksaddr{}, onClose)\n\treturn nil\n}\n\ntype PacketConn struct {\n\tnet.Conn\n\treadWaitOptions N.ReadWaitOptions\n}\n\nfunc (c *PacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {\n\treturn ReadPacket(c.Conn, buffer)\n}\n\nfunc (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {\n\treturn WritePacket(c.Conn, buffer, destination)\n}\n\nfunc (c *PacketConn) FrontHeadroom() int {\n\treturn M.MaxSocksaddrLength + 4\n}\n\nfunc (c *PacketConn) NeedAdditionalReadDeadline() bool {\n\treturn true\n}\n\nfunc (c *PacketConn) Upstream() any {\n\treturn c.Conn\n}\n"
  },
  {
    "path": "transport/trojan/service_wait.go",
    "content": "package trojan\n\nimport (\n\t\"encoding/binary\"\n\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/rw\"\n)\n\nvar _ N.PacketReadWaiter = (*PacketConn)(nil)\n\nfunc (c *PacketConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {\n\tc.readWaitOptions = options\n\treturn false\n}\n\nfunc (c *PacketConn) WaitReadPacket() (buffer *buf.Buffer, destination M.Socksaddr, err error) {\n\tdestination, err = M.SocksaddrSerializer.ReadAddrPort(c.Conn)\n\tif err != nil {\n\t\treturn nil, M.Socksaddr{}, E.Cause(err, \"read destination\")\n\t}\n\n\tvar length uint16\n\terr = binary.Read(c.Conn, binary.BigEndian, &length)\n\tif err != nil {\n\t\treturn nil, M.Socksaddr{}, E.Cause(err, \"read chunk length\")\n\t}\n\n\terr = rw.SkipN(c.Conn, 2)\n\tif err != nil {\n\t\treturn nil, M.Socksaddr{}, E.Cause(err, \"skip crlf\")\n\t}\n\n\tbuffer = c.readWaitOptions.NewPacketBuffer()\n\t_, err = buffer.ReadFullFrom(c.Conn, int(length))\n\tif err != nil {\n\t\tbuffer.Release()\n\t\treturn\n\t}\n\tc.readWaitOptions.PostReturn(buffer)\n\treturn\n}\n"
  },
  {
    "path": "transport/v2ray/grpc.go",
    "content": "//go:build with_grpc\n\npackage v2ray\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2raygrpc\"\n\t\"github.com/sagernet/sing-box/transport/v2raygrpclite\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc NewGRPCServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {\n\tif options.ForceLite {\n\t\treturn v2raygrpclite.NewServer(ctx, logger, options, tlsConfig, handler)\n\t}\n\treturn v2raygrpc.NewServer(ctx, logger, options, tlsConfig, handler)\n}\n\nfunc NewGRPCClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {\n\tif options.ForceLite {\n\t\treturn v2raygrpclite.NewClient(ctx, dialer, serverAddr, options, tlsConfig), nil\n\t}\n\treturn v2raygrpc.NewClient(ctx, dialer, serverAddr, options, tlsConfig)\n}\n"
  },
  {
    "path": "transport/v2ray/grpc_lite.go",
    "content": "//go:build !with_grpc\n\npackage v2ray\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2raygrpclite\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nfunc NewGRPCServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {\n\treturn v2raygrpclite.NewServer(ctx, logger, options, tlsConfig, handler)\n}\n\nfunc NewGRPCClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {\n\treturn v2raygrpclite.NewClient(ctx, dialer, serverAddr, options, tlsConfig), nil\n}\n"
  },
  {
    "path": "transport/v2ray/quic.go",
    "content": "package v2ray\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nvar (\n\tquicServerConstructor ServerConstructor[option.V2RayQUICOptions]\n\tquicClientConstructor ClientConstructor[option.V2RayQUICOptions]\n)\n\nfunc RegisterQUICConstructor(server ServerConstructor[option.V2RayQUICOptions], client ClientConstructor[option.V2RayQUICOptions]) {\n\tquicServerConstructor = server\n\tquicClientConstructor = client\n}\n\nfunc NewQUICServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {\n\tif quicServerConstructor == nil {\n\t\treturn nil, os.ErrInvalid\n\t}\n\treturn quicServerConstructor(ctx, logger, options, tlsConfig, handler)\n}\n\nfunc NewQUICClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {\n\tif quicClientConstructor == nil {\n\t\treturn nil, os.ErrInvalid\n\t}\n\treturn quicClientConstructor(ctx, dialer, serverAddr, options, tlsConfig)\n}\n"
  },
  {
    "path": "transport/v2ray/transport.go",
    "content": "package v2ray\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2rayhttp\"\n\t\"github.com/sagernet/sing-box/transport/v2rayhttpupgrade\"\n\t\"github.com/sagernet/sing-box/transport/v2raywebsocket\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype (\n\tServerConstructor[O any] func(ctx context.Context, logger logger.ContextLogger, options O, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error)\n\tClientConstructor[O any] func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options O, tlsConfig tls.Config) (adapter.V2RayClientTransport, error)\n)\n\nfunc NewServerTransport(ctx context.Context, logger logger.ContextLogger, options option.V2RayTransportOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {\n\tif options.Type == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch options.Type {\n\tcase C.V2RayTransportTypeHTTP:\n\t\treturn v2rayhttp.NewServer(ctx, logger, options.HTTPOptions, tlsConfig, handler)\n\tcase C.V2RayTransportTypeWebsocket:\n\t\treturn v2raywebsocket.NewServer(ctx, logger, options.WebsocketOptions, tlsConfig, handler)\n\tcase C.V2RayTransportTypeQUIC:\n\t\tif tlsConfig == nil {\n\t\t\treturn nil, C.ErrTLSRequired\n\t\t}\n\t\treturn NewQUICServer(ctx, logger, options.QUICOptions, tlsConfig, handler)\n\tcase C.V2RayTransportTypeGRPC:\n\t\treturn NewGRPCServer(ctx, logger, options.GRPCOptions, tlsConfig, handler)\n\tcase C.V2RayTransportTypeHTTPUpgrade:\n\t\treturn v2rayhttpupgrade.NewServer(ctx, logger, options.HTTPUpgradeOptions, tlsConfig, handler)\n\tdefault:\n\t\treturn nil, E.New(\"unknown transport type: \" + options.Type)\n\t}\n}\n\nfunc NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayTransportOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {\n\tif options.Type == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch options.Type {\n\tcase C.V2RayTransportTypeHTTP:\n\t\treturn v2rayhttp.NewClient(ctx, dialer, serverAddr, options.HTTPOptions, tlsConfig)\n\tcase C.V2RayTransportTypeGRPC:\n\t\treturn NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig)\n\tcase C.V2RayTransportTypeWebsocket:\n\t\treturn v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig)\n\tcase C.V2RayTransportTypeQUIC:\n\t\tif tlsConfig == nil {\n\t\t\treturn nil, C.ErrTLSRequired\n\t\t}\n\t\treturn NewQUICClient(ctx, dialer, serverAddr, options.QUICOptions, tlsConfig)\n\tcase C.V2RayTransportTypeHTTPUpgrade:\n\t\treturn v2rayhttpupgrade.NewClient(ctx, dialer, serverAddr, options.HTTPUpgradeOptions, tlsConfig)\n\tdefault:\n\t\treturn nil, E.New(\"unknown transport type: \" + options.Type)\n\t}\n}\n"
  },
  {
    "path": "transport/v2raygrpc/client.go",
    "content": "package v2raygrpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"golang.org/x/net/http2\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/backoff\"\n\t\"google.golang.org/grpc/connectivity\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/keepalive\"\n)\n\nvar _ adapter.V2RayClientTransport = (*Client)(nil)\n\ntype Client struct {\n\tctx         context.Context\n\tdialer      N.Dialer\n\tserverAddr  string\n\tserviceName string\n\tdialOptions []grpc.DialOption\n\tconn        atomic.Pointer[grpc.ClientConn]\n\tconnAccess  sync.Mutex\n}\n\nfunc NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {\n\tvar dialOptions []grpc.DialOption\n\tif tlsConfig != nil {\n\t\tif len(tlsConfig.NextProtos()) == 0 {\n\t\t\ttlsConfig.SetNextProtos([]string{http2.NextProtoTLS})\n\t\t}\n\t\tdialOptions = append(dialOptions, grpc.WithTransportCredentials(NewTLSTransportCredentials(tlsConfig)))\n\t} else {\n\t\tdialOptions = append(dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))\n\t}\n\tif options.IdleTimeout > 0 {\n\t\tdialOptions = append(dialOptions, grpc.WithKeepaliveParams(keepalive.ClientParameters{\n\t\t\tTime:                time.Duration(options.IdleTimeout),\n\t\t\tTimeout:             time.Duration(options.PingTimeout),\n\t\t\tPermitWithoutStream: options.PermitWithoutStream,\n\t\t}))\n\t}\n\tdialOptions = append(dialOptions, grpc.WithConnectParams(grpc.ConnectParams{\n\t\tBackoff: backoff.Config{\n\t\t\tBaseDelay:  500 * time.Millisecond,\n\t\t\tMultiplier: 1.5,\n\t\t\tJitter:     0.2,\n\t\t\tMaxDelay:   19 * time.Second,\n\t\t},\n\t\tMinConnectTimeout: 5 * time.Second,\n\t}))\n\tdialOptions = append(dialOptions, grpc.WithContextDialer(func(ctx context.Context, server string) (net.Conn, error) {\n\t\treturn dialer.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddr(server))\n\t}))\n\t//nolint:staticcheck\n\tdialOptions = append(dialOptions, grpc.WithReturnConnectionError())\n\treturn &Client{\n\t\tctx:         ctx,\n\t\tdialer:      dialer,\n\t\tserverAddr:  serverAddr.String(),\n\t\tserviceName: options.ServiceName,\n\t\tdialOptions: dialOptions,\n\t}, nil\n}\n\nfunc (c *Client) connect() (*grpc.ClientConn, error) {\n\tconn := c.conn.Load()\n\tif conn != nil && conn.GetState() != connectivity.Shutdown {\n\t\treturn conn, nil\n\t}\n\tc.connAccess.Lock()\n\tdefer c.connAccess.Unlock()\n\tconn = c.conn.Load()\n\tif conn != nil && conn.GetState() != connectivity.Shutdown {\n\t\treturn conn, nil\n\t}\n\t//nolint:staticcheck\n\tconn, err := grpc.DialContext(c.ctx, c.serverAddr, c.dialOptions...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc.conn.Store(conn)\n\treturn conn, nil\n}\n\nfunc (c *Client) DialContext(ctx context.Context) (net.Conn, error) {\n\tclientConn, err := c.connect()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclient := NewGunServiceClient(clientConn).(GunServiceCustomNameClient)\n\tctx, cancel := common.ContextWithCancelCause(ctx)\n\tstream, err := client.TunCustomName(ctx, c.serviceName)\n\tif err != nil {\n\t\tcancel(err)\n\t\treturn nil, err\n\t}\n\treturn NewGRPCConn(stream, cancel), nil\n}\n\nfunc (c *Client) Close() error {\n\tconn := c.conn.Swap(nil)\n\tif conn != nil {\n\t\tconn.Close()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "transport/v2raygrpc/conn.go",
    "content": "package v2raygrpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common/baderror\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nvar _ net.Conn = (*GRPCConn)(nil)\n\ntype GRPCConn struct {\n\tGunService\n\tcache     []byte\n\tcancel    context.CancelCauseFunc\n\tcloseOnce sync.Once\n}\n\nfunc NewGRPCConn(service GunService, cancel context.CancelCauseFunc) *GRPCConn {\n\t//nolint:staticcheck\n\tif client, isClient := service.(GunService_TunClient); isClient {\n\t\tservice = &clientConnWrapper{client}\n\t}\n\treturn &GRPCConn{\n\t\tGunService: service,\n\t\tcancel:     cancel,\n\t}\n}\n\nfunc (c *GRPCConn) Read(b []byte) (n int, err error) {\n\tif len(c.cache) > 0 {\n\t\tn = copy(b, c.cache)\n\t\tc.cache = c.cache[n:]\n\t\treturn\n\t}\n\thunk, err := c.Recv()\n\terr = baderror.WrapGRPC(err)\n\tif err != nil {\n\t\treturn\n\t}\n\tn = copy(b, hunk.Data)\n\tif n < len(hunk.Data) {\n\t\tc.cache = hunk.Data[n:]\n\t}\n\treturn\n}\n\nfunc (c *GRPCConn) Write(b []byte) (n int, err error) {\n\terr = baderror.WrapGRPC(c.Send(&Hunk{Data: b}))\n\tif err != nil {\n\t\treturn\n\t}\n\treturn len(b), nil\n}\n\nfunc (c *GRPCConn) Close() error {\n\tc.closeOnce.Do(func() {\n\t\tif c.cancel != nil {\n\t\t\tc.cancel(nil)\n\t\t}\n\t})\n\treturn nil\n}\n\nfunc (c *GRPCConn) LocalAddr() net.Addr {\n\treturn M.Socksaddr{}\n}\n\nfunc (c *GRPCConn) RemoteAddr() net.Addr {\n\treturn M.Socksaddr{}\n}\n\nfunc (c *GRPCConn) SetDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *GRPCConn) SetReadDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *GRPCConn) SetWriteDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *GRPCConn) NeedAdditionalReadDeadline() bool {\n\treturn true\n}\n\nfunc (c *GRPCConn) Upstream() any {\n\treturn c.GunService\n}\n\nvar _ N.WriteCloser = (*clientConnWrapper)(nil)\n\ntype clientConnWrapper struct {\n\tGunService_TunClient\n}\n\nfunc (c *clientConnWrapper) CloseWrite() error {\n\treturn c.CloseSend()\n}\n"
  },
  {
    "path": "transport/v2raygrpc/credentials/credentials.go",
    "content": "/*\n * Copyright 2021 gRPC authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage credentials\n\nimport (\n\t\"context\"\n)\n\n// requestInfoKey is a struct to be used as the key to store RequestInfo in a\n// context.\ntype requestInfoKey struct{}\n\n// NewRequestInfoContext creates a context with ri.\nfunc NewRequestInfoContext(ctx context.Context, ri interface{}) context.Context {\n\treturn context.WithValue(ctx, requestInfoKey{}, ri)\n}\n\n// RequestInfoFromContext extracts the RequestInfo from ctx.\nfunc RequestInfoFromContext(ctx context.Context) interface{} {\n\treturn ctx.Value(requestInfoKey{})\n}\n\n// clientHandshakeInfoKey is a struct used as the key to store\n// ClientHandshakeInfo in a context.\ntype clientHandshakeInfoKey struct{}\n\n// ClientHandshakeInfoFromContext extracts the ClientHandshakeInfo from ctx.\nfunc ClientHandshakeInfoFromContext(ctx context.Context) interface{} {\n\treturn ctx.Value(clientHandshakeInfoKey{})\n}\n\n// NewClientHandshakeInfoContext creates a context with chi.\nfunc NewClientHandshakeInfoContext(ctx context.Context, chi interface{}) context.Context {\n\treturn context.WithValue(ctx, clientHandshakeInfoKey{}, chi)\n}\n"
  },
  {
    "path": "transport/v2raygrpc/credentials/spiffe.go",
    "content": "/*\n *\n * Copyright 2020 gRPC authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n// Package credentials defines APIs for parsing SPIFFE ID.\n//\n// All APIs in this package are experimental.\npackage credentials\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"net/url\"\n\n\t\"google.golang.org/grpc/grpclog\"\n)\n\nvar logger = grpclog.Component(\"credentials\")\n\n// SPIFFEIDFromState parses the SPIFFE ID from State. If the SPIFFE ID format\n// is invalid, return nil with warning.\nfunc SPIFFEIDFromState(state tls.ConnectionState) *url.URL {\n\tif len(state.PeerCertificates) == 0 || len(state.PeerCertificates[0].URIs) == 0 {\n\t\treturn nil\n\t}\n\treturn SPIFFEIDFromCert(state.PeerCertificates[0])\n}\n\n// SPIFFEIDFromCert parses the SPIFFE ID from x509.Certificate. If the SPIFFE\n// ID format is invalid, return nil with warning.\nfunc SPIFFEIDFromCert(cert *x509.Certificate) *url.URL {\n\tif cert == nil || cert.URIs == nil {\n\t\treturn nil\n\t}\n\tvar spiffeID *url.URL\n\tfor _, uri := range cert.URIs {\n\t\tif uri == nil || uri.Scheme != \"spiffe\" || uri.Opaque != \"\" || (uri.User != nil && uri.User.Username() != \"\") {\n\t\t\tcontinue\n\t\t}\n\t\t// From this point, we assume the uri is intended for a SPIFFE ID.\n\t\tif len(uri.String()) > 2048 {\n\t\t\tlogger.Warning(\"invalid SPIFFE ID: total ID length larger than 2048 bytes\")\n\t\t\treturn nil\n\t\t}\n\t\tif len(uri.Host) == 0 || len(uri.Path) == 0 {\n\t\t\tlogger.Warning(\"invalid SPIFFE ID: domain or workload ID is empty\")\n\t\t\treturn nil\n\t\t}\n\t\tif len(uri.Host) > 255 {\n\t\t\tlogger.Warning(\"invalid SPIFFE ID: domain length larger than 255 characters\")\n\t\t\treturn nil\n\t\t}\n\t\t// A valid SPIFFE certificate can only have exactly one URI SAN field.\n\t\tif len(cert.URIs) > 1 {\n\t\t\tlogger.Warning(\"invalid SPIFFE ID: multiple URI SANs\")\n\t\t\treturn nil\n\t\t}\n\t\tspiffeID = uri\n\t}\n\treturn spiffeID\n}\n"
  },
  {
    "path": "transport/v2raygrpc/credentials/syscallconn.go",
    "content": "/*\n *\n * Copyright 2018 gRPC authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage credentials\n\nimport (\n\t\"net\"\n\t\"syscall\"\n)\n\ntype sysConn = syscall.Conn\n\n// syscallConn keeps reference of rawConn to support syscall.Conn for channelz.\n// SyscallConn() (the method in interface syscall.Conn) is explicitly\n// implemented on this type,\n//\n// Interface syscall.Conn is implemented by most net.Conn implementations (e.g.\n// TCPConn, UnixConn), but is not part of net.Conn interface. So wrapper conns\n// that embed net.Conn don't implement syscall.Conn. (Side note: tls.Conn\n// doesn't embed net.Conn, so even if syscall.Conn is part of net.Conn, it won't\n// help here).\ntype syscallConn struct {\n\tnet.Conn\n\t// sysConn is a type alias of syscall.Conn. It's necessary because the name\n\t// `Conn` collides with `net.Conn`.\n\tsysConn\n}\n\n// WrapSyscallConn tries to wrap rawConn and newConn into a net.Conn that\n// implements syscall.Conn. rawConn will be used to support syscall, and newConn\n// will be used for read/write.\n//\n// This function returns newConn if rawConn doesn't implement syscall.Conn.\nfunc WrapSyscallConn(rawConn, newConn net.Conn) net.Conn {\n\tsysConn, ok := rawConn.(syscall.Conn)\n\tif !ok {\n\t\treturn newConn\n\t}\n\treturn &syscallConn{\n\t\tConn:    newConn,\n\t\tsysConn: sysConn,\n\t}\n}\n"
  },
  {
    "path": "transport/v2raygrpc/credentials/util.go",
    "content": "/*\n *\n * Copyright 2020 gRPC authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage credentials\n\nimport (\n\t\"crypto/tls\"\n)\n\nconst alpnProtoStrH2 = \"h2\"\n\n// AppendH2ToNextProtos appends h2 to next protos.\nfunc AppendH2ToNextProtos(ps []string) []string {\n\tfor _, p := range ps {\n\t\tif p == alpnProtoStrH2 {\n\t\t\treturn ps\n\t\t}\n\t}\n\tret := make([]string, 0, len(ps)+1)\n\tret = append(ret, ps...)\n\treturn append(ret, alpnProtoStrH2)\n}\n\n// CloneTLSConfig returns a shallow clone of the exported\n// fields of cfg, ignoring the unexported sync.Once, which\n// contains a mutex and must not be copied.\n//\n// If cfg is nil, a new zero tls.Config is returned.\n//\n// TODO: inline this function if possible.\nfunc CloneTLSConfig(cfg *tls.Config) *tls.Config {\n\tif cfg == nil {\n\t\treturn &tls.Config{}\n\t}\n\n\treturn cfg.Clone()\n}\n"
  },
  {
    "path": "transport/v2raygrpc/custom_name.go",
    "content": "package v2raygrpc\n\nimport (\n\t\"context\"\n\n\t\"google.golang.org/grpc\"\n)\n\ntype GunService interface {\n\tContext() context.Context\n\tSend(*Hunk) error\n\tRecv() (*Hunk, error)\n}\n\nfunc ServerDesc(name string) grpc.ServiceDesc {\n\treturn grpc.ServiceDesc{\n\t\tServiceName: name,\n\t\tHandlerType: (*GunServiceServer)(nil),\n\t\tMethods:     []grpc.MethodDesc{},\n\t\tStreams: []grpc.StreamDesc{\n\t\t\t{\n\t\t\t\tStreamName:    \"Tun\",\n\t\t\t\tHandler:       _GunService_Tun_Handler,\n\t\t\t\tServerStreams: true,\n\t\t\t\tClientStreams: true,\n\t\t\t},\n\t\t},\n\t\tMetadata: \"gun.proto\",\n\t}\n}\n\nfunc (c *gunServiceClient) TunCustomName(ctx context.Context, name string, opts ...grpc.CallOption) (GunService_TunClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ServerDesc(name).Streams[0], \"/\"+name+\"/Tun\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[Hunk, Hunk]{ClientStream: stream}\n\treturn x, nil\n}\n\nvar _ GunServiceCustomNameClient = (*gunServiceClient)(nil)\n\ntype GunServiceCustomNameClient interface {\n\tTunCustomName(ctx context.Context, name string, opts ...grpc.CallOption) (GunService_TunClient, error)\n\tTun(ctx context.Context, opts ...grpc.CallOption) (GunService_TunClient, error)\n}\n\nfunc RegisterGunServiceCustomNameServer(s *grpc.Server, srv GunServiceServer, name string) {\n\tdesc := ServerDesc(name)\n\ts.RegisterService(&desc, srv)\n}\n"
  },
  {
    "path": "transport/v2raygrpc/server.go",
    "content": "package v2raygrpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"golang.org/x/net/http2\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/keepalive\"\n\tgM \"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/peer\"\n)\n\nvar _ adapter.V2RayServerTransport = (*Server)(nil)\n\ntype Server struct {\n\tctx     context.Context\n\tlogger  logger.ContextLogger\n\thandler adapter.V2RayServerTransportHandler\n\tserver  *grpc.Server\n}\n\nfunc NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {\n\tvar serverOptions []grpc.ServerOption\n\tif tlsConfig != nil {\n\t\tif !common.Contains(tlsConfig.NextProtos(), http2.NextProtoTLS) {\n\t\t\ttlsConfig.SetNextProtos(append([]string{\"h2\"}, tlsConfig.NextProtos()...))\n\t\t}\n\t\tserverOptions = append(serverOptions, grpc.Creds(NewTLSTransportCredentials(tlsConfig)))\n\t}\n\tif options.IdleTimeout > 0 {\n\t\tserverOptions = append(serverOptions, grpc.KeepaliveParams(keepalive.ServerParameters{\n\t\t\tTime:    time.Duration(options.IdleTimeout),\n\t\t\tTimeout: time.Duration(options.PingTimeout),\n\t\t}))\n\t}\n\tserver := &Server{ctx, logger, handler, grpc.NewServer(serverOptions...)}\n\tRegisterGunServiceCustomNameServer(server.server, server, options.ServiceName)\n\treturn server, nil\n}\n\nfunc (s *Server) Tun(server GunService_TunServer) error {\n\tconn := NewGRPCConn(server, nil)\n\tvar source M.Socksaddr\n\tif remotePeer, loaded := peer.FromContext(server.Context()); loaded {\n\t\tsource = M.SocksaddrFromNet(remotePeer.Addr)\n\t}\n\tif grpcMetadata, loaded := gM.FromIncomingContext(server.Context()); loaded {\n\t\tforwardFrom := strings.Join(grpcMetadata.Get(\"X-Forwarded-For\"), \",\")\n\t\tif forwardFrom != \"\" {\n\t\t\tfor _, from := range strings.Split(forwardFrom, \",\") {\n\t\t\t\toriginAddr := M.ParseSocksaddr(from)\n\t\t\t\tif originAddr.IsValid() {\n\t\t\t\t\tsource = originAddr.Unwrap()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tdone := make(chan struct{})\n\tgo s.handler.NewConnectionEx(log.ContextWithNewID(s.ctx), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) {\n\t\tclose(done)\n\t}))\n\t<-done\n\treturn nil\n}\n\nfunc (s *Server) mustEmbedUnimplementedGunServiceServer() {\n}\n\nfunc (s *Server) Network() []string {\n\treturn []string{N.NetworkTCP}\n}\n\nfunc (s *Server) Serve(listener net.Listener) error {\n\treturn s.server.Serve(listener)\n}\n\nfunc (s *Server) ServePacket(listener net.PacketConn) error {\n\treturn os.ErrInvalid\n}\n\nfunc (s *Server) Close() error {\n\ts.server.Stop()\n\treturn nil\n}\n"
  },
  {
    "path": "transport/v2raygrpc/stream.pb.go",
    "content": "package v2raygrpc\n\nimport (\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Hunk struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tData          []byte                 `protobuf:\"bytes,1,opt,name=data,proto3\" json:\"data,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Hunk) Reset() {\n\t*x = Hunk{}\n\tmi := &file_transport_v2raygrpc_stream_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Hunk) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Hunk) ProtoMessage() {}\n\nfunc (x *Hunk) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_v2raygrpc_stream_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Hunk.ProtoReflect.Descriptor instead.\nfunc (*Hunk) Descriptor() ([]byte, []int) {\n\treturn file_transport_v2raygrpc_stream_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Hunk) GetData() []byte {\n\tif x != nil {\n\t\treturn x.Data\n\t}\n\treturn nil\n}\n\nvar File_transport_v2raygrpc_stream_proto protoreflect.FileDescriptor\n\nconst file_transport_v2raygrpc_stream_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\" transport/v2raygrpc/stream.proto\\x12\\x13transport.v2raygrpc\\\"\\x1a\\n\" +\n\t\"\\x04Hunk\\x12\\x12\\n\" +\n\t\"\\x04data\\x18\\x01 \\x01(\\fR\\x04data2M\\n\" +\n\t\"\\n\" +\n\t\"GunService\\x12?\\n\" +\n\t\"\\x03Tun\\x12\\x19.transport.v2raygrpc.Hunk\\x1a\\x19.transport.v2raygrpc.Hunk(\\x010\\x01B2Z0github.com/sagernet/sing-box/transport/v2raygrpcb\\x06proto3\"\n\nvar (\n\tfile_transport_v2raygrpc_stream_proto_rawDescOnce sync.Once\n\tfile_transport_v2raygrpc_stream_proto_rawDescData []byte\n)\n\nfunc file_transport_v2raygrpc_stream_proto_rawDescGZIP() []byte {\n\tfile_transport_v2raygrpc_stream_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_v2raygrpc_stream_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_v2raygrpc_stream_proto_rawDesc), len(file_transport_v2raygrpc_stream_proto_rawDesc)))\n\t})\n\treturn file_transport_v2raygrpc_stream_proto_rawDescData\n}\n\nvar (\n\tfile_transport_v2raygrpc_stream_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\n\tfile_transport_v2raygrpc_stream_proto_goTypes  = []any{\n\t\t(*Hunk)(nil), // 0: transport.v2raygrpc.Hunk\n\t}\n)\n\nvar file_transport_v2raygrpc_stream_proto_depIdxs = []int32{\n\t0, // 0: transport.v2raygrpc.GunService.Tun:input_type -> transport.v2raygrpc.Hunk\n\t0, // 1: transport.v2raygrpc.GunService.Tun:output_type -> transport.v2raygrpc.Hunk\n\t1, // [1:2] is the sub-list for method output_type\n\t0, // [0:1] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_v2raygrpc_stream_proto_init() }\nfunc file_transport_v2raygrpc_stream_proto_init() {\n\tif File_transport_v2raygrpc_stream_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_v2raygrpc_stream_proto_rawDesc), len(file_transport_v2raygrpc_stream_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_transport_v2raygrpc_stream_proto_goTypes,\n\t\tDependencyIndexes: file_transport_v2raygrpc_stream_proto_depIdxs,\n\t\tMessageInfos:      file_transport_v2raygrpc_stream_proto_msgTypes,\n\t}.Build()\n\tFile_transport_v2raygrpc_stream_proto = out.File\n\tfile_transport_v2raygrpc_stream_proto_goTypes = nil\n\tfile_transport_v2raygrpc_stream_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/v2raygrpc/stream.proto",
    "content": "syntax = \"proto3\";\n\npackage transport.v2raygrpc;\noption go_package = \"github.com/sagernet/sing-box/transport/v2raygrpc\";\n\nmessage Hunk {\n  bytes data = 1;\n}\n\nservice GunService {\n  rpc Tun (stream Hunk) returns (stream Hunk);\n}\n"
  },
  {
    "path": "transport/v2raygrpc/stream_grpc.pb.go",
    "content": "package v2raygrpc\n\nimport (\n\tcontext \"context\"\n\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tGunService_Tun_FullMethodName = \"/transport.v2raygrpc.GunService/Tun\"\n)\n\n// GunServiceClient is the client API for GunService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype GunServiceClient interface {\n\tTun(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Hunk, Hunk], error)\n}\n\ntype gunServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewGunServiceClient(cc grpc.ClientConnInterface) GunServiceClient {\n\treturn &gunServiceClient{cc}\n}\n\nfunc (c *gunServiceClient) Tun(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Hunk, Hunk], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &GunService_ServiceDesc.Streams[0], GunService_Tun_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[Hunk, Hunk]{ClientStream: stream}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype GunService_TunClient = grpc.BidiStreamingClient[Hunk, Hunk]\n\n// GunServiceServer is the server API for GunService service.\n// All implementations must embed UnimplementedGunServiceServer\n// for forward compatibility.\ntype GunServiceServer interface {\n\tTun(grpc.BidiStreamingServer[Hunk, Hunk]) error\n\tmustEmbedUnimplementedGunServiceServer()\n}\n\n// UnimplementedGunServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedGunServiceServer struct{}\n\nfunc (UnimplementedGunServiceServer) Tun(grpc.BidiStreamingServer[Hunk, Hunk]) error {\n\treturn status.Error(codes.Unimplemented, \"method Tun not implemented\")\n}\nfunc (UnimplementedGunServiceServer) mustEmbedUnimplementedGunServiceServer() {}\nfunc (UnimplementedGunServiceServer) testEmbeddedByValue()                    {}\n\n// UnsafeGunServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to GunServiceServer will\n// result in compilation errors.\ntype UnsafeGunServiceServer interface {\n\tmustEmbedUnimplementedGunServiceServer()\n}\n\nfunc RegisterGunServiceServer(s grpc.ServiceRegistrar, srv GunServiceServer) {\n\t// If the following call panics, it indicates UnimplementedGunServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&GunService_ServiceDesc, srv)\n}\n\nfunc _GunService_Tun_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(GunServiceServer).Tun(&grpc.GenericServerStream[Hunk, Hunk]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype GunService_TunServer = grpc.BidiStreamingServer[Hunk, Hunk]\n\n// GunService_ServiceDesc is the grpc.ServiceDesc for GunService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar GunService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"transport.v2raygrpc.GunService\",\n\tHandlerType: (*GunServiceServer)(nil),\n\tMethods:     []grpc.MethodDesc{},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"Tun\",\n\t\t\tHandler:       _GunService_Tun_Handler,\n\t\t\tServerStreams: true,\n\t\t\tClientStreams: true,\n\t\t},\n\t},\n\tMetadata: \"transport/v2raygrpc/stream.proto\",\n}\n"
  },
  {
    "path": "transport/v2raygrpc/tls_credentials.go",
    "content": "package v2raygrpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tinternal_credentials \"github.com/sagernet/sing-box/transport/v2raygrpc/credentials\"\n\n\t\"google.golang.org/grpc/credentials\"\n)\n\ntype TLSTransportCredentials struct {\n\tconfig tls.Config\n}\n\nfunc NewTLSTransportCredentials(config tls.Config) credentials.TransportCredentials {\n\treturn &TLSTransportCredentials{config}\n}\n\nfunc (c *TLSTransportCredentials) Info() credentials.ProtocolInfo {\n\treturn credentials.ProtocolInfo{\n\t\tSecurityProtocol: \"tls\",\n\t\tSecurityVersion:  \"1.2\",\n\t\tServerName:       c.config.ServerName(),\n\t}\n}\n\nfunc (c *TLSTransportCredentials) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {\n\tcfg := c.config.Clone()\n\tif cfg.ServerName() == \"\" {\n\t\tserverName, _, err := net.SplitHostPort(authority)\n\t\tif err != nil {\n\t\t\tserverName = authority\n\t\t}\n\t\tcfg.SetServerName(serverName)\n\t}\n\tconn, err := tls.ClientHandshake(ctx, rawConn, cfg)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\ttlsInfo := credentials.TLSInfo{\n\t\tState: conn.ConnectionState(),\n\t\tCommonAuthInfo: credentials.CommonAuthInfo{\n\t\t\tSecurityLevel: credentials.PrivacyAndIntegrity,\n\t\t},\n\t}\n\tid := internal_credentials.SPIFFEIDFromState(conn.ConnectionState())\n\tif id != nil {\n\t\ttlsInfo.SPIFFEID = id\n\t}\n\treturn internal_credentials.WrapSyscallConn(rawConn, conn), tlsInfo, nil\n}\n\nfunc (c *TLSTransportCredentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {\n\tserverConfig, isServer := c.config.(tls.ServerConfig)\n\tif !isServer {\n\t\treturn nil, nil, os.ErrInvalid\n\t}\n\tconn, err := tls.ServerHandshake(context.Background(), rawConn, serverConfig)\n\tif err != nil {\n\t\trawConn.Close()\n\t\treturn nil, nil, err\n\t}\n\ttlsInfo := credentials.TLSInfo{\n\t\tState: conn.ConnectionState(),\n\t\tCommonAuthInfo: credentials.CommonAuthInfo{\n\t\t\tSecurityLevel: credentials.PrivacyAndIntegrity,\n\t\t},\n\t}\n\tid := internal_credentials.SPIFFEIDFromState(conn.ConnectionState())\n\tif id != nil {\n\t\ttlsInfo.SPIFFEID = id\n\t}\n\treturn internal_credentials.WrapSyscallConn(rawConn, conn), tlsInfo, nil\n}\n\nfunc (c *TLSTransportCredentials) Clone() credentials.TransportCredentials {\n\treturn NewTLSTransportCredentials(c.config)\n}\n\nfunc (c *TLSTransportCredentials) OverrideServerName(serverNameOverride string) error {\n\tc.config.SetServerName(serverNameOverride)\n\treturn nil\n}\n"
  },
  {
    "path": "transport/v2raygrpclite/client.go",
    "content": "package v2raygrpclite\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2rayhttp\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\n\t\"golang.org/x/net/http2\"\n)\n\nvar _ adapter.V2RayClientTransport = (*Client)(nil)\n\nvar defaultClientHeader = http.Header{\n\t\"Content-Type\": []string{\"application/grpc\"},\n\t\"User-Agent\":   []string{\"grpc-go/1.48.0\"},\n\t\"TE\":           []string{\"trailers\"},\n}\n\ntype Client struct {\n\tctx        context.Context\n\tserverAddr M.Socksaddr\n\ttransport  *http2.Transport\n\toptions    option.V2RayGRPCOptions\n\turl        *url.URL\n\thost       string\n}\n\nfunc NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) adapter.V2RayClientTransport {\n\tvar host string\n\tif tlsConfig != nil && tlsConfig.ServerName() != \"\" {\n\t\thost = M.ParseSocksaddrHostPort(tlsConfig.ServerName(), serverAddr.Port).String()\n\t} else {\n\t\thost = serverAddr.String()\n\t}\n\tclient := &Client{\n\t\tctx:        ctx,\n\t\tserverAddr: serverAddr,\n\t\toptions:    options,\n\t\ttransport: &http2.Transport{\n\t\t\tReadIdleTimeout:    time.Duration(options.IdleTimeout),\n\t\t\tPingTimeout:        time.Duration(options.PingTimeout),\n\t\t\tDisableCompression: true,\n\t\t},\n\t\turl: &url.URL{\n\t\t\tScheme:  \"https\",\n\t\t\tHost:    serverAddr.String(),\n\t\t\tPath:    \"/\" + options.ServiceName + \"/Tun\",\n\t\t\tRawPath: \"/\" + url.PathEscape(options.ServiceName) + \"/Tun\",\n\t\t},\n\t\thost: host,\n\t}\n\tif tlsConfig == nil {\n\t\tclient.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {\n\t\t\treturn dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t\t}\n\t} else {\n\t\tif len(tlsConfig.NextProtos()) == 0 {\n\t\t\ttlsConfig.SetNextProtos([]string{http2.NextProtoTLS})\n\t\t}\n\t\ttlsDialer := tls.NewDialer(dialer, tlsConfig)\n\t\tclient.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {\n\t\t\treturn tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr))\n\t\t}\n\t}\n\n\treturn client\n}\n\nfunc (c *Client) DialContext(ctx context.Context) (net.Conn, error) {\n\tpipeInReader, pipeInWriter := io.Pipe()\n\trequest := &http.Request{\n\t\tMethod: http.MethodPost,\n\t\tBody:   pipeInReader,\n\t\tURL:    c.url,\n\t\tHeader: defaultClientHeader,\n\t\tHost:   c.host,\n\t}\n\trequest = request.WithContext(ctx)\n\tconn := newLateGunConn(pipeInWriter)\n\tgo func() {\n\t\tresponse, err := c.transport.RoundTrip(request)\n\t\tif err != nil {\n\t\t\tconn.setup(nil, err)\n\t\t} else if response.StatusCode != 200 {\n\t\t\tresponse.Body.Close()\n\t\t\tconn.setup(nil, E.New(\"v2ray-grpc: unexpected status: \", response.Status))\n\t\t} else {\n\t\t\tconn.setup(response.Body, nil)\n\t\t}\n\t}()\n\treturn conn, nil\n}\n\nfunc (c *Client) Close() error {\n\tv2rayhttp.ResetTransport(c.transport)\n\treturn nil\n}\n"
  },
  {
    "path": "transport/v2raygrpclite/conn.go",
    "content": "package v2raygrpclite\n\nimport (\n\tstd_bufio \"bufio\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/baderror\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/varbin\"\n)\n\n// kanged from: https://github.com/Qv2ray/gun-lite\n\nvar _ net.Conn = (*GunConn)(nil)\n\ntype GunConn struct {\n\trawReader     io.Reader\n\treader        *std_bufio.Reader\n\twriter        io.Writer\n\tflusher       http.Flusher\n\tcreate        chan struct{}\n\terr           error\n\treadRemaining int\n}\n\nfunc newGunConn(reader io.Reader, writer io.Writer, flusher http.Flusher) *GunConn {\n\treturn &GunConn{\n\t\trawReader: reader,\n\t\treader:    std_bufio.NewReader(reader),\n\t\twriter:    writer,\n\t\tflusher:   flusher,\n\t}\n}\n\nfunc newLateGunConn(writer io.Writer) *GunConn {\n\treturn &GunConn{\n\t\tcreate: make(chan struct{}),\n\t\twriter: writer,\n\t}\n}\n\nfunc (c *GunConn) setup(reader io.Reader, err error) {\n\tif reader != nil {\n\t\tc.rawReader = reader\n\t\tc.reader = std_bufio.NewReader(reader)\n\t}\n\tc.err = err\n\tclose(c.create)\n}\n\nfunc (c *GunConn) Read(b []byte) (n int, err error) {\n\tn, err = c.read(b)\n\treturn n, baderror.WrapH2(err)\n}\n\nfunc (c *GunConn) read(b []byte) (n int, err error) {\n\tif c.reader == nil {\n\t\t<-c.create\n\t\tif c.err != nil {\n\t\t\treturn 0, c.err\n\t\t}\n\t}\n\n\tif c.readRemaining > 0 {\n\t\tif len(b) > c.readRemaining {\n\t\t\tb = b[:c.readRemaining]\n\t\t}\n\t\tn, err = c.reader.Read(b)\n\t\tc.readRemaining -= n\n\t\treturn\n\t}\n\n\t_, err = c.reader.Discard(6)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdataLen, err := binary.ReadUvarint(c.reader)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treadLen := int(dataLen)\n\tc.readRemaining = readLen\n\tif len(b) > readLen {\n\t\tb = b[:readLen]\n\t}\n\n\tn, err = c.reader.Read(b)\n\tc.readRemaining -= n\n\treturn\n}\n\nfunc (c *GunConn) Write(b []byte) (n int, err error) {\n\tvarLen := varbin.UvarintLen(uint64(len(b)))\n\tbuffer := buf.NewSize(6 + varLen + len(b))\n\theader := buffer.Extend(6 + varLen)\n\theader[0] = 0x00\n\tbinary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+len(b)))\n\theader[5] = 0x0A\n\tbinary.PutUvarint(header[6:], uint64(len(b)))\n\tcommon.Must1(buffer.Write(b))\n\t_, err = c.writer.Write(buffer.Bytes())\n\tif err != nil {\n\t\treturn 0, baderror.WrapH2(err)\n\t}\n\tif c.flusher != nil {\n\t\tc.flusher.Flush()\n\t}\n\treturn len(b), nil\n}\n\nfunc (c *GunConn) WriteBuffer(buffer *buf.Buffer) error {\n\tdefer buffer.Release()\n\tdataLen := buffer.Len()\n\tvarLen := varbin.UvarintLen(uint64(dataLen))\n\theader := buffer.ExtendHeader(6 + varLen)\n\theader[0] = 0x00\n\tbinary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+dataLen))\n\theader[5] = 0x0A\n\tbinary.PutUvarint(header[6:], uint64(dataLen))\n\terr := common.Error(c.writer.Write(buffer.Bytes()))\n\tif err != nil {\n\t\treturn baderror.WrapH2(err)\n\t}\n\tif c.flusher != nil {\n\t\tc.flusher.Flush()\n\t}\n\treturn nil\n}\n\nfunc (c *GunConn) FrontHeadroom() int {\n\treturn 6 + binary.MaxVarintLen64\n}\n\nfunc (c *GunConn) Close() error {\n\treturn common.Close(c.rawReader, c.writer)\n}\n\nfunc (c *GunConn) LocalAddr() net.Addr {\n\treturn M.Socksaddr{}\n}\n\nfunc (c *GunConn) RemoteAddr() net.Addr {\n\treturn M.Socksaddr{}\n}\n\nfunc (c *GunConn) SetDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *GunConn) SetReadDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *GunConn) SetWriteDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *GunConn) NeedAdditionalReadDeadline() bool {\n\treturn true\n}\n"
  },
  {
    "path": "transport/v2raygrpclite/server.go",
    "content": "package v2raygrpclite\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2rayhttp\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\tsHttp \"github.com/sagernet/sing/protocol/http\"\n\n\t\"golang.org/x/net/http2\"\n\t\"golang.org/x/net/http2/h2c\"\n)\n\nvar _ adapter.V2RayServerTransport = (*Server)(nil)\n\ntype Server struct {\n\ttlsConfig  tls.ServerConfig\n\tlogger     logger.ContextLogger\n\thandler    adapter.V2RayServerTransportHandler\n\thttpServer *http.Server\n\th2Server   *http2.Server\n\th2cHandler http.Handler\n\tpath       string\n}\n\nfunc NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {\n\tserver := &Server{\n\t\ttlsConfig: tlsConfig,\n\t\tlogger:    logger,\n\t\thandler:   handler,\n\t\tpath:      \"/\" + options.ServiceName + \"/Tun\",\n\t\th2Server: &http2.Server{\n\t\t\tIdleTimeout: time.Duration(options.IdleTimeout),\n\t\t},\n\t}\n\tserver.httpServer = &http.Server{\n\t\tHandler: server,\n\t\tBaseContext: func(net.Listener) context.Context {\n\t\t\treturn ctx\n\t\t},\n\t\tConnContext: func(ctx context.Context, c net.Conn) context.Context {\n\t\t\treturn log.ContextWithNewID(ctx)\n\t\t},\n\t}\n\tserver.h2cHandler = h2c.NewHandler(server, server.h2Server)\n\treturn server, nil\n}\n\nfunc (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {\n\tif request.Method == \"PRI\" && len(request.Header) == 0 && request.URL.Path == \"*\" && request.Proto == \"HTTP/2.0\" {\n\t\ts.h2cHandler.ServeHTTP(writer, request)\n\t\treturn\n\t}\n\tif request.URL.Path != s.path {\n\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"bad path: \", request.URL.Path))\n\t\treturn\n\t}\n\tif request.Method != http.MethodPost {\n\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"bad method: \", request.Method))\n\t\treturn\n\t}\n\tif ct := request.Header.Get(\"Content-Type\"); !strings.HasPrefix(ct, \"application/grpc\") {\n\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"bad content type: \", ct))\n\t\treturn\n\t}\n\twriter.Header().Set(\"Content-Type\", \"application/grpc\")\n\twriter.Header().Set(\"TE\", \"trailers\")\n\twriter.WriteHeader(http.StatusOK)\n\tdone := make(chan struct{})\n\tconn := v2rayhttp.NewHTTP2Wrapper(newGunConn(request.Body, writer, writer.(http.Flusher)))\n\ts.handler.NewConnectionEx(request.Context(), conn, sHttp.SourceAddress(request), M.Socksaddr{}, N.OnceClose(func(it error) {\n\t\tclose(done)\n\t}))\n\t<-done\n\tconn.CloseWrapper()\n}\n\nfunc (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {\n\tif statusCode > 0 {\n\t\twriter.WriteHeader(statusCode)\n\t}\n\ts.logger.ErrorContext(request.Context(), E.Cause(err, \"process connection from \", request.RemoteAddr))\n}\n\nfunc (s *Server) Network() []string {\n\treturn []string{N.NetworkTCP}\n}\n\nfunc (s *Server) Serve(listener net.Listener) error {\n\tif s.tlsConfig != nil {\n\t\tif !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {\n\t\t\ts.tlsConfig.SetNextProtos(append([]string{\"h2\"}, s.tlsConfig.NextProtos()...))\n\t\t}\n\t\tlistener = aTLS.NewListener(listener, s.tlsConfig)\n\t}\n\treturn s.httpServer.Serve(listener)\n}\n\nfunc (s *Server) ServePacket(listener net.PacketConn) error {\n\treturn os.ErrInvalid\n}\n\nfunc (s *Server) Close() error {\n\treturn common.Close(common.PtrOrNil(s.httpServer))\n}\n"
  },
  {
    "path": "transport/v2rayhttp/client.go",
    "content": "package v2rayhttp\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/option\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\tsHTTP \"github.com/sagernet/sing/protocol/http\"\n\n\t\"golang.org/x/net/http2\"\n)\n\nvar _ adapter.V2RayClientTransport = (*Client)(nil)\n\ntype Client struct {\n\tctx        context.Context\n\tdialer     N.Dialer\n\tserverAddr M.Socksaddr\n\ttransport  http.RoundTripper\n\thttp2      bool\n\trequestURL url.URL\n\thost       []string\n\tmethod     string\n\theaders    http.Header\n}\n\nfunc NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {\n\tvar transport http.RoundTripper\n\tif tlsConfig == nil {\n\t\ttransport = &http.Transport{\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\treturn dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))\n\t\t\t},\n\t\t}\n\t} else {\n\t\tif len(tlsConfig.NextProtos()) == 0 {\n\t\t\ttlsConfig.SetNextProtos([]string{http2.NextProtoTLS})\n\t\t}\n\t\ttlsDialer := tls.NewDialer(dialer, tlsConfig)\n\t\ttransport = &http2.Transport{\n\t\t\tReadIdleTimeout: time.Duration(options.IdleTimeout),\n\t\t\tPingTimeout:     time.Duration(options.PingTimeout),\n\t\t\tDialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {\n\t\t\t\treturn tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr))\n\t\t\t},\n\t\t}\n\t}\n\tif options.Method == \"\" {\n\t\toptions.Method = http.MethodPut\n\t}\n\tvar requestURL url.URL\n\tif tlsConfig == nil {\n\t\trequestURL.Scheme = \"http\"\n\t} else {\n\t\trequestURL.Scheme = \"https\"\n\t}\n\trequestURL.Host = serverAddr.String()\n\trequestURL.Path = options.Path\n\terr := sHTTP.URLSetPath(&requestURL, options.Path)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"parse path\")\n\t}\n\tif !strings.HasPrefix(requestURL.Path, \"/\") {\n\t\trequestURL.Path = \"/\" + requestURL.Path\n\t}\n\treturn &Client{\n\t\tctx:        ctx,\n\t\tdialer:     dialer,\n\t\tserverAddr: serverAddr,\n\t\trequestURL: requestURL,\n\t\thost:       options.Host,\n\t\tmethod:     options.Method,\n\t\theaders:    options.Headers.Build(),\n\t\ttransport:  transport,\n\t\thttp2:      tlsConfig != nil,\n\t}, nil\n}\n\nfunc (c *Client) DialContext(ctx context.Context) (net.Conn, error) {\n\tif !c.http2 {\n\t\treturn c.dialHTTP(ctx)\n\t} else {\n\t\treturn c.dialHTTP2(ctx)\n\t}\n}\n\nfunc (c *Client) dialHTTP(ctx context.Context) (net.Conn, error) {\n\tconn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trequest := &http.Request{\n\t\tMethod: c.method,\n\t\tURL:    &c.requestURL,\n\t\tHeader: c.headers.Clone(),\n\t}\n\tswitch hostLen := len(c.host); hostLen {\n\tcase 0:\n\t\trequest.Host = c.serverAddr.AddrString()\n\tcase 1:\n\t\trequest.Host = c.host[0]\n\tdefault:\n\t\trequest.Host = c.host[rand.Intn(hostLen)]\n\t}\n\n\treturn NewHTTP1Conn(conn, request), nil\n}\n\nfunc (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {\n\tpipeInReader, pipeInWriter := io.Pipe()\n\trequest := &http.Request{\n\t\tMethod: c.method,\n\t\tBody:   pipeInReader,\n\t\tURL:    &c.requestURL,\n\t\tHeader: c.headers.Clone(),\n\t}\n\trequest = request.WithContext(ctx)\n\tswitch hostLen := len(c.host); hostLen {\n\tcase 0:\n\t\t// https://github.com/v2fly/v2ray-core/blob/master/transport/internet/http/config.go#L13\n\t\trequest.Host = \"www.example.com\"\n\tcase 1:\n\t\trequest.Host = c.host[0]\n\tdefault:\n\t\trequest.Host = c.host[rand.Intn(hostLen)]\n\t}\n\tconn := NewLateHTTPConn(pipeInWriter)\n\tgo func() {\n\t\tresponse, err := c.transport.RoundTrip(request)\n\t\tif err != nil {\n\t\t\tconn.Setup(nil, err)\n\t\t} else if response.StatusCode != 200 {\n\t\t\tresponse.Body.Close()\n\t\t\tconn.Setup(nil, E.New(\"v2ray-http: unexpected status: \", response.Status))\n\t\t} else {\n\t\t\tconn.Setup(response.Body, nil)\n\t\t}\n\t}()\n\treturn conn, nil\n}\n\nfunc (c *Client) Close() error {\n\tc.transport = ResetTransport(c.transport)\n\treturn nil\n}\n"
  },
  {
    "path": "transport/v2rayhttp/conn.go",
    "content": "package v2rayhttp\n\nimport (\n\tstd_bufio \"bufio\"\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/baderror\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype HTTPConn struct {\n\tnet.Conn\n\trequest        *http.Request\n\trequestWritten bool\n\tresponseRead   bool\n\tresponseCache  *buf.Buffer\n}\n\nfunc NewHTTP1Conn(conn net.Conn, request *http.Request) *HTTPConn {\n\tif request.Header.Get(\"Host\") == \"\" {\n\t\trequest.Header.Set(\"Host\", request.Host)\n\t}\n\treturn &HTTPConn{\n\t\tConn:    conn,\n\t\trequest: request,\n\t}\n}\n\nfunc (c *HTTPConn) Read(b []byte) (n int, err error) {\n\tif !c.responseRead {\n\t\treader := std_bufio.NewReader(c.Conn)\n\t\tresponse, err := http.ReadResponse(reader, c.request)\n\t\tif err != nil {\n\t\t\treturn 0, E.Cause(err, \"read response\")\n\t\t}\n\t\tif response.StatusCode != 200 {\n\t\t\treturn 0, E.New(\"v2ray-http: unexpected status: \", response.Status)\n\t\t}\n\t\tif cacheLen := reader.Buffered(); cacheLen > 0 {\n\t\t\tc.responseCache = buf.NewSize(cacheLen)\n\t\t\t_, err = c.responseCache.ReadFullFrom(reader, cacheLen)\n\t\t\tif err != nil {\n\t\t\t\tc.responseCache.Release()\n\t\t\t\treturn 0, E.Cause(err, \"read cache\")\n\t\t\t}\n\t\t}\n\t\tc.responseRead = true\n\t}\n\tif c.responseCache != nil {\n\t\tn, err = c.responseCache.Read(b)\n\t\tif err == io.EOF {\n\t\t\tc.responseCache.Release()\n\t\t\tc.responseCache = nil\n\t\t}\n\t\tif n > 0 {\n\t\t\treturn n, nil\n\t\t}\n\t}\n\treturn c.Conn.Read(b)\n}\n\nfunc (c *HTTPConn) Write(b []byte) (int, error) {\n\tif !c.requestWritten {\n\t\terr := c.writeRequest(b)\n\t\tif err != nil {\n\t\t\treturn 0, E.Cause(err, \"write request\")\n\t\t}\n\t\tc.requestWritten = true\n\t\treturn len(b), nil\n\t}\n\treturn c.Conn.Write(b)\n}\n\nfunc (c *HTTPConn) writeRequest(payload []byte) error {\n\twriter := bufio.NewBufferedWriter(c.Conn, buf.New())\n\tconst CRLF = \"\\r\\n\"\n\t_, err := writer.Write([]byte(F.ToString(c.request.Method, \" \", c.request.URL.RequestURI(), \" HTTP/1.1\", CRLF)))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor key, value := range c.request.Header {\n\t\t_, err = writer.Write([]byte(F.ToString(key, \": \", strings.Join(value, \", \"), CRLF)))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t_, err = writer.Write([]byte(CRLF))\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = writer.Write(payload)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = writer.Fallthrough()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (c *HTTPConn) ReaderReplaceable() bool {\n\treturn c.responseRead\n}\n\nfunc (c *HTTPConn) WriterReplaceable() bool {\n\treturn c.requestWritten\n}\n\nfunc (c *HTTPConn) NeedHandshake() bool {\n\treturn !c.requestWritten\n}\n\nfunc (c *HTTPConn) Upstream() any {\n\treturn c.Conn\n}\n\ntype HTTP2Conn struct {\n\treader io.Reader\n\twriter io.Writer\n\tcreate chan struct{}\n\terr    error\n}\n\nfunc NewHTTPConn(reader io.Reader, writer io.Writer) HTTP2Conn {\n\treturn HTTP2Conn{\n\t\treader: reader,\n\t\twriter: writer,\n\t}\n}\n\nfunc NewLateHTTPConn(writer io.Writer) *HTTP2Conn {\n\treturn &HTTP2Conn{\n\t\tcreate: make(chan struct{}),\n\t\twriter: writer,\n\t}\n}\n\nfunc (c *HTTP2Conn) Setup(reader io.Reader, err error) {\n\tc.reader = reader\n\tc.err = err\n\tclose(c.create)\n}\n\nfunc (c *HTTP2Conn) Read(b []byte) (n int, err error) {\n\tif c.reader == nil {\n\t\t<-c.create\n\t\tif c.err != nil {\n\t\t\treturn 0, c.err\n\t\t}\n\t}\n\tn, err = c.reader.Read(b)\n\treturn n, baderror.WrapH2(err)\n}\n\nfunc (c *HTTP2Conn) Write(b []byte) (n int, err error) {\n\tn, err = c.writer.Write(b)\n\treturn n, baderror.WrapH2(err)\n}\n\nfunc (c *HTTP2Conn) Close() error {\n\treturn common.Close(c.reader, c.writer)\n}\n\nfunc (c *HTTP2Conn) LocalAddr() net.Addr {\n\treturn M.Socksaddr{}\n}\n\nfunc (c *HTTP2Conn) RemoteAddr() net.Addr {\n\treturn M.Socksaddr{}\n}\n\nfunc (c *HTTP2Conn) SetDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *HTTP2Conn) SetReadDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *HTTP2Conn) SetWriteDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *HTTP2Conn) NeedAdditionalReadDeadline() bool {\n\treturn true\n}\n\ntype ServerHTTPConn struct {\n\tHTTP2Conn\n\tFlusher http.Flusher\n}\n\nfunc (c *ServerHTTPConn) Write(b []byte) (n int, err error) {\n\tn, err = c.writer.Write(b)\n\tif err == nil {\n\t\tc.Flusher.Flush()\n\t}\n\treturn\n}\n\ntype HTTP2ConnWrapper struct {\n\tN.ExtendedConn\n\taccess sync.Mutex\n\tclosed bool\n}\n\nfunc NewHTTP2Wrapper(conn net.Conn) *HTTP2ConnWrapper {\n\treturn &HTTP2ConnWrapper{\n\t\tExtendedConn: bufio.NewExtendedConn(conn),\n\t}\n}\n\nfunc (w *HTTP2ConnWrapper) Write(p []byte) (n int, err error) {\n\tw.access.Lock()\n\tdefer w.access.Unlock()\n\tif w.closed {\n\t\treturn 0, net.ErrClosed\n\t}\n\treturn w.ExtendedConn.Write(p)\n}\n\nfunc (w *HTTP2ConnWrapper) WriteBuffer(buffer *buf.Buffer) error {\n\tw.access.Lock()\n\tdefer w.access.Unlock()\n\tif w.closed {\n\t\treturn net.ErrClosed\n\t}\n\treturn w.ExtendedConn.WriteBuffer(buffer)\n}\n\nfunc (w *HTTP2ConnWrapper) CloseWrapper() {\n\tw.access.Lock()\n\tdefer w.access.Unlock()\n\tw.closed = true\n}\n\nfunc (w *HTTP2ConnWrapper) Close() error {\n\tw.CloseWrapper()\n\treturn w.ExtendedConn.Close()\n}\n\nfunc (w *HTTP2ConnWrapper) Upstream() any {\n\treturn w.ExtendedConn\n}\n\nfunc DupContext(ctx context.Context) context.Context {\n\tid, loaded := log.IDFromContext(ctx)\n\tif !loaded {\n\t\treturn context.Background()\n\t}\n\treturn log.ContextWithID(context.Background(), id)\n}\n"
  },
  {
    "path": "transport/v2rayhttp/force_close.go",
    "content": "package v2rayhttp\n\nimport (\n\t\"net/http\"\n\t\"reflect\"\n\t\"sync\"\n\t\"unsafe\"\n\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\n\t\"golang.org/x/net/http2\"\n)\n\ntype clientConnPool struct {\n\tt     *http2.Transport\n\tmu    sync.Mutex\n\tconns map[string][]*http2.ClientConn // key is host:port\n}\n\ntype efaceWords struct {\n\ttyp  unsafe.Pointer\n\tdata unsafe.Pointer\n}\n\nfunc ResetTransport(rawTransport http.RoundTripper) http.RoundTripper {\n\tswitch transport := rawTransport.(type) {\n\tcase *http.Transport:\n\t\ttransport.CloseIdleConnections()\n\t\treturn transport.Clone()\n\tcase *http2.Transport:\n\t\tconnPool := transportConnPool(transport)\n\t\tp := (*clientConnPool)((*efaceWords)(unsafe.Pointer(&connPool)).data)\n\t\tp.mu.Lock()\n\t\tdefer p.mu.Unlock()\n\t\tfor _, vv := range p.conns {\n\t\t\tfor _, cc := range vv {\n\t\t\t\tcc.Close()\n\t\t\t}\n\t\t}\n\t\treturn transport\n\tdefault:\n\t\tpanic(E.New(\"unknown transport type: \", reflect.TypeOf(transport)))\n\t}\n}\n\n//go:linkname transportConnPool golang.org/x/net/http2.(*Transport).connPool\nfunc transportConnPool(t *http2.Transport) http2.ClientConnPool\n"
  },
  {
    "path": "transport/v2rayhttp/pool.go",
    "content": "package v2rayhttp\n\nimport \"net/http\"\n\ntype ConnectionPool interface {\n\tCloseIdleConnections()\n}\n\nfunc CloseIdleConnections(transport http.RoundTripper) {\n\tif connectionPool, ok := transport.(ConnectionPool); ok {\n\t\tconnectionPool.CloseIdleConnections()\n\t}\n}\n"
  },
  {
    "path": "transport/v2rayhttp/server.go",
    "content": "package v2rayhttp\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\tsHttp \"github.com/sagernet/sing/protocol/http\"\n\n\t\"golang.org/x/net/http2\"\n\t\"golang.org/x/net/http2/h2c\"\n)\n\nvar _ adapter.V2RayServerTransport = (*Server)(nil)\n\ntype Server struct {\n\tctx        context.Context\n\tlogger     logger.ContextLogger\n\ttlsConfig  tls.ServerConfig\n\thandler    adapter.V2RayServerTransportHandler\n\thttpServer *http.Server\n\th2Server   *http2.Server\n\th2cHandler http.Handler\n\thost       []string\n\tpath       string\n\tmethod     string\n\theaders    http.Header\n}\n\nfunc NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {\n\tserver := &Server{\n\t\tctx:       ctx,\n\t\ttlsConfig: tlsConfig,\n\t\tlogger:    logger,\n\t\thandler:   handler,\n\t\th2Server: &http2.Server{\n\t\t\tIdleTimeout: time.Duration(options.IdleTimeout),\n\t\t},\n\t\thost:    options.Host,\n\t\tpath:    options.Path,\n\t\tmethod:  options.Method,\n\t\theaders: options.Headers.Build(),\n\t}\n\tif !strings.HasPrefix(server.path, \"/\") {\n\t\tserver.path = \"/\" + server.path\n\t}\n\tserver.httpServer = &http.Server{\n\t\tHandler:           server,\n\t\tReadHeaderTimeout: C.TCPTimeout,\n\t\tMaxHeaderBytes:    http.DefaultMaxHeaderBytes,\n\t\tBaseContext: func(net.Listener) context.Context {\n\t\t\treturn ctx\n\t\t},\n\t\tConnContext: func(ctx context.Context, c net.Conn) context.Context {\n\t\t\treturn log.ContextWithNewID(ctx)\n\t\t},\n\t}\n\tserver.h2cHandler = h2c.NewHandler(server, server.h2Server)\n\treturn server, nil\n}\n\nfunc (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {\n\tif request.Method == \"PRI\" && len(request.Header) == 0 && request.URL.Path == \"*\" && request.Proto == \"HTTP/2.0\" {\n\t\ts.h2cHandler.ServeHTTP(writer, request)\n\t\treturn\n\t}\n\thost := request.Host\n\tif len(s.host) > 0 && !common.Contains(s.host, host) {\n\t\ts.invalidRequest(writer, request, http.StatusBadRequest, E.New(\"bad host: \", host))\n\t\treturn\n\t}\n\tif !strings.HasPrefix(request.URL.Path, s.path) {\n\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"bad path: \", request.URL.Path))\n\t\treturn\n\t}\n\tif s.method != \"\" && request.Method != s.method {\n\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"bad method: \", request.Method))\n\t\treturn\n\t}\n\n\twriter.Header().Set(\"Cache-Control\", \"no-store\")\n\n\tfor key, values := range s.headers {\n\t\tfor _, value := range values {\n\t\t\twriter.Header().Set(key, value)\n\t\t}\n\t}\n\n\tsource := sHttp.SourceAddress(request)\n\tif h, ok := writer.(http.Hijacker); ok {\n\t\tvar requestBody *buf.Buffer\n\t\tif contentLength := int(request.ContentLength); contentLength > 0 {\n\t\t\trequestBody = buf.NewSize(contentLength)\n\t\t\t_, err := requestBody.ReadFullFrom(request.Body, contentLength)\n\t\t\tif err != nil {\n\t\t\t\ts.invalidRequest(writer, request, 0, E.Cause(err, \"read request\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\twriter.WriteHeader(http.StatusOK)\n\t\twriter.(http.Flusher).Flush()\n\t\tconn, reader, err := h.Hijack()\n\t\tif err != nil {\n\t\t\ts.invalidRequest(writer, request, 0, E.Cause(err, \"hijack conn\"))\n\t\t\treturn\n\t\t}\n\t\tif cacheLen := reader.Reader.Buffered(); cacheLen > 0 {\n\t\t\tcache := buf.NewSize(cacheLen)\n\t\t\t_, err = cache.ReadFullFrom(reader.Reader, cacheLen)\n\t\t\tif err != nil {\n\t\t\t\tconn.Close()\n\t\t\t\ts.invalidRequest(writer, request, 0, E.Cause(err, \"read cache\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconn = bufio.NewCachedConn(conn, cache)\n\t\t}\n\t\tif requestBody != nil {\n\t\t\tconn = bufio.NewCachedConn(conn, requestBody)\n\t\t}\n\t\ts.handler.NewConnectionEx(DupContext(request.Context()), conn, source, M.Socksaddr{}, nil)\n\t} else {\n\t\twriter.WriteHeader(http.StatusOK)\n\t\tflusher := writer.(http.Flusher)\n\t\tflusher.Flush()\n\t\tdone := make(chan struct{})\n\t\tconn := NewHTTP2Wrapper(&ServerHTTPConn{\n\t\t\tNewHTTPConn(request.Body, writer),\n\t\t\tflusher,\n\t\t})\n\t\ts.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) {\n\t\t\tclose(done)\n\t\t}))\n\t\t<-done\n\t\tconn.CloseWrapper()\n\t}\n}\n\nfunc (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {\n\tif statusCode > 0 {\n\t\twriter.WriteHeader(statusCode)\n\t}\n\ts.logger.ErrorContext(request.Context(), E.Cause(err, \"process connection from \", request.RemoteAddr))\n}\n\nfunc (s *Server) Network() []string {\n\treturn []string{N.NetworkTCP}\n}\n\nfunc (s *Server) Serve(listener net.Listener) error {\n\tif s.tlsConfig != nil {\n\t\tif len(s.tlsConfig.NextProtos()) == 0 {\n\t\t\ts.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, \"http/1.1\"})\n\t\t} else if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {\n\t\t\ts.tlsConfig.SetNextProtos(append([]string{http2.NextProtoTLS}, s.tlsConfig.NextProtos()...))\n\t\t}\n\t\tlistener = aTLS.NewListener(listener, s.tlsConfig)\n\t}\n\treturn s.httpServer.Serve(listener)\n}\n\nfunc (s *Server) ServePacket(listener net.PacketConn) error {\n\treturn os.ErrInvalid\n}\n\nfunc (s *Server) Close() error {\n\treturn common.Close(common.PtrOrNil(s.httpServer))\n}\n"
  },
  {
    "path": "transport/v2rayhttpupgrade/client.go",
    "content": "package v2rayhttpupgrade\n\nimport (\n\tstd_bufio \"bufio\"\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\tsHTTP \"github.com/sagernet/sing/protocol/http\"\n)\n\nvar _ adapter.V2RayClientTransport = (*Client)(nil)\n\ntype Client struct {\n\tdialer     N.Dialer\n\tserverAddr M.Socksaddr\n\trequestURL url.URL\n\theaders    http.Header\n\thost       string\n}\n\nfunc NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.Config) (*Client, error) {\n\tif tlsConfig != nil {\n\t\tif len(tlsConfig.NextProtos()) == 0 {\n\t\t\ttlsConfig.SetNextProtos([]string{\"http/1.1\"})\n\t\t}\n\t\tdialer = tls.NewDialer(dialer, tlsConfig)\n\t}\n\tvar host string\n\tif options.Host != \"\" {\n\t\thost = options.Host\n\t} else if tlsConfig != nil && tlsConfig.ServerName() != \"\" {\n\t\thost = tlsConfig.ServerName()\n\t} else {\n\t\thost = serverAddr.String()\n\t}\n\tvar requestURL url.URL\n\tif tlsConfig == nil {\n\t\trequestURL.Scheme = \"http\"\n\t} else {\n\t\trequestURL.Scheme = \"https\"\n\t}\n\trequestURL.Host = serverAddr.String()\n\trequestURL.Path = options.Path\n\terr := sHTTP.URLSetPath(&requestURL, options.Path)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"parse path\")\n\t}\n\tif !strings.HasPrefix(requestURL.Path, \"/\") {\n\t\trequestURL.Path = \"/\" + requestURL.Path\n\t}\n\theaders := make(http.Header)\n\tfor key, value := range options.Headers {\n\t\theaders[key] = value\n\t}\n\treturn &Client{\n\t\tdialer:     dialer,\n\t\tserverAddr: serverAddr,\n\t\trequestURL: requestURL,\n\t\theaders:    headers,\n\t\thost:       host,\n\t}, nil\n}\n\nfunc (c *Client) DialContext(ctx context.Context) (net.Conn, error) {\n\tconn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trequest := &http.Request{\n\t\tMethod: http.MethodGet,\n\t\tURL:    &c.requestURL,\n\t\tHeader: c.headers.Clone(),\n\t\tHost:   c.host,\n\t}\n\trequest.Header.Set(\"Connection\", \"Upgrade\")\n\trequest.Header.Set(\"Upgrade\", \"websocket\")\n\terr = request.Write(conn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbufReader := std_bufio.NewReader(conn)\n\tresponse, err := http.ReadResponse(bufReader, request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif response.StatusCode != 101 ||\n\t\t!strings.EqualFold(response.Header.Get(\"Connection\"), \"upgrade\") ||\n\t\t!strings.EqualFold(response.Header.Get(\"Upgrade\"), \"websocket\") {\n\t\treturn nil, E.New(\"v2ray-http-upgrade: unexpected status: \", response.Status)\n\t}\n\tif bufReader.Buffered() > 0 {\n\t\tbuffer := buf.NewSize(bufReader.Buffered())\n\t\t_, err = buffer.ReadFullFrom(bufReader, buffer.Len())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconn = bufio.NewCachedConn(conn, buffer)\n\t}\n\treturn conn, nil\n}\n\nfunc (c *Client) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "transport/v2rayhttpupgrade/server.go",
    "content": "package v2rayhttpupgrade\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2rayhttp\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\tsHttp \"github.com/sagernet/sing/protocol/http\"\n)\n\nvar _ adapter.V2RayServerTransport = (*Server)(nil)\n\ntype Server struct {\n\tctx        context.Context\n\tlogger     logger.ContextLogger\n\ttlsConfig  tls.ServerConfig\n\thandler    adapter.V2RayServerTransportHandler\n\thttpServer *http.Server\n\thost       string\n\tpath       string\n\theaders    http.Header\n}\n\nfunc NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {\n\tserver := &Server{\n\t\tctx:       ctx,\n\t\tlogger:    logger,\n\t\ttlsConfig: tlsConfig,\n\t\thandler:   handler,\n\t\thost:      options.Host,\n\t\tpath:      options.Path,\n\t\theaders:   options.Headers.Build(),\n\t}\n\tif !strings.HasPrefix(server.path, \"/\") {\n\t\tserver.path = \"/\" + server.path\n\t}\n\tserver.httpServer = &http.Server{\n\t\tHandler:           server,\n\t\tReadHeaderTimeout: C.TCPTimeout,\n\t\tMaxHeaderBytes:    http.DefaultMaxHeaderBytes,\n\t\tBaseContext: func(net.Listener) context.Context {\n\t\t\treturn ctx\n\t\t},\n\t\tConnContext: func(ctx context.Context, c net.Conn) context.Context {\n\t\t\treturn log.ContextWithNewID(ctx)\n\t\t},\n\t\tTLSNextProto: make(map[string]func(*http.Server, *tls.STDConn, http.Handler)),\n\t}\n\treturn server, nil\n}\n\ntype httpFlusher interface {\n\tFlushError() error\n}\n\nfunc (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {\n\thost := request.Host\n\tif len(s.host) > 0 && host != s.host {\n\t\ts.invalidRequest(writer, request, http.StatusBadRequest, E.New(\"bad host: \", host))\n\t\treturn\n\t}\n\tif request.URL.Path != s.path {\n\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"bad path: \", request.URL.Path))\n\t\treturn\n\t}\n\tif request.Method != http.MethodGet {\n\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"bad method: \", request.Method))\n\t\treturn\n\t}\n\tif !strings.EqualFold(request.Header.Get(\"Connection\"), \"upgrade\") {\n\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"not a upgrade request\"))\n\t\treturn\n\t}\n\tif !strings.EqualFold(request.Header.Get(\"Upgrade\"), \"websocket\") {\n\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"not a websocket request\"))\n\t\treturn\n\t}\n\tif request.Header.Get(\"Sec-WebSocket-Key\") != \"\" {\n\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"real websocket request received\"))\n\t\treturn\n\t}\n\twriter.Header().Set(\"Connection\", \"upgrade\")\n\twriter.Header().Set(\"Upgrade\", \"websocket\")\n\twriter.WriteHeader(http.StatusSwitchingProtocols)\n\tif flusher, isFlusher := writer.(httpFlusher); isFlusher {\n\t\terr := flusher.FlushError()\n\t\tif err != nil {\n\t\t\ts.invalidRequest(writer, request, http.StatusInternalServerError, E.New(\"flush response\"))\n\t\t}\n\t}\n\thijacker, canHijack := writer.(http.Hijacker)\n\tif !canHijack {\n\t\ts.invalidRequest(writer, request, http.StatusInternalServerError, E.New(\"invalid connection, maybe HTTP/2\"))\n\t\treturn\n\t}\n\tconn, _, err := hijacker.Hijack()\n\tif err != nil {\n\t\ts.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, \"hijack failed\"))\n\t\treturn\n\t}\n\ts.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil)\n}\n\nfunc (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {\n\tif statusCode > 0 {\n\t\twriter.WriteHeader(statusCode)\n\t}\n\ts.logger.ErrorContext(request.Context(), E.Cause(err, \"process connection from \", request.RemoteAddr))\n}\n\nfunc (s *Server) Network() []string {\n\treturn []string{N.NetworkTCP}\n}\n\nfunc (s *Server) Serve(listener net.Listener) error {\n\tif s.tlsConfig != nil {\n\t\tif len(s.tlsConfig.NextProtos()) == 0 {\n\t\t\ts.tlsConfig.SetNextProtos([]string{\"http/1.1\"})\n\t\t}\n\t\tlistener = aTLS.NewListener(listener, s.tlsConfig)\n\t}\n\treturn s.httpServer.Serve(listener)\n}\n\nfunc (s *Server) ServePacket(listener net.PacketConn) error {\n\treturn os.ErrInvalid\n}\n\nfunc (s *Server) Close() error {\n\treturn common.Close(common.PtrOrNil(s.httpServer))\n}\n"
  },
  {
    "path": "transport/v2rayquic/client.go",
    "content": "//go:build with_quic\n\npackage v2rayquic\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/sagernet/quic-go\"\n\t\"github.com/sagernet/quic-go/http3\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-quic\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nvar _ adapter.V2RayClientTransport = (*Client)(nil)\n\ntype Client struct {\n\tctx        context.Context\n\tdialer     N.Dialer\n\tserverAddr M.Socksaddr\n\ttlsConfig  tls.Config\n\tquicConfig *quic.Config\n\tconnAccess sync.Mutex\n\tconn       common.TypedValue[*quic.Conn]\n\trawConn    net.Conn\n}\n\nfunc NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {\n\tquicConfig := &quic.Config{\n\t\tDisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,\n\t}\n\tif len(tlsConfig.NextProtos()) == 0 {\n\t\ttlsConfig.SetNextProtos([]string{http3.NextProtoH3})\n\t}\n\treturn &Client{\n\t\tctx:        ctx,\n\t\tdialer:     dialer,\n\t\tserverAddr: serverAddr,\n\t\ttlsConfig:  tlsConfig,\n\t\tquicConfig: quicConfig,\n\t}, nil\n}\n\nfunc (c *Client) offer() (*quic.Conn, error) {\n\tconn := c.conn.Load()\n\tif conn != nil && !common.Done(conn.Context()) {\n\t\treturn conn, nil\n\t}\n\tc.connAccess.Lock()\n\tdefer c.connAccess.Unlock()\n\tconn = c.conn.Load()\n\tif conn != nil && !common.Done(conn.Context()) {\n\t\treturn conn, nil\n\t}\n\tconn, err := c.offerNew()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn conn, nil\n}\n\nfunc (c *Client) offerNew() (*quic.Conn, error) {\n\tudpConn, err := c.dialer.DialContext(c.ctx, \"udp\", c.serverAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpacketConn := bufio.NewUnbindPacketConn(udpConn)\n\tquicConn, err := qtls.Dial(c.ctx, packetConn, udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig)\n\tif err != nil {\n\t\tpacketConn.Close()\n\t\treturn nil, err\n\t}\n\tc.conn.Store(quicConn)\n\tc.rawConn = udpConn\n\treturn quicConn, nil\n}\n\nfunc (c *Client) DialContext(ctx context.Context) (net.Conn, error) {\n\tconn, err := c.offer()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstream, err := conn.OpenStream()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &StreamWrapper{Conn: conn, Stream: stream}, nil\n}\n\nfunc (c *Client) Close() error {\n\tc.connAccess.Lock()\n\tdefer c.connAccess.Unlock()\n\tconn := c.conn.Swap(nil)\n\tif conn != nil {\n\t\tconn.CloseWithError(0, \"\")\n\t}\n\tif c.rawConn != nil {\n\t\tc.rawConn.Close()\n\t}\n\tc.rawConn = nil\n\treturn nil\n}\n"
  },
  {
    "path": "transport/v2rayquic/init.go",
    "content": "//go:build with_quic\n\npackage v2rayquic\n\nimport \"github.com/sagernet/sing-box/transport/v2ray\"\n\nfunc init() {\n\tv2ray.RegisterQUICConstructor(NewServer, NewClient)\n}\n"
  },
  {
    "path": "transport/v2rayquic/server.go",
    "content": "//go:build with_quic\n\npackage v2rayquic\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sagernet/quic-go\"\n\t\"github.com/sagernet/quic-go/http3\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-quic\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nvar _ adapter.V2RayServerTransport = (*Server)(nil)\n\ntype Server struct {\n\tctx          context.Context\n\tlogger       logger.ContextLogger\n\ttlsConfig    tls.ServerConfig\n\tquicConfig   *quic.Config\n\thandler      adapter.V2RayServerTransportHandler\n\tudpListener  net.PacketConn\n\tquicListener qtls.Listener\n}\n\nfunc NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {\n\tquicConfig := &quic.Config{\n\t\tDisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,\n\t}\n\tif len(tlsConfig.NextProtos()) == 0 {\n\t\ttlsConfig.SetNextProtos([]string{http3.NextProtoH3})\n\t}\n\tserver := &Server{\n\t\tctx:        ctx,\n\t\tlogger:     logger,\n\t\ttlsConfig:  tlsConfig,\n\t\tquicConfig: quicConfig,\n\t\thandler:    handler,\n\t}\n\treturn server, nil\n}\n\nfunc (s *Server) Network() []string {\n\treturn []string{N.NetworkUDP}\n}\n\nfunc (s *Server) Serve(listener net.Listener) error {\n\treturn os.ErrInvalid\n}\n\nfunc (s *Server) ServePacket(listener net.PacketConn) error {\n\tquicListener, err := qtls.Listen(listener, s.tlsConfig, s.quicConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.udpListener = listener\n\ts.quicListener = quicListener\n\tgo s.acceptLoop()\n\treturn nil\n}\n\nfunc (s *Server) acceptLoop() {\n\tfor {\n\t\tconn, err := s.quicListener.Accept(s.ctx)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tgo func() {\n\t\t\thErr := s.streamAcceptLoop(conn)\n\t\t\tif hErr != nil && !E.IsClosedOrCanceled(hErr) {\n\t\t\t\ts.logger.ErrorContext(conn.Context(), hErr)\n\t\t\t}\n\t\t}()\n\t}\n}\n\nfunc (s *Server) streamAcceptLoop(conn *quic.Conn) error {\n\tfor {\n\t\tstream, err := conn.AcceptStream(s.ctx)\n\t\tif err != nil {\n\t\t\treturn qtls.WrapError(err)\n\t\t}\n\t\tgo s.handler.NewConnectionEx(conn.Context(), &StreamWrapper{Conn: conn, Stream: stream}, M.SocksaddrFromNet(conn.RemoteAddr()), M.Socksaddr{}, nil)\n\t}\n}\n\nfunc (s *Server) Close() error {\n\treturn common.Close(s.udpListener, s.quicListener)\n}\n"
  },
  {
    "path": "transport/v2rayquic/stream.go",
    "content": "package v2rayquic\n\nimport (\n\t\"net\"\n\n\t\"github.com/sagernet/quic-go\"\n\tqtls \"github.com/sagernet/sing-quic\"\n)\n\ntype StreamWrapper struct {\n\tConn *quic.Conn\n\t*quic.Stream\n}\n\nfunc (s *StreamWrapper) Read(p []byte) (n int, err error) {\n\tn, err = s.Stream.Read(p)\n\treturn n, qtls.WrapError(err)\n}\n\nfunc (s *StreamWrapper) Write(p []byte) (n int, err error) {\n\tn, err = s.Stream.Write(p)\n\treturn n, qtls.WrapError(err)\n}\n\nfunc (s *StreamWrapper) LocalAddr() net.Addr {\n\treturn s.Conn.LocalAddr()\n}\n\nfunc (s *StreamWrapper) RemoteAddr() net.Addr {\n\treturn s.Conn.RemoteAddr()\n}\n\nfunc (s *StreamWrapper) Upstream() any {\n\treturn s.Stream\n}\n\nfunc (s *StreamWrapper) Close() error {\n\ts.CancelRead(0)\n\ts.Stream.Close()\n\treturn nil\n}\n"
  },
  {
    "path": "transport/v2raywebsocket/client.go",
    "content": "package v2raywebsocket\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\t\"github.com/sagernet/sing/common/bufio/deadline\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\tsHTTP \"github.com/sagernet/sing/protocol/http\"\n\t\"github.com/sagernet/ws\"\n)\n\nvar _ adapter.V2RayClientTransport = (*Client)(nil)\n\ntype Client struct {\n\tdialer              N.Dialer\n\tserverAddr          M.Socksaddr\n\trequestURL          url.URL\n\theaders             http.Header\n\tmaxEarlyData        uint32\n\tearlyDataHeaderName string\n}\n\nfunc NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayWebsocketOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {\n\tif tlsConfig != nil {\n\t\tif len(tlsConfig.NextProtos()) == 0 {\n\t\t\ttlsConfig.SetNextProtos([]string{\"http/1.1\"})\n\t\t}\n\t\tdialer = tls.NewDialer(dialer, tlsConfig)\n\t}\n\tvar requestURL url.URL\n\tif tlsConfig == nil {\n\t\trequestURL.Scheme = \"ws\"\n\t} else {\n\t\trequestURL.Scheme = \"wss\"\n\t}\n\trequestURL.Host = serverAddr.String()\n\trequestURL.Path = options.Path\n\terr := sHTTP.URLSetPath(&requestURL, options.Path)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"parse path\")\n\t}\n\tif !strings.HasPrefix(requestURL.Path, \"/\") {\n\t\trequestURL.Path = \"/\" + requestURL.Path\n\t}\n\theaders := options.Headers.Build()\n\tif host := headers.Get(\"Host\"); host != \"\" {\n\t\theaders.Del(\"Host\")\n\t\trequestURL.Host = host\n\t}\n\tif headers.Get(\"User-Agent\") == \"\" {\n\t\theaders.Set(\"User-Agent\", \"Go-http-client/1.1\")\n\t}\n\treturn &Client{\n\t\tdialer,\n\t\tserverAddr,\n\t\trequestURL,\n\t\theaders,\n\t\toptions.MaxEarlyData,\n\t\toptions.EarlyDataHeaderName,\n\t}, nil\n}\n\nfunc (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers http.Header) (*WebsocketConn, error) {\n\tconn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar deadlineConn net.Conn\n\tif deadline.NeedAdditionalReadDeadline(conn) {\n\t\tdeadlineConn = deadline.NewConn(conn)\n\t} else {\n\t\tdeadlineConn = conn\n\t}\n\tdeadlineConn.SetDeadline(time.Now().Add(C.TCPTimeout))\n\tvar protocols []string\n\tif protocolHeader := headers.Get(\"Sec-WebSocket-Protocol\"); protocolHeader != \"\" {\n\t\tprotocols = []string{protocolHeader}\n\t\theaders.Del(\"Sec-WebSocket-Protocol\")\n\t}\n\treader, _, err := ws.Dialer{Header: ws.HandshakeHeaderHTTP(headers), Protocols: protocols}.Upgrade(deadlineConn, requestURL)\n\tdeadlineConn.SetDeadline(time.Time{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif reader != nil {\n\t\tbuffer := buf.NewSize(reader.Buffered())\n\t\t_, err = buffer.ReadFullFrom(reader, buffer.Len())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconn = bufio.NewCachedConn(conn, buffer)\n\t}\n\treturn NewConn(conn, nil, ws.StateClientSide), nil\n}\n\nfunc (c *Client) DialContext(ctx context.Context) (net.Conn, error) {\n\tif c.maxEarlyData <= 0 {\n\t\tconn, err := c.dialContext(ctx, &c.requestURL, c.headers)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn conn, nil\n\t} else {\n\t\treturn &EarlyWebsocketConn{Client: c, ctx: ctx, create: make(chan struct{})}, nil\n\t}\n}\n\nfunc (c *Client) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "transport/v2raywebsocket/conn.go",
    "content": "package v2raywebsocket\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/debug\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/ws\"\n\t\"github.com/sagernet/ws/wsutil\"\n)\n\ntype WebsocketConn struct {\n\tnet.Conn\n\t*Writer\n\tstate          ws.State\n\treader         *wsutil.Reader\n\tcontrolHandler wsutil.FrameHandlerFunc\n\tremoteAddr     net.Addr\n}\n\nfunc NewConn(conn net.Conn, remoteAddr net.Addr, state ws.State) *WebsocketConn {\n\tcontrolHandler := wsutil.ControlFrameHandler(conn, state)\n\treturn &WebsocketConn{\n\t\tConn:  conn,\n\t\tstate: state,\n\t\treader: &wsutil.Reader{\n\t\t\tSource:          conn,\n\t\t\tState:           state,\n\t\t\tSkipHeaderCheck: !debug.Enabled,\n\t\t\tOnIntermediate:  controlHandler,\n\t\t},\n\t\tcontrolHandler: controlHandler,\n\t\tremoteAddr:     remoteAddr,\n\t\tWriter:         NewWriter(conn, state),\n\t}\n}\n\nfunc (c *WebsocketConn) Close() error {\n\tc.Conn.SetWriteDeadline(time.Now().Add(C.TCPTimeout))\n\tframe := ws.NewCloseFrame(ws.NewCloseFrameBody(\n\t\tws.StatusNormalClosure, \"\",\n\t))\n\tif c.state == ws.StateClientSide {\n\t\tframe = ws.MaskFrameInPlace(frame)\n\t}\n\tws.WriteFrame(c.Conn, frame)\n\tc.Conn.Close()\n\treturn nil\n}\n\nfunc (c *WebsocketConn) Read(b []byte) (n int, err error) {\n\tvar header ws.Header\n\tfor {\n\t\tn, err = c.reader.Read(b)\n\t\tif n > 0 {\n\t\t\terr = nil\n\t\t\treturn\n\t\t}\n\t\tif !E.IsMulti(err, io.EOF, wsutil.ErrNoFrameAdvance) {\n\t\t\terr = wrapWsError(err)\n\t\t\treturn\n\t\t}\n\t\theader, err = wrapWsError0(c.reader.NextFrame())\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif header.OpCode.IsControl() {\n\t\t\tif header.Length > 128 {\n\t\t\t\terr = wsutil.ErrFrameTooLarge\n\t\t\t\treturn\n\t\t\t}\n\t\t\terr = wrapWsError(c.controlHandler(header, c.reader))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif header.OpCode&ws.OpBinary == 0 {\n\t\t\terr = wrapWsError(c.reader.Discard())\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc (c *WebsocketConn) Write(p []byte) (n int, err error) {\n\terr = wrapWsError(wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p))\n\tif err != nil {\n\t\treturn\n\t}\n\tn = len(p)\n\treturn\n}\n\nfunc (c *WebsocketConn) RemoteAddr() net.Addr {\n\tif c.remoteAddr != nil {\n\t\treturn c.remoteAddr\n\t}\n\treturn c.Conn.RemoteAddr()\n}\n\nfunc (c *WebsocketConn) SetDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *WebsocketConn) SetReadDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *WebsocketConn) SetWriteDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *WebsocketConn) NeedAdditionalReadDeadline() bool {\n\treturn true\n}\n\nfunc (c *WebsocketConn) Upstream() any {\n\treturn c.Conn\n}\n\ntype EarlyWebsocketConn struct {\n\t*Client\n\tctx    context.Context\n\tconn   atomic.Pointer[WebsocketConn]\n\taccess sync.Mutex\n\tcreate chan struct{}\n\terr    error\n}\n\nfunc (c *EarlyWebsocketConn) Read(b []byte) (n int, err error) {\n\tconn := c.conn.Load()\n\tif conn == nil {\n\t\t<-c.create\n\t\tif c.err != nil {\n\t\t\treturn 0, c.err\n\t\t}\n\t\tconn = c.conn.Load()\n\t}\n\treturn wrapWsError0(conn.Read(b))\n}\n\nfunc (c *EarlyWebsocketConn) writeRequest(content []byte) error {\n\tvar (\n\t\tearlyData []byte\n\t\tlateData  []byte\n\t\tconn      *WebsocketConn\n\t\terr       error\n\t)\n\tif len(content) > int(c.maxEarlyData) {\n\t\tearlyData = content[:c.maxEarlyData]\n\t\tlateData = content[c.maxEarlyData:]\n\t} else {\n\t\tearlyData = content\n\t}\n\tif len(earlyData) > 0 {\n\t\tearlyDataString := base64.RawURLEncoding.EncodeToString(earlyData)\n\t\tif c.earlyDataHeaderName == \"\" {\n\t\t\trequestURL := c.requestURL\n\t\t\trequestURL.Path += earlyDataString\n\t\t\tconn, err = c.dialContext(c.ctx, &requestURL, c.headers)\n\t\t} else {\n\t\t\theaders := c.headers.Clone()\n\t\t\theaders.Set(c.earlyDataHeaderName, earlyDataString)\n\t\t\tconn, err = c.dialContext(c.ctx, &c.requestURL, headers)\n\t\t}\n\t} else {\n\t\tconn, err = c.dialContext(c.ctx, &c.requestURL, c.headers)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(lateData) > 0 {\n\t\t_, err = conn.Write(lateData)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tc.conn.Store(conn)\n\treturn nil\n}\n\nfunc (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {\n\tconn := c.conn.Load()\n\tif conn != nil {\n\t\treturn wrapWsError0(conn.Write(b))\n\t}\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tconn = c.conn.Load()\n\tif c.err != nil {\n\t\treturn 0, c.err\n\t}\n\tif conn != nil {\n\t\treturn wrapWsError0(conn.Write(b))\n\t}\n\terr = c.writeRequest(b)\n\tc.err = err\n\tclose(c.create)\n\tif err != nil {\n\t\treturn\n\t}\n\treturn len(b), nil\n}\n\nfunc (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {\n\tconn := c.conn.Load()\n\tif conn != nil {\n\t\treturn wrapWsError(conn.WriteBuffer(buffer))\n\t}\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tif c.err != nil {\n\t\treturn c.err\n\t}\n\tconn = c.conn.Load()\n\tif conn != nil {\n\t\treturn wrapWsError(conn.WriteBuffer(buffer))\n\t}\n\terr := c.writeRequest(buffer.Bytes())\n\tc.err = err\n\tclose(c.create)\n\treturn err\n}\n\nfunc (c *EarlyWebsocketConn) Close() error {\n\tconn := c.conn.Load()\n\tif conn == nil {\n\t\treturn nil\n\t}\n\treturn conn.Close()\n}\n\nfunc (c *EarlyWebsocketConn) LocalAddr() net.Addr {\n\tconn := c.conn.Load()\n\tif conn == nil {\n\t\treturn M.Socksaddr{}\n\t}\n\treturn conn.LocalAddr()\n}\n\nfunc (c *EarlyWebsocketConn) RemoteAddr() net.Addr {\n\tconn := c.conn.Load()\n\tif conn == nil {\n\t\treturn M.Socksaddr{}\n\t}\n\treturn conn.RemoteAddr()\n}\n\nfunc (c *EarlyWebsocketConn) SetDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *EarlyWebsocketConn) SetReadDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *EarlyWebsocketConn) SetWriteDeadline(t time.Time) error {\n\treturn os.ErrInvalid\n}\n\nfunc (c *EarlyWebsocketConn) NeedAdditionalReadDeadline() bool {\n\treturn true\n}\n\nfunc (c *EarlyWebsocketConn) Upstream() any {\n\treturn common.PtrOrNil(c.conn.Load())\n}\n\nfunc (c *EarlyWebsocketConn) LazyHeadroom() bool {\n\treturn c.conn.Load() == nil\n}\n\nfunc wrapWsError(err error) error {\n\tif err == nil {\n\t\treturn nil\n\t}\n\tvar closedErr wsutil.ClosedError\n\tif errors.As(err, &closedErr) {\n\t\tif closedErr.Code == ws.StatusNormalClosure || closedErr.Code == ws.StatusNoStatusRcvd {\n\t\t\terr = io.EOF\n\t\t}\n\t}\n\treturn err\n}\n\nfunc wrapWsError0[T any](value T, err error) (T, error) {\n\tif err == nil {\n\t\treturn value, nil\n\t}\n\treturn value, wrapWsError(err)\n}\n"
  },
  {
    "path": "transport/v2raywebsocket/server.go",
    "content": "package v2raywebsocket\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/tls\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/transport/v2rayhttp\"\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\taTLS \"github.com/sagernet/sing/common/tls\"\n\tsHttp \"github.com/sagernet/sing/protocol/http\"\n\t\"github.com/sagernet/ws\"\n)\n\nvar _ adapter.V2RayServerTransport = (*Server)(nil)\n\ntype Server struct {\n\tctx                 context.Context\n\tlogger              logger.ContextLogger\n\ttlsConfig           tls.ServerConfig\n\thandler             adapter.V2RayServerTransportHandler\n\thttpServer          *http.Server\n\tpath                string\n\tmaxEarlyData        uint32\n\tearlyDataHeaderName string\n\tupgrader            ws.HTTPUpgrader\n}\n\nfunc NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {\n\tserver := &Server{\n\t\tctx:                 ctx,\n\t\tlogger:              logger,\n\t\ttlsConfig:           tlsConfig,\n\t\thandler:             handler,\n\t\tpath:                options.Path,\n\t\tmaxEarlyData:        options.MaxEarlyData,\n\t\tearlyDataHeaderName: options.EarlyDataHeaderName,\n\t\tupgrader: ws.HTTPUpgrader{\n\t\t\tTimeout: C.TCPTimeout,\n\t\t\tHeader:  options.Headers.Build(),\n\t\t},\n\t}\n\tif !strings.HasPrefix(server.path, \"/\") {\n\t\tserver.path = \"/\" + server.path\n\t}\n\tserver.httpServer = &http.Server{\n\t\tHandler:           server,\n\t\tReadHeaderTimeout: C.TCPTimeout,\n\t\tMaxHeaderBytes:    http.DefaultMaxHeaderBytes,\n\t\tBaseContext: func(net.Listener) context.Context {\n\t\t\treturn ctx\n\t\t},\n\t\tConnContext: func(ctx context.Context, c net.Conn) context.Context {\n\t\t\treturn log.ContextWithNewID(ctx)\n\t\t},\n\t}\n\treturn server, nil\n}\n\nfunc (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {\n\tif s.maxEarlyData == 0 || s.earlyDataHeaderName != \"\" {\n\t\tif request.URL.Path != s.path {\n\t\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"bad path: \", request.URL.Path))\n\t\t\treturn\n\t\t}\n\t}\n\tvar (\n\t\tearlyData []byte\n\t\terr       error\n\t\tconn      net.Conn\n\t)\n\tif s.earlyDataHeaderName == \"\" {\n\t\tif strings.HasPrefix(request.URL.RequestURI(), s.path) {\n\t\t\tearlyDataStr := request.URL.RequestURI()[len(s.path):]\n\t\t\tearlyData, err = base64.RawURLEncoding.DecodeString(earlyDataStr)\n\t\t} else {\n\t\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"bad path: \", request.URL.Path))\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tif request.URL.Path != s.path {\n\t\t\ts.invalidRequest(writer, request, http.StatusNotFound, E.New(\"bad path: \", request.URL.Path))\n\t\t\treturn\n\t\t}\n\t\tearlyDataStr := request.Header.Get(s.earlyDataHeaderName)\n\t\tif earlyDataStr != \"\" {\n\t\t\tearlyData, err = base64.RawURLEncoding.DecodeString(earlyDataStr)\n\t\t}\n\t}\n\tif err != nil {\n\t\ts.invalidRequest(writer, request, http.StatusBadRequest, E.Cause(err, \"decode early data\"))\n\t\treturn\n\t}\n\twsConn, _, _, err := ws.UpgradeHTTP(request, writer)\n\tif err != nil {\n\t\ts.invalidRequest(writer, request, 0, E.Cause(err, \"upgrade websocket connection\"))\n\t\treturn\n\t}\n\tsource := sHttp.SourceAddress(request)\n\tconn = NewConn(wsConn, source, ws.StateServerSide)\n\tif len(earlyData) > 0 {\n\t\tconn = bufio.NewCachedConn(conn, buf.As(earlyData))\n\t}\n\ts.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, source, M.Socksaddr{}, nil)\n}\n\nfunc (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {\n\tif statusCode > 0 {\n\t\twriter.WriteHeader(statusCode)\n\t}\n\ts.logger.ErrorContext(request.Context(), E.Cause(err, \"process connection from \", request.RemoteAddr))\n}\n\nfunc (s *Server) Network() []string {\n\treturn []string{N.NetworkTCP}\n}\n\nfunc (s *Server) Serve(listener net.Listener) error {\n\tif s.tlsConfig != nil {\n\t\tlistener = aTLS.NewListener(listener, s.tlsConfig)\n\t}\n\treturn s.httpServer.Serve(listener)\n}\n\nfunc (s *Server) ServePacket(listener net.PacketConn) error {\n\treturn os.ErrInvalid\n}\n\nfunc (s *Server) Close() error {\n\treturn common.Close(common.PtrOrNil(s.httpServer))\n}\n"
  },
  {
    "path": "transport/v2raywebsocket/writer.go",
    "content": "package v2raywebsocket\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\t\"math/rand\"\n\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/ws\"\n)\n\ntype Writer struct {\n\twriter   N.ExtendedWriter\n\tisServer bool\n}\n\nfunc NewWriter(writer io.Writer, state ws.State) *Writer {\n\treturn &Writer{\n\t\tbufio.NewExtendedWriter(writer),\n\t\tstate == ws.StateServerSide,\n\t}\n}\n\nfunc (w *Writer) WriteBuffer(buffer *buf.Buffer) error {\n\tvar payloadBitLength int\n\tdataLen := buffer.Len()\n\tdata := buffer.Bytes()\n\tif dataLen < 126 {\n\t\tpayloadBitLength = 1\n\t} else if dataLen < 65536 {\n\t\tpayloadBitLength = 3\n\t} else {\n\t\tpayloadBitLength = 9\n\t}\n\n\tvar headerLen int\n\theaderLen += 1 // FIN / RSV / OPCODE\n\theaderLen += payloadBitLength\n\tif !w.isServer {\n\t\theaderLen += 4 // MASK KEY\n\t}\n\n\theader := buffer.ExtendHeader(headerLen)\n\theader[0] = byte(ws.OpBinary) | 0x80\n\tif w.isServer {\n\t\theader[1] = 0\n\t} else {\n\t\theader[1] = 1 << 7\n\t}\n\n\tif dataLen < 126 {\n\t\theader[1] |= byte(dataLen)\n\t} else if dataLen < 65536 {\n\t\theader[1] |= 126\n\t\tbinary.BigEndian.PutUint16(header[2:], uint16(dataLen))\n\t} else {\n\t\theader[1] |= 127\n\t\tbinary.BigEndian.PutUint64(header[2:], uint64(dataLen))\n\t}\n\n\tif !w.isServer {\n\t\tmaskKey := rand.Uint32()\n\t\tbinary.BigEndian.PutUint32(header[1+payloadBitLength:], maskKey)\n\t\tws.Cipher(data, [4]byte(header[1+payloadBitLength:]), 0)\n\t}\n\n\treturn wrapWsError(w.writer.WriteBuffer(buffer))\n}\n\nfunc (w *Writer) FrontHeadroom() int {\n\treturn 14\n}\n"
  },
  {
    "path": "transport/wireguard/client_bind.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/pause\"\n\t\"github.com/sagernet/wireguard-go/conn\"\n)\n\nvar _ conn.Bind = (*ClientBind)(nil)\n\ntype ClientBind struct {\n\tctx                 context.Context\n\tlogger              logger.Logger\n\tpauseManager        pause.Manager\n\tbindCtx             context.Context\n\tbindDone            context.CancelFunc\n\tdialer              N.Dialer\n\treservedForEndpoint map[netip.AddrPort][3]uint8\n\tconnAccess          sync.Mutex\n\tconn                *wireConn\n\tdone                chan struct{}\n\tisConnect           bool\n\tconnectAddr         netip.AddrPort\n\treserved            [3]uint8\n}\n\nfunc NewClientBind(ctx context.Context, logger logger.Logger, dialer N.Dialer, isConnect bool, connectAddr netip.AddrPort, reserved [3]uint8) *ClientBind {\n\treturn &ClientBind{\n\t\tctx:                 ctx,\n\t\tlogger:              logger,\n\t\tpauseManager:        service.FromContext[pause.Manager](ctx),\n\t\tdialer:              dialer,\n\t\treservedForEndpoint: make(map[netip.AddrPort][3]uint8),\n\t\tdone:                make(chan struct{}),\n\t\tisConnect:           isConnect,\n\t\tconnectAddr:         connectAddr,\n\t\treserved:            reserved,\n\t}\n}\n\nfunc (c *ClientBind) connect() (*wireConn, error) {\n\tserverConn := c.conn\n\tif serverConn != nil {\n\t\tselect {\n\t\tcase <-serverConn.done:\n\t\t\tserverConn = nil\n\t\tdefault:\n\t\t\treturn serverConn, nil\n\t\t}\n\t}\n\tc.connAccess.Lock()\n\tdefer c.connAccess.Unlock()\n\tselect {\n\tcase <-c.done:\n\t\treturn nil, net.ErrClosed\n\tdefault:\n\t}\n\tserverConn = c.conn\n\tif serverConn != nil {\n\t\tselect {\n\t\tcase <-serverConn.done:\n\t\t\tserverConn = nil\n\t\tdefault:\n\t\t\treturn serverConn, nil\n\t\t}\n\t}\n\tif c.isConnect {\n\t\tudpConn, err := c.dialer.DialContext(c.bindCtx, N.NetworkUDP, M.SocksaddrFromNetIP(c.connectAddr))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc.conn = &wireConn{\n\t\t\tPacketConn: bufio.NewUnbindPacketConn(udpConn),\n\t\t\tdone:       make(chan struct{}),\n\t\t}\n\t} else {\n\t\tudpConn, err := c.dialer.ListenPacket(c.bindCtx, M.Socksaddr{Addr: netip.IPv4Unspecified()})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc.conn = &wireConn{\n\t\t\tPacketConn: bufio.NewPacketConn(udpConn),\n\t\t\tdone:       make(chan struct{}),\n\t\t}\n\t}\n\treturn c.conn, nil\n}\n\nfunc (c *ClientBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {\n\tselect {\n\tcase <-c.done:\n\t\tc.done = make(chan struct{})\n\tdefault:\n\t}\n\tc.bindCtx, c.bindDone = context.WithCancel(c.ctx)\n\treturn []conn.ReceiveFunc{c.receive}, 0, nil\n}\n\nfunc (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) (count int, err error) {\n\tudpConn, err := c.connect()\n\tif err != nil {\n\t\tselect {\n\t\tcase <-c.done:\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\tc.logger.Error(E.Cause(err, \"connect to server\"))\n\t\terr = nil\n\t\tc.pauseManager.WaitActive()\n\t\ttime.Sleep(time.Second)\n\t\treturn\n\t}\n\tn, addr, err := udpConn.ReadFrom(packets[0])\n\tif err != nil {\n\t\tudpConn.Close()\n\t\tselect {\n\t\tcase <-c.done:\n\t\tdefault:\n\t\t\tc.logger.Error(E.Cause(err, \"read packet\"))\n\t\t\terr = nil\n\t\t}\n\t\treturn\n\t}\n\tsizes[0] = n\n\tif n > 3 {\n\t\tb := packets[0]\n\t\tcommon.ClearArray(b[1:4])\n\t}\n\teps[0] = remoteEndpoint(M.SocksaddrFromNet(addr).Unwrap().AddrPort())\n\tcount = 1\n\treturn\n}\n\nfunc (c *ClientBind) Close() error {\n\tselect {\n\tcase <-c.done:\n\tdefault:\n\t\tclose(c.done)\n\t}\n\tif c.bindDone != nil {\n\t\tc.bindDone()\n\t}\n\tc.connAccess.Lock()\n\tdefer c.connAccess.Unlock()\n\tcommon.Close(common.PtrOrNil(c.conn))\n\treturn nil\n}\n\nfunc (c *ClientBind) SetMark(mark uint32) error {\n\treturn nil\n}\n\nfunc (c *ClientBind) Send(bufs [][]byte, ep conn.Endpoint, offset int) error {\n\tudpConn, err := c.connect()\n\tif err != nil {\n\t\tc.pauseManager.WaitActive()\n\t\ttime.Sleep(time.Second)\n\t\treturn err\n\t}\n\tdestination := netip.AddrPort(ep.(remoteEndpoint))\n\tfor _, buf := range bufs {\n\t\tif offset > 0 {\n\t\t\tbuf = buf[offset:]\n\t\t}\n\t\tif len(buf) > 3 {\n\t\t\treserved, loaded := c.reservedForEndpoint[destination]\n\t\t\tif !loaded {\n\t\t\t\treserved = c.reserved\n\t\t\t}\n\t\t\tcopy(buf[1:4], reserved[:])\n\t\t}\n\t\t_, err = udpConn.WriteToUDPAddrPort(buf, destination)\n\t\tif err != nil {\n\t\t\tudpConn.Close()\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *ClientBind) ParseEndpoint(s string) (conn.Endpoint, error) {\n\tap, err := netip.ParseAddrPort(s)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn remoteEndpoint(ap), nil\n}\n\nfunc (c *ClientBind) BatchSize() int {\n\treturn 1\n}\n\nfunc (c *ClientBind) SetReservedForEndpoint(destination netip.AddrPort, reserved [3]byte) {\n\tc.reservedForEndpoint[destination] = reserved\n}\n\ntype wireConn struct {\n\tnet.PacketConn\n\tconn   net.Conn\n\taccess sync.Mutex\n\tdone   chan struct{}\n}\n\nfunc (w *wireConn) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error) {\n\tif w.conn != nil {\n\t\treturn w.conn.Write(b)\n\t}\n\treturn w.PacketConn.WriteTo(b, M.SocksaddrFromNetIP(addr).UDPAddr())\n}\n\nfunc (w *wireConn) Close() error {\n\tw.access.Lock()\n\tdefer w.access.Unlock()\n\tselect {\n\tcase <-w.done:\n\t\treturn net.ErrClosed\n\tdefault:\n\t}\n\tw.PacketConn.Close()\n\tclose(w.done)\n\treturn nil\n}\n\nvar _ conn.Endpoint = (*remoteEndpoint)(nil)\n\ntype remoteEndpoint netip.AddrPort\n\nfunc (e remoteEndpoint) ClearSrc() {\n}\n\nfunc (e remoteEndpoint) SrcToString() string {\n\treturn \"\"\n}\n\nfunc (e remoteEndpoint) DstToString() string {\n\treturn (netip.AddrPort)(e).String()\n}\n\nfunc (e remoteEndpoint) DstToBytes() []byte {\n\tb, _ := (netip.AddrPort)(e).MarshalBinary()\n\treturn b\n}\n\nfunc (e remoteEndpoint) DstIP() netip.Addr {\n\treturn (netip.AddrPort)(e).Addr()\n}\n\nfunc (e remoteEndpoint) SrcIP() netip.Addr {\n\treturn netip.Addr{}\n}\n"
  },
  {
    "path": "transport/wireguard/device.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/wireguard-go/device\"\n\twgTun \"github.com/sagernet/wireguard-go/tun\"\n)\n\ntype Device interface {\n\twgTun.Device\n\tN.Dialer\n\tStart() error\n\tSetDevice(device *device.Device)\n\tInet4Address() netip.Addr\n\tInet6Address() netip.Addr\n}\n\ntype DeviceOptions struct {\n\tContext        context.Context\n\tLogger         logger.ContextLogger\n\tSystem         bool\n\tHandler        tun.Handler\n\tUDPTimeout     time.Duration\n\tCreateDialer   func(interfaceName string) N.Dialer\n\tName           string\n\tMTU            uint32\n\tAddress        []netip.Prefix\n\tAllowedAddress []netip.Prefix\n}\n\nfunc NewDevice(options DeviceOptions) (Device, error) {\n\tif !options.System {\n\t\treturn newStackDevice(options)\n\t} else if !tun.WithGVisor {\n\t\treturn newSystemDevice(options)\n\t} else {\n\t\treturn newSystemStackDevice(options)\n\t}\n}\n\ntype NatDevice interface {\n\tDevice\n\tCreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)\n}\n"
  },
  {
    "path": "transport/wireguard/device_nat.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing-tun/ping\"\n\t\"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/logger\"\n)\n\nvar _ Device = (*natDeviceWrapper)(nil)\n\ntype natDeviceWrapper struct {\n\tDevice\n\tctx            context.Context\n\tlogger         logger.ContextLogger\n\tpacketOutbound chan *buf.Buffer\n\trewriter       *ping.SourceRewriter\n\tbuffer         [][]byte\n}\n\nfunc NewNATDevice(ctx context.Context, logger logger.ContextLogger, upstream Device) NatDevice {\n\twrapper := &natDeviceWrapper{\n\t\tDevice:         upstream,\n\t\tctx:            ctx,\n\t\tlogger:         logger,\n\t\tpacketOutbound: make(chan *buf.Buffer, 256),\n\t\trewriter:       ping.NewSourceRewriter(ctx, logger, upstream.Inet4Address(), upstream.Inet6Address()),\n\t}\n\treturn wrapper\n}\n\nfunc (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {\n\tselect {\n\tcase packet := <-d.packetOutbound:\n\t\tdefer packet.Release()\n\t\tsizes[0] = copy(bufs[0][offset:], packet.Bytes())\n\t\treturn 1, nil\n\tdefault:\n\t}\n\treturn d.Device.Read(bufs, sizes, offset)\n}\n\nfunc (d *natDeviceWrapper) Write(bufs [][]byte, offset int) (int, error) {\n\tfor _, buffer := range bufs {\n\t\thandled, err := d.rewriter.WriteBack(buffer[offset:])\n\t\tif handled {\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t} else {\n\t\t\td.buffer = append(d.buffer, buffer)\n\t\t}\n\t}\n\tif len(d.buffer) > 0 {\n\t\t_, err := d.Device.Write(d.buffer, offset)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\td.buffer = d.buffer[:0]\n\t}\n\treturn 0, nil\n}\n\nfunc (d *natDeviceWrapper) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\tctx := log.ContextWithNewID(d.ctx)\n\tsession := tun.DirectRouteSession{\n\t\tSource:      metadata.Source.Addr,\n\t\tDestination: metadata.Destination.Addr,\n\t}\n\td.rewriter.CreateSession(session, routeContext)\n\td.logger.InfoContext(ctx, \"linked \", metadata.Network, \" connection from \", metadata.Source.AddrString(), \" to \", metadata.Destination.AddrString())\n\treturn &natDestination{device: d, session: session}, nil\n}\n\nvar _ tun.DirectRouteDestination = (*natDestination)(nil)\n\ntype natDestination struct {\n\tdevice  *natDeviceWrapper\n\tsession tun.DirectRouteSession\n\tclosed  atomic.Bool\n}\n\nfunc (d *natDestination) WritePacket(buffer *buf.Buffer) error {\n\td.device.rewriter.RewritePacket(buffer.Bytes())\n\td.device.packetOutbound <- buffer\n\treturn nil\n}\n\nfunc (d *natDestination) Close() error {\n\td.closed.Store(true)\n\td.device.rewriter.DeleteSession(d.session)\n\treturn nil\n}\n\nfunc (d *natDestination) IsClosed() bool {\n\treturn d.closed.Load()\n}\n"
  },
  {
    "path": "transport/wireguard/device_stack.go",
    "content": "//go:build with_gvisor\n\npackage wireguard\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sagernet/gvisor/pkg/buffer\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/header\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/stack\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/transport/udp\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing-tun/ping\"\n\t\"github.com/sagernet/sing/common/buf\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/wireguard-go/device\"\n\twgTun \"github.com/sagernet/wireguard-go/tun\"\n)\n\nvar _ NatDevice = (*stackDevice)(nil)\n\ntype stackDevice struct {\n\tctx            context.Context\n\tlogger         log.ContextLogger\n\tstack          *stack.Stack\n\tmtu            uint32\n\tevents         chan wgTun.Event\n\toutbound       chan *stack.PacketBuffer\n\tpacketOutbound chan *buf.Buffer\n\tdone           chan struct{}\n\tdispatcher     stack.NetworkDispatcher\n\tinet4Address   netip.Addr\n\tinet6Address   netip.Addr\n}\n\nfunc newStackDevice(options DeviceOptions) (*stackDevice, error) {\n\ttunDevice := &stackDevice{\n\t\tctx:            options.Context,\n\t\tlogger:         options.Logger,\n\t\tmtu:            options.MTU,\n\t\tevents:         make(chan wgTun.Event, 1),\n\t\toutbound:       make(chan *stack.PacketBuffer, 256),\n\t\tpacketOutbound: make(chan *buf.Buffer, 256),\n\t\tdone:           make(chan struct{}),\n\t}\n\tipStack, err := tun.NewGVisorStackWithOptions((*wireEndpoint)(tunDevice), stack.NICOptions{}, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar (\n\t\tinet4Address netip.Addr\n\t\tinet6Address netip.Addr\n\t)\n\tfor _, prefix := range options.Address {\n\t\taddr := tun.AddressFromAddr(prefix.Addr())\n\t\tprotoAddr := tcpip.ProtocolAddress{\n\t\t\tAddressWithPrefix: tcpip.AddressWithPrefix{\n\t\t\t\tAddress:   addr,\n\t\t\t\tPrefixLen: prefix.Bits(),\n\t\t\t},\n\t\t}\n\t\tif prefix.Addr().Is4() {\n\t\t\tinet4Address = prefix.Addr()\n\t\t\ttunDevice.inet4Address = inet4Address\n\t\t\tprotoAddr.Protocol = ipv4.ProtocolNumber\n\t\t} else {\n\t\t\tinet6Address = prefix.Addr()\n\t\t\ttunDevice.inet6Address = inet6Address\n\t\t\tprotoAddr.Protocol = ipv6.ProtocolNumber\n\t\t}\n\t\tgErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})\n\t\tif gErr != nil {\n\t\t\treturn nil, E.New(\"parse local address \", protoAddr.AddressWithPrefix, \": \", gErr.String())\n\t\t}\n\t}\n\ttunDevice.stack = ipStack\n\tif options.Handler != nil {\n\t\tipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)\n\t\tipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)\n\t\ticmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout)\n\t\ticmpForwarder.SetLocalAddresses(inet4Address, inet6Address)\n\t\tipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)\n\t\tipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)\n\t}\n\treturn tunDevice, nil\n}\n\nfunc (w *stackDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\taddr := tcpip.FullAddress{\n\t\tNIC:  tun.DefaultNIC,\n\t\tPort: destination.Port,\n\t\tAddr: tun.AddressFromAddr(destination.Addr),\n\t}\n\tbind := tcpip.FullAddress{\n\t\tNIC: tun.DefaultNIC,\n\t}\n\tvar networkProtocol tcpip.NetworkProtocolNumber\n\tif destination.IsIPv4() {\n\t\tif !w.inet4Address.IsValid() {\n\t\t\treturn nil, E.New(\"missing IPv4 local address\")\n\t\t}\n\t\tnetworkProtocol = header.IPv4ProtocolNumber\n\t\tbind.Addr = tun.AddressFromAddr(w.inet4Address)\n\t} else {\n\t\tif !w.inet6Address.IsValid() {\n\t\t\treturn nil, E.New(\"missing IPv6 local address\")\n\t\t}\n\t\tnetworkProtocol = header.IPv6ProtocolNumber\n\t\tbind.Addr = tun.AddressFromAddr(w.inet6Address)\n\t}\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\ttcpConn, err := DialTCPWithBind(ctx, w.stack, bind, addr, networkProtocol)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn tcpConn, nil\n\tcase N.NetworkUDP:\n\t\tudpConn, err := gonet.DialUDP(w.stack, &bind, &addr, networkProtocol)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn udpConn, nil\n\tdefault:\n\t\treturn nil, E.Extend(N.ErrUnknownNetwork, network)\n\t}\n}\n\nfunc (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tbind := tcpip.FullAddress{\n\t\tNIC: tun.DefaultNIC,\n\t}\n\tvar networkProtocol tcpip.NetworkProtocolNumber\n\tif destination.IsIPv4() {\n\t\tnetworkProtocol = header.IPv4ProtocolNumber\n\t\tbind.Addr = tun.AddressFromAddr(w.inet4Address)\n\t} else {\n\t\tnetworkProtocol = header.IPv6ProtocolNumber\n\t\tbind.Addr = tun.AddressFromAddr(w.inet4Address)\n\t}\n\tudpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn udpConn, nil\n}\n\nfunc (w *stackDevice) Inet4Address() netip.Addr {\n\treturn w.inet4Address\n}\n\nfunc (w *stackDevice) Inet6Address() netip.Addr {\n\treturn w.inet6Address\n}\n\nfunc (w *stackDevice) SetDevice(device *device.Device) {\n}\n\nfunc (w *stackDevice) Start() error {\n\tw.events <- wgTun.EventUp\n\treturn nil\n}\n\nfunc (w *stackDevice) File() *os.File {\n\treturn nil\n}\n\nfunc (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {\n\tselect {\n\tcase packet, ok := <-w.outbound:\n\t\tif !ok {\n\t\t\treturn 0, os.ErrClosed\n\t\t}\n\t\tdefer packet.DecRef()\n\t\tvar copyN int\n\t\t/*rangeIterate(packet.Data().AsRange(), func(view *buffer.View) {\n\t\t\tcopyN += copy(bufs[0][offset+copyN:], view.AsSlice())\n\t\t})*/\n\t\tfor _, view := range packet.AsSlices() {\n\t\t\tcopyN += copy(bufs[0][offset+copyN:], view)\n\t\t}\n\t\tsizes[0] = copyN\n\t\treturn 1, nil\n\tcase packet := <-w.packetOutbound:\n\t\tdefer packet.Release()\n\t\tsizes[0] = copy(bufs[0][offset:], packet.Bytes())\n\t\treturn 1, nil\n\tcase <-w.done:\n\t\treturn 0, os.ErrClosed\n\t}\n}\n\nfunc (w *stackDevice) Write(bufs [][]byte, offset int) (count int, err error) {\n\tfor _, b := range bufs {\n\t\tb = b[offset:]\n\t\tif len(b) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tvar networkProtocol tcpip.NetworkProtocolNumber\n\t\tswitch header.IPVersion(b) {\n\t\tcase header.IPv4Version:\n\t\t\tnetworkProtocol = header.IPv4ProtocolNumber\n\t\tcase header.IPv6Version:\n\t\t\tnetworkProtocol = header.IPv6ProtocolNumber\n\t\t}\n\t\tpacketBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{\n\t\t\tPayload: buffer.MakeWithData(b),\n\t\t})\n\t\tw.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer)\n\t\tpacketBuffer.DecRef()\n\t\tcount++\n\t}\n\treturn\n}\n\nfunc (w *stackDevice) Flush() error {\n\treturn nil\n}\n\nfunc (w *stackDevice) MTU() (int, error) {\n\treturn int(w.mtu), nil\n}\n\nfunc (w *stackDevice) Name() (string, error) {\n\treturn \"sing-box\", nil\n}\n\nfunc (w *stackDevice) Events() <-chan wgTun.Event {\n\treturn w.events\n}\n\nfunc (w *stackDevice) Close() error {\n\tclose(w.done)\n\tclose(w.events)\n\tw.stack.Close()\n\tfor _, endpoint := range w.stack.CleanupEndpoints() {\n\t\tendpoint.Abort()\n\t}\n\tw.stack.Wait()\n\treturn nil\n}\n\nfunc (w *stackDevice) BatchSize() int {\n\treturn 1\n}\n\nfunc (w *stackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\tctx := log.ContextWithNewID(w.ctx)\n\tdestination, err := ping.ConnectGVisor(\n\t\tctx, w.logger,\n\t\tmetadata.Source.Addr, metadata.Destination.Addr,\n\t\trouteContext,\n\t\tw.stack,\n\t\tw.inet4Address, w.inet6Address,\n\t\ttimeout,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tw.logger.InfoContext(ctx, \"linked \", metadata.Network, \" connection from \", metadata.Source.AddrString(), \" to \", metadata.Destination.AddrString())\n\treturn destination, nil\n}\n\nvar _ stack.LinkEndpoint = (*wireEndpoint)(nil)\n\ntype wireEndpoint stackDevice\n\nfunc (ep *wireEndpoint) MTU() uint32 {\n\treturn ep.mtu\n}\n\nfunc (ep *wireEndpoint) SetMTU(mtu uint32) {\n}\n\nfunc (ep *wireEndpoint) MaxHeaderLength() uint16 {\n\treturn 0\n}\n\nfunc (ep *wireEndpoint) LinkAddress() tcpip.LinkAddress {\n\treturn \"\"\n}\n\nfunc (ep *wireEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {\n}\n\nfunc (ep *wireEndpoint) Capabilities() stack.LinkEndpointCapabilities {\n\treturn stack.CapabilityRXChecksumOffload\n}\n\nfunc (ep *wireEndpoint) Attach(dispatcher stack.NetworkDispatcher) {\n\tep.dispatcher = dispatcher\n}\n\nfunc (ep *wireEndpoint) IsAttached() bool {\n\treturn ep.dispatcher != nil\n}\n\nfunc (ep *wireEndpoint) Wait() {\n}\n\nfunc (ep *wireEndpoint) ARPHardwareType() header.ARPHardwareType {\n\treturn header.ARPHardwareNone\n}\n\nfunc (ep *wireEndpoint) AddHeader(buffer *stack.PacketBuffer) {\n}\n\nfunc (ep *wireEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {\n\treturn true\n}\n\nfunc (ep *wireEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) {\n\tfor _, packetBuffer := range list.AsSlice() {\n\t\tpacketBuffer.IncRef()\n\t\tselect {\n\t\tcase <-ep.done:\n\t\t\treturn 0, &tcpip.ErrClosedForSend{}\n\t\tcase ep.outbound <- packetBuffer:\n\t\t}\n\t}\n\treturn list.Len(), nil\n}\n\nfunc (ep *wireEndpoint) Close() {\n}\n\nfunc (ep *wireEndpoint) SetOnCloseAction(f func()) {\n}\n"
  },
  {
    "path": "transport/wireguard/device_stack_gonet.go",
    "content": "//go:build with_gvisor\n\npackage wireguard\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/sagernet/gvisor/pkg/tcpip\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/stack\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp\"\n\t\"github.com/sagernet/gvisor/pkg/waiter\"\n\t\"github.com/sagernet/sing-tun\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n)\n\nfunc DialTCPWithBind(ctx context.Context, s *stack.Stack, localAddr, remoteAddr tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*gonet.TCPConn, error) {\n\t// Create TCP endpoint, then connect.\n\tvar wq waiter.Queue\n\tep, err := s.NewEndpoint(tcp.ProtocolNumber, network, &wq)\n\tif err != nil {\n\t\treturn nil, errors.New(err.String())\n\t}\n\n\t// Create wait queue entry that notifies a channel.\n\t//\n\t// We do this unconditionally as Connect will always return an error.\n\twaitEntry, notifyCh := waiter.NewChannelEntry(waiter.WritableEvents)\n\twq.EventRegister(&waitEntry)\n\tdefer wq.EventUnregister(&waitEntry)\n\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\tdefault:\n\t}\n\n\t// Bind before connect if requested.\n\tif localAddr != (tcpip.FullAddress{}) {\n\t\tif err = ep.Bind(localAddr); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"ep.Bind(%+v) = %s\", localAddr, err)\n\t\t}\n\t}\n\n\terr = ep.Connect(remoteAddr)\n\tif _, ok := err.(*tcpip.ErrConnectStarted); ok {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tep.Close()\n\t\t\treturn nil, ctx.Err()\n\t\tcase <-notifyCh:\n\t\t}\n\n\t\terr = ep.LastError()\n\t}\n\tif err != nil {\n\t\tep.Close()\n\t\treturn nil, &net.OpError{\n\t\t\tOp:   \"connect\",\n\t\t\tNet:  \"tcp\",\n\t\t\tAddr: M.SocksaddrFromNetIP(netip.AddrPortFrom(tun.AddrFromAddress(remoteAddr.Addr), remoteAddr.Port)).TCPAddr(),\n\t\t\tErr:  errors.New(err.String()),\n\t\t}\n\t}\n\n\t// sing-box added: set keepalive\n\tep.SocketOptions().SetKeepAlive(true)\n\tkeepAliveIdle := tcpip.KeepaliveIdleOption(15 * time.Second)\n\tep.SetSockOpt(&keepAliveIdle)\n\tkeepAliveInterval := tcpip.KeepaliveIntervalOption(15 * time.Second)\n\tep.SetSockOpt(&keepAliveInterval)\n\n\treturn gonet.NewTCPConn(&wq, ep), nil\n}\n"
  },
  {
    "path": "transport/wireguard/device_stack_stub.go",
    "content": "//go:build !with_gvisor\n\npackage wireguard\n\nimport \"github.com/sagernet/sing-tun\"\n\nfunc newStackDevice(options DeviceOptions) (Device, error) {\n\treturn nil, tun.ErrGVisorNotIncluded\n}\n\nfunc newSystemStackDevice(options DeviceOptions) (Device, error) {\n\treturn nil, tun.ErrGVisorNotIncluded\n}\n"
  },
  {
    "path": "transport/wireguard/device_system.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"runtime\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/wireguard-go/device\"\n\twgTun \"github.com/sagernet/wireguard-go/tun\"\n)\n\nvar _ Device = (*systemDevice)(nil)\n\ntype systemDevice struct {\n\toptions      DeviceOptions\n\tdialer       N.Dialer\n\tdevice       tun.Tun\n\tbatchDevice  tun.LinuxTUN\n\tevents       chan wgTun.Event\n\tcloseOnce    sync.Once\n\tinet4Address netip.Addr\n\tinet6Address netip.Addr\n}\n\nfunc newSystemDevice(options DeviceOptions) (*systemDevice, error) {\n\tif options.Name == \"\" {\n\t\toptions.Name = tun.CalculateInterfaceName(\"wg\")\n\t}\n\tvar inet4Address netip.Addr\n\tvar inet6Address netip.Addr\n\tif len(options.Address) > 0 {\n\t\tif prefix := common.Find(options.Address, func(it netip.Prefix) bool {\n\t\t\treturn it.Addr().Is4()\n\t\t}); prefix.IsValid() {\n\t\t\tinet4Address = prefix.Addr()\n\t\t}\n\t}\n\tif len(options.Address) > 0 {\n\t\tif prefix := common.Find(options.Address, func(it netip.Prefix) bool {\n\t\t\treturn it.Addr().Is6()\n\t\t}); prefix.IsValid() {\n\t\t\tinet6Address = prefix.Addr()\n\t\t}\n\t}\n\treturn &systemDevice{\n\t\toptions:      options,\n\t\tdialer:       options.CreateDialer(options.Name),\n\t\tevents:       make(chan wgTun.Event, 1),\n\t\tinet4Address: inet4Address,\n\t\tinet6Address: inet6Address,\n\t}, nil\n}\n\nfunc (w *systemDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\treturn w.dialer.DialContext(ctx, network, destination)\n}\n\nfunc (w *systemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn w.dialer.ListenPacket(ctx, destination)\n}\n\nfunc (w *systemDevice) Inet4Address() netip.Addr {\n\treturn w.inet4Address\n}\n\nfunc (w *systemDevice) Inet6Address() netip.Addr {\n\treturn w.inet6Address\n}\n\nfunc (w *systemDevice) SetDevice(device *device.Device) {\n}\n\nfunc (w *systemDevice) Start() error {\n\tnetworkManager := service.FromContext[adapter.NetworkManager](w.options.Context)\n\ttunOptions := tun.Options{\n\t\tName: w.options.Name,\n\t\tInet4Address: common.Filter(w.options.Address, func(it netip.Prefix) bool {\n\t\t\treturn it.Addr().Is4()\n\t\t}),\n\t\tInet6Address: common.Filter(w.options.Address, func(it netip.Prefix) bool {\n\t\t\treturn it.Addr().Is6()\n\t\t}),\n\t\tMTU:            w.options.MTU,\n\t\tGSO:            true,\n\t\tInterfaceScope: true,\n\t\tInet4RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool {\n\t\t\treturn it.Addr().Is4()\n\t\t}),\n\t\tInet6RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool { return it.Addr().Is6() }),\n\t\tInterfaceMonitor:  networkManager.InterfaceMonitor(),\n\t\tInterfaceFinder:   networkManager.InterfaceFinder(),\n\t\tLogger:            w.options.Logger,\n\t}\n\t// works with Linux, macOS with IFSCOPE routes, not tested on Windows\n\tif runtime.GOOS == \"darwin\" {\n\t\ttunOptions.AutoRoute = true\n\t}\n\ttunInterface, err := tun.New(tunOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = tunInterface.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\tw.options.Logger.Info(\"started at \", w.options.Name)\n\tw.device = tunInterface\n\tbatchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN)\n\tif isBatchTUN && batchTUN.BatchSize() > 1 {\n\t\tw.batchDevice = batchTUN\n\t}\n\tw.events <- wgTun.EventUp\n\treturn nil\n}\n\nfunc (w *systemDevice) File() *os.File {\n\treturn nil\n}\n\nfunc (w *systemDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {\n\tif w.batchDevice != nil {\n\t\tcount, err = w.batchDevice.BatchRead(bufs, offset-tun.PacketOffset, sizes)\n\t} else {\n\t\tsizes[0], err = w.device.Read(bufs[0][offset-tun.PacketOffset:])\n\t\tif err == nil {\n\t\t\tcount = 1\n\t\t} else if errors.Is(err, tun.ErrTooManySegments) {\n\t\t\terr = wgTun.ErrTooManySegments\n\t\t}\n\t}\n\treturn\n}\n\nfunc (w *systemDevice) Write(bufs [][]byte, offset int) (count int, err error) {\n\tif w.batchDevice != nil {\n\t\treturn w.batchDevice.BatchWrite(bufs, offset)\n\t} else {\n\t\tfor _, packet := range bufs {\n\t\t\tif tun.PacketOffset > 0 {\n\t\t\t\tcommon.ClearArray(packet[offset-tun.PacketOffset : offset])\n\t\t\t\ttun.PacketFillHeader(packet[offset-tun.PacketOffset:], tun.PacketIPVersion(packet[offset:]))\n\t\t\t}\n\t\t\t_, err = w.device.Write(packet[offset-tun.PacketOffset:])\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\t// WireGuard will not read count\n\treturn\n}\n\nfunc (w *systemDevice) Flush() error {\n\treturn nil\n}\n\nfunc (w *systemDevice) MTU() (int, error) {\n\treturn int(w.options.MTU), nil\n}\n\nfunc (w *systemDevice) Name() (string, error) {\n\treturn w.options.Name, nil\n}\n\nfunc (w *systemDevice) Events() <-chan wgTun.Event {\n\treturn w.events\n}\n\nfunc (w *systemDevice) Close() error {\n\tclose(w.events)\n\treturn w.device.Close()\n}\n\nfunc (w *systemDevice) BatchSize() int {\n\tif w.batchDevice != nil {\n\t\treturn w.batchDevice.BatchSize()\n\t}\n\treturn 1\n}\n"
  },
  {
    "path": "transport/wireguard/device_system_stack.go",
    "content": "//go:build with_gvisor\n\npackage wireguard\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/sagernet/gvisor/pkg/buffer\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/header\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/stack\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp\"\n\t\"github.com/sagernet/gvisor/pkg/tcpip/transport/udp\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing-tun/ping\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/sagernet/wireguard-go/device\"\n)\n\nvar _ Device = (*systemStackDevice)(nil)\n\ntype systemStackDevice struct {\n\t*systemDevice\n\tctx       context.Context\n\tlogger    logger.ContextLogger\n\tstack     *stack.Stack\n\tendpoint  *deviceEndpoint\n\twriteBufs [][]byte\n}\n\nfunc newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {\n\tsystem, err := newSystemDevice(options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tendpoint := &deviceEndpoint{\n\t\tmtu:  options.MTU,\n\t\tdone: make(chan struct{}),\n\t}\n\tipStack, err := tun.NewGVisorStackWithOptions(endpoint, stack.NICOptions{}, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar (\n\t\tinet4Address netip.Addr\n\t\tinet6Address netip.Addr\n\t)\n\tfor _, prefix := range options.Address {\n\t\taddr := tun.AddressFromAddr(prefix.Addr())\n\t\tprotoAddr := tcpip.ProtocolAddress{\n\t\t\tAddressWithPrefix: tcpip.AddressWithPrefix{\n\t\t\t\tAddress:   addr,\n\t\t\t\tPrefixLen: prefix.Bits(),\n\t\t\t},\n\t\t}\n\t\tif prefix.Addr().Is4() {\n\t\t\tinet4Address = prefix.Addr()\n\t\t\tprotoAddr.Protocol = ipv4.ProtocolNumber\n\t\t} else {\n\t\t\tinet6Address = prefix.Addr()\n\t\t\tprotoAddr.Protocol = ipv6.ProtocolNumber\n\t\t}\n\t\tgErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})\n\t\tif gErr != nil {\n\t\t\treturn nil, E.New(\"parse local address \", protoAddr.AddressWithPrefix, \": \", gErr.String())\n\t\t}\n\t}\n\tif options.Handler != nil {\n\t\tipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)\n\t\tipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)\n\t\ticmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout)\n\t\ticmpForwarder.SetLocalAddresses(inet4Address, inet6Address)\n\t\tipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)\n\t\tipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)\n\t}\n\treturn &systemStackDevice{\n\t\tctx:          options.Context,\n\t\tlogger:       options.Logger,\n\t\tsystemDevice: system,\n\t\tstack:        ipStack,\n\t\tendpoint:     endpoint,\n\t}, nil\n}\n\nfunc (w *systemStackDevice) SetDevice(device *device.Device) {\n\tw.endpoint.device = device\n}\n\nfunc (w *systemStackDevice) Write(bufs [][]byte, offset int) (count int, err error) {\n\tif w.batchDevice != nil {\n\t\tw.writeBufs = w.writeBufs[:0]\n\t\tfor _, packet := range bufs {\n\t\t\tif !w.writeStack(packet[offset:]) {\n\t\t\t\tw.writeBufs = append(w.writeBufs, packet)\n\t\t\t}\n\t\t}\n\t\tif len(w.writeBufs) > 0 {\n\t\t\treturn w.batchDevice.BatchWrite(bufs, offset)\n\t\t}\n\t} else {\n\t\tfor _, packet := range bufs {\n\t\t\tif !w.writeStack(packet[offset:]) {\n\t\t\t\tif tun.PacketOffset > 0 {\n\t\t\t\t\tcommon.ClearArray(packet[offset-tun.PacketOffset : offset])\n\t\t\t\t\ttun.PacketFillHeader(packet[offset-tun.PacketOffset:], tun.PacketIPVersion(packet[offset:]))\n\t\t\t\t}\n\t\t\t\t_, err = w.device.Write(packet[offset-tun.PacketOffset:])\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\t// WireGuard will not read count\n\treturn\n}\n\nfunc (w *systemStackDevice) Close() error {\n\tclose(w.endpoint.done)\n\tw.stack.Close()\n\tfor _, endpoint := range w.stack.CleanupEndpoints() {\n\t\tendpoint.Abort()\n\t}\n\tw.stack.Wait()\n\treturn w.systemDevice.Close()\n}\n\nfunc (w *systemStackDevice) writeStack(packet []byte) bool {\n\tvar (\n\t\tnetworkProtocol tcpip.NetworkProtocolNumber\n\t\tdestination     netip.Addr\n\t)\n\tswitch header.IPVersion(packet) {\n\tcase header.IPv4Version:\n\t\tnetworkProtocol = header.IPv4ProtocolNumber\n\t\tdestination = netip.AddrFrom4(header.IPv4(packet).DestinationAddress().As4())\n\tcase header.IPv6Version:\n\t\tnetworkProtocol = header.IPv6ProtocolNumber\n\t\tdestination = netip.AddrFrom16(header.IPv6(packet).DestinationAddress().As16())\n\t}\n\tfor _, prefix := range w.options.Address {\n\t\tif prefix.Contains(destination) {\n\t\t\treturn false\n\t\t}\n\t}\n\tpacketBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{\n\t\tPayload: buffer.MakeWithData(packet),\n\t})\n\tw.endpoint.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer)\n\tpacketBuffer.DecRef()\n\treturn true\n}\n\nfunc (w *systemStackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\tctx := log.ContextWithNewID(w.ctx)\n\tdestination, err := ping.ConnectGVisor(\n\t\tctx, w.logger,\n\t\tmetadata.Source.Addr, metadata.Destination.Addr,\n\t\trouteContext,\n\t\tw.stack,\n\t\tw.inet4Address, w.inet6Address,\n\t\ttimeout,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tw.logger.InfoContext(ctx, \"linked \", metadata.Network, \" connection from \", metadata.Source.AddrString(), \" to \", metadata.Destination.AddrString())\n\treturn destination, nil\n}\n\ntype deviceEndpoint struct {\n\tmtu        uint32\n\tdone       chan struct{}\n\tdevice     *device.Device\n\tdispatcher stack.NetworkDispatcher\n}\n\nfunc (ep *deviceEndpoint) MTU() uint32 {\n\treturn ep.mtu\n}\n\nfunc (ep *deviceEndpoint) SetMTU(mtu uint32) {\n}\n\nfunc (ep *deviceEndpoint) MaxHeaderLength() uint16 {\n\treturn 0\n}\n\nfunc (ep *deviceEndpoint) LinkAddress() tcpip.LinkAddress {\n\treturn \"\"\n}\n\nfunc (ep *deviceEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {\n}\n\nfunc (ep *deviceEndpoint) Capabilities() stack.LinkEndpointCapabilities {\n\treturn stack.CapabilityRXChecksumOffload\n}\n\nfunc (ep *deviceEndpoint) Attach(dispatcher stack.NetworkDispatcher) {\n\tep.dispatcher = dispatcher\n}\n\nfunc (ep *deviceEndpoint) IsAttached() bool {\n\treturn ep.dispatcher != nil\n}\n\nfunc (ep *deviceEndpoint) Wait() {\n}\n\nfunc (ep *deviceEndpoint) ARPHardwareType() header.ARPHardwareType {\n\treturn header.ARPHardwareNone\n}\n\nfunc (ep *deviceEndpoint) AddHeader(buffer *stack.PacketBuffer) {\n}\n\nfunc (ep *deviceEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {\n\treturn true\n}\n\nfunc (ep *deviceEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) {\n\tfor _, packetBuffer := range list.AsSlice() {\n\t\tdestination := packetBuffer.Network().DestinationAddress()\n\t\tep.device.InputPacket(destination.AsSlice(), packetBuffer.AsSlices())\n\t}\n\treturn list.Len(), nil\n}\n\nfunc (ep *deviceEndpoint) Close() {\n}\n\nfunc (ep *deviceEndpoint) SetOnCloseAction(f func()) {\n}\n"
  },
  {
    "path": "transport/wireguard/endpoint.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tF \"github.com/sagernet/sing/common/format\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/x/list\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/pause\"\n\t\"github.com/sagernet/wireguard-go/conn\"\n\t\"github.com/sagernet/wireguard-go/device\"\n\n\t\"go4.org/netipx\"\n)\n\ntype Endpoint struct {\n\toptions        EndpointOptions\n\tpeers          []peerConfig\n\tipcConf        string\n\tallowedAddress []netip.Prefix\n\ttunDevice      Device\n\tnatDevice      NatDevice\n\tdevice         *device.Device\n\tallowedIPs     *device.AllowedIPs\n\tpause          pause.Manager\n\tpauseCallback  *list.Element[pause.Callback]\n}\n\nfunc NewEndpoint(options EndpointOptions) (*Endpoint, error) {\n\tif options.PrivateKey == \"\" {\n\t\treturn nil, E.New(\"missing private key\")\n\t}\n\tprivateKeyBytes, err := base64.StdEncoding.DecodeString(options.PrivateKey)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"decode private key\")\n\t}\n\tprivateKey := hex.EncodeToString(privateKeyBytes)\n\tipcConf := \"private_key=\" + privateKey\n\tif options.ListenPort != 0 {\n\t\tipcConf += \"\\nlisten_port=\" + F.ToString(options.ListenPort)\n\t}\n\tvar peers []peerConfig\n\tfor peerIndex, rawPeer := range options.Peers {\n\t\tpeer := peerConfig{\n\t\t\tallowedIPs: rawPeer.AllowedIPs,\n\t\t\tkeepalive:  rawPeer.PersistentKeepaliveInterval,\n\t\t}\n\t\tif rawPeer.Endpoint.Addr.IsValid() {\n\t\t\tpeer.endpoint = rawPeer.Endpoint.AddrPort()\n\t\t} else if rawPeer.Endpoint.IsDomain() {\n\t\t\tpeer.destination = rawPeer.Endpoint\n\t\t}\n\t\tpublicKeyBytes, err := base64.StdEncoding.DecodeString(rawPeer.PublicKey)\n\t\tif err != nil {\n\t\t\treturn nil, E.Cause(err, \"decode public key for peer \", peerIndex)\n\t\t}\n\t\tpeer.publicKeyHex = hex.EncodeToString(publicKeyBytes)\n\t\tif rawPeer.PreSharedKey != \"\" {\n\t\t\tpreSharedKeyBytes, err := base64.StdEncoding.DecodeString(rawPeer.PreSharedKey)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, E.Cause(err, \"decode pre shared key for peer \", peerIndex)\n\t\t\t}\n\t\t\tpeer.preSharedKeyHex = hex.EncodeToString(preSharedKeyBytes)\n\t\t}\n\t\tif len(rawPeer.AllowedIPs) == 0 {\n\t\t\treturn nil, E.New(\"missing allowed ips for peer \", peerIndex)\n\t\t}\n\t\tif len(rawPeer.Reserved) > 0 {\n\t\t\tif len(rawPeer.Reserved) != 3 {\n\t\t\t\treturn nil, E.New(\"invalid reserved value for peer \", peerIndex, \", required 3 bytes, got \", len(peer.reserved))\n\t\t\t}\n\t\t\tcopy(peer.reserved[:], rawPeer.Reserved[:])\n\t\t}\n\t\tpeers = append(peers, peer)\n\t}\n\tvar allowedPrefixBuilder netipx.IPSetBuilder\n\tfor _, peer := range options.Peers {\n\t\tfor _, prefix := range peer.AllowedIPs {\n\t\t\tallowedPrefixBuilder.AddPrefix(prefix)\n\t\t}\n\t}\n\tallowedIPSet, err := allowedPrefixBuilder.IPSet()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tallowedAddresses := allowedIPSet.Prefixes()\n\tif options.MTU == 0 {\n\t\toptions.MTU = 1408\n\t}\n\tdeviceOptions := DeviceOptions{\n\t\tContext:        options.Context,\n\t\tLogger:         options.Logger,\n\t\tSystem:         options.System,\n\t\tHandler:        options.Handler,\n\t\tUDPTimeout:     options.UDPTimeout,\n\t\tCreateDialer:   options.CreateDialer,\n\t\tName:           options.Name,\n\t\tMTU:            options.MTU,\n\t\tAddress:        options.Address,\n\t\tAllowedAddress: allowedAddresses,\n\t}\n\ttunDevice, err := NewDevice(deviceOptions)\n\tif err != nil {\n\t\treturn nil, E.Cause(err, \"create WireGuard device\")\n\t}\n\tnatDevice, isNatDevice := tunDevice.(NatDevice)\n\tif !isNatDevice {\n\t\tnatDevice = NewNATDevice(options.Context, options.Logger, tunDevice)\n\t}\n\treturn &Endpoint{\n\t\toptions:        options,\n\t\tpeers:          peers,\n\t\tipcConf:        ipcConf,\n\t\tallowedAddress: allowedAddresses,\n\t\ttunDevice:      tunDevice,\n\t\tnatDevice:      natDevice,\n\t}, nil\n}\n\nfunc (e *Endpoint) Start(resolve bool) error {\n\tif common.Any(e.peers, func(peer peerConfig) bool {\n\t\treturn !peer.endpoint.IsValid() && peer.destination.IsDomain()\n\t}) {\n\t\tif !resolve {\n\t\t\treturn nil\n\t\t}\n\t\tfor peerIndex, peer := range e.peers {\n\t\t\tif peer.endpoint.IsValid() || !peer.destination.IsDomain() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdestinationAddress, err := e.options.ResolvePeer(peer.destination.Fqdn)\n\t\t\tif err != nil {\n\t\t\t\treturn E.Cause(err, \"resolve endpoint domain for peer[\", peerIndex, \"]: \", peer.destination)\n\t\t\t}\n\t\t\te.peers[peerIndex].endpoint = netip.AddrPortFrom(destinationAddress, peer.destination.Port)\n\t\t}\n\t} else if resolve {\n\t\treturn nil\n\t}\n\tvar bind conn.Bind\n\twgListener, isWgListener := common.Cast[dialer.WireGuardListener](e.options.Dialer)\n\tif isWgListener {\n\t\tbind = conn.NewStdNetBind(wgListener.WireGuardControl())\n\t} else {\n\t\tvar (\n\t\t\tisConnect   bool\n\t\t\tconnectAddr netip.AddrPort\n\t\t\treserved    [3]uint8\n\t\t)\n\t\tif len(e.peers) == 1 && e.peers[0].endpoint.IsValid() {\n\t\t\tisConnect = true\n\t\t\tconnectAddr = e.peers[0].endpoint\n\t\t\treserved = e.peers[0].reserved\n\t\t}\n\t\tbind = NewClientBind(e.options.Context, e.options.Logger, e.options.Dialer, isConnect, connectAddr, reserved)\n\t}\n\tif isWgListener || len(e.peers) > 1 {\n\t\tfor _, peer := range e.peers {\n\t\t\tif peer.reserved != [3]uint8{} {\n\t\t\t\tbind.SetReservedForEndpoint(peer.endpoint, peer.reserved)\n\t\t\t}\n\t\t}\n\t}\n\terr := e.tunDevice.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogger := &device.Logger{\n\t\tVerbosef: func(format string, args ...interface{}) {\n\t\t\te.options.Logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))\n\t\t},\n\t\tErrorf: func(format string, args ...interface{}) {\n\t\t\te.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))\n\t\t},\n\t}\n\tvar deviceInput Device\n\tif e.natDevice != nil {\n\t\tdeviceInput = e.natDevice\n\t} else {\n\t\tdeviceInput = e.tunDevice\n\t}\n\twgDevice := device.NewDevice(e.options.Context, deviceInput, bind, logger, e.options.Workers)\n\te.tunDevice.SetDevice(wgDevice)\n\tipcConf := e.ipcConf\n\tfor _, peer := range e.peers {\n\t\tipcConf += peer.GenerateIpcLines()\n\t}\n\terr = wgDevice.IpcSet(ipcConf)\n\tif err != nil {\n\t\treturn E.Cause(err, \"setup wireguard: \\n\", ipcConf)\n\t}\n\te.device = wgDevice\n\te.pause = service.FromContext[pause.Manager](e.options.Context)\n\tif e.pause != nil {\n\t\te.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated)\n\t}\n\te.allowedIPs = (*device.AllowedIPs)(unsafe.Pointer(reflect.Indirect(reflect.ValueOf(wgDevice)).FieldByName(\"allowedips\").UnsafeAddr()))\n\treturn nil\n}\n\nfunc (e *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\tif !destination.Addr.IsValid() {\n\t\treturn nil, E.Cause(os.ErrInvalid, \"invalid non-IP destination\")\n\t}\n\treturn e.tunDevice.DialContext(ctx, network, destination)\n}\n\nfunc (e *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\tif !destination.Addr.IsValid() {\n\t\treturn nil, E.Cause(os.ErrInvalid, \"invalid non-IP destination\")\n\t}\n\treturn e.tunDevice.ListenPacket(ctx, destination)\n}\n\nfunc (e *Endpoint) Close() error {\n\tif e.device != nil {\n\t\te.device.Close()\n\t}\n\tif e.pauseCallback != nil {\n\t\te.pause.UnregisterCallback(e.pauseCallback)\n\t}\n\treturn nil\n}\n\nfunc (e *Endpoint) Lookup(address netip.Addr) *device.Peer {\n\tif e.allowedIPs == nil {\n\t\treturn nil\n\t}\n\treturn e.allowedIPs.Lookup(address.AsSlice())\n}\n\nfunc (e *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {\n\tif e.natDevice == nil {\n\t\treturn nil, os.ErrInvalid\n\t}\n\treturn e.natDevice.CreateDestination(metadata, routeContext, timeout)\n}\n\nfunc (e *Endpoint) onPauseUpdated(event int) {\n\tswitch event {\n\tcase pause.EventDevicePaused, pause.EventNetworkPause:\n\t\te.device.Down()\n\tcase pause.EventDeviceWake, pause.EventNetworkWake:\n\t\te.device.Up()\n\t}\n}\n\ntype peerConfig struct {\n\tdestination     M.Socksaddr\n\tendpoint        netip.AddrPort\n\tpublicKeyHex    string\n\tpreSharedKeyHex string\n\tallowedIPs      []netip.Prefix\n\tkeepalive       uint16\n\treserved        [3]uint8\n}\n\nfunc (c peerConfig) GenerateIpcLines() string {\n\tipcLines := \"\\npublic_key=\" + c.publicKeyHex\n\tif c.endpoint.IsValid() {\n\t\tipcLines += \"\\nendpoint=\" + c.endpoint.String()\n\t}\n\tif c.preSharedKeyHex != \"\" {\n\t\tipcLines += \"\\npreshared_key=\" + c.preSharedKeyHex\n\t}\n\tfor _, allowedIP := range c.allowedIPs {\n\t\tipcLines += \"\\nallowed_ip=\" + allowedIP.String()\n\t}\n\tif c.keepalive > 0 {\n\t\tipcLines += \"\\npersistent_keepalive_interval=\" + F.ToString(c.keepalive)\n\t}\n\treturn ipcLines\n}\n"
  },
  {
    "path": "transport/wireguard/endpoint_options.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\ntype EndpointOptions struct {\n\tContext      context.Context\n\tLogger       logger.ContextLogger\n\tSystem       bool\n\tHandler      tun.Handler\n\tUDPTimeout   time.Duration\n\tDialer       N.Dialer\n\tCreateDialer func(interfaceName string) N.Dialer\n\tName         string\n\tMTU          uint32\n\tAddress      []netip.Prefix\n\tPrivateKey   string\n\tListenPort   uint16\n\tResolvePeer  func(domain string) (netip.Addr, error)\n\tPeers        []PeerOptions\n\tWorkers      int\n}\n\ntype PeerOptions struct {\n\tEndpoint                    M.Socksaddr\n\tPublicKey                   string\n\tPreSharedKey                string\n\tAllowedIPs                  []netip.Prefix\n\tPersistentKeepaliveInterval uint16\n\tReserved                    []uint8\n}\n"
  }
]