diff --git a/glorytun-udp/Makefile b/glorytun-udp/Makefile index 321f16ebc..eae86fe70 100644 --- a/glorytun-udp/Makefile +++ b/glorytun-udp/Makefile @@ -13,7 +13,7 @@ PKG_SOURCE_URL:=https://github.com/ysurac/glorytun.git PKG_SOURCE_VERSION:=203c5f2223d7d5146d659b51159b2f02c2480d15 PKG_NAME:=glorytun-udp PKG_VERSION:=0.3.4 -PKG_RELEASE:=6 +PKG_RELEASE:=7 PKG_LICENSE:=BSD-2-Clause PKG_LICENSE_FILES:=LICENSE diff --git a/glorytun-udp/init b/glorytun-udp/init index cb43b0496..70ea41a77 100755 --- a/glorytun-udp/init +++ b/glorytun-udp/init @@ -36,6 +36,29 @@ validate_section() { 'dev:string' } +# persist keeps the device, and the address set on it, once glorytun is +# disabled: the next VPN on that device (OpenVPN's tun0) got both, and +# omr-tracker saw its gateway down. Only this exact address is removed, +# the device may already belong to that VPN +release_addr() { + [ -n "$1" ] && [ -n "$2" ] && [ -n "$3" ] || return 0 + ip addr del "$2" peer "$3" dev "$1" 2>/dev/null + return 0 +} + +stopped_instance() { + local enable dev localip remoteip + validate_section "${1}" || return 0 + [ "${enable}" = "1" ] || release_addr "$dev" "$localip" "$remoteip" +} + +# force_stop_vpn disables then stops, without a start; an enabled instance +# keeps its address across a restart +service_stopped() { + config_load glorytun-udp + config_foreach stopped_instance glorytun-udp +} + start_instance() { local enable key host port listener proto mtuauto chacha dev mode local persist kxtimeout timetolerance keepalive localip remoteip @@ -47,7 +70,10 @@ start_instance() { return 1 } - [ "${enable}" = "1" ] || return 1 + [ "${enable}" = "1" ] || { + release_addr "$dev" "$localip" "$remoteip" + return 1 + } [ -n "${key}" ] || return 1 [ "${key}" != "secretkey" ] || return 1 diff --git a/tracebox/Makefile b/tracebox/Makefile index e225d970a..62fb89ec9 100644 --- a/tracebox/Makefile +++ b/tracebox/Makefile @@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=tracebox PKG_VERSION:=0.4.4 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.bz2 PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) diff --git a/tracebox/files/usr/share/tracebox/omr-mptcp-trace.lua b/tracebox/files/usr/share/tracebox/omr-mptcp-trace.lua index f7afd471b..99864f9e3 100644 --- a/tracebox/files/usr/share/tracebox/omr-mptcp-trace.lua +++ b/tracebox/files/usr/share/tracebox/omr-mptcp-trace.lua @@ -1,10 +1,45 @@ -pkt = ip{dst=to} / tcp{dst=65101} / MPCAPABLE +local function uci_get(key) + local f = io.popen("uci -q get " .. key) + local v = f and f:read("*l") or "" + if f then f:close() end + return v or "" +end + +-- Probe the port of the proxy in use: 65101 is shadowsocks-libev's, a VPS +-- may not run it or the path may treat it differently than the real one. +local port +local proxy = uci_get("openmptcprouter.settings.proxy") +if proxy:match("^shadowsocks%-rust") then + port = uci_get("shadowsocks-rust.sss0.server_port") +elseif proxy == "shadowsocks" then + port = uci_get("shadowsocks-libev.sss0.server_port") +end +port = tonumber(port) or 65101 + +-- tracebox's MPCAPABLE is MPTCP v0 (out-of-tree kernel), which the upstream +-- (v1, RFC 8684) kernel on the VPS ignores: build the 4-byte v1 SYN option +-- by hand (subtype 0 / version 1, flags H = HMAC-SHA256). +MPCAPABLE_V1 = TCPOption.new{kind=30, length=4, data={0x01, 0x01}} +pkt = ip{dst=to} / tcp{dst=port} / MPCAPABLE_V1 function cb(ttl, r_ip, mod) if r_ip == nil then r_ip = "*" end - print(ttl .. ": " .. tostring(r_ip) .. " " .. tostring(mod)) --- print(tostring(mod:original():ip())) + mod = tostring(mod) + -- Crafter can't parse a 4-byte MP_CAPABLE quoted back in ICMP and shows + -- it as raw bytes: that's the option kept, not stripped. The server's + -- 12-byte SYN/ACK option likewise shows as "-TCPOption +...Capable". + if mod:find("+RawLayer", 1, true) or mod:find("+TCPOptionMPTCPCapable", 1, true) then + mod = mod:gsub("%-TCPOption ", ""):gsub("%+RawLayer ", "") + end + print(ttl .. ": " .. tostring(r_ip) .. " " .. mod) end +print("Port: " .. port) result = tracebox(pkt, {callback="cb"}) -print("\nResult:\n" .. tostring(result)) \ No newline at end of file +print("\nResult:\n" .. tostring(result)) +-- The page's help text and omr-tracebox-mptcp key on these option names. +if result == nil then + print("No reply from the server") +elseif not tostring(result):find("TCPOptionMPTCPCapable", 1, true) then + print("-TCPOptionMPTCPCapable: no MPTCP in the server reply") +end diff --git a/v2raya-core/Makefile b/v2raya-core/Makefile index cacbb7ff4..b810c2048 100644 --- a/v2raya-core/Makefile +++ b/v2raya-core/Makefile @@ -11,7 +11,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=v2raya-core PKG_VERSION:=2.5.15 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_SOURCE:=v2rayA-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/v2rayA/v2rayA/tar.gz/v$(PKG_VERSION)? diff --git a/v2raya-core/test.sh b/v2raya-core/test.sh deleted file mode 100644 index 81243588d..000000000 --- a/v2raya-core/test.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-3.0-only -# -# Copyright (C) 2026 Amr Fateem - -case "$1" in -v2raya-core) - /usr/bin/v2raya_core version | grep -F "$2" - ;; -*) - exit 1 - ;; -esac diff --git a/v2raya/Makefile b/v2raya/Makefile index afe7ee386..7538a92ce 100644 --- a/v2raya/Makefile +++ b/v2raya/Makefile @@ -14,12 +14,15 @@ include $(TOPDIR)/rules.mk PKG_NAME:=v2raya PKG_VERSION:=2.5.15 -PKG_RELEASE:=10 +PKG_RELEASE:=11 PKG_SOURCE:=v2rayA-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/v2rayA/v2rayA/tar.gz/v$(PKG_VERSION)? PKG_HASH:=skip +WEB_FILE:=v2raya-web-$(PKG_VERSION).tar.gz +WEB_HASH:=76774f195ee74604fbc43a48fe74e5f3363e81ec68af02d1c26feeeacc0f63ae + PKG_BUILD_DIR:=$(BUILD_DIR)/v2rayA-$(PKG_VERSION)/service PKG_LICENSE:=AGPL-3.0-only @@ -117,12 +120,6 @@ define Package/v2raya/prerm } endef -# v2rayA publishes the built web UI as web.tar.gz on its releases. The -# GitHub release CDN has been returning 504/404 for it, which makes every -# matrix build fail. Keep the 871 KiB asset in the package instead; the -# hash of the upstream v2.5.13 asset is -# 76774f195ee74604fbc43a48fe74e5f3363e81ec68af02d1c26feeeacc0f63ae. - define Build/Prepare $(call Build/Prepare/Default) @@ -156,7 +153,7 @@ define Build/Prepare ( \ mkdir -p $(PKG_BUILD_DIR)/server/router/web ; \ - gzip -dc $(CURDIR)/files/web.tar.gz | $(HOST_TAR) -C $(PKG_BUILD_DIR)/server/router/web $(TAR_OPTIONS) ; \ + gzip -dc $(DL_DIR)/$(WEB_FILE) | $(HOST_TAR) -C $(PKG_BUILD_DIR)/server/router/web $(TAR_OPTIONS) ; \ ) endef @@ -184,5 +181,13 @@ define Package/v2raya/install $(INSTALL_DIR) $(1)/usr/share/v2raya endef +define Download/v2raya-web + URL:=https://github.com/v2rayA/v2rayA/releases/download/v$(PKG_VERSION)/ + URL_FILE:=web.tar.gz + FILE:=$(WEB_FILE) + HASH:=skip +endef + +$(eval $(call Download,v2raya-web)) $(eval $(call GoBinPackage,v2raya)) $(eval $(call BuildPackage,v2raya)) diff --git a/v2raya/files/v2raya.init b/v2raya/files/v2raya.init index b0051667c..51aaa5c69 100755 --- a/v2raya/files/v2raya.init +++ b/v2raya/files/v2raya.init @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-only # # Copyright (C) 2021 Tianling Shen +# Copyright (C) 2021 Tianling Shen # Copyright (C) 2026 Amr Fateem # # procd init script for v2rayA. diff --git a/v2raya/files/web.tar.gz b/v2raya/files/web.tar.gz deleted file mode 100644 index 9adfc6671..000000000 Binary files a/v2raya/files/web.tar.gz and /dev/null differ diff --git a/v2raya/test.sh b/v2raya/test.sh deleted file mode 100644 index 5714d961e..000000000 --- a/v2raya/test.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-3.0-only -# -# Copyright (C) 2026 Amr Fateem - -case "$1" in -v2raya) - /usr/bin/v2raya --version | grep -F "$2" - ;; -*) - exit 1 - ;; -esac diff --git a/xray-core/Makefile b/xray-core/Makefile index 21734cd1e..e52e916f8 100644 --- a/xray-core/Makefile +++ b/xray-core/Makefile @@ -1,23 +1,26 @@ include $(TOPDIR)/rules.mk -PKG_NAME:=Xray-core +PKG_NAME:=xray-core PKG_VERSION:=26.11.9 -PKG_RELEASE:=30 +PKG_RELEASE:=31 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/XTLS/Xray-core/tar.gz/v$(PKG_VERSION)? PKG_HASH:=skip PKG_MAINTAINER:=Tianling Shen -PKG_LICENSE:=MPL-2.0 -PKG_LICENSE_FILES:=LICENSE +PKG_LICENSE:=MPL-2.0 GPL-3.0-or-later MIT +PKG_LICENSE_FILES:=LICENSE proxy/anytls/LICENSE.sing-anytls proxy/naive/LICENSE.sing-box proxy/shadowsocksr/LICENSE proxy/tuic/LICENSE.mihomo proxy/snell/LICENSE.mihomo transport/internet/shadowtls/LICENSE.mihomo transport/internet/kcptun/LICENSE.mihomo transport/internet/kcptun/LICENSE.kcptun -PKG_BUILD_DEPENDS:=golang/host +PKG_BUILD_DIR:=$(BUILD_DIR)/Xray-core-$(PKG_VERSION) +PKG_BUILD_DEPENDS:=golang/host upx/host +PKG_CONFIG_DEPENDS:=CONFIG_XRAY_CORE_NAIVE PKG_BUILD_PARALLEL:=1 PKG_USE_MIPS16:=0 PKG_BUILD_FLAGS:=no-mips16 GO_PKG:=github.com/xtls/xray-core +GO_PKG_LDFLAGS:=-s -w GO_PKG_BUILD_PKG:=$(GO_PKG)/main GO_PKG_LDFLAGS_X:= \ $(GO_PKG)/core.build=OpenWrt \ @@ -26,36 +29,93 @@ GO_PKG_LDFLAGS_X:= \ include $(INCLUDE_DIR)/package.mk include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk -define Package/xray/template +# Cronet's native archives contain LLVM bitcode. Use its matching linker while +# retaining OpenWrt's target GCC, sysroot, ABI flags and C runtime. +CRONET_CLANG_VERSION:=llvmorg-24-init-3796-g20e97c4b-27 +CRONET_CLANG_FILE:=clang-$(CRONET_CLANG_VERSION).tar.xz +CRONET_CLANG_DIR:=$(PKG_BUILD_DIR)/.cronet-clang + +ifeq ($(CONFIG_XRAY_CORE_NAIVE),y) + GO_PKG_TAGS+=with_naive_outbound + ifeq ($(CONFIG_USE_MUSL),y) + GO_PKG_TAGS+=with_musl + endif + # golang-package.mk also appends TARGET_LDFLAGS through -extldflags, after + # CGO_LDFLAGS. Select LLD here so the SDK cannot override it with ld.bfd. + TARGET_LDFLAGS:=$(filter-out -fuse-ld=%,$(TARGET_LDFLAGS)) -B$(CRONET_CLANG_DIR)/bin -fuse-ld=lld $(if $(filter arm i386 mipsel loongarch64,$(ARCH)),-Wl$(comma)-no-pie) $(if $(filter loongarch64,$(ARCH)),-Wl$(comma)--no-relax) + + ifeq ($(HOST_OS)-$(HOST_ARCH),Linux-x86_64) + define Download/cronet-clang + URL:=https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64 + URL_FILE:=$(CRONET_CLANG_FILE) + FILE:=$(CRONET_CLANG_FILE) + HASH:=skip + endef + $(eval $(call Download,cronet-clang)) + endif + + define Build/Prepare + @if [ "$(HOST_OS)-$(HOST_ARCH)" != "Linux-x86_64" ]; then \ + echo "xray-core: Naive outbound requires a Linux x86_64 build host; disable CONFIG_XRAY_CORE_NAIVE on this host." >&2; \ + exit 1; \ + fi + $(call Build/Prepare/Default) + $(INSTALL_DIR) $(CRONET_CLANG_DIR) + $(TAR) -xJf $(DL_DIR)/$(CRONET_CLANG_FILE) -C $(CRONET_CLANG_DIR) + endef +endif + +# Apply only to this package; runtime DT detection restricts use to AN7581. +# Resolve Go through the same versioned PATH and environment as the build. +ifeq ($(GO_ARCH),arm64) + define Build/Compile + $(GO_PKG_VARS) python3 $(PKG_BUILD_DIR)/tools/eip93/overlay.py --go go --out $(PKG_BUILD_DIR)/.eip93-overlay + $(call GoPackage/Build/Compile,-overlay=$(PKG_BUILD_DIR)/.eip93-overlay/overlay.json) + endef +endif + +# Compress once per build, not once per package/rootfs installation. +# Keep Go's output intact and let the .built stamp guard the packed copy. +define Build/Install + $(INSTALL_DIR) $(PKG_BUILD_DIR)/.upx + $(INSTALL_BIN) $(GO_PKG_BUILD_BIN_DIR)/main $(PKG_BUILD_DIR)/.upx/xray + $(STAGING_DIR_HOST)/bin/upx --lzma --best $(PKG_BUILD_DIR)/.upx/xray +endef + +# Go's -s -w flags strip the binary before UPX; do not strip it again. +RSTRIP:=: + +define Package/xray-core TITLE:=A platform for building proxies to bypass network restrictions SECTION:=net CATEGORY:=Network URL:=https://xtls.github.io -endef - -define Package/xray-core - $(call Package/xray/template) DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle endef -define Package/xray/description +define Package/xray-core/config + config XRAY_CORE_NAIVE + bool "Enable NaiveProxy outbound (Chromium/Cronet)" + depends on PACKAGE_xray-core + depends on (aarch64 || arm || i386 || x86_64 || mipsel || riscv64 || loongarch64) + depends on (USE_MUSL || USE_GLIBC) + depends on !arm || (arm_v7 && HAS_FPU) + default $(if $(filter Linux-x86_64,$(HOST_OS)-$(HOST_ARCH)),y,n) + help + Link Chromium/Cronet for NaiveProxy HTTP/2 and HTTP/3 outbounds. + Requires an x86_64 Linux build host and adds a native library to Xray. + Disabled by default on other build hosts, including ARM and ARM64. + Naive inbound support remains available when this option is disabled. +endef + +define Package/xray-core/description Xray, Penetrates Everything. It helps you to build your own computer network. It secures your network connections and thus protects your privacy. endef -define Package/xray-core/description - $(call Package/xray/description) -endef - -define Package/xray-core/conffiles -/etc/xray/ -/etc/config/xray -endef - define Package/xray-core/install - $(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR)) - $(INSTALL_DIR) $(1)/usr/bin/ - $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/main $(1)/usr/bin/xray + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/.upx/xray $(1)/usr/bin/xray endef $(eval $(call BuildPackage,xray-core)) diff --git a/xray-core/README.eip93.md b/xray-core/README.eip93.md new file mode 100644 index 000000000..5b058bf18 --- /dev/null +++ b/xray-core/README.eip93.md @@ -0,0 +1,173 @@ +# AN7581 EIP93 hybrid AES-GCM + +Xray 26.10.31 includes an automatic AN7581-only AES-GCM path through Linux AF_ALG. +EIP93 performs AES-CTR; GHASH stays on the CPU. This is not native hardware GCM. + +The ARM64 package build generates a private Go source overlay. It never changes +the shared toolchain. The overlay is checked against Go 1.27's implementation; +unknown source layouts fail the build so that they can be reviewed explicitly. +Go TLS/uTLS and other users of Go AES-GCM can use it. Native Cronet TLS does not. +TLS framing, nonce management, overlap checks and authentication checks remain +in their original implementations. + +## Automatic selection and fallback + +- Read the NUL-delimited root DT `compatible` property from sysfs or procfs. +- Enable only `airoha,an7581` / `airoha,en7581`, with no ARM AES instructions. +- Explicitly exclude AN7583/EN7583, other platforms, missing/malformed DT and FIPS. +- Use only standard 12-byte nonces, 16-byte tags and 4–32 KiB payloads. +- Bind `gcm_base(ctr(aes-eip93),ghash-generic)` on older kernels, retrying + `gcm_base(ctr(aes-eip93),ghash-lib)` on kernels such as 7.3 when the old + name fails to bind, including driver or policy errors. Both names require + the exact EIP93 CTR driver; failure of both keeps the original Go path. +- Verify each newly opened key against Go encryption/decryption and bad-tag + rejection before using it. Bound sockets, serialize each key's operations, + set socket timeouts and clear private buffers. The kernel must also be healthy; + socket timeouts do not recover a hung DMA engine. +- Missing modules, failed verification or syscall errors use the original Go + implementation. Authentication failures remain failures. +- `XRAY_EIP93=off` disables the optimization; there is no force-enable override. + +The first successful activation prints: +`Xray EIP93: AN7581 AES-GCM hybrid backend enabled (AF_ALG CTR + CPU GHASH)`. +This message alone is not performance evidence; use engine interrupt counters. + +Firmware needs `kmod-crypto-hw-airoha-eip93`, `kmod-crypto-gcm`, `kmod-crypto-user` and +GHASH/DES dependencies. The QWRT EIP93 package selects these dependencies. +BSP 5.4 needs the supplied driver and DT node. Linux 6.18 uses its native driver +with matching DMA cache-line and immutable-SA fixes. Linux 7.3 carries the +same protections plus the CTR next-IV carry fix, and additionally +needs the supplied AF_ALG exception for the exact EIP93 hybrid GCM driver; +leave `crypto.af_alg_restrict` at its default value. Native hardware authenc +is withheld because its bad-tag tests fail; hybrid GCM authenticates through +the kernel GCM implementation and passes its own bad-tag tests. AN7583 remains on the normal +Xray path until separate hardware measurements justify an alternative. + +## Validation + +The entire 100–190 patch stack replays on the official 26.10.31 archive with +`patch --fuzz=0`. The version upgrade refreshed dependency contexts and fixed +patch 100's TLS protobuf descriptor length; `infra/conf` tests catch that issue. +Existing protocol packages compile and their available tests pass. The ARM64 +OpenWrt package also builds with `with_naive_outbound with_musl` and Cronet LLD. +This is not a claim of rerunning every historical cross-core protocol matrix. + +On Nokia XG-040G-MD UBI / AN7581 / Linux 5.4.302: + +- AES-128/192/256 comparisons against the independent Go generic implementation. +- Payload boundaries, in-place operations, corrupted tags, concurrent requests, + closed-socket fallback, nonstandard nonce/tag exclusion and the off switch. +- Go `crypto/cipher` short regression suite on the device. +- Ordinary client → optimized server and optimized client → ordinary server, + plus ordinary controls, using VLESS/TLS 1.2 AES-256-GCM. Each connection sends + and receives deterministic data and checks SHA-256. Hardware interrupts grow + only in optimized runs. + +Local raw evidence and reproducible test sources: +`/home/lean/Desktop/airoha-5.4-evidence/eip93-20261022/`. + +## Final Xray throughput + +VLESS/TLS 1.2, server restricted to AES-256-GCM, same patched source and +Naive/Cronet build features. AN7581 at 1.2 GHz, Gigabit LAN. Each of two +alternating runs per row sends and receives 32 MiB and verifies SHA-256. +The optimized binary is the packaged UPX artifact; startup completes before +timing. Both ordinary-client/optimized-server and optimized-client/ordinary- +server directions passed. Values are mean Mbps; connection/relay overhead is +included. Receive results varied roughly 95–112 Mbps across optimized runs. +These final measurements use the driver extracted from the final IPK, including +the immutable-SA and native-authenc fixes; see `xray-e2e-packaged-final.log`. + +| AN7581 role | Build | Router sends | Router receives | +| --- | --- | ---: | ---: | +| server | normal | 35.0 | 32.1 | +| server | eip93 | 136.6 | 98.2 | +| client | normal | 34.4 | 32.5 | +| client | eip93 | 133.3 | 104.5 | + +All eight runs agree on SHA-256: +`e09320c5b00b34bb704802136c599a95b3996332ba84d7c7f21112b6231b6bd0`. +Ordinary runs add no EIP93 interrupts; each optimized run adds 8198. + +This supports enabling the AN7581 path for bulk Go AES-GCM traffic, with +roughly 3–4x throughput versus ordinary Go. It does not imply gigabit GCM, +the same gain over optimized OpenSSL, or any measured benefit on AN7583. + +The packaged binary with `XRAY_EIP93=off` also passed bidirectional hash +checks at 33.5/34.9 Mbps, with no EIP93 interrupt increase. The host AMD64 +Go cipher regression passes with the overlay present and acceleration disabled. + +The native-authenc quarantine preserves the kernel `authenc` composition over +EIP93 CBC/HMAC primitives. That composed implementation passes 12- and 13-byte +AAD encryption/decryption and corrupted-tag tests; `/proc/crypto` identifies +its module as `authenc`, rather than `crypto_hw_eip93`. + +The final Nokia UBI firmware builds and includes Xray 26.10.21, EIP93, libdes, +GCM, GHASH and AF_ALG modules. Its embedded FIT DTB has the EIP93 node at +0x1fb70000 / GIC SPI 44. All 16 generated AN7581 BSP DTBs contain that node, +including standalone Nokia, Fiberhome and W1700K DTS layouts. + +The final image passes `sysupgrade -T` on the Nokia test board +(`Signature check OK`). This validates the image without flashing it. +The board governor is restored to `schedutil` and test proxy processes stop +after each run. The final driver remains loaded temporarily for inspection. + +## Linux 7.3 regression validation (2026-10-29) + +On H3C HM2004-DU, five repeated hardware test runs passed AES-128/192/256-GCM, +boundary lengths, in-place operations, concurrent use, bad-tag rejection and +closed-socket fallback. CBC encrypt/decrypt/encrypt and CTR exact-rollover IV +tests failed with the original 7.3 driver and passed after the driver fixes. +The packaged Xray completed a 4 MiB AES-256-GCM Shadowsocks transfer with +matching SHA-256 and EIP93 activation at both ends. The disabled backend +completed the same transfer without increasing EIP93 interrupts. +`crypto.af_alg_restrict` remained at 1. These are correctness checks, not +throughput measurements. The legacy 5.4 name is retained; 5.4 hardware was +not rerun in this validation. + +## Xray 26.10.29 regression validation (2026-10-31) + +All ten patches were refreshed using temporary files and labeled unified diffs. +They replay on the official v26.10.29 archive with zero fuzz and no offsets. +The ARM64 OpenWrt package builds with Naive/Cronet enabled and passes the UPX +integrity check. Configuration, AnyTLS and VMess tests pass; the other added +protocol packages compile in the focused regression run. + +On FiberHome HG5585F CT / AN7581 / Linux 7.3, the package was installed as +26.10.29-1 and its binary hash matched the IPK. The EIP93 hardware tests pass +AES-128/192/256, payload boundaries, in-place operations, bad-tag rejection, +concurrency and closed-socket fallback. A 4 MiB AES-256-GCM Shadowsocks +loopback transfer with both peers in the packaged Xray matches SHA-256 +`2b07811057df887086f06a67edc6ebf911de8b6741156e7a2eb1416a4b8b1b2e`. +With `XRAY_EIP93=off`, the interrupt count stays at 1818; the enabled run +increases it to 2651, and unmodified `/sbin/usage` reports the same count. +`crypto.af_alg_restrict` remains 1. + +These checks use the fixed EIP93 and AF_ALG modules loaded temporarily on the +test machine. They establish correctness and acceleration activation, not +throughput, a full firmware build or persistence of the kernel fixes after +reboot. The firmware must include the matching Linux 7.3 fixes. + +## Xray 26.10.31 build validation (2026-11-01) + +The source archive SHA-256 is +`be44f95d497ad4ba6e9d1dcfbbd71e8e51f812aff077d4c15c9947eff5e3046a`. +All ten patches replay with zero fuzz and zero offsets. Temporary files and +labeled unified diffs refresh the changed dependency contexts in patches 150 +and 180, including upstream's protoc-gen-go-grpc update to 1.6.2. + +EIP93 backend selection now tries the exact `ghash-lib` composition after any +legacy-name bind error, matching the Mihomo compatibility behavior. An ARM64 +QEMU regression checks success, missing algorithms, invalid arguments, absent +drivers and permission errors, including failure of both names. DT gating and +the Go GCM tests pass; the hardware-only test is skipped without AN7581. + +The ARM64 OpenWrt package builds with Naive/Cronet, musl and the private EIP93 +Go overlay enabled. Configuration, AnyTLS and VMess tests pass, and the other +added protocol packages compile. The resulting ARM64 program starts under QEMU +and reports 26.10.31; the UPX integrity check passes. + +This run does not repeat the historical board tests or validate Linux 7.3 +hardware throughput. The matching kernel EIP93/AF_ALG fixes described above +remain required; native Cronet TLS does not use the Go acceleration overlay. +Local build and regression logs: `/tmp/xray-26.10.31-validation/`. diff --git a/xray-core/patches/100-restore-allow-insecure.patch b/xray-core/patches/100-restore-allow-insecure.patch new file mode 100644 index 000000000..56a1d32b4 --- /dev/null +++ b/xray-core/patches/100-restore-allow-insecure.patch @@ -0,0 +1,154 @@ +From: coolsnowwolf +Subject: [PATCH] tls: restore explicit allowInsecure compatibility + +Restore the opt-in TLS option for legacy proxy certificates, including +Hysteria2 servers without SANs. Keep certificate verification enabled by +default and continue enforcing explicit certificate pins and names. + +Signed-off-by: coolsnowwolf + +--- a/infra/conf/transport_security.go ++++ b/infra/conf/transport_security.go +@@ -359,9 +359,7 @@ + config.RejectUnknownSni = c.RejectUnknownSNI + config.MasterKeyLog = c.MasterKeyLog + +- if c.AllowInsecure { +- return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn)`) +- } ++ config.AllowInsecure = c.AllowInsecure + if c.PinnedPeerCertSha256 != "" { + for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") { + v = strings.TrimSpace(v) +--- a/infra/conf/tls_allow_insecure_test.go ++++ b/infra/conf/tls_allow_insecure_test.go +@@ -0,0 +1,70 @@ ++package conf_test ++ ++import ( ++ "crypto/sha256" ++ "crypto/tls" ++ "encoding/hex" ++ "encoding/json" ++ "io" ++ "log" ++ "net" ++ "net/http" ++ "net/http/httptest" ++ "strings" ++ "testing" ++ "time" ++ ++ "github.com/xtls/xray-core/infra/conf" ++ xraytls "github.com/xtls/xray-core/transport/internet/tls" ++ "google.golang.org/protobuf/proto" ++) ++ ++func TestTLSAllowInsecure(t *testing.T) { ++ server := httptest.NewUnstartedServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) ++ server.Config.ErrorLog = log.New(io.Discard, "", 0) ++ server.StartTLS() ++ defer server.Close() ++ hash := sha256.Sum256(server.Certificate().Raw) ++ pin := hex.EncodeToString(hash[:]) ++ for _, tc := range []struct { ++ name string ++ options string ++ wantOK bool ++ }{ ++ {"default rejects untrusted certificate", `{}`, false}, ++ {"false rejects untrusted certificate", `{"allowInsecure":false}`, false}, ++ {"true accepts untrusted certificate", `{"allowInsecure":true}`, true}, ++ {"valid pin", `{"pinnedPeerCertSha256":"` + pin + `"}`, true}, ++ {"true with valid pin", `{"allowInsecure":true,"pinnedPeerCertSha256":"` + pin + `"}`, true}, ++ {"true preserves invalid pin rejection", `{"allowInsecure":true,"pinnedPeerCertSha256":"` + strings.Repeat("00", 32) + `"}`, false}, ++ {"true preserves explicit name verification", `{"allowInsecure":true,"verifyPeerCertByName":"wrong.invalid"}`, false}, ++ } { ++ t.Run(tc.name, func(t *testing.T) { ++ var input conf.TLSConfig ++ if err := json.Unmarshal([]byte(tc.options), &input); err != nil { ++ t.Fatal(err) ++ } ++ input.ServerName = "example.com" ++ built, err := input.Build() ++ if err != nil { ++ t.Fatal(err) ++ } ++ // Exercise the protobuf path used by the core, not just the JSON loader. ++ data, err := proto.Marshal(built) ++ if err != nil { ++ t.Fatal(err) ++ } ++ var config xraytls.Config ++ if err := proto.Unmarshal(data, &config); err != nil { ++ t.Fatal(err) ++ } ++ conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 3 * time.Second}, "tcp", server.Listener.Addr().String(), config.GetTLSConfig()) ++ if conn != nil { ++ conn.Close() ++ } ++ if (err == nil) != tc.wantOK { ++ t.Fatalf("handshake error = %v, want success %v", err, tc.wantOK) ++ } ++ }) ++ } ++} +--- a/transport/internet/tls/config.proto ++++ b/transport/internet/tls/config.proto +@@ -38,7 +38,8 @@ + } + + message Config { +- // Number 1 was assigned and used by an legacy option. ++ // Allow unverified certificates only when explicitly enabled by the client. ++ bool allow_insecure = 1; + + // List of certificates to be served on server. + repeated Certificate certificate = 2; +--- a/transport/internet/tls/config.pb.go ++++ b/transport/internet/tls/config.pb.go +@@ -178,6 +178,8 @@ + + type Config struct { + state protoimpl.MessageState `protogen:"open.v1"` ++ // Allow unverified certificates only when explicitly enabled by the client. ++ AllowInsecure bool `protobuf:"varint,1,opt,name=allow_insecure,json=allowInsecure,proto3" json:"allow_insecure,omitempty"` + // List of certificates to be served on server. + Certificate []*Certificate `protobuf:"bytes,2,rep,name=certificate,proto3" json:"certificate,omitempty"` + // Override server name. +@@ -241,6 +243,13 @@ + return file_transport_internet_tls_config_proto_rawDescGZIP(), []int{1} + } + ++func (x *Config) GetAllowInsecure() bool { ++ if x != nil { ++ return x.AllowInsecure ++ } ++ return false ++} ++ + func (x *Config) GetCertificate() []*Certificate { + if x != nil { + return x.Certificate +@@ -385,8 +394,9 @@ + "\x05Usage\x12\x10\n" + + "\fENCIPHERMENT\x10\x00\x12\x14\n" + + "\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" + +- "\x0fAUTHORITY_ISSUE\x10\x02\"\xca\x06\n" + +- "\x06Config\x12J\n" + ++ "\x0fAUTHORITY_ISSUE\x10\x02\"\xf1\x06\n" + ++ "\x06Config\x12%\n" + ++ "\x0eallow_insecure\x18\x01 \x01(\bR\rallowInsecure\x12J\n" + + "\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" + + "\vserver_name\x18\x03 \x01(\tR\n" + + "serverName\x12#\n" + +--- a/transport/internet/tls/config.go ++++ b/transport/internet/tls/config.go +@@ -385,6 +385,7 @@ + PinnedPeerCertSha256: c.PinnedPeerCertSha256, + } + config := &tls.Config{ ++ InsecureSkipVerify: c.AllowInsecure, + Rand: randCarrier, + ClientSessionCache: globalSessionCache, + RootCAs: root, diff --git a/xray-core/patches/110-add-anytls-proxy.patch b/xray-core/patches/110-add-anytls-proxy.patch new file mode 100644 index 000000000..3a41d046f --- /dev/null +++ b/xray-core/patches/110-add-anytls-proxy.patch @@ -0,0 +1,1202 @@ +From: coolsnowwolf +Subject: [PATCH] proxy: add AnyTLS inbound and outbound + +Use sing-anytls at 580984e4d8cb for authenticated reusable TLS sessions. +Adapt both directions to Xray routing, TCP and UDP-over-TCP. Support +multiple inbound users, padding negotiation and fragmented prologues. +Require TLS on inbound, preserve private-target restrictions, and join +inbound copy loops before releasing stream state. Retain outbound TLS, +socket marks, dialer chains and session-pool lifecycle handling. + +Reference: https://github.com/XTLS/Xray-core/pull/5907 +Signed-off-by: coolsnowwolf + +--- a/go.mod ++++ b/go.mod +@@ -53,6 +53,8 @@ + github.com/pion/logging v0.2.4 // indirect + github.com/pion/transport/v4 v4.1.0 // indirect + github.com/quic-go/qpack v0.6.0 // indirect ++ github.com/sagernet/sing v0.8.14 ++ github.com/sagernet/sing-anytls v0.0.0-20260928104022-580984e4d8cb + github.com/vishvananda/netns v0.0.5 // indirect + github.com/wlynxg/anet v0.0.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect +--- a/go.sum ++++ b/go.sum +@@ -73,6 +73,10 @@ + github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= + github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= + github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= ++github.com/sagernet/sing v0.8.14 h1:S6Netv4F61uNAuD/sUbHnGuNUEPwtL08Ouk0//CVYgM= ++github.com/sagernet/sing v0.8.14/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA= ++github.com/sagernet/sing-anytls v0.0.0-20260928104022-580984e4d8cb h1:n4sF2zv0CR1gcb4awh61t11t0FZmcP40x5enB8HARjE= ++github.com/sagernet/sing-anytls v0.0.0-20260928104022-580984e4d8cb/go.mod h1:VRKLnj0ObEsN228h+zuzf91p+6sjtYE8SBaLPVsBy5o= + github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= + github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= + github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= +--- a/infra/conf/anytls.go ++++ b/infra/conf/anytls.go +@@ -0,0 +1,63 @@ ++package conf ++ ++import ( ++ "github.com/xtls/xray-core/common/errors" ++ "github.com/xtls/xray-core/proxy/anytls" ++ "google.golang.org/protobuf/proto" ++ "strings" ++) ++ ++// AnyTLSClientConfig follows the outbound schema proposed in Xray-core#5907. ++// Session intervals are expressed in seconds, as in that proposal. ++type AnyTLSClientConfig struct { ++ Address *Address `json:"address"` ++ Port uint16 `json:"port"` ++ Password string `json:"password"` ++ Level uint32 `json:"level"` ++ IdleSessionCheckInterval uint32 `json:"idleSessionCheckInterval"` ++ IdleSessionTimeout uint32 `json:"idleSessionTimeout"` ++ MinIdleSession int32 `json:"minIdleSession"` ++} ++ ++func (c *AnyTLSClientConfig) Build() (proto.Message, error) { ++ if c.Address == nil || c.Port == 0 { ++ return nil, errors.New("anytls: server address and port are required") ++ } ++ if c.Password == "" { ++ return nil, errors.New("anytls: password is required") ++ } ++ if c.MinIdleSession < 0 { ++ return nil, errors.New("anytls: minIdleSession must be non-negative") ++ } ++ return &anytls.ClientConfig{ ++ Address: c.Address.Build(), Port: uint32(c.Port), Password: c.Password, Level: c.Level, ++ IdleSessionCheckInterval: c.IdleSessionCheckInterval, ++ IdleSessionTimeout: c.IdleSessionTimeout, MinIdleSession: uint32(c.MinIdleSession), ++ }, nil ++} ++ ++type AnyTLSUser struct { ++ Password string `json:"password"` ++ Email string `json:"email"` ++ Level uint32 `json:"level"` ++} ++type AnyTLSServerConfig struct { ++ Users []AnyTLSUser `json:"users"` ++ PaddingScheme []string `json:"paddingScheme"` ++} ++ ++func (c *AnyTLSServerConfig) Build() (proto.Message, error) { ++ if len(c.Users) == 0 { ++ return nil, errors.New("anytls: users required") ++ } ++ config := &anytls.ServerConfig{PaddingScheme: []byte(strings.Join(c.PaddingScheme, "\n"))} ++ seen := map[string]bool{} ++ for _, u := range c.Users { ++ if u.Password == "" || seen[u.Password] { ++ return nil, errors.New("anytls: empty or duplicate password") ++ } ++ seen[u.Password] = true ++ config.Users = append(config.Users, &anytls.User{Password: u.Password, Email: u.Email, Level: u.Level}) ++ } ++ return config, nil ++} +--- a/infra/conf/anytls_test.go ++++ b/infra/conf/anytls_test.go +@@ -0,0 +1,45 @@ ++package conf_test ++ ++import ( ++ "encoding/json" ++ "github.com/xtls/xray-core/infra/conf" ++ "github.com/xtls/xray-core/proxy/anytls" ++ "google.golang.org/protobuf/proto" ++ "testing" ++) ++ ++func TestAnyTLSConfig(t *testing.T) { ++ for _, tc := range []struct { ++ input string ++ valid bool ++ }{ ++ {`{"address":"example.com","port":443,"password":"test","idleSessionCheckInterval":30,"idleSessionTimeout":60,"minIdleSession":2}`, true}, ++ {`{"port":443,"password":"test"}`, false}, ++ {`{"address":"example.com","password":"test"}`, false}, ++ {`{"address":"example.com","port":443}`, false}, ++ {`{"address":"example.com","port":443,"password":"test","minIdleSession":-1}`, false}, ++ } { ++ var config conf.AnyTLSClientConfig ++ if err := json.Unmarshal([]byte(tc.input), &config); err != nil { ++ t.Fatal(err) ++ } ++ built, err := config.Build() ++ if (err == nil) != tc.valid { ++ t.Fatalf("Build(%s): %v", tc.input, err) ++ } ++ if !tc.valid { ++ continue ++ } ++ data, err := proto.Marshal(built) ++ if err != nil { ++ t.Fatal(err) ++ } ++ var result anytls.ClientConfig ++ if err = proto.Unmarshal(data, &result); err != nil { ++ t.Fatal(err) ++ } ++ if result.Password != "test" || result.Port != 443 || result.IdleSessionTimeout != 60 || result.MinIdleSession != 2 { ++ t.Fatal("configuration lost during protobuf serialization") ++ } ++ } ++} +--- a/infra/conf/xray.go ++++ b/infra/conf/xray.go +@@ -35,6 +35,7 @@ + "vless": func() interface{} { return new(VLessInboundConfig) }, + "vmess": func() interface{} { return new(VMessInboundConfig) }, + "trojan": func() interface{} { return new(TrojanServerConfig) }, ++ "anytls": func() interface{} { return new(AnyTLSServerConfig) }, + "wireguard": func() interface{} { return &WireGuardConfig{IsClient: false} }, + "hysteria": func() interface{} { return new(HysteriaServerConfig) }, + "masque": func() interface{} { return new(MasqueServerConfig) }, +@@ -53,6 +54,7 @@ + "vless": func() interface{} { return new(VLessOutboundConfig) }, + "vmess": func() interface{} { return new(VMessOutboundConfig) }, + "trojan": func() interface{} { return new(TrojanClientConfig) }, ++ "anytls": func() interface{} { return new(AnyTLSClientConfig) }, + "hysteria": func() interface{} { return new(HysteriaClientConfig) }, + "masque": func() interface{} { return new(MasqueClientConfig) }, + "dns": func() interface{} { return new(DNSOutboundConfig) }, +--- a/main/distro/all/all.go ++++ b/main/distro/all/all.go +@@ -35,6 +35,7 @@ + _ "github.com/xtls/xray-core/app/observatory" + + // Inbound and outbound proxies. ++ _ "github.com/xtls/xray-core/proxy/anytls" + _ "github.com/xtls/xray-core/proxy/blackhole" + _ "github.com/xtls/xray-core/proxy/dns" + _ "github.com/xtls/xray-core/proxy/dokodemo" +--- a/proxy/anytls/LICENSE.sing-anytls ++++ b/proxy/anytls/LICENSE.sing-anytls +@@ -0,0 +1,14 @@ ++Copyright (C) 2022 by nekohasekai ++ ++This program is free software: you can redistribute it and/or modify ++it under the terms of the GNU General Public License as published by ++the Free Software Foundation, either version 3 of the License, or ++(at your option) any later version. ++ ++This program is distributed in the hope that it will be useful, ++but WITHOUT ANY WARRANTY; without even the implied warranty of ++MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++GNU General Public License for more details. ++ ++You should have received a copy of the GNU General Public License ++along with this program. If not, see . +--- a/proxy/anytls/client.go ++++ b/proxy/anytls/client.go +@@ -0,0 +1,156 @@ ++package anytls ++ ++import ( ++ "context" ++ "sync" ++ "time" ++ ++ singanytls "github.com/sagernet/sing-anytls" ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/sagernet/sing/common/uot" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/errors" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/common/signal" ++ "github.com/xtls/xray-core/common/task" ++ "github.com/xtls/xray-core/core" ++ "github.com/xtls/xray-core/features/policy" ++ "github.com/xtls/xray-core/transport" ++ "github.com/xtls/xray-core/transport/internet" ++) ++ ++// Client adapts the same AnyTLS implementation used by sing-box. The session ++// pool belongs to the outbound handler, not to an individual proxied request. ++type Client struct { ++ server net.Destination ++ policy policy.Session ++ options singanytls.ClientOptions ++ mu sync.Mutex ++ client *singanytls.Client ++ closed bool ++} ++ ++func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { ++ if config.Address == nil || config.Port == 0 || config.Port > 65535 || config.Password == "" { ++ return nil, errors.New("anytls: invalid server or password") ++ } ++ manager := core.MustFromContext(ctx).GetFeature(policy.ManagerType()).(policy.Manager) ++ return &Client{ ++ server: net.TCPDestination(config.Address.AsAddress(), net.Port(config.Port)), ++ policy: manager.ForLevel(config.Level), ++ options: singanytls.ClientOptions{ ++ Password: config.Password, ++ IdleSessionCheckInterval: time.Duration(config.IdleSessionCheckInterval) * time.Second, ++ IdleSessionTimeout: time.Duration(config.IdleSessionTimeout) * time.Second, ++ MinIdleSession: int(config.MinIdleSession), ++ }, ++ }, nil ++} ++ ++func (c *Client) getClient(dialer internet.Dialer) (*singanytls.Client, error) { ++ c.mu.Lock() ++ defer c.mu.Unlock() ++ if c.closed { ++ return nil, errors.New("anytls: client closed") ++ } ++ if c.client == nil { ++ options := c.options ++ options.DialOut = func(ctx context.Context) (net.Conn, error) { ++ // Keep Xray's configured TLS/uTLS, socket mark, and outbound chaining. ++ return dialer.Dial(ctx, c.server) ++ } ++ client, err := singanytls.NewClient(options) ++ if err != nil { ++ return nil, err ++ } ++ c.client = client ++ } ++ return c.client, nil ++} ++ ++func (c *Client) Close() error { ++ c.mu.Lock() ++ c.closed = true ++ client := c.client ++ c.mu.Unlock() ++ if client != nil { ++ return client.Close() ++ } ++ return nil ++} ++ ++func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { ++ outbounds := session.OutboundsFromContext(ctx) ++ if len(outbounds) == 0 { ++ return errors.New("anytls: target not specified") ++ } ++ ob := outbounds[len(outbounds)-1] ++ target := ob.Target ++ if !target.IsValid() || (target.Network != net.Network_TCP && target.Network != net.Network_UDP) { ++ return errors.New("anytls: unsupported target") ++ } ++ ob.Name = "anytls" ++ ob.CanSpliceCopy = 3 ++ client, err := c.getClient(dialer) ++ if err != nil { ++ return err ++ } ++ destination := socksaddr(target) ++ streamDestination := destination ++ if target.Network == net.Network_UDP { ++ streamDestination = uot.RequestDestination(uot.Version) ++ } ++ dialCtx, dialCancel := context.WithTimeout(ctx, c.policy.Timeouts.Handshake) ++ conn, err := client.DialContext(dialCtx, streamDestination) ++ dialCancel() ++ if err != nil { ++ return errors.New("anytls: failed to open stream").Base(err) ++ } ++ defer conn.Close() ++ errors.LogInfo(ctx, "tunneling request to ", target, " via ", c.server.NetAddr()) ++ ++ var reader buf.Reader = buf.NewReader(conn) ++ var writer buf.Writer = buf.NewWriter(conn) ++ if target.Network == net.Network_UDP { ++ // UoT v2, with an address per datagram for transparent UDP and SOCKS5. ++ conn.SetWriteDeadline(time.Now().Add(c.policy.Timeouts.Handshake)) ++ packetConn, err := (&uot.Client{Version: uot.Version}).DialConn(conn, false, destination) ++ conn.SetWriteDeadline(time.Time{}) ++ if err != nil { ++ return errors.New("anytls: failed to start UoT").Base(err) ++ } ++ reader = &packetReader{conn: packetConn} ++ writer = &packetWriter{conn: packetConn, destination: destination} ++ } ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ timer := signal.CancelAfterInactivity(ctx, cancel, c.policy.Timeouts.ConnectionIdle) ++ defer timer.SetTimeout(0) ++ request := func() error { ++ defer timer.SetTimeout(c.policy.Timeouts.DownlinkOnly) ++ return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)) ++ } ++ response := func() error { ++ defer timer.SetTimeout(c.policy.Timeouts.UplinkOnly) ++ return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) ++ } ++ if err := task.Run(ctx, request, task.OnSuccess(response, task.Close(link.Writer))); err != nil { ++ // Unblock the other copy task on cancellation as well as I/O failures. ++ common.Interrupt(link.Reader) ++ common.Interrupt(link.Writer) ++ return errors.New("anytls: connection ends").Base(err) ++ } ++ return nil ++} ++ ++func socksaddr(destination net.Destination) M.Socksaddr { ++ return M.ParseSocksaddrHostPort(destination.Address.String(), uint16(destination.Port)) ++} ++ ++func init() { ++ common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { ++ return NewClient(ctx, config.(*ClientConfig)) ++ })) ++} +--- a/proxy/anytls/client_test.go ++++ b/proxy/anytls/client_test.go +@@ -0,0 +1,199 @@ ++package anytls ++ ++import ( ++ "bytes" ++ "context" ++ "io" ++ stdnet "net" ++ "sync/atomic" ++ "testing" ++ "time" ++ ++ singanytls "github.com/sagernet/sing-anytls" ++ singbuf "github.com/sagernet/sing/common/buf" ++ M "github.com/sagernet/sing/common/metadata" ++ N "github.com/sagernet/sing/common/network" ++ "github.com/sagernet/sing/common/uot" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/features/policy" ++ "github.com/xtls/xray-core/transport" ++ "github.com/xtls/xray-core/transport/internet/stat" ++ "github.com/xtls/xray-core/transport/pipe" ++) ++ ++type echoHandler struct{} ++ ++func (echoHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ++ defer conn.Close() ++ if destination.Fqdn != uot.MagicAddress { ++ io.Copy(conn, conn) ++ return ++ } ++ request, err := uot.ReadRequest(conn) ++ if err != nil { ++ return ++ } ++ packets := uot.NewConn(conn, *request) ++ data := singbuf.NewSize(65535) ++ defer data.Release() ++ for { ++ data.Reset() ++ address, err := packets.ReadPacket(data) ++ if err != nil { ++ return ++ } ++ if err := packets.WritePacket(data, address); err != nil { ++ return ++ } ++ } ++} ++ ++type testDialer struct { ++ service *singanytls.Service ++ dialed atomic.Int32 ++ active atomic.Int32 ++} ++ ++func (d *testDialer) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) { ++ a, b := stdnet.Pipe() ++ d.dialed.Add(1) ++ d.active.Add(1) ++ go func() { ++ defer d.active.Add(-1) ++ defer b.Close() ++ d.service.NewConnection(context.Background(), b, M.Socksaddr{}, nil) ++ }() ++ return a, nil ++} ++func (*testDialer) DestIpAddress() net.IP { return nil } ++func (*testDialer) SetOutboundGateway(context.Context, *session.Outbound) {} ++ ++func newTestClient(t *testing.T, password string) (*Client, *testDialer) { ++ t.Helper() ++ service, err := singanytls.NewService("test-password", singanytls.ServiceOptions{Handler: echoHandler{}}) ++ if err != nil { ++ t.Fatal(err) ++ } ++ c := &Client{server: net.TCPDestination(net.LocalHostIP, 443), policy: policy.SessionDefault(), options: singanytls.ClientOptions{Password: password}} ++ d := &testDialer{service: service} ++ t.Cleanup(func() { ++ c.Close() ++ deadline := time.Now().Add(3 * time.Second) ++ for d.active.Load() != 0 && time.Now().Before(deadline) { ++ time.Sleep(time.Millisecond) ++ } ++ if d.active.Load() != 0 { ++ t.Error("session connection leaked after Close") ++ } ++ if _, err := c.getClient(d); err == nil { ++ t.Error("client reopened after Close") ++ } ++ }) ++ return c, d ++} ++ ++func exchange(t *testing.T, c *Client, d *testDialer, udp bool) { ++ t.Helper() ++ target := net.TCPDestination(net.DomainAddress("echo.example"), 443) ++ if udp { ++ target = net.UDPDestination(net.DomainAddress("dns.example"), 53) ++ } ++ ctx, cancel := context.WithCancel(session.ContextWithOutbounds(context.Background(), []*session.Outbound{{Target: target}})) ++ uplink, input := pipe.New() ++ output, downlink := pipe.New() ++ done := make(chan error, 1) ++ go func() { done <- c.Process(ctx, &transport.Link{Reader: uplink, Writer: downlink}, d) }() ++ defer func() { ++ cancel() ++ input.Close() ++ output.Interrupt() ++ select { ++ case <-done: ++ case <-time.After(3 * time.Second): ++ t.Error("Process did not exit after cancellation") ++ } ++ }() ++ sizes := []int{13, 9000} ++ for i, size := range sizes { ++ payload := bytes.Repeat([]byte{byte(i + 1)}, size) ++ b := buf.FromBytes(payload) ++ address := target ++ if udp { ++ if i == 1 { ++ address = net.UDPDestination(net.ParseAddress("2001:db8::1"), 12345) ++ } ++ b.UDP = &address ++ } ++ if err := input.WriteMultiBuffer(buf.MultiBuffer{b}); err != nil { ++ t.Fatal(err) ++ } ++ received := []byte{} ++ for len(received) < len(payload) { ++ mb, err := output.ReadMultiBufferTimeout(3 * time.Second) ++ if err != nil { ++ t.Fatal(err) ++ } ++ for _, packet := range mb { ++ if udp && (packet.UDP == nil || *packet.UDP != address) { ++ t.Errorf("UDP source mismatch: %v != %v", packet.UDP, address) ++ } ++ received = append(received, packet.Bytes()...) ++ } ++ buf.ReleaseMulti(mb) ++ } ++ if !bytes.Equal(received, payload) { ++ t.Fatal("payload corrupted") ++ } ++ } ++} ++ ++func TestAnyTLSProcess(t *testing.T) { ++ for _, udp := range []bool{false, true} { ++ name := "TCP" ++ if udp { ++ name = "UDP" ++ } ++ t.Run(name, func(t *testing.T) { c, d := newTestClient(t, "test-password"); exchange(t, c, d, udp) }) ++ } ++} ++ ++func TestAnyTLSReuseAndClose(t *testing.T) { ++ c, d := newTestClient(t, "test-password") ++ for i := 0; i < 10; i++ { ++ exchange(t, c, d, false) ++ } ++ if n := d.dialed.Load(); n > 2 { ++ t.Fatalf("sessions were not reused: %d dials for 10 requests", n) ++ } ++ c.Close() ++ c.Close() ++} ++ ++func TestAnyTLSWrongPassword(t *testing.T) { ++ c, d := newTestClient(t, "wrong-password") ++ ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ++ defer cancel() ++ ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{Target: net.TCPDestination(net.DomainAddress("echo.example"), 443)}}) ++ uplink, input := pipe.New() ++ output, downlink := pipe.New() ++ defer input.Close() ++ defer output.Interrupt() ++ done := make(chan error, 1) ++ go func() { done <- c.Process(ctx, &transport.Link{Reader: uplink, Writer: downlink}, d) }() ++ input.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes([]byte("must not be echoed"))}) ++ select { ++ case err := <-done: ++ if err == nil { ++ t.Fatal("wrong password accepted") ++ } ++ case <-ctx.Done(): ++ t.Fatal("wrong password did not fail promptly") ++ } ++ mb, err := output.ReadMultiBufferTimeout(time.Second) ++ defer buf.ReleaseMulti(mb) ++ if err == nil && len(mb) > 0 { ++ t.Fatal("received payload with wrong password") ++ } ++} +--- a/proxy/anytls/config.pb.go ++++ b/proxy/anytls/config.pb.go +@@ -0,0 +1,301 @@ ++// Code generated by protoc-gen-go. DO NOT EDIT. ++// versions: ++// protoc-gen-go v1.36.12 ++// protoc v3.21.12 ++// source: proxy/anytls/config.proto ++ ++package anytls ++ ++import ( ++ net "github.com/xtls/xray-core/common/net" ++ protoreflect "google.golang.org/protobuf/reflect/protoreflect" ++ protoimpl "google.golang.org/protobuf/runtime/protoimpl" ++ reflect "reflect" ++ sync "sync" ++ unsafe "unsafe" ++) ++ ++const ( ++ // Verify that this generated code is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) ++ // Verify that runtime/protoimpl is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ++) ++ ++type ClientConfig struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` ++ Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` ++ Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` ++ Level uint32 `protobuf:"varint,4,opt,name=level,proto3" json:"level,omitempty"` ++ IdleSessionCheckInterval uint32 `protobuf:"varint,5,opt,name=idle_session_check_interval,json=idleSessionCheckInterval,proto3" json:"idle_session_check_interval,omitempty"` ++ IdleSessionTimeout uint32 `protobuf:"varint,6,opt,name=idle_session_timeout,json=idleSessionTimeout,proto3" json:"idle_session_timeout,omitempty"` ++ MinIdleSession uint32 `protobuf:"varint,7,opt,name=min_idle_session,json=minIdleSession,proto3" json:"min_idle_session,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *ClientConfig) Reset() { ++ *x = ClientConfig{} ++ mi := &file_proxy_anytls_config_proto_msgTypes[0] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *ClientConfig) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*ClientConfig) ProtoMessage() {} ++ ++func (x *ClientConfig) ProtoReflect() protoreflect.Message { ++ mi := &file_proxy_anytls_config_proto_msgTypes[0] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. ++func (*ClientConfig) Descriptor() ([]byte, []int) { ++ return file_proxy_anytls_config_proto_rawDescGZIP(), []int{0} ++} ++ ++func (x *ClientConfig) GetAddress() *net.IPOrDomain { ++ if x != nil { ++ return x.Address ++ } ++ return nil ++} ++ ++func (x *ClientConfig) GetPort() uint32 { ++ if x != nil { ++ return x.Port ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetPassword() string { ++ if x != nil { ++ return x.Password ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetLevel() uint32 { ++ if x != nil { ++ return x.Level ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetIdleSessionCheckInterval() uint32 { ++ if x != nil { ++ return x.IdleSessionCheckInterval ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetIdleSessionTimeout() uint32 { ++ if x != nil { ++ return x.IdleSessionTimeout ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetMinIdleSession() uint32 { ++ if x != nil { ++ return x.MinIdleSession ++ } ++ return 0 ++} ++ ++type User struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` ++ Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` ++ Level uint32 `protobuf:"varint,3,opt,name=level,proto3" json:"level,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *User) Reset() { ++ *x = User{} ++ mi := &file_proxy_anytls_config_proto_msgTypes[1] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *User) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*User) ProtoMessage() {} ++ ++func (x *User) ProtoReflect() protoreflect.Message { ++ mi := &file_proxy_anytls_config_proto_msgTypes[1] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use User.ProtoReflect.Descriptor instead. ++func (*User) Descriptor() ([]byte, []int) { ++ return file_proxy_anytls_config_proto_rawDescGZIP(), []int{1} ++} ++ ++func (x *User) GetPassword() string { ++ if x != nil { ++ return x.Password ++ } ++ return "" ++} ++ ++func (x *User) GetEmail() string { ++ if x != nil { ++ return x.Email ++ } ++ return "" ++} ++ ++func (x *User) GetLevel() uint32 { ++ if x != nil { ++ return x.Level ++ } ++ return 0 ++} ++ ++type ServerConfig struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Users []*User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` ++ PaddingScheme []byte `protobuf:"bytes,2,opt,name=padding_scheme,json=paddingScheme,proto3" json:"padding_scheme,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *ServerConfig) Reset() { ++ *x = ServerConfig{} ++ mi := &file_proxy_anytls_config_proto_msgTypes[2] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *ServerConfig) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*ServerConfig) ProtoMessage() {} ++ ++func (x *ServerConfig) ProtoReflect() protoreflect.Message { ++ mi := &file_proxy_anytls_config_proto_msgTypes[2] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead. ++func (*ServerConfig) Descriptor() ([]byte, []int) { ++ return file_proxy_anytls_config_proto_rawDescGZIP(), []int{2} ++} ++ ++func (x *ServerConfig) GetUsers() []*User { ++ if x != nil { ++ return x.Users ++ } ++ return nil ++} ++ ++func (x *ServerConfig) GetPaddingScheme() []byte { ++ if x != nil { ++ return x.PaddingScheme ++ } ++ return nil ++} ++ ++var File_proxy_anytls_config_proto protoreflect.FileDescriptor ++ ++const file_proxy_anytls_config_proto_rawDesc = "" + ++ "\n" + ++ "\x19proxy/anytls/config.proto\x12\x11xray.proxy.anytls\x1a\x18common/net/address.proto\"\xa6\x02\n" + ++ "\fClientConfig\x125\n" + ++ "\aaddress\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" + ++ "\x04port\x18\x02 \x01(\rR\x04port\x12\x1a\n" + ++ "\bpassword\x18\x03 \x01(\tR\bpassword\x12\x14\n" + ++ "\x05level\x18\x04 \x01(\rR\x05level\x12=\n" + ++ "\x1bidle_session_check_interval\x18\x05 \x01(\rR\x18idleSessionCheckInterval\x120\n" + ++ "\x14idle_session_timeout\x18\x06 \x01(\rR\x12idleSessionTimeout\x12(\n" + ++ "\x10min_idle_session\x18\a \x01(\rR\x0eminIdleSession\"N\n" + ++ "\x04User\x12\x1a\n" + ++ "\bpassword\x18\x01 \x01(\tR\bpassword\x12\x14\n" + ++ "\x05email\x18\x02 \x01(\tR\x05email\x12\x14\n" + ++ "\x05level\x18\x03 \x01(\rR\x05level\"d\n" + ++ "\fServerConfig\x12-\n" + ++ "\x05users\x18\x01 \x03(\v2\x17.xray.proxy.anytls.UserR\x05users\x12%\n" + ++ "\x0epadding_scheme\x18\x02 \x01(\fR\rpaddingSchemeB(Z&github.com/xtls/xray-core/proxy/anytlsb\x06proto3" ++ ++var ( ++ file_proxy_anytls_config_proto_rawDescOnce sync.Once ++ file_proxy_anytls_config_proto_rawDescData []byte ++) ++ ++func file_proxy_anytls_config_proto_rawDescGZIP() []byte { ++ file_proxy_anytls_config_proto_rawDescOnce.Do(func() { ++ file_proxy_anytls_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_anytls_config_proto_rawDesc), len(file_proxy_anytls_config_proto_rawDesc))) ++ }) ++ return file_proxy_anytls_config_proto_rawDescData ++} ++ ++var file_proxy_anytls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) ++var file_proxy_anytls_config_proto_goTypes = []any{ ++ (*ClientConfig)(nil), // 0: xray.proxy.anytls.ClientConfig ++ (*User)(nil), // 1: xray.proxy.anytls.User ++ (*ServerConfig)(nil), // 2: xray.proxy.anytls.ServerConfig ++ (*net.IPOrDomain)(nil), // 3: xray.common.net.IPOrDomain ++} ++var file_proxy_anytls_config_proto_depIdxs = []int32{ ++ 3, // 0: xray.proxy.anytls.ClientConfig.address:type_name -> xray.common.net.IPOrDomain ++ 1, // 1: xray.proxy.anytls.ServerConfig.users:type_name -> xray.proxy.anytls.User ++ 2, // [2:2] is the sub-list for method output_type ++ 2, // [2:2] is the sub-list for method input_type ++ 2, // [2:2] is the sub-list for extension type_name ++ 2, // [2:2] is the sub-list for extension extendee ++ 0, // [0:2] is the sub-list for field type_name ++} ++ ++func init() { file_proxy_anytls_config_proto_init() } ++func file_proxy_anytls_config_proto_init() { ++ if File_proxy_anytls_config_proto != nil { ++ return ++ } ++ type x struct{} ++ out := protoimpl.TypeBuilder{ ++ File: protoimpl.DescBuilder{ ++ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), ++ RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_anytls_config_proto_rawDesc), len(file_proxy_anytls_config_proto_rawDesc)), ++ NumEnums: 0, ++ NumMessages: 3, ++ NumExtensions: 0, ++ NumServices: 0, ++ }, ++ GoTypes: file_proxy_anytls_config_proto_goTypes, ++ DependencyIndexes: file_proxy_anytls_config_proto_depIdxs, ++ MessageInfos: file_proxy_anytls_config_proto_msgTypes, ++ }.Build() ++ File_proxy_anytls_config_proto = out.File ++ file_proxy_anytls_config_proto_goTypes = nil ++ file_proxy_anytls_config_proto_depIdxs = nil ++} +--- a/proxy/anytls/config.proto ++++ b/proxy/anytls/config.proto +@@ -0,0 +1,24 @@ ++syntax = "proto3"; ++package xray.proxy.anytls; ++option go_package = "github.com/xtls/xray-core/proxy/anytls"; ++import "common/net/address.proto"; ++ ++message ClientConfig { ++ xray.common.net.IPOrDomain address = 1; ++ uint32 port = 2; ++ string password = 3; ++ uint32 level = 4; ++ uint32 idle_session_check_interval = 5; ++ uint32 idle_session_timeout = 6; ++ uint32 min_idle_session = 7; ++} ++ ++message User { ++ string password = 1; ++ string email = 2; ++ uint32 level = 3; ++} ++message ServerConfig { ++ repeated User users = 1; ++ bytes padding_scheme = 2; ++} +--- a/proxy/anytls/packet.go ++++ b/proxy/anytls/packet.go +@@ -0,0 +1,44 @@ ++package anytls ++ ++import ( ++ singbuf "github.com/sagernet/sing/common/buf" ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/sagernet/sing/common/uot" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/net" ++) ++ ++type packetReader struct{ conn *uot.Conn } ++ ++func (r *packetReader) ReadMultiBuffer() (buf.MultiBuffer, error) { ++ // Read one whole datagram, including packets larger than Xray's 8K buffer. ++ packet := singbuf.NewSize(65535) ++ defer packet.Release() ++ address, err := r.conn.ReadPacket(packet) ++ if err != nil { ++ return nil, err ++ } ++ b := buf.FromBytes(append([]byte(nil), packet.Bytes()...)) ++ destination := net.UDPDestination(net.ParseAddress(address.AddrString()), net.Port(address.Port)) ++ b.UDP = &destination ++ return buf.MultiBuffer{b}, nil ++} ++ ++type packetWriter struct { ++ conn *uot.Conn ++ destination M.Socksaddr ++} ++ ++func (w *packetWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { ++ defer buf.ReleaseMulti(mb) ++ for _, b := range mb { ++ destination := w.destination ++ if b.UDP != nil { ++ destination = socksaddr(*b.UDP) ++ } ++ if err := w.conn.WritePacket(singbuf.As(b.Bytes()), destination); err != nil { ++ return err ++ } ++ } ++ return nil ++} +--- a/proxy/anytls/relay.go ++++ b/proxy/anytls/relay.go +@@ -0,0 +1,60 @@ ++package anytls ++ ++import ( ++ "context" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/signal" ++ "github.com/xtls/xray-core/features/policy" ++) ++ ++// Stop blocked reads and join both copy loops before releasing stream state. ++func relay(ctx context.Context, upR buf.Reader, upW buf.Writer, downR buf.Reader, downW buf.Writer, stopIO func(), p policy.Session) error { ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) ++ defer timer.SetTimeout(0) ++ results := make(chan error, 2) ++ go func() { ++ e := buf.Copy(upR, upW, buf.UpdateActivity(timer)) ++ if e == nil { ++ common.Close(upW) ++ } ++ timer.SetTimeout(p.Timeouts.DownlinkOnly) ++ results <- e ++ }() ++ go func() { ++ e := buf.Copy(downR, downW, buf.UpdateActivity(timer)) ++ if e == nil { ++ common.Close(downW) ++ } ++ timer.SetTimeout(p.Timeouts.UplinkOnly) ++ results <- e ++ }() ++ var first error ++ count := 0 ++ for count < 2 { ++ select { ++ case e := <-results: ++ count++ ++ if e != nil { ++ first = e ++ } ++ case <-ctx.Done(): ++ first = ctx.Err() ++ } ++ if first != nil || count == 2 { ++ break ++ } ++ } ++ stopIO() ++ common.Interrupt(upR) ++ common.Interrupt(upW) ++ common.Interrupt(downR) ++ common.Interrupt(downW) ++ for count < 2 { ++ <-results ++ count++ ++ } ++ return first ++} +--- a/proxy/anytls/server.go ++++ b/proxy/anytls/server.go +@@ -0,0 +1,189 @@ ++package anytls ++ ++import ( ++ "bytes" ++ "context" ++ "encoding/binary" ++ "io" ++ "time" ++ ++ A "github.com/sagernet/sing-anytls" ++ "github.com/sagernet/sing/common/auth" ++ M "github.com/sagernet/sing/common/metadata" ++ N "github.com/sagernet/sing/common/network" ++ "github.com/sagernet/sing/common/uot" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/errors" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/protocol" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/core" ++ "github.com/xtls/xray-core/features/policy" ++ "github.com/xtls/xray-core/features/routing" ++ "github.com/xtls/xray-core/transport/internet" ++ "github.com/xtls/xray-core/transport/internet/stat" ++ "github.com/xtls/xray-core/transport/internet/tls" ++) ++ ++type Server struct { ++ service *A.MultiService[*User] ++ policy policy.Manager ++} ++type dispatcherKey struct{} ++ ++func NewServer(ctx context.Context, c *ServerConfig) (*Server, error) { ++ m, _ := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig) ++ if m == nil { ++ return nil, errors.New("anytls inbound requires TLS") ++ } ++ if _, ok := m.SecuritySettings.(*tls.Config); !ok { ++ return nil, errors.New("anytls inbound requires TLS") ++ } ++ if len(c.Users) == 0 { ++ return nil, errors.New("anytls: users required") ++ } ++ s := &Server{policy: core.MustFromContext(ctx).GetFeature(policy.ManagerType()).(policy.Manager)} ++ service, e := A.NewMultiService[*User](A.ServiceOptions{PaddingScheme: c.PaddingScheme, Handler: s}) ++ if e != nil { ++ return nil, e ++ } ++ passwords := make([]string, len(c.Users)) ++ seen := map[string]bool{} ++ for i, u := range c.Users { ++ if u == nil || u.Password == "" || seen[u.Password] { ++ return nil, errors.New("anytls: empty or duplicate password") ++ } ++ seen[u.Password] = true ++ passwords[i] = u.Password ++ } ++ if e = service.UpdateUsers(c.Users, passwords); e != nil { ++ return nil, e ++ } ++ s.service = service ++ return s, nil ++} ++func (*Server) Network() []net.Network { return []net.Network{net.Network_TCP} } ++ ++type prefaceConn struct { ++ net.Conn ++ reader io.Reader ++} ++ ++func (c *prefaceConn) Read(b []byte) (int, error) { return c.reader.Read(b) } ++func (s *Server) Process(ctx context.Context, _ net.Network, conn stat.Connection, d routing.Dispatcher) error { ++ defer conn.Close() ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ done := make(chan struct{}) ++ stop := context.AfterFunc(ctx, func() { conn.Close(); close(done) }) ++ defer func() { ++ if !stop() { ++ <-done ++ } ++ }() ++ conn.SetReadDeadline(time.Now().Add(s.policy.ForLevel(0).Timeouts.Handshake)) ++ // sing-anytls inspects the password in its first Read. Preserve fragmented ++ // TLS/application writes by collecting the bounded prologue before handing it off. ++ var header [34]byte ++ if _, e := io.ReadFull(conn, header[:]); e != nil { ++ return e ++ } ++ preface := make([]byte, 34+int(binary.BigEndian.Uint16(header[32:]))) ++ copy(preface, header[:]) ++ if _, e := io.ReadFull(conn, preface[34:]); e != nil { ++ return e ++ } ++ conn.SetReadDeadline(time.Time{}) ++ wrapped := &prefaceConn{conn, io.MultiReader(bytes.NewReader(preface), conn)} ++ ctx = context.WithValue(ctx, dispatcherKey{}, d) ++ return s.service.NewConnection(ctx, wrapped, M.SocksaddrFromNet(conn.RemoteAddr()), nil) ++} ++func (s *Server) NewConnectionEx(ctx context.Context, conn net.Conn, _ M.Socksaddr, dest M.Socksaddr, onClose N.CloseHandlerFunc) { ++ e := s.handleStream(ctx, conn, dest) ++ conn.Close() ++ if onClose != nil { ++ onClose(e) ++ } ++ if e != nil { ++ errors.LogInfoInner(ctx, e, "anytls stream ends") ++ } ++} ++ ++type initialReader struct { ++ first buf.MultiBuffer ++ reader buf.Reader ++} ++ ++func (r *initialReader) ReadMultiBuffer() (buf.MultiBuffer, error) { ++ if r.first != nil { ++ b := r.first ++ r.first = nil ++ return b, nil ++ } ++ return r.reader.ReadMultiBuffer() ++} ++func (s *Server) handleStream(ctx context.Context, conn net.Conn, dest M.Socksaddr) error { ++ u, ok := auth.UserFromContext[*User](ctx) ++ if !ok { ++ return errors.New("anytls: missing user") ++ } ++ p := s.policy.ForLevel(u.Level) ++ if in := session.InboundFromContext(ctx); in != nil { ++ cp := *in ++ cp.Name = "anytls" ++ cp.CanSpliceCopy = 3 ++ cp.User = &protocol.MemoryUser{Email: u.Email, Level: u.Level} ++ ctx = session.ContextWithInbound(ctx, &cp) ++ } ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ stop := context.AfterFunc(ctx, func() { conn.Close() }) ++ defer stop() ++ target := net.TCPDestination(net.ParseAddress(dest.AddrString()), net.Port(dest.Port)) ++ var reader buf.Reader = buf.NewReader(conn) ++ var writer buf.Writer = buf.NewWriter(conn) ++ var first buf.MultiBuffer ++ if dest.Fqdn == uot.MagicAddress || dest.Fqdn == uot.LegacyMagicAddress { ++ conn.SetReadDeadline(time.Now().Add(p.Timeouts.Handshake)) ++ request := uot.Request{} ++ if dest.Fqdn == uot.MagicAddress { ++ r, e := uot.ReadRequest(conn) ++ if e != nil { ++ return e ++ } ++ request = *r ++ } ++ pc := uot.NewConn(conn, request) ++ pr := &packetReader{pc} ++ var e error ++ first, e = pr.ReadMultiBuffer() ++ if e != nil { ++ return e ++ } ++ target = *first[0].UDP ++ reader = &initialReader{first: first, reader: pr} ++ writer = &packetWriter{pc, socksaddr(target)} ++ conn.SetReadDeadline(time.Time{}) ++ } ++ if !target.IsValid() || target.Port == 0 { ++ buf.ReleaseMulti(first) ++ return errors.New("anytls: invalid destination") ++ } ++ dispatcher, ok := ctx.Value(dispatcherKey{}).(routing.Dispatcher) ++ if !ok { ++ buf.ReleaseMulti(first) ++ return errors.New("anytls: missing dispatcher") ++ } ++ link, e := dispatcher.Dispatch(ctx, target) ++ if e != nil { ++ buf.ReleaseMulti(first) ++ return e ++ } ++ return relay(ctx, reader, link.Writer, link.Reader, writer, func() { conn.Close() }, p) ++} ++func init() { ++ common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, c interface{}) (interface{}, error) { ++ return NewServer(ctx, c.(*ServerConfig)) ++ })) ++} +--- a/proxy/freedom/freedom.go ++++ b/proxy/freedom/freedom.go +@@ -166,6 +166,9 @@ + if inbound == nil { + return nil + } ++ if inbound.Name == "anytls" { ++ return defaultBlockPrivateRule ++ } + switch inbound.Name { + case "vless-reverse": + return defaultBlockAllRule diff --git a/xray-core/patches/120-restore-vmess-alterid-outbound.patch b/xray-core/patches/120-restore-vmess-alterid-outbound.patch new file mode 100644 index 000000000..3fb0c25c3 --- /dev/null +++ b/xray-core/patches/120-restore-vmess-alterid-outbound.patch @@ -0,0 +1,669 @@ +From: coolsnowwolf +Subject: [PATCH] vmess: restore legacy alterId outbound authentication + +Carry alterId through JSON, protobuf and memory accounts, restoring the +legacy HMAC-MD5 authentication and AES-CFB request/response headers for +explicitly configured nonzero alterId outbounds. Keep alterId=0 on AEAD +and reject legacy inbound settings, whose server support is not restored. +Initialize response keys before the concurrent request/response tasks. +Use the current timestamp, as Mihomo does, for legacy server compatibility. + +Adapt the legacy client handshake from Xray v1.5.5. Cover alternative UUID +vectors, config/protobuf round trips, TCP/UDP payloads, both AEAD payload +ciphers and malformed legacy responses; retain existing AEAD header tests. + +Reference: https://github.com/XTLS/Xray-core/blob/v1.5.5/proxy/vmess/encoding/client.go +Signed-off-by: coolsnowwolf + +--- a/infra/conf/vmess.go ++++ b/infra/conf/vmess.go +@@ -17,6 +17,7 @@ + + type VMessAccount struct { + ID string `json:"id"` ++ AlterID uint16 `json:"alterId"` + Security string `json:"security"` + Experiments string `json:"experiments"` + } +@@ -35,7 +36,8 @@ + st = protocol.SecurityType_AUTO + } + return &vmess.Account{ +- Id: a.ID, ++ Id: a.ID, ++ AlterId: uint32(a.AlterID), + SecuritySettings: &protocol.SecurityConfig{ + Type: st, + }, +@@ -85,6 +87,10 @@ + return errors.New("invalid VMess user").Base(err) + } + ++ if account.AlterID != 0 { ++ return errors.New("legacy VMess alterId is supported for outbounds only") ++ } ++ + u, err := uuid.ParseString(account.ID) + if err != nil { + return err +@@ -114,6 +120,7 @@ + Level uint32 `json:"level"` + Email string `json:"email"` + ID string `json:"id"` ++ AlterID uint16 `json:"alterId"` + Security string `json:"security"` + Experiments string `json:"experiments"` + Receivers []*VMessOutboundTarget `json:"vnext"` +@@ -160,6 +167,7 @@ + account := new(VMessAccount) + if c.Address != nil { + account.ID = c.ID ++ account.AlterID = c.AlterID + account.Security = c.Security + account.Experiments = c.Experiments + } else { +--- a/infra/conf/vmess_legacy_test.go ++++ b/infra/conf/vmess_legacy_test.go +@@ -0,0 +1,69 @@ ++package conf_test ++ ++import ( ++ "encoding/json" ++ "fmt" ++ "testing" ++ ++ "github.com/xtls/xray-core/infra/conf" ++ "github.com/xtls/xray-core/proxy/vmess" ++ "github.com/xtls/xray-core/proxy/vmess/outbound" ++ "google.golang.org/protobuf/proto" ++) ++ ++func TestVMessAlterIDConfig(t *testing.T) { ++ for _, aid := range []int{0, 1, 64} { ++ for _, flat := range []bool{false, true} { ++ t.Run(fmt.Sprintf("%d/flat=%v", aid, flat), func(t *testing.T) { ++ fields := fmt.Sprintf(`"id":"e641f5ad-9397-41e3-bf1a-e8740dfed019","alterId":%d`, aid) ++ raw := `{"address":"127.0.0.1","port":443,` + fields + `}` ++ if !flat { ++ raw = `{"vnext":[{"address":"127.0.0.1","port":443,"users":[{` + fields + `}]}]}` ++ } ++ var config conf.VMessOutboundConfig ++ if err := json.Unmarshal([]byte(raw), &config); err != nil { ++ t.Fatal(err) ++ } ++ message, err := config.Build() ++ if err != nil { ++ t.Fatal(err) ++ } ++ user, err := message.(*outbound.Config).Receiver.User.ToMemoryUser() ++ if err != nil { ++ t.Fatal(err) ++ } ++ account := user.Account.(*vmess.MemoryAccount) ++ if len(account.AlterIDs) != aid { ++ t.Fatalf("lost alterId: %d", len(account.AlterIDs)) ++ } ++ wire, err := proto.Marshal(account.ToProto()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ var decoded vmess.Account ++ if err = proto.Unmarshal(wire, &decoded); err != nil { ++ t.Fatal(err) ++ } ++ if decoded.AlterId != uint32(aid) { ++ t.Fatal("protobuf round trip lost alterId") ++ } ++ }) ++ } ++ } ++ for _, raw := range []string{`{"alterId":-1}`, `{"alterId":65536}`, `{"alterId":1.5}`} { ++ var c conf.VMessOutboundConfig ++ if err := json.Unmarshal([]byte(raw), &c); err == nil { ++ t.Fatalf("accepted %s", raw) ++ } ++ } ++ if _, err := (&vmess.Account{Id: "e641f5ad-9397-41e3-bf1a-e8740dfed019", AlterId: 65536}).AsAccount(); err == nil { ++ t.Fatal("accepted oversized protobuf alterId") ++ } ++ var inbound conf.VMessInboundConfig ++ if err := json.Unmarshal([]byte(`{"clients":[{"id":"e641f5ad-9397-41e3-bf1a-e8740dfed019","alterId":1}]}`), &inbound); err != nil { ++ t.Fatal(err) ++ } ++ if _, err := inbound.Build(); err == nil { ++ t.Fatal("silently accepted unsupported legacy inbound") ++ } ++} +--- a/proxy/vmess/account.proto ++++ b/proxy/vmess/account.proto +@@ -12,6 +12,8 @@ + // ID of the account, in the form of a UUID, e.g., + // "66ad4540-b58c-4ad2-9926-ea63445a9b57". + string id = 1; ++ // Number of alternative IDs. Nonzero enables legacy outbound authentication. ++ uint32 alter_id = 2; + // Security settings. Only applies to client side. + xray.common.protocol.SecurityConfig security_settings = 3; + // Define tests enabled for this account +--- a/proxy/vmess/account.pb.go ++++ b/proxy/vmess/account.pb.go +@@ -27,6 +27,8 @@ + // ID of the account, in the form of a UUID, e.g., + // "66ad4540-b58c-4ad2-9926-ea63445a9b57". + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` ++ // Number of alternative IDs. Nonzero enables legacy outbound authentication. ++ AlterId uint32 `protobuf:"varint,2,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"` + // Security settings. Only applies to client side. + SecuritySettings *protocol.SecurityConfig `protobuf:"bytes,3,opt,name=security_settings,json=securitySettings,proto3" json:"security_settings,omitempty"` + // Define tests enabled for this account +@@ -72,6 +74,13 @@ + return "" + } + ++func (x *Account) GetAlterId() uint32 { ++ if x != nil { ++ return x.AlterId ++ } ++ return 0 ++} ++ + func (x *Account) GetSecuritySettings() *protocol.SecurityConfig { + if x != nil { + return x.SecuritySettings +@@ -90,9 +99,10 @@ + + const file_proxy_vmess_account_proto_rawDesc = "" + + "\n" + +- "\x19proxy/vmess/account.proto\x12\x10xray.proxy.vmess\x1a\x1dcommon/protocol/headers.proto\"\x91\x01\n" + ++ "\x19proxy/vmess/account.proto\x12\x10xray.proxy.vmess\x1a\x1dcommon/protocol/headers.proto\"\xac\x01\n" + + "\aAccount\x12\x0e\n" + +- "\x02id\x18\x01 \x01(\tR\x02id\x12Q\n" + ++ "\x02id\x18\x01 \x01(\tR\x02id\x12\x19\n" + ++ "\balter_id\x18\x02 \x01(\rR\aalterId\x12Q\n" + + "\x11security_settings\x18\x03 \x01(\v2$.xray.common.protocol.SecurityConfigR\x10securitySettings\x12#\n" + + "\rtests_enabled\x18\x04 \x01(\tR\ftestsEnabledBR\n" + + "\x14com.xray.proxy.vmessP\x01Z%github.com/xtls/xray-core/proxy/vmess\xaa\x02\x10Xray.Proxy.Vmessb\x06proto3" +--- a/proxy/vmess/account.go ++++ b/proxy/vmess/account.go +@@ -1,8 +1,12 @@ + package vmess + + import ( ++ "bytes" ++ "crypto/md5" + "strings" + ++ "github.com/xtls/xray-core/common/dice" ++ + "google.golang.org/protobuf/proto" + + "github.com/xtls/xray-core/common/errors" +@@ -14,6 +18,8 @@ + type MemoryAccount struct { + // ID is the main ID of the account. + ID *protocol.ID ++ // AlterIDs are used only for legacy outbound authentication. ++ AlterIDs []*protocol.ID + // Security type of the account. Used for client connections. + Security protocol.SecurityType + +@@ -40,6 +46,7 @@ + } + return &Account{ + Id: a.ID.String(), ++ AlterId: uint32(len(a.AlterIDs)), + TestsEnabled: test, + SecuritySettings: &protocol.SecurityConfig{Type: a.Security}, + } +@@ -51,6 +58,9 @@ + if err != nil { + return nil, errors.New("failed to parse ID").Base(err) + } ++ if a.AlterId > 65535 { ++ return nil, errors.New("VMess alterId exceeds 65535") ++ } + protoID := protocol.NewID(id) + var AuthenticatedLength, NoTerminationSignal bool + if strings.Contains(a.TestsEnabled, "AuthenticatedLength") { +@@ -61,8 +71,41 @@ + } + return &MemoryAccount{ + ID: protoID, ++ AlterIDs: newAlterIDs(id, a.AlterId), + Security: a.SecuritySettings.GetSecurityType(), + AuthenticatedLengthExperiment: AuthenticatedLength, + NoTerminationSignal: NoTerminationSignal, + }, nil + } ++ ++// AnyValidID chooses an authentication ID without changing the primary command key. ++func (a *MemoryAccount) AnyValidID() *protocol.ID { ++ if len(a.AlterIDs) == 0 { ++ return a.ID ++ } ++ return a.AlterIDs[dice.Roll(len(a.AlterIDs))] ++} ++ ++// newAlterIDs follows the original VMess alternative UUID derivation. ++func newAlterIDs(previous uuid.UUID, count uint32) []*protocol.ID { ++ if count == 0 { ++ return nil ++ } ++ ids := make([]*protocol.ID, count) ++ for i := range ids { ++ h := md5.New() ++ h.Write(previous.Bytes()) ++ h.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81")) ++ var next uuid.UUID ++ for { ++ h.Sum(next[:0]) ++ if !bytes.Equal(next.Bytes(), previous.Bytes()) { ++ break ++ } ++ h.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2")) ++ } ++ ids[i] = protocol.NewID(next) ++ previous = next ++ } ++ return ids ++} +--- a/proxy/vmess/account_legacy_test.go ++++ b/proxy/vmess/account_legacy_test.go +@@ -0,0 +1,19 @@ ++package vmess ++ ++import "testing" ++ ++func TestLegacyAlterIDDerivation(t *testing.T) { ++ a, err := (&Account{Id: "e641f5ad-9397-41e3-bf1a-e8740dfed019", AlterId: 2}).AsAccount() ++ if err != nil { ++ t.Fatal(err) ++ } ++ m := a.(*MemoryAccount) ++ for i, want := range []string{"5c1ef6b5-fd16-7081-c694-c2308d07559b", "283b417b-0e6e-eda7-50b7-fded0002659a"} { ++ if got := m.AlterIDs[i].String(); got != want { ++ t.Fatalf("ID %d: %s != %s", i, got, want) ++ } ++ } ++ if m.ID.String() != "e641f5ad-9397-41e3-bf1a-e8740dfed019" { ++ t.Fatal("primary ID changed") ++ } ++} +--- a/proxy/vmess/encoding/client.go ++++ b/proxy/vmess/encoding/client.go +@@ -3,6 +3,8 @@ + import ( + "bytes" + "context" ++ "crypto/hmac" ++ "crypto/md5" + "crypto/rand" + "crypto/sha256" + "encoding/binary" +@@ -24,6 +26,7 @@ + + // ClientSession stores connection session info for VMess client. + type ClientSession struct { ++ legacy bool + requestBodyKey [16]byte + requestBodyIV [16]byte + responseBodyKey [16]byte +@@ -35,8 +38,8 @@ + } + + // NewClientSession creates a new ClientSession. +-func NewClientSession(ctx context.Context, behaviorSeed int64) *ClientSession { +- session := &ClientSession{} ++func NewClientSession(ctx context.Context, behaviorSeed int64, legacy bool) *ClientSession { ++ session := &ClientSession{legacy: legacy} + + randomBytes := make([]byte, 33) // 16 + 16 + 1 + common.Must2(rand.Read(randomBytes)) +@@ -44,10 +47,15 @@ + copy(session.requestBodyIV[:], randomBytes[16:32]) + session.responseHeader = randomBytes[32] + +- BodyKey := sha256.Sum256(session.requestBodyKey[:]) +- copy(session.responseBodyKey[:], BodyKey[:16]) +- BodyIV := sha256.Sum256(session.requestBodyIV[:]) +- copy(session.responseBodyIV[:], BodyIV[:16]) ++ if legacy { ++ session.responseBodyKey = md5.Sum(session.requestBodyKey[:]) ++ session.responseBodyIV = md5.Sum(session.requestBodyIV[:]) ++ } else { ++ BodyKey := sha256.Sum256(session.requestBodyKey[:]) ++ copy(session.responseBodyKey[:], BodyKey[:16]) ++ BodyIV := sha256.Sum256(session.requestBodyIV[:]) ++ copy(session.responseBodyIV[:], BodyIV[:16]) ++ } + { + var err error + session.readDrainer, err = drain.NewBehaviorSeedLimitedDrainer(behaviorSeed, 18, 3266, 64) +@@ -62,6 +70,15 @@ + + func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writer io.Writer) error { + account := header.User.Account.(*vmess.MemoryAccount) ++ var timestamp [8]byte ++ if c.legacy { ++ binary.BigEndian.PutUint64(timestamp[:], uint64(protocol.NowTime())) ++ auth := hmac.New(md5.New, account.AnyValidID().Bytes()) ++ auth.Write(timestamp[:]) ++ if _, err := writer.Write(auth.Sum(nil)); err != nil { ++ return err ++ } ++ } + + buffer := buf.New() + defer buffer.Release() +@@ -93,6 +110,17 @@ + fnv1a.Sum(hashBytes[:0]) + } + ++ if c.legacy { ++ iv := md5.New() ++ for i := 0; i < 4; i++ { ++ iv.Write(timestamp[:]) ++ } ++ stream := crypto.NewAesEncryptionStream(account.ID.CmdKey(), iv.Sum(nil)) ++ stream.XORKeyStream(buffer.Bytes(), buffer.Bytes()) ++ _, err := writer.Write(buffer.Bytes()) ++ return err ++ } ++ + var fixedLengthCmdKey [16]byte + copy(fixedLengthCmdKey[:], account.ID.CmdKey()) + vmessout := vmessaead.SealVMessAEADHeader(fixedLengthCmdKey, buffer.Bytes()) +@@ -163,46 +191,51 @@ + } + + func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.ResponseHeader, error) { +- aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey) +- aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12] +- +- aeadResponseHeaderLengthEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderLengthEncryptionKey) +- +- var aeadEncryptedResponseHeaderLength [18]byte +- var decryptedResponseHeaderLength int +- var decryptedResponseHeaderLengthBinaryDeserializeBuffer uint16 +- +- if n, err := io.ReadFull(reader, aeadEncryptedResponseHeaderLength[:]); err != nil { +- c.readDrainer.AcknowledgeReceive(n) +- return nil, drain.WithError(c.readDrainer, reader, errors.New("Unable to Read Header Len").Base(err)) +- } else { // nolint: golint +- c.readDrainer.AcknowledgeReceive(n) +- } +- if decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil); err != nil { +- return nil, drain.WithError(c.readDrainer, reader, errors.New("Failed To Decrypt Length").Base(err)) +- } else { // nolint: golint +- common.Must(binary.Read(bytes.NewReader(decryptedResponseHeaderLengthBinaryBuffer), binary.BigEndian, &decryptedResponseHeaderLengthBinaryDeserializeBuffer)) +- decryptedResponseHeaderLength = int(decryptedResponseHeaderLengthBinaryDeserializeBuffer) +- } +- +- aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey) +- aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12] +- +- aeadResponseHeaderPayloadEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderPayloadEncryptionKey) +- +- encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16) +- +- if n, err := io.ReadFull(reader, encryptedResponseHeaderBuffer); err != nil { +- c.readDrainer.AcknowledgeReceive(n) +- return nil, drain.WithError(c.readDrainer, reader, errors.New("Unable to Read Header Data").Base(err)) +- } else { // nolint: golint +- c.readDrainer.AcknowledgeReceive(n) +- } +- +- if decryptedResponseHeaderBuffer, err := aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil); err != nil { +- return nil, drain.WithError(c.readDrainer, reader, errors.New("Failed To Decrypt Payload").Base(err)) +- } else { // nolint: golint +- c.responseReader = bytes.NewReader(decryptedResponseHeaderBuffer) ++ if c.legacy { ++ stream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:]) ++ c.responseReader = crypto.NewCryptionReader(stream, reader) ++ } else { ++ aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey) ++ aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12] ++ ++ aeadResponseHeaderLengthEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderLengthEncryptionKey) ++ ++ var aeadEncryptedResponseHeaderLength [18]byte ++ var decryptedResponseHeaderLength int ++ var decryptedResponseHeaderLengthBinaryDeserializeBuffer uint16 ++ ++ if n, err := io.ReadFull(reader, aeadEncryptedResponseHeaderLength[:]); err != nil { ++ c.readDrainer.AcknowledgeReceive(n) ++ return nil, drain.WithError(c.readDrainer, reader, errors.New("Unable to Read Header Len").Base(err)) ++ } else { // nolint: golint ++ c.readDrainer.AcknowledgeReceive(n) ++ } ++ if decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil); err != nil { ++ return nil, drain.WithError(c.readDrainer, reader, errors.New("Failed To Decrypt Length").Base(err)) ++ } else { // nolint: golint ++ common.Must(binary.Read(bytes.NewReader(decryptedResponseHeaderLengthBinaryBuffer), binary.BigEndian, &decryptedResponseHeaderLengthBinaryDeserializeBuffer)) ++ decryptedResponseHeaderLength = int(decryptedResponseHeaderLengthBinaryDeserializeBuffer) ++ } ++ ++ aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey) ++ aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12] ++ ++ aeadResponseHeaderPayloadEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderPayloadEncryptionKey) ++ ++ encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16) ++ ++ if n, err := io.ReadFull(reader, encryptedResponseHeaderBuffer); err != nil { ++ c.readDrainer.AcknowledgeReceive(n) ++ return nil, drain.WithError(c.readDrainer, reader, errors.New("Unable to Read Header Data").Base(err)) ++ } else { // nolint: golint ++ c.readDrainer.AcknowledgeReceive(n) ++ } ++ ++ if decryptedResponseHeaderBuffer, err := aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil); err != nil { ++ return nil, drain.WithError(c.readDrainer, reader, errors.New("Failed To Decrypt Payload").Base(err)) ++ } else { // nolint: golint ++ c.responseReader = bytes.NewReader(decryptedResponseHeaderBuffer) ++ } + } + + buffer := buf.StackNew() +--- a/proxy/vmess/encoding/encoding_test.go ++++ b/proxy/vmess/encoding/encoding_test.go +@@ -41,7 +41,7 @@ + } + + buffer := buf.New() +- client := NewClientSession(context.TODO(), 0) ++ client := NewClientSession(context.TODO(), 0, false) + common.Must(client.EncodeRequestHeader(expectedRequest, buffer)) + + buffer2 := buf.New() +@@ -90,7 +90,7 @@ + } + + buffer := buf.New() +- client := NewClientSession(context.TODO(), 0) ++ client := NewClientSession(context.TODO(), 0, false) + common.Must(client.EncodeRequestHeader(expectedRequest, buffer)) + + buffer2 := buf.New() +@@ -130,7 +130,7 @@ + } + + buffer := buf.New() +- client := NewClientSession(context.TODO(), 0) ++ client := NewClientSession(context.TODO(), 0, false) + common.Must(client.EncodeRequestHeader(expectedRequest, buffer)) + + buffer2 := buf.New() +--- a/proxy/vmess/encoding/legacy_test.go ++++ b/proxy/vmess/encoding/legacy_test.go +@@ -0,0 +1,159 @@ ++package encoding ++ ++import ( ++ "bytes" ++ "context" ++ "crypto/aes" ++ "crypto/cipher" ++ "crypto/hmac" ++ "crypto/md5" ++ "encoding/binary" ++ "fmt" ++ "hash/fnv" ++ "io" ++ "testing" ++ "time" ++ ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/protocol" ++ "github.com/xtls/xray-core/proxy/vmess" ++) ++ ++func legacyRequest(t *testing.T, security protocol.SecurityType, command protocol.RequestCommand) (*ClientSession, *protocol.RequestHeader, []byte) { ++ t.Helper() ++ a, err := (&vmess.Account{Id: "e641f5ad-9397-41e3-bf1a-e8740dfed019", AlterId: 1}).AsAccount() ++ if err != nil { ++ t.Fatal(err) ++ } ++ req := &protocol.RequestHeader{Version: 1, User: &protocol.MemoryUser{Account: a}, Command: command, Address: net.DomainAddress("example.com"), Port: 443, Security: security, Option: protocol.RequestOptionChunkStream | protocol.RequestOptionChunkMasking | protocol.RequestOptionGlobalPadding} ++ c := NewClientSession(context.Background(), 0, true) ++ var wire bytes.Buffer ++ if err := c.EncodeRequestHeader(req, &wire); err != nil { ++ t.Fatal(err) ++ } ++ raw := wire.Bytes() ++ // Independently recover the timestamp from the legacy HMAC-MD5 authenticator. ++ var stamp [8]byte ++ found := false ++ for ts := time.Now().Unix() - 35; ts <= time.Now().Unix()+35; ts++ { ++ binary.BigEndian.PutUint64(stamp[:], uint64(ts)) ++ auth := hmac.New(md5.New, a.(*vmess.MemoryAccount).AlterIDs[0].Bytes()) ++ auth.Write(stamp[:]) ++ if hmac.Equal(auth.Sum(nil), raw[:16]) { ++ found = true ++ break ++ } ++ } ++ if !found { ++ t.Fatal("legacy authentication ID or timestamp mismatch") ++ } ++ iv := md5.Sum(bytes.Repeat(stamp[:], 4)) ++ block, err := aes.NewCipher(a.(*vmess.MemoryAccount).ID.CmdKey()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ header := append([]byte(nil), raw[16:]...) ++ cipher.NewCFBDecrypter(block, iv[:]).XORKeyStream(header, header) ++ h := fnv.New32a() ++ h.Write(header[:len(header)-4]) ++ if binary.BigEndian.Uint32(header[len(header)-4:]) != h.Sum32() { ++ t.Fatal("invalid request header checksum") ++ } ++ if header[0] != 1 || header[37] != byte(command) || header[35]&15 != byte(security) { ++ t.Fatal("invalid request header fields") ++ } ++ return c, req, header ++} ++ ++func TestLegacyVMessTCPAndUDP(t *testing.T) { ++ for _, security := range []protocol.SecurityType{protocol.SecurityType_AES128_GCM, protocol.SecurityType_CHACHA20_POLY1305} { ++ for _, command := range []protocol.RequestCommand{protocol.RequestCommandTCP, protocol.RequestCommandUDP} { ++ t.Run(fmt.Sprintf("%v/%v", security, command), func(t *testing.T) { ++ c, req, header := legacyRequest(t, security, command) ++ server := &ServerSession{} ++ copy(server.requestBodyIV[:], header[1:17]) ++ copy(server.requestBodyKey[:], header[17:33]) ++ server.responseBodyKey = md5.Sum(header[17:33]) ++ server.responseBodyIV = md5.Sum(header[1:17]) ++ var upload, download bytes.Buffer ++ writer, err := c.EncodeRequestBody(req, &upload) ++ if err != nil { ++ t.Fatal(err) ++ } ++ payload := bytes.Repeat([]byte("legacy vmess packet "), 40) ++ if err = writer.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(payload)}); err != nil { ++ t.Fatal(err) ++ } ++ reader, err := server.DecodeRequestBody(req, &upload) ++ if err != nil { ++ t.Fatal(err) ++ } ++ assertPacket(t, reader, payload) ++ response := []byte{header[33], 0, 0, 0} ++ block, err := aes.NewCipher(server.responseBodyKey[:]) ++ if err != nil { ++ t.Fatal(err) ++ } ++ cipher.NewCFBEncrypter(block, server.responseBodyIV[:]).XORKeyStream(response, response) ++ download.Write(response) ++ writer, err = server.EncodeResponseBody(req, &download) ++ if err != nil { ++ t.Fatal(err) ++ } ++ if err = writer.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(payload)}); err != nil { ++ t.Fatal(err) ++ } ++ if _, err = c.DecodeResponseHeader(&download); err != nil { ++ t.Fatal(err) ++ } ++ reader, err = c.DecodeResponseBody(req, &download) ++ if err != nil { ++ t.Fatal(err) ++ } ++ assertPacket(t, reader, payload) ++ }) ++ } ++ } ++} ++ ++func assertPacket(t *testing.T, r buf.Reader, want []byte) { ++ t.Helper() ++ b, err := r.ReadMultiBuffer() ++ defer buf.ReleaseMulti(b) ++ if err != nil { ++ t.Fatal(err) ++ } ++ var got []byte ++ for _, part := range b { ++ got = append(got, part.Bytes()...) ++ } ++ if !bytes.Equal(got, want) { ++ t.Fatal("payload mismatch") ++ } ++} ++ ++func TestLegacyResponseRejectsInvalidHeader(t *testing.T) { ++ for _, truncated := range []bool{false, true} { ++ c, _, header := legacyRequest(t, protocol.SecurityType_AES128_GCM, protocol.RequestCommandTCP) ++ response := []byte{header[33] ^ 1, 0, 0, 0} ++ block, _ := aes.NewCipher(c.responseBodyKey[:]) ++ cipher.NewCFBEncrypter(block, c.responseBodyIV[:]).XORKeyStream(response, response) ++ if truncated { ++ response = response[:2] ++ } ++ if _, err := c.DecodeResponseHeader(bytes.NewReader(response)); err == nil { ++ t.Fatal("accepted invalid response") ++ } ++ } ++} ++ ++type failedLegacyWriter struct{} ++ ++func (failedLegacyWriter) Write([]byte) (int, error) { return 0, io.ErrClosedPipe } ++func TestLegacyRequestWriteError(t *testing.T) { ++ c, req, _ := legacyRequest(t, protocol.SecurityType_AES128_GCM, protocol.RequestCommandTCP) ++ if err := c.EncodeRequestHeader(req, failedLegacyWriter{}); err != io.ErrClosedPipe { ++ t.Fatalf("got %v", err) ++ } ++} +--- a/proxy/vmess/outbound/outbound.go ++++ b/proxy/vmess/outbound/outbound.go +@@ -132,7 +132,7 @@ + newCtx, newCancel = context.WithCancel(context.Background()) + } + +- session := encoding.NewClientSession(ctx, int64(behaviorSeed)) ++ session := encoding.NewClientSession(ctx, int64(behaviorSeed), len(account.AlterIDs) != 0) + sessionPolicy := h.policyManager.ForLevel(request.User.Level) + + ctx, cancel := context.WithCancel(ctx) diff --git a/xray-core/patches/130-add-naive-proxy.patch b/xray-core/patches/130-add-naive-proxy.patch new file mode 100644 index 000000000..74578e82b --- /dev/null +++ b/xray-core/patches/130-add-naive-proxy.patch @@ -0,0 +1,1648 @@ +From: coolsnowwolf +Subject: [PATCH] proxy: add Naive inbound and Chromium/Cronet outbound + +Adapt sing-box Naive CONNECT authentication, padding and UoT to Xray. +Use the same cronet-go revision and Chromium 154 native libraries for +outbound HTTP/2 and HTTP/3, preserving the actual browser network stack. +Route tunneled TCP/UDP through Xray, retain outbound socket options and +dialer chains, and keep TLS verification mandatory in Cronet. + +Add an HTTP/1.1, HTTP/2 and HTTP/3 inbound transport with per-stream +lifecycle handling. Match Xray's default private-target restrictions. +Naive outbound is opt-in with with_naive_outbound; with_musl selects +static OpenWrt archives. Inbound needs no native library. + +Port source: sing-box dfb80349bf5779ec0a3f834c9ec2794345bff8f0 +Cronet: 4d18a60dc3a8 (native libraries a1cafd93eb1f) +Sing-box-derived code is GPL-3.0-or-later; include its license. +Signed-off-by: coolsnowwolf + +--- a/go.mod ++++ b/go.mod +@@ -18,6 +18,23 @@ + github.com/pires/go-proxyproto v0.16.0 + github.com/refraction-networking/utls v1.8.3-0.20261108164504-a13c59ffe98b + github.com/robfig/cron/v3 v3.0.1 ++ github.com/sagernet/cronet-go v0.0.0-20260929213745-4d18a60dc3a8 ++ github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260929213014-a1cafd93eb1f ++ github.com/sagernet/sing v0.8.14 ++ github.com/sagernet/sing-anytls v0.0.0-20260928104022-580984e4d8cb + github.com/stretchr/testify v1.12.1 + github.com/vishvananda/netlink v1.3.1 + github.com/xtls/reality v0.0.0-20261108101329-4bb46b2afd8b +@@ -41,6 +58,7 @@ + ) + + require ( ++ github.com/ebitengine/purego v0.10.0 // indirect + github.com/google/gopacket v1.1.19 // indirect + github.com/huin/goupnp v1.2.0 // indirect + github.com/jackpal/go-nat-pmp v1.0.2 // indirect +@@ -53,8 +71,6 @@ + github.com/pion/logging v0.2.4 // indirect + github.com/pion/transport/v4 v4.1.0 // indirect + github.com/quic-go/qpack v0.6.0 // indirect +- github.com/sagernet/sing v0.8.14 +- github.com/sagernet/sing-anytls v0.0.0-20260928104022-580984e4d8cb + github.com/vishvananda/netns v0.0.5 // indirect + github.com/wlynxg/anet v0.0.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect +--- a/go.sum ++++ b/go.sum +@@ -5,6 +5,8 @@ + github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= + github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= ++github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= ++github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= + github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= + github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= + github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474= +@@ -73,6 +75,36 @@ + github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= + github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= + github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= ++github.com/sagernet/cronet-go v0.0.0-20260929213745-4d18a60dc3a8 h1:yW/KNhSxWxllsPjLx5EUPohHLa4Kvzl2Qt9kHkB0ZIc= ++github.com/sagernet/cronet-go v0.0.0-20260929213745-4d18a60dc3a8/go.mod h1:T/mwtrpC4JlWfScw73CmSBvHzIvc7BatQ1MhRr+cYNw= ++github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260929213014-a1cafd93eb1f h1:YKTs7860q5YC+arXDoxuAuCq4Ba7hcJnau7/QtkVgcs= ++github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= ++github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260929213014-a1cafd93eb1f h1:GbLerpkeRw1SGLHkwSNZXzR02f4aecpOBscKOp2vRkE= ++github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= ++github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260929213014-a1cafd93eb1f h1:dAbYhebHYrOBOvJux8hcuj4yTEIT+UqZmuTBKsLs6cQ= ++github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= ++github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260929213014-a1cafd93eb1f h1:PAK8bwbOxhTdyjnAX/UNLEtbwtN9Hyry4N7RRG8lq30= ++github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= ++github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260929213014-a1cafd93eb1f h1:1z4WrEbt5eB6Arpltl/x4pL/cooe6sn+grHrXk0lDCw= ++github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= ++github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260929213014-a1cafd93eb1f h1:ka6fV7nmcXuxxxViMv3lcO1VZQ7og9BWDTsChbdOs5o= ++github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= ++github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260929213014-a1cafd93eb1f h1:+ZJPetIdQY6gSB3oEXcFuN803z7hsochw63LUVNbAAw= ++github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= ++github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260929213014-a1cafd93eb1f h1:6h/hBfvE9xr+FszN4fvHPlRuMQmkhveOVnlfPiJcNW0= ++github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= ++github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260929213014-a1cafd93eb1f h1:/Vy00lZZkG5usxUMAD/9vRTT93KvJh8NAO4lP1IZEVU= ++github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk= ++github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260929213014-a1cafd93eb1f h1:r6u8oKJ19t4gNtmggIgFgybaMLa3bliALxFTNN/OUy0= ++github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E= ++github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260929213014-a1cafd93eb1f h1:kYGMQ9qe7MyK8qpTuFaleAxVrQx9DAez1jNKFCFMLVQ= ++github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w= ++github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260929213014-a1cafd93eb1f h1:a2/KeGNQagO+eexS97/qykr68YLr616QRFot4aV4sJ4= ++github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0= ++github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260929213014-a1cafd93eb1f h1:lxJLDTBemaS8j+xdOeK+EdxH4FSEndxNG321AO7f/ds= ++github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs= ++github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260929213014-a1cafd93eb1f h1:1ycIIAvgJsm6opK1nrXgle7QVCo9lOtyUGEDEeDOJQQ= ++github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260929213014-a1cafd93eb1f/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc= + github.com/sagernet/sing v0.8.14 h1:S6Netv4F61uNAuD/sUbHnGuNUEPwtL08Ouk0//CVYgM= + github.com/sagernet/sing v0.8.14/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA= + github.com/sagernet/sing-anytls v0.0.0-20260928104022-580984e4d8cb h1:n4sF2zv0CR1gcb4awh61t11t0FZmcP40x5enB8HARjE= +--- a/infra/conf/naive.go ++++ b/infra/conf/naive.go +@@ -0,0 +1,87 @@ ++package conf ++ ++import ( ++ "encoding/base64" ++ "fmt" ++ "github.com/xtls/xray-core/proxy/naive" ++ "golang.org/x/net/http/httpguts" ++ "google.golang.org/protobuf/proto" ++ "os" ++ "strings" ++) ++ ++type NaiveAccount struct { ++ Username string `json:"username"` ++ Password string `json:"password"` ++} ++type NaiveServerConfig struct { ++ Users []NaiveAccount `json:"users"` ++ Level uint32 `json:"level"` ++} ++ ++func (c *NaiveServerConfig) Build() (proto.Message, error) { ++ if len(c.Users) == 0 { ++ return nil, fmt.Errorf("naive: users required") ++ } ++ out := &naive.ServerConfig{Level: c.Level} ++ seen := map[string]bool{} ++ for _, u := range c.Users { ++ if u.Username == "" || strings.Contains(u.Username, ":") || u.Password == "" || seen[u.Username] { ++ return nil, fmt.Errorf("naive: invalid or duplicate user") ++ } ++ seen[u.Username] = true ++ out.Users = append(out.Users, &naive.Account{Username: u.Username, Password: u.Password}) ++ } ++ return out, nil ++} ++ ++type NaiveClientConfig struct { ++ Address *Address `json:"address"` ++ Port uint16 `json:"port"` ++ Username string `json:"username"` ++ Password string `json:"password"` ++ ServerName string `json:"serverName"` ++ Certificate []string `json:"certificate"` ++ CertificateFile string `json:"certificateFile"` ++ QUIC bool `json:"quic"` ++ UDPOverTCP bool `json:"udpOverTcp"` ++ Level uint32 `json:"level"` ++ InsecureConcurrency uint32 `json:"insecureConcurrency"` ++ ExtraHeaders map[string]string `json:"extraHeaders"` ++ ReceiveWindow uint64 `json:"receiveWindow"` ++ QUICCongestionControl string `json:"quicCongestionControl"` ++ QUICSessionReceiveWindow uint64 `json:"quicSessionReceiveWindow"` ++ ECHConfigList string `json:"echConfigList"` ++} ++ ++func (c *NaiveClientConfig) Build() (proto.Message, error) { ++ if c.Address == nil || c.Port == 0 || c.Username == "" || strings.Contains(c.Username, ":") || c.Password == "" { ++ return nil, fmt.Errorf("naive: address, port, username and password required") ++ } ++ switch c.QUICCongestionControl { ++ case "", "bbr", "bbr2", "cubic", "reno": ++ default: ++ return nil, fmt.Errorf("naive: invalid QUIC congestion control") ++ } ++ for k, v := range c.ExtraHeaders { ++ if !httpguts.ValidHeaderFieldName(k) || !httpguts.ValidHeaderFieldValue(v) || strings.ContainsAny(k+v, "\r\n") || strings.EqualFold(k, "Proxy-Authorization") || strings.EqualFold(k, "Padding") || strings.EqualFold(k, "Host") { ++ return nil, fmt.Errorf("naive: invalid or reserved extra header %q", k) ++ } ++ } ++ cert := strings.Join(c.Certificate, "\n") ++ if c.CertificateFile != "" { ++ if cert != "" { ++ return nil, fmt.Errorf("naive: specify certificate or certificateFile") ++ } ++ b, e := os.ReadFile(c.CertificateFile) ++ if e != nil { ++ return nil, e ++ } ++ cert = string(b) ++ } ++ ech, e := base64.StdEncoding.DecodeString(c.ECHConfigList) ++ if e != nil { ++ return nil, fmt.Errorf("naive: invalid ECH config: %w", e) ++ } ++ return &naive.ClientConfig{Address: c.Address.Build(), Port: uint32(c.Port), Username: c.Username, Password: c.Password, ServerName: c.ServerName, TrustedRootCertificates: cert, Quic: c.QUIC, UdpOverTcp: c.UDPOverTCP, Level: c.Level, Concurrency: c.InsecureConcurrency, ExtraHeaders: c.ExtraHeaders, ReceiveWindow: c.ReceiveWindow, QuicCongestionControl: c.QUICCongestionControl, QuicSessionReceiveWindow: c.QUICSessionReceiveWindow, EchConfigList: ech}, nil ++} +--- a/infra/conf/transport_internet.go ++++ b/infra/conf/transport_internet.go +@@ -38,6 +38,8 @@ + return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3") + case "hysteria": + return "hysteria", nil ++ case "naive": ++ return "naive", nil + case "masque": + return "masque", nil + case "xdrive": +--- a/infra/conf/xray.go ++++ b/infra/conf/xray.go +@@ -27,6 +27,7 @@ + var ( + inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ + "tunnel": func() interface{} { return new(DokodemoConfig) }, ++ "naive": func() interface{} { return new(NaiveServerConfig) }, + "dokodemo-door": func() interface{} { return new(DokodemoConfig) }, + "http": func() interface{} { return new(HTTPServerConfig) }, + "shadowsocks": func() interface{} { return new(ShadowsocksServerConfig) }, +@@ -54,6 +55,7 @@ + "vless": func() interface{} { return new(VLessOutboundConfig) }, + "vmess": func() interface{} { return new(VMessOutboundConfig) }, + "trojan": func() interface{} { return new(TrojanClientConfig) }, ++ "naive": func() interface{} { return new(NaiveClientConfig) }, + "anytls": func() interface{} { return new(AnyTLSClientConfig) }, + "hysteria": func() interface{} { return new(HysteriaClientConfig) }, + "masque": func() interface{} { return new(MasqueClientConfig) }, +--- a/main/distro/all/all.go ++++ b/main/distro/all/all.go +@@ -43,6 +43,7 @@ + _ "github.com/xtls/xray-core/proxy/http" + _ "github.com/xtls/xray-core/proxy/loopback" + _ "github.com/xtls/xray-core/proxy/masque" ++ _ "github.com/xtls/xray-core/proxy/naive" + _ "github.com/xtls/xray-core/proxy/shadowsocks" + _ "github.com/xtls/xray-core/proxy/socks" + _ "github.com/xtls/xray-core/proxy/trojan" +--- a/proxy/freedom/freedom.go ++++ b/proxy/freedom/freedom.go +@@ -172,7 +172,7 @@ + switch inbound.Name { + case "vless-reverse": + return defaultBlockAllRule +- case "vless", "vmess", "trojan", "hysteria", "wireguard", "masque": ++ case "vless", "vmess", "trojan", "hysteria", "wireguard", "masque", "naive": + return defaultBlockPrivateRule + default: + if strings.HasPrefix(inbound.Name, "shadowsocks") { +--- a/proxy/naive/LICENSE.sing-box ++++ b/proxy/naive/LICENSE.sing-box +@@ -0,0 +1,17 @@ ++Copyright (C) 2022 by nekohasekai ++ ++This program is free software: you can redistribute it and/or modify ++it under the terms of the GNU General Public License as published by ++the Free Software Foundation, either version 3 of the License, or ++(at your option) any later version. ++ ++This program is distributed in the hope that it will be useful, ++but WITHOUT ANY WARRANTY; without even the implied warranty of ++MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++GNU General Public License for more details. ++ ++You should have received a copy of the GNU General Public License ++along with this program. If not, see . ++ ++In addition, no derivative work may use the name or imply association ++with this application without prior consent. +--- a/proxy/naive/address.go ++++ b/proxy/naive/address.go +@@ -0,0 +1,10 @@ ++package naive ++ ++import ( ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/xtls/xray-core/common/net" ++) ++ ++func socksaddr(d net.Destination) M.Socksaddr { ++ return M.ParseSocksaddrHostPort(d.Address.String(), uint16(d.Port)) ++} +--- a/proxy/naive/client.go ++++ b/proxy/naive/client.go +@@ -0,0 +1,175 @@ ++//go:build with_naive_outbound ++ ++// Adapted from sing-box protocol/naive/outbound.go (GPL-3.0-or-later). ++package naive ++ ++import ( ++ "context" ++ "fmt" ++ "strings" ++ "sync" ++ "time" ++ ++ mdns "github.com/miekg/dns" ++ "github.com/sagernet/cronet-go" ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/sagernet/sing/common/uot" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/core" ++ "github.com/xtls/xray-core/features/dns" ++ "github.com/xtls/xray-core/features/policy" ++ "github.com/xtls/xray-core/transport" ++ "github.com/xtls/xray-core/transport/internet" ++) ++ ++type Client struct { ++ config *ClientConfig ++ options cronet.NaiveClientOptions ++ policy policy.Session ++ mu sync.Mutex ++ client *cronet.NaiveClient ++ closed bool ++ cancel context.CancelFunc ++} ++ ++func NewClient(ctx context.Context, c *ClientConfig) (*Client, error) { ++ if c.Address == nil || c.Port == 0 || c.Port > 65535 || c.Username == "" || c.Password == "" { ++ return nil, fmt.Errorf("naive: invalid server or credentials") ++ } ++ settings, _ := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig) ++ if settings != nil && (settings.ProtocolName != "tcp" || settings.SecurityType != "") { ++ return nil, fmt.Errorf("naive: use raw/tcp transport without stream security; Cronet handles TLS") ++ } ++ v := core.MustFromContext(ctx) ++ resolver := v.GetFeature(dns.ClientType()).(dns.Client) ++ lifetime, cancel := context.WithCancel(ctx) ++ cc := map[string]cronet.QUICCongestionControl{"": cronet.QUICCongestionControlDefault, "bbr": cronet.QUICCongestionControlBBR, "bbr2": cronet.QUICCongestionControlBBRv2, "cubic": cronet.QUICCongestionControlCubic, "reno": cronet.QUICCongestionControlReno} ++ congestion, ok := cc[c.QuicCongestionControl] ++ if !ok { ++ cancel() ++ return nil, fmt.Errorf("invalid QUIC congestion control") ++ } ++ return &Client{config: c, cancel: cancel, policy: v.GetFeature(policy.ManagerType()).(policy.Manager).ForLevel(c.Level), options: cronet.NaiveClientOptions{ ++ Context: lifetime, ServerAddress: M.ParseSocksaddrHostPort(c.Address.AsAddress().String(), uint16(c.Port)), ServerName: c.ServerName, Username: c.Username, Password: c.Password, TrustedRootCertificates: c.TrustedRootCertificates, QUIC: c.Quic, InsecureConcurrency: int(c.Concurrency), ExtraHeaders: c.ExtraHeaders, ReceiveWindow: c.ReceiveWindow, QUICCongestionControl: congestion, QUICSessionReceiveWindow: c.QuicSessionReceiveWindow, ECHEnabled: len(c.EchConfigList) > 0, ECHConfigList: c.EchConfigList, ++ DNSResolver: func(ctx context.Context, q *mdns.Msg) *mdns.Msg { ++ r := new(mdns.Msg) ++ r.SetReply(q) ++ for _, question := range q.Question { ++ if question.Qtype != mdns.TypeA && question.Qtype != mdns.TypeAAAA { ++ continue ++ } ++ ips, ttl, err := resolver.LookupIP(strings.TrimSuffix(question.Name, "."), dns.IPOption{IPv4Enable: question.Qtype == mdns.TypeA, IPv6Enable: question.Qtype == mdns.TypeAAAA}) ++ if err != nil { ++ r.Rcode = mdns.RcodeServerFailure ++ return r ++ } ++ for _, ip := range ips { ++ h := mdns.RR_Header{Name: question.Name, Rrtype: question.Qtype, Class: mdns.ClassINET, Ttl: ttl} ++ if question.Qtype == mdns.TypeA && ip.To4() != nil { ++ r.Answer = append(r.Answer, &mdns.A{Hdr: h, A: ip.To4()}) ++ } else if question.Qtype == mdns.TypeAAAA && ip.To4() == nil { ++ r.Answer = append(r.Answer, &mdns.AAAA{Hdr: h, AAAA: ip}) ++ } ++ } ++ } ++ return r ++ }}}, nil ++} ++func (c *Client) getClient(d internet.Dialer) (*cronet.NaiveClient, error) { ++ c.mu.Lock() ++ defer c.mu.Unlock() ++ if c.closed { ++ return nil, fmt.Errorf("naive client closed") ++ } ++ if c.client == nil { ++ options := c.options ++ options.Dialer = &cronetDialer{d} ++ cl, e := cronet.NewNaiveClient(options) ++ if e != nil { ++ return nil, e ++ } ++ if e = cl.Start(); e != nil { ++ cl.Close() ++ return nil, e ++ } ++ c.client = cl ++ } ++ return c.client, nil ++} ++func (c *Client) Close() error { ++ c.cancel() ++ c.mu.Lock() ++ c.closed = true ++ cl := c.client ++ c.mu.Unlock() ++ if cl != nil { ++ return cl.Close() ++ } ++ return nil ++} ++func (c *Client) Process(ctx context.Context, link *transport.Link, d internet.Dialer) error { ++ obs := session.OutboundsFromContext(ctx) ++ if len(obs) == 0 { ++ return fmt.Errorf("naive: missing target") ++ } ++ ob := obs[len(obs)-1] ++ target := ob.Target ++ if !target.IsValid() || (target.Network != net.Network_TCP && target.Network != net.Network_UDP) { ++ return fmt.Errorf("naive: invalid target") ++ } ++ if target.Network == net.Network_UDP && !c.config.UdpOverTcp { ++ return fmt.Errorf("naive: UDP requires udpOverTcp") ++ } ++ ob.Name = "naive" ++ ob.CanSpliceCopy = 3 ++ client, err := c.getClient(d) ++ if err != nil { ++ return err ++ } ++ destination := socksaddr(target) ++ streamDestination := destination ++ if target.Network == net.Network_UDP { ++ streamDestination = uot.RequestDestination(uot.Version) ++ } ++ dialCtx, cancelDial := context.WithTimeout(ctx, c.policy.Timeouts.Handshake) ++ conn, err := client.DialContext(dialCtx, "tcp", streamDestination) ++ cancelDial() ++ if err != nil { ++ return err ++ } ++ defer conn.Close() ++ var reader buf.Reader = buf.NewReader(conn) ++ var writer buf.Writer = buf.NewWriter(conn) ++ if target.Network == net.Network_UDP { ++ conn.SetWriteDeadline(time.Now().Add(c.policy.Timeouts.Handshake)) ++ pc, e := (&uot.Client{Version: uot.Version}).DialConn(conn, false, destination) ++ conn.SetWriteDeadline(time.Time{}) ++ if e != nil { ++ return e ++ } ++ reader = &packetReader{conn: pc} ++ writer = &packetWriter{conn: pc, destination: destination} ++ } ++ return relay(ctx, link.Reader, writer, reader, link.Writer, conn, c.policy) ++} ++ ++type cronetDialer struct{ internet.Dialer } ++ ++func (d *cronetDialer) DialContext(ctx context.Context, network string, a M.Socksaddr) (net.Conn, error) { ++ dest := net.TCPDestination(net.ParseAddress(a.AddrString()), net.Port(a.Port)) ++ if network == "udp" { ++ dest.Network = net.Network_UDP ++ } ++ return d.Dial(ctx, dest) ++} ++func (d *cronetDialer) ListenPacket(context.Context, M.Socksaddr) (net.PacketConn, error) { ++ return nil, fmt.Errorf("unconnected UDP is not used by Cronet") ++} ++func init() { ++ common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, c interface{}) (interface{}, error) { ++ return NewClient(ctx, c.(*ClientConfig)) ++ })) ++} +--- a/proxy/naive/client_stub.go ++++ b/proxy/naive/client_stub.go +@@ -0,0 +1,15 @@ ++//go:build !with_naive_outbound ++ ++package naive ++ ++import ( ++ "context" ++ "fmt" ++ "github.com/xtls/xray-core/common" ++) ++ ++func init() { ++ common.Must(common.RegisterConfig((*ClientConfig)(nil), func(context.Context, interface{}) (interface{}, error) { ++ return nil, fmt.Errorf("Naive outbound requires a Cronet-enabled build (with_naive_outbound)") ++ })) ++} +--- a/proxy/naive/config.pb.go ++++ b/proxy/naive/config.pb.go +@@ -0,0 +1,372 @@ ++// Code generated by protoc-gen-go. DO NOT EDIT. ++// versions: ++// protoc-gen-go v1.36.12 ++// protoc v3.21.12 ++// source: proxy/naive/config.proto ++ ++package naive ++ ++import ( ++ net "github.com/xtls/xray-core/common/net" ++ protoreflect "google.golang.org/protobuf/reflect/protoreflect" ++ protoimpl "google.golang.org/protobuf/runtime/protoimpl" ++ reflect "reflect" ++ sync "sync" ++ unsafe "unsafe" ++) ++ ++const ( ++ // Verify that this generated code is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) ++ // Verify that runtime/protoimpl is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ++) ++ ++type Account struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` ++ Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *Account) Reset() { ++ *x = Account{} ++ mi := &file_proxy_naive_config_proto_msgTypes[0] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *Account) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*Account) ProtoMessage() {} ++ ++func (x *Account) ProtoReflect() protoreflect.Message { ++ mi := &file_proxy_naive_config_proto_msgTypes[0] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use Account.ProtoReflect.Descriptor instead. ++func (*Account) Descriptor() ([]byte, []int) { ++ return file_proxy_naive_config_proto_rawDescGZIP(), []int{0} ++} ++ ++func (x *Account) GetUsername() string { ++ if x != nil { ++ return x.Username ++ } ++ return "" ++} ++ ++func (x *Account) GetPassword() string { ++ if x != nil { ++ return x.Password ++ } ++ return "" ++} ++ ++type ServerConfig struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Users []*Account `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` ++ Level uint32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *ServerConfig) Reset() { ++ *x = ServerConfig{} ++ mi := &file_proxy_naive_config_proto_msgTypes[1] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *ServerConfig) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*ServerConfig) ProtoMessage() {} ++ ++func (x *ServerConfig) ProtoReflect() protoreflect.Message { ++ mi := &file_proxy_naive_config_proto_msgTypes[1] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead. ++func (*ServerConfig) Descriptor() ([]byte, []int) { ++ return file_proxy_naive_config_proto_rawDescGZIP(), []int{1} ++} ++ ++func (x *ServerConfig) GetUsers() []*Account { ++ if x != nil { ++ return x.Users ++ } ++ return nil ++} ++ ++func (x *ServerConfig) GetLevel() uint32 { ++ if x != nil { ++ return x.Level ++ } ++ return 0 ++} ++ ++type ClientConfig struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` ++ Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` ++ Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` ++ Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"` ++ ServerName string `protobuf:"bytes,5,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` ++ TrustedRootCertificates string `protobuf:"bytes,6,opt,name=trusted_root_certificates,json=trustedRootCertificates,proto3" json:"trusted_root_certificates,omitempty"` ++ Quic bool `protobuf:"varint,7,opt,name=quic,proto3" json:"quic,omitempty"` ++ UdpOverTcp bool `protobuf:"varint,8,opt,name=udp_over_tcp,json=udpOverTcp,proto3" json:"udp_over_tcp,omitempty"` ++ Level uint32 `protobuf:"varint,9,opt,name=level,proto3" json:"level,omitempty"` ++ Concurrency uint32 `protobuf:"varint,10,opt,name=concurrency,proto3" json:"concurrency,omitempty"` ++ ExtraHeaders map[string]string `protobuf:"bytes,11,rep,name=extra_headers,json=extraHeaders,proto3" json:"extra_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` ++ ReceiveWindow uint64 `protobuf:"varint,12,opt,name=receive_window,json=receiveWindow,proto3" json:"receive_window,omitempty"` ++ QuicCongestionControl string `protobuf:"bytes,13,opt,name=quic_congestion_control,json=quicCongestionControl,proto3" json:"quic_congestion_control,omitempty"` ++ QuicSessionReceiveWindow uint64 `protobuf:"varint,14,opt,name=quic_session_receive_window,json=quicSessionReceiveWindow,proto3" json:"quic_session_receive_window,omitempty"` ++ EchConfigList []byte `protobuf:"bytes,15,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *ClientConfig) Reset() { ++ *x = ClientConfig{} ++ mi := &file_proxy_naive_config_proto_msgTypes[2] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *ClientConfig) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*ClientConfig) ProtoMessage() {} ++ ++func (x *ClientConfig) ProtoReflect() protoreflect.Message { ++ mi := &file_proxy_naive_config_proto_msgTypes[2] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. ++func (*ClientConfig) Descriptor() ([]byte, []int) { ++ return file_proxy_naive_config_proto_rawDescGZIP(), []int{2} ++} ++ ++func (x *ClientConfig) GetAddress() *net.IPOrDomain { ++ if x != nil { ++ return x.Address ++ } ++ return nil ++} ++ ++func (x *ClientConfig) GetPort() uint32 { ++ if x != nil { ++ return x.Port ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetUsername() string { ++ if x != nil { ++ return x.Username ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetPassword() string { ++ if x != nil { ++ return x.Password ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetServerName() string { ++ if x != nil { ++ return x.ServerName ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetTrustedRootCertificates() string { ++ if x != nil { ++ return x.TrustedRootCertificates ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetQuic() bool { ++ if x != nil { ++ return x.Quic ++ } ++ return false ++} ++ ++func (x *ClientConfig) GetUdpOverTcp() bool { ++ if x != nil { ++ return x.UdpOverTcp ++ } ++ return false ++} ++ ++func (x *ClientConfig) GetLevel() uint32 { ++ if x != nil { ++ return x.Level ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetConcurrency() uint32 { ++ if x != nil { ++ return x.Concurrency ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetExtraHeaders() map[string]string { ++ if x != nil { ++ return x.ExtraHeaders ++ } ++ return nil ++} ++ ++func (x *ClientConfig) GetReceiveWindow() uint64 { ++ if x != nil { ++ return x.ReceiveWindow ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetQuicCongestionControl() string { ++ if x != nil { ++ return x.QuicCongestionControl ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetQuicSessionReceiveWindow() uint64 { ++ if x != nil { ++ return x.QuicSessionReceiveWindow ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetEchConfigList() []byte { ++ if x != nil { ++ return x.EchConfigList ++ } ++ return nil ++} ++ ++var File_proxy_naive_config_proto protoreflect.FileDescriptor ++ ++const file_proxy_naive_config_proto_rawDesc = "" + ++ "\n" + ++ "\x18proxy/naive/config.proto\x12\x10xray.proxy.naive\x1a\x18common/net/address.proto\"A\n" + ++ "\aAccount\x12\x1a\n" + ++ "\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" + ++ "\bpassword\x18\x02 \x01(\tR\bpassword\"U\n" + ++ "\fServerConfig\x12/\n" + ++ "\x05users\x18\x01 \x03(\v2\x19.xray.proxy.naive.AccountR\x05users\x12\x14\n" + ++ "\x05level\x18\x02 \x01(\rR\x05level\"\xba\x05\n" + ++ "\fClientConfig\x125\n" + ++ "\aaddress\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" + ++ "\x04port\x18\x02 \x01(\rR\x04port\x12\x1a\n" + ++ "\busername\x18\x03 \x01(\tR\busername\x12\x1a\n" + ++ "\bpassword\x18\x04 \x01(\tR\bpassword\x12\x1f\n" + ++ "\vserver_name\x18\x05 \x01(\tR\n" + ++ "serverName\x12:\n" + ++ "\x19trusted_root_certificates\x18\x06 \x01(\tR\x17trustedRootCertificates\x12\x12\n" + ++ "\x04quic\x18\a \x01(\bR\x04quic\x12 \n" + ++ "\fudp_over_tcp\x18\b \x01(\bR\n" + ++ "udpOverTcp\x12\x14\n" + ++ "\x05level\x18\t \x01(\rR\x05level\x12 \n" + ++ "\vconcurrency\x18\n" + ++ " \x01(\rR\vconcurrency\x12U\n" + ++ "\rextra_headers\x18\v \x03(\v20.xray.proxy.naive.ClientConfig.ExtraHeadersEntryR\fextraHeaders\x12%\n" + ++ "\x0ereceive_window\x18\f \x01(\x04R\rreceiveWindow\x126\n" + ++ "\x17quic_congestion_control\x18\r \x01(\tR\x15quicCongestionControl\x12=\n" + ++ "\x1bquic_session_receive_window\x18\x0e \x01(\x04R\x18quicSessionReceiveWindow\x12&\n" + ++ "\x0fech_config_list\x18\x0f \x01(\fR\rechConfigList\x1a?\n" + ++ "\x11ExtraHeadersEntry\x12\x10\n" + ++ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + ++ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B'Z%github.com/xtls/xray-core/proxy/naiveb\x06proto3" ++ ++var ( ++ file_proxy_naive_config_proto_rawDescOnce sync.Once ++ file_proxy_naive_config_proto_rawDescData []byte ++) ++ ++func file_proxy_naive_config_proto_rawDescGZIP() []byte { ++ file_proxy_naive_config_proto_rawDescOnce.Do(func() { ++ file_proxy_naive_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_naive_config_proto_rawDesc), len(file_proxy_naive_config_proto_rawDesc))) ++ }) ++ return file_proxy_naive_config_proto_rawDescData ++} ++ ++var file_proxy_naive_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) ++var file_proxy_naive_config_proto_goTypes = []any{ ++ (*Account)(nil), // 0: xray.proxy.naive.Account ++ (*ServerConfig)(nil), // 1: xray.proxy.naive.ServerConfig ++ (*ClientConfig)(nil), // 2: xray.proxy.naive.ClientConfig ++ nil, // 3: xray.proxy.naive.ClientConfig.ExtraHeadersEntry ++ (*net.IPOrDomain)(nil), // 4: xray.common.net.IPOrDomain ++} ++var file_proxy_naive_config_proto_depIdxs = []int32{ ++ 0, // 0: xray.proxy.naive.ServerConfig.users:type_name -> xray.proxy.naive.Account ++ 4, // 1: xray.proxy.naive.ClientConfig.address:type_name -> xray.common.net.IPOrDomain ++ 3, // 2: xray.proxy.naive.ClientConfig.extra_headers:type_name -> xray.proxy.naive.ClientConfig.ExtraHeadersEntry ++ 3, // [3:3] is the sub-list for method output_type ++ 3, // [3:3] is the sub-list for method input_type ++ 3, // [3:3] is the sub-list for extension type_name ++ 3, // [3:3] is the sub-list for extension extendee ++ 0, // [0:3] is the sub-list for field type_name ++} ++ ++func init() { file_proxy_naive_config_proto_init() } ++func file_proxy_naive_config_proto_init() { ++ if File_proxy_naive_config_proto != nil { ++ return ++ } ++ type x struct{} ++ out := protoimpl.TypeBuilder{ ++ File: protoimpl.DescBuilder{ ++ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), ++ RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_naive_config_proto_rawDesc), len(file_proxy_naive_config_proto_rawDesc)), ++ NumEnums: 0, ++ NumMessages: 4, ++ NumExtensions: 0, ++ NumServices: 0, ++ }, ++ GoTypes: file_proxy_naive_config_proto_goTypes, ++ DependencyIndexes: file_proxy_naive_config_proto_depIdxs, ++ MessageInfos: file_proxy_naive_config_proto_msgTypes, ++ }.Build() ++ File_proxy_naive_config_proto = out.File ++ file_proxy_naive_config_proto_goTypes = nil ++ file_proxy_naive_config_proto_depIdxs = nil ++} +--- a/proxy/naive/config.proto ++++ b/proxy/naive/config.proto +@@ -0,0 +1,23 @@ ++syntax = "proto3"; ++package xray.proxy.naive; ++option go_package = "github.com/xtls/xray-core/proxy/naive"; ++import "common/net/address.proto"; ++message Account { string username = 1; string password = 2; } ++message ServerConfig { repeated Account users = 1; uint32 level = 2; } ++message ClientConfig { ++ xray.common.net.IPOrDomain address = 1; ++ uint32 port = 2; ++ string username = 3; ++ string password = 4; ++ string server_name = 5; ++ string trusted_root_certificates = 6; ++ bool quic = 7; ++ bool udp_over_tcp = 8; ++ uint32 level = 9; ++ uint32 concurrency = 10; ++ map extra_headers = 11; ++ uint64 receive_window = 12; ++ string quic_congestion_control = 13; ++ uint64 quic_session_receive_window = 14; ++ bytes ech_config_list = 15; ++} +--- a/proxy/naive/cronet_linux_386.go ++++ b/proxy/naive/cronet_linux_386.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && 386 && !with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_386" +--- a/proxy/naive/cronet_linux_386_musl.go ++++ b/proxy/naive/cronet_linux_386_musl.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && 386 && with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_386_musl" +--- a/proxy/naive/cronet_linux_amd64.go ++++ b/proxy/naive/cronet_linux_amd64.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && amd64 && !with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_amd64" +--- a/proxy/naive/cronet_linux_amd64_musl.go ++++ b/proxy/naive/cronet_linux_amd64_musl.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && amd64 && with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_amd64_musl" +--- a/proxy/naive/cronet_linux_arm.go ++++ b/proxy/naive/cronet_linux_arm.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && arm && !with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_arm" +--- a/proxy/naive/cronet_linux_arm64.go ++++ b/proxy/naive/cronet_linux_arm64.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && arm64 && !with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_arm64" +--- a/proxy/naive/cronet_linux_arm64_musl.go ++++ b/proxy/naive/cronet_linux_arm64_musl.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && arm64 && with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_arm64_musl" +--- a/proxy/naive/cronet_linux_arm_musl.go ++++ b/proxy/naive/cronet_linux_arm_musl.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && arm && with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_arm_musl" +--- a/proxy/naive/cronet_linux_loong64.go ++++ b/proxy/naive/cronet_linux_loong64.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && loong64 && !with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_loong64" +--- a/proxy/naive/cronet_linux_loong64_musl.go ++++ b/proxy/naive/cronet_linux_loong64_musl.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && loong64 && with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_loong64_musl" +--- a/proxy/naive/cronet_linux_mipsle.go ++++ b/proxy/naive/cronet_linux_mipsle.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && mipsle && !with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_mipsle" +--- a/proxy/naive/cronet_linux_mipsle_musl.go ++++ b/proxy/naive/cronet_linux_mipsle_musl.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && mipsle && with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_mipsle_musl" +--- a/proxy/naive/cronet_linux_riscv64.go ++++ b/proxy/naive/cronet_linux_riscv64.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && riscv64 && !with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_riscv64" +--- a/proxy/naive/cronet_linux_riscv64_musl.go ++++ b/proxy/naive/cronet_linux_riscv64_musl.go +@@ -0,0 +1,5 @@ ++//go:build with_naive_outbound && linux && !android && riscv64 && with_musl ++ ++package naive ++ ++import _ "github.com/sagernet/cronet-go/lib/linux_riscv64_musl" +--- a/proxy/naive/packet.go ++++ b/proxy/naive/packet.go +@@ -0,0 +1,44 @@ ++package naive ++ ++import ( ++ singbuf "github.com/sagernet/sing/common/buf" ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/sagernet/sing/common/uot" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/net" ++) ++ ++type packetReader struct{ conn *uot.Conn } ++ ++func (r *packetReader) ReadMultiBuffer() (buf.MultiBuffer, error) { ++ // Read one whole datagram, including packets larger than Xray's 8K buffer. ++ packet := singbuf.NewSize(65535) ++ defer packet.Release() ++ address, err := r.conn.ReadPacket(packet) ++ if err != nil { ++ return nil, err ++ } ++ b := buf.FromBytes(append([]byte(nil), packet.Bytes()...)) ++ destination := net.UDPDestination(net.ParseAddress(address.AddrString()), net.Port(address.Port)) ++ b.UDP = &destination ++ return buf.MultiBuffer{b}, nil ++} ++ ++type packetWriter struct { ++ conn *uot.Conn ++ destination M.Socksaddr ++} ++ ++func (w *packetWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { ++ defer buf.ReleaseMulti(mb) ++ for _, b := range mb { ++ destination := w.destination ++ if b.UDP != nil { ++ destination = socksaddr(*b.UDP) ++ } ++ if err := w.conn.WritePacket(singbuf.As(b.Bytes()), destination); err != nil { ++ return err ++ } ++ } ++ return nil ++} +--- a/proxy/naive/padding.go ++++ b/proxy/naive/padding.go +@@ -0,0 +1,93 @@ ++// Adapted from sing-box protocol/naive/inbound_conn.go (GPL-3.0-or-later). ++package naive ++ ++import ( ++ "encoding/binary" ++ "io" ++ "math/rand" ++ "net" ++) ++ ++const paddingCount = 8 ++ ++func generatePaddingHeader() string { ++ p := make([]byte, 30+rand.Intn(32)) ++ bits := rand.Uint64() ++ for i := range 16 { ++ p[i] = "!#$()+<>?@[]^`{}"[bits&15] ++ bits >>= 4 ++ } ++ for i := 16; i < len(p); i++ { ++ p[i] = '~' ++ } ++ return string(p) ++} ++ ++type paddingConn struct { ++ net.Conn ++ readPadding, writePadding, remaining, skip int ++} ++ ++func (c *paddingConn) Read(p []byte) (int, error) { ++ if len(p) == 0 { ++ return 0, nil ++ } ++ for { ++ if c.remaining > 0 { ++ if len(p) > c.remaining { ++ p = p[:c.remaining] ++ } ++ n, e := c.Conn.Read(p) ++ c.remaining -= n ++ if e == io.EOF && c.remaining > 0 { ++ e = io.ErrUnexpectedEOF ++ } ++ return n, e ++ } ++ if c.skip > 0 { ++ _, e := io.CopyN(io.Discard, c.Conn, int64(c.skip)) ++ if e != nil { ++ if e == io.EOF { ++ e = io.ErrUnexpectedEOF ++ } ++ return 0, e ++ } ++ c.skip = 0 ++ } ++ if c.readPadding >= paddingCount { ++ return c.Conn.Read(p) ++ } ++ var h [3]byte ++ if _, e := io.ReadFull(c.Conn, h[:]); e != nil { ++ return 0, e ++ } ++ c.remaining = int(binary.BigEndian.Uint16(h[:2])) ++ c.skip = int(h[2]) ++ c.readPadding++ ++ } ++} ++func (c *paddingConn) Write(p []byte) (n int, err error) { ++ for len(p) > 0 { ++ if c.writePadding >= paddingCount { ++ k, e := c.Conn.Write(p) ++ return n + k, e ++ } ++ k := min(len(p), 65535) ++ pad := rand.Intn(256) ++ frame := make([]byte, 3+k+pad) ++ binary.BigEndian.PutUint16(frame[:2], uint16(k)) ++ frame[2] = byte(pad) ++ copy(frame[3:], p[:k]) ++ written, e := c.Conn.Write(frame) ++ if e == nil && written != len(frame) { ++ e = io.ErrShortWrite ++ } ++ if e != nil { ++ return n, e ++ } ++ c.writePadding++ ++ n += k ++ p = p[k:] ++ } ++ return ++} +--- a/proxy/naive/relay.go ++++ b/proxy/naive/relay.go +@@ -0,0 +1,62 @@ ++package naive ++ ++import ( ++ "context" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/signal" ++ "github.com/xtls/xray-core/features/policy" ++ "io" ++) ++ ++// Wait for BOTH copy loops before returning: HTTP response writers cease to be ++// valid when ServeHTTP returns. task.Run alone returns before its other task. ++func relay(ctx context.Context, upR buf.Reader, upW buf.Writer, downR buf.Reader, downW buf.Writer, conn io.Closer, p policy.Session) error { ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) ++ defer timer.SetTimeout(0) ++ results := make(chan error, 2) ++ go func() { ++ e := buf.Copy(upR, upW, buf.UpdateActivity(timer)) ++ if e == nil { ++ common.Close(upW) ++ } ++ timer.SetTimeout(p.Timeouts.DownlinkOnly) ++ results <- e ++ }() ++ go func() { ++ e := buf.Copy(downR, downW, buf.UpdateActivity(timer)) ++ if e == nil { ++ common.Close(downW) ++ } ++ timer.SetTimeout(p.Timeouts.UplinkOnly) ++ results <- e ++ }() ++ var first error ++ count := 0 ++ for count < 2 { ++ select { ++ case e := <-results: ++ count++ ++ if e != nil { ++ first = e ++ } ++ case <-ctx.Done(): ++ first = ctx.Err() ++ } ++ if first != nil || count == 2 { ++ break ++ } ++ } ++ conn.Close() ++ common.Interrupt(upR) ++ common.Interrupt(upW) ++ common.Interrupt(downR) ++ common.Interrupt(downW) ++ for count < 2 { ++ <-results ++ count++ ++ } ++ return first ++} +--- a/proxy/naive/server.go ++++ b/proxy/naive/server.go +@@ -0,0 +1,122 @@ ++package naive ++ ++import ( ++ "context" ++ "crypto/subtle" ++ "encoding/base64" ++ "errors" ++ "net/http" ++ "strings" ++ ++ "github.com/sagernet/sing/common/uot" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/protocol" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/core" ++ "github.com/xtls/xray-core/features/policy" ++ "github.com/xtls/xray-core/features/routing" ++ "github.com/xtls/xray-core/transport/internet" ++ nt "github.com/xtls/xray-core/transport/internet/naive" ++ "github.com/xtls/xray-core/transport/internet/stat" ++) ++ ++type Server struct { ++ config *ServerConfig ++ policy policy.Session ++} ++ ++func NewServer(ctx context.Context, c *ServerConfig) (*Server, error) { ++ settings, _ := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig) ++ if settings == nil || settings.ProtocolName != "naive" { ++ return nil, errors.New("naive inbound requires naive transport") ++ } ++ if len(c.Users) == 0 { ++ return nil, errors.New("naive requires users") ++ } ++ return &Server{config: c, policy: core.MustFromContext(ctx).GetFeature(policy.ManagerType()).(policy.Manager).ForLevel(c.Level)}, nil ++} ++func (*Server) Network() []net.Network { return []net.Network{net.Network_TCP} } ++func (s *Server) Process(ctx context.Context, _ net.Network, conn stat.Connection, d routing.Dispatcher) error { ++ c, ok := stat.TryUnwrapStatsConn(conn).(*nt.Conn) ++ if !ok { ++ return errors.New("naive requires HTTP CONNECT transport") ++ } ++ defer c.Finish() ++ r := c.Request ++ if r.Method != http.MethodConnect || r.Header.Get("Padding") == "" { ++ c.Respond(400, "") ++ return errors.New("invalid naive CONNECT") ++ } ++ raw, e := base64.StdEncoding.DecodeString(strings.TrimPrefix(r.Header.Get("Proxy-Authorization"), "Basic ")) ++ user, pass, valid := strings.Cut(string(raw), ":") ++ auth := 0 ++ if e == nil && valid && strings.HasPrefix(r.Header.Get("Proxy-Authorization"), "Basic ") { ++ for _, a := range s.config.Users { ++ auth |= subtle.ConstantTimeCompare([]byte(user), []byte(a.Username)) & subtle.ConstantTimeCompare([]byte(pass), []byte(a.Password)) ++ } ++ } ++ if auth != 1 { ++ c.Respond(407, "") ++ return errors.New("naive authentication failed") ++ } ++ host := r.Header.Get("-connect-authority") ++ if host == "" { ++ host = r.Host ++ } ++ dest, e := net.ParseDestination("tcp:" + host) ++ if e != nil || dest.Address == nil { ++ c.Respond(400, "") ++ return errors.New("invalid CONNECT authority") ++ } ++ isUOT := dest.Address.String() == uot.MagicAddress || dest.Address.String() == uot.LegacyMagicAddress ++ if dest.Port == 0 && !isUOT { ++ c.Respond(400, "") ++ return errors.New("invalid CONNECT port") ++ } ++ // HTTP/2 requests share a physical connection: copy session state per stream. ++ if in := session.InboundFromContext(ctx); in != nil { ++ copyIn := *in ++ copyIn.Name = "naive" ++ copyIn.CanSpliceCopy = 3 ++ copyIn.User = &protocol.MemoryUser{Email: user, Level: s.config.Level} ++ ctx = session.ContextWithInbound(ctx, ©In) ++ } ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ stop := context.AfterFunc(r.Context(), cancel) ++ defer stop() ++ if e = c.Respond(200, generatePaddingHeader()); e != nil { ++ return e ++ } ++ padded := &paddingConn{Conn: conn} ++ var reader buf.Reader = buf.NewReader(padded) ++ var writer buf.Writer = buf.NewWriter(padded) ++ if isUOT { ++ req := &uot.Request{} ++ if dest.Address.String() == uot.MagicAddress { ++ req, e = uot.ReadRequest(padded) ++ if e != nil { ++ return e ++ } ++ } ++ pc := uot.NewConn(padded, *req) ++ reader = &packetReader{conn: pc} ++ writer = &packetWriter{conn: pc, destination: req.Destination} ++ dest = net.UDPDestination(net.ParseAddress("127.0.0.1"), 1) ++ if req.Destination.IsValid() { ++ dest = net.UDPDestination(net.ParseAddress(req.Destination.AddrString()), net.Port(req.Destination.Port)) ++ } ++ } ++ link, err := d.Dispatch(ctx, dest) ++ if err != nil { ++ return err ++ } ++ return relay(ctx, reader, link.Writer, link.Reader, writer, padded, s.policy) ++} ++func init() { ++ common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, c interface{}) (interface{}, error) { ++ return NewServer(ctx, c.(*ServerConfig)) ++ })) ++} +--- a/transport/internet/naive/config.pb.go ++++ b/transport/internet/naive/config.pb.go +@@ -0,0 +1,113 @@ ++// Code generated by protoc-gen-go. DO NOT EDIT. ++// versions: ++// protoc-gen-go v1.36.12 ++// protoc v3.21.12 ++// source: transport/internet/naive/config.proto ++ ++package naive ++ ++import ( ++ protoreflect "google.golang.org/protobuf/reflect/protoreflect" ++ protoimpl "google.golang.org/protobuf/runtime/protoimpl" ++ reflect "reflect" ++ sync "sync" ++ unsafe "unsafe" ++) ++ ++const ( ++ // Verify that this generated code is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) ++ // Verify that runtime/protoimpl is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ++) ++ ++type Config struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *Config) Reset() { ++ *x = Config{} ++ mi := &file_transport_internet_naive_config_proto_msgTypes[0] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *Config) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*Config) ProtoMessage() {} ++ ++func (x *Config) ProtoReflect() protoreflect.Message { ++ mi := &file_transport_internet_naive_config_proto_msgTypes[0] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use Config.ProtoReflect.Descriptor instead. ++func (*Config) Descriptor() ([]byte, []int) { ++ return file_transport_internet_naive_config_proto_rawDescGZIP(), []int{0} ++} ++ ++var File_transport_internet_naive_config_proto protoreflect.FileDescriptor ++ ++const file_transport_internet_naive_config_proto_rawDesc = "" + ++ "\n" + ++ "%transport/internet/naive/config.proto\x12\x1dxray.transport.internet.naive\"\b\n" + ++ "\x06ConfigB4Z2github.com/xtls/xray-core/transport/internet/naiveb\x06proto3" ++ ++var ( ++ file_transport_internet_naive_config_proto_rawDescOnce sync.Once ++ file_transport_internet_naive_config_proto_rawDescData []byte ++) ++ ++func file_transport_internet_naive_config_proto_rawDescGZIP() []byte { ++ file_transport_internet_naive_config_proto_rawDescOnce.Do(func() { ++ file_transport_internet_naive_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_naive_config_proto_rawDesc), len(file_transport_internet_naive_config_proto_rawDesc))) ++ }) ++ return file_transport_internet_naive_config_proto_rawDescData ++} ++ ++var file_transport_internet_naive_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) ++var file_transport_internet_naive_config_proto_goTypes = []any{ ++ (*Config)(nil), // 0: xray.transport.internet.naive.Config ++} ++var file_transport_internet_naive_config_proto_depIdxs = []int32{ ++ 0, // [0:0] is the sub-list for method output_type ++ 0, // [0:0] is the sub-list for method input_type ++ 0, // [0:0] is the sub-list for extension type_name ++ 0, // [0:0] is the sub-list for extension extendee ++ 0, // [0:0] is the sub-list for field type_name ++} ++ ++func init() { file_transport_internet_naive_config_proto_init() } ++func file_transport_internet_naive_config_proto_init() { ++ if File_transport_internet_naive_config_proto != nil { ++ return ++ } ++ type x struct{} ++ out := protoimpl.TypeBuilder{ ++ File: protoimpl.DescBuilder{ ++ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), ++ RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_naive_config_proto_rawDesc), len(file_transport_internet_naive_config_proto_rawDesc)), ++ NumEnums: 0, ++ NumMessages: 1, ++ NumExtensions: 0, ++ NumServices: 0, ++ }, ++ GoTypes: file_transport_internet_naive_config_proto_goTypes, ++ DependencyIndexes: file_transport_internet_naive_config_proto_depIdxs, ++ MessageInfos: file_transport_internet_naive_config_proto_msgTypes, ++ }.Build() ++ File_transport_internet_naive_config_proto = out.File ++ file_transport_internet_naive_config_proto_goTypes = nil ++ file_transport_internet_naive_config_proto_depIdxs = nil ++} +--- a/transport/internet/naive/config.proto ++++ b/transport/internet/naive/config.proto +@@ -0,0 +1,4 @@ ++syntax = "proto3"; ++package xray.transport.internet.naive; ++option go_package = "github.com/xtls/xray-core/transport/internet/naive"; ++message Config {} +--- a/transport/internet/naive/hub.go ++++ b/transport/internet/naive/hub.go +@@ -0,0 +1,197 @@ ++// Naive HTTP CONNECT transport. Protocol authentication belongs to proxy/naive. ++package naive ++ ++import ( ++ "bufio" ++ "context" ++ "crypto/tls" ++ "errors" ++ "io" ++ "net" ++ "net/http" ++ "slices" ++ "sync" ++ "time" ++ ++ "github.com/apernet/quic-go/http3" ++ "github.com/xtls/xray-core/common" ++ xnet "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/transport/internet" ++ xtls "github.com/xtls/xray-core/transport/internet/tls" ++ "golang.org/x/net/http2" ++) ++ ++type Conn struct { ++ Request *http.Request ++ writer http.ResponseWriter ++ done chan struct{} ++ once sync.Once ++ closeOnce sync.Once ++ local net.Addr ++ raw net.Conn ++ buffered *bufio.ReadWriter ++} ++ ++func (c *Conn) Respond(status int, padding string) error { ++ if padding != "" { ++ c.writer.Header().Set("Padding", padding) ++ } ++ c.writer.WriteHeader(status) ++ if err := http.NewResponseController(c.writer).Flush(); err != nil { ++ return err ++ } ++ if c.Request.ProtoMajor == 1 && status == 200 { ++ var err error ++ c.raw, c.buffered, err = http.NewResponseController(c.writer).Hijack() ++ return err ++ } ++ return nil ++} ++ ++func (c *Conn) Read(p []byte) (int, error) { ++ if c.raw != nil { ++ return c.buffered.Read(p) ++ } ++ return c.Request.Body.Read(p) ++} ++ ++func (c *Conn) Write(p []byte) (int, error) { ++ if c.raw != nil { ++ return c.raw.Write(p) ++ } ++ n, err := c.writer.Write(p) ++ if err == nil { ++ err = http.NewResponseController(c.writer).Flush() ++ } ++ return n, err ++} ++func (c *Conn) Close() error { ++ c.closeOnce.Do(func() { ++ if c.raw != nil { ++ c.raw.Close() ++ return ++ } ++ c.SetReadDeadline(time.Now()) ++ c.Request.Body.Close() ++ c.SetWriteDeadline(time.Now()) ++ }) ++ return nil ++} ++func (c *Conn) Finish() { c.Close(); c.once.Do(func() { close(c.done) }) } ++ ++func (c *Conn) LocalAddr() net.Addr { return c.local } ++func (c *Conn) RemoteAddr() net.Addr { ++ a, _ := net.ResolveTCPAddr("tcp", c.Request.RemoteAddr) ++ return a ++} ++func (c *Conn) SetReadDeadline(t time.Time) error { ++ if c.raw != nil { ++ return c.raw.SetReadDeadline(t) ++ } ++ return http.NewResponseController(c.writer).SetReadDeadline(t) ++} ++func (c *Conn) SetWriteDeadline(t time.Time) error { ++ if c.raw != nil { ++ return c.raw.SetWriteDeadline(t) ++ } ++ return http.NewResponseController(c.writer).SetWriteDeadline(t) ++} ++func (c *Conn) SetDeadline(t time.Time) error { ++ return errors.Join(c.SetReadDeadline(t), c.SetWriteDeadline(t)) ++} ++ ++type Listener struct { ++ tcp net.Listener ++ udp net.PacketConn ++ h2 *http.Server ++ h3 *http3.Server ++ add internet.ConnHandler ++ ctx context.Context ++ cancel context.CancelFunc ++ addr net.Addr ++} ++ ++func (l *Listener) Addr() net.Addr { return l.addr } ++func (l *Listener) Close() error { ++ l.cancel() ++ if l.h2 != nil { ++ l.h2.Close() ++ } ++ if l.h3 != nil { ++ l.h3.Close() ++ } ++ if l.tcp != nil { ++ l.tcp.Close() ++ } ++ if l.udp != nil { ++ l.udp.Close() ++ } ++ return nil ++} ++func (l *Listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { ++ ++ c := &Conn{Request: r, writer: w, done: make(chan struct{}), local: l.addr} ++ l.add(c) ++ select { ++ case <-c.done: ++ case <-r.Context().Done(): ++ r.Body.Close() ++ <-c.done ++ case <-l.ctx.Done(): ++ r.Body.Close() ++ <-c.done ++ } ++} ++func Listen(ctx context.Context, address xnet.Address, port xnet.Port, settings *internet.MemoryStreamConfig, add internet.ConnHandler) (internet.Listener, error) { ++ tc := xtls.ConfigFromStreamSettings(settings) ++ if tc == nil { ++ return nil, errors.New("naive transport requires TLS") ++ } ++ if address.Family().IsDomain() { ++ return nil, errors.New("naive listen address must be an IP") ++ } ++ l := &Listener{add: add, addr: &net.TCPAddr{IP: address.IP(), Port: int(port)}} ++ l.ctx, l.cancel = context.WithCancel(ctx) ++ h2 := len(tc.NextProtocol) == 0 || slices.Contains(tc.NextProtocol, "h2") || slices.Contains(tc.NextProtocol, "http/1.1") ++ h3 := slices.Contains(tc.NextProtocol, "h3") ++ if !h2 && !h3 { ++ l.Close() ++ return nil, errors.New("naive ALPN must include h2 or h3") ++ } ++ if h2 { ++ raw, err := internet.ListenSystem(ctx, l.addr, settings.SocketSettings) ++ if err != nil { ++ l.Close() ++ return nil, err ++ } ++ cfg := tc.GetTLSConfig() ++ cfg.NextProtos = []string{"h2", "http/1.1"} ++ l.tcp = tls.NewListener(raw, cfg) ++ l.h2 = &http.Server{Handler: l, ReadHeaderTimeout: 15 * time.Second, BaseContext: func(net.Listener) context.Context { return l.ctx }} ++ if err = http2.ConfigureServer(l.h2, &http2.Server{}); err != nil { ++ l.Close() ++ return nil, err ++ } ++ go l.h2.Serve(l.tcp) ++ } ++ if h3 { ++ pc, err := internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: address.IP(), Port: int(port)}, settings.SocketSettings) ++ if err != nil { ++ l.Close() ++ return nil, err ++ } ++ l.udp = pc ++ cfg := tc.GetTLSConfig() ++ cfg.NextProtos = []string{"h3"} ++ l.h3 = &http3.Server{Handler: l, TLSConfig: cfg} ++ go l.h3.Serve(pc) ++ } ++ return l, nil ++} ++ ++var _ io.ReadWriteCloser = (*Conn)(nil) ++ ++func init() { ++ common.Must(internet.RegisterProtocolConfigCreator("naive", func() interface{} { return &Config{} })) ++ common.Must(internet.RegisterTransportListener("naive", Listen)) ++} diff --git a/xray-core/patches/140-add-shadowsocksr-proxy.patch b/xray-core/patches/140-add-shadowsocksr-proxy.patch new file mode 100644 index 000000000..53b9394dd --- /dev/null +++ b/xray-core/patches/140-add-shadowsocksr-proxy.patch @@ -0,0 +1,5117 @@ +From: coolsnowwolf +Subject: [PATCH] proxy: add ShadowsocksR inbound and outbound + +Retain Mihomo v1.19.32 client protocols, obfuscation and stream ciphers. +Add the server counterpart with TCP and native UDP routing, authenticated +multiuser protocols, bounded replay checks and fragmented handshakes. +Keep private-target restrictions and join inbound relay workers on close. +Support origin, auth_sha1_v4, auth_aes128_md5/sha1 and auth_chain_a/b; +plain, HTTP, random_head and TLS ticket camouflage; all existing ciphers. + +Wire format references: shadowsocksrr/shadowsocksr server auth/auth_chain, +and the shadowsocksr-libev server HTTP/TLS patches in this package feed. +Mihomo-derived code is GPL-3.0-or-later; include its license. +Signed-off-by: coolsnowwolf + +--- a/go.mod ++++ b/go.mod +@@ -12,6 +12,7 @@ + github.com/gorilla/websocket v1.5.3 + github.com/klauspost/cpuid/v2 v2.4.2 + github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 ++ github.com/metacubex/chacha v0.1.5 + github.com/miekg/dns v1.1.73 + github.com/pelletier/go-toml v1.9.5 + github.com/pion/stun/v3 v3.1.7 +--- a/go.sum ++++ b/go.sum +@@ -47,6 +47,8 @@ + github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138/go.mod h1:TXQg5tfSy+bUjnhT5728j5j/MBj7keIYqqZ1+8k/ui8= + github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= + github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= ++github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M= ++github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= + github.com/miekg/dns v1.1.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE= + github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ= + github.com/molecule-man/go-brrr v1.2.0 h1:dOJU45BC3Gc2WXoxAW0rgCAgVuu2gruAOGncfieA0Jw= +--- a/infra/conf/shadowsocksr.go ++++ b/infra/conf/shadowsocksr.go +@@ -0,0 +1,64 @@ ++package conf ++ ++import ( ++ "github.com/xtls/xray-core/common/errors" ++ "github.com/xtls/xray-core/proxy/shadowsocksr" ++ "google.golang.org/protobuf/proto" ++) ++ ++type ShadowsocksRClientConfig struct { ++ Address *Address `json:"address"` ++ Port uint16 `json:"port"` ++ Password string `json:"password"` ++ Method string `json:"method"` ++ Protocol string `json:"protocol"` ++ ProtocolParam string `json:"protocolParam"` ++ Obfs string `json:"obfs"` ++ ObfsParam string `json:"obfsParam"` ++ Level uint32 `json:"level"` ++} ++ ++func (c *ShadowsocksRClientConfig) Build() (proto.Message, error) { ++ if c.Address == nil { ++ return nil, errors.New("shadowsocksr: address is required") ++ } ++ p := c.Protocol ++ if p == "" { ++ p = "origin" ++ } ++ o := c.Obfs ++ if o == "" { ++ o = "plain" ++ } ++ config := &shadowsocksr.ClientConfig{Address: c.Address.Build(), Port: uint32(c.Port), Password: c.Password, Method: c.Method, Protocol: p, ProtocolParam: c.ProtocolParam, Obfs: o, ObfsParam: c.ObfsParam, Level: c.Level} ++ if err := shadowsocksr.Validate(config); err != nil { ++ return nil, err ++ } ++ return config, nil ++} ++ ++type ShadowsocksRServerConfig struct { ++ Password string `json:"password"` ++ Method string `json:"method"` ++ Protocol string `json:"protocol"` ++ ProtocolParam string `json:"protocolParam"` ++ Obfs string `json:"obfs"` ++ ObfsParam string `json:"obfsParam"` ++ Level uint32 `json:"level"` ++ Network *NetworkList `json:"network"` ++} ++ ++func (c *ShadowsocksRServerConfig) Build() (proto.Message, error) { ++ p, o := c.Protocol, c.Obfs ++ if p == "" { ++ p = "origin" ++ } ++ if o == "" { ++ o = "plain" ++ } ++ config := &shadowsocksr.ServerConfig{Password: c.Password, Method: c.Method, Protocol: p, ProtocolParam: c.ProtocolParam, Obfs: o, ObfsParam: c.ObfsParam, Level: c.Level, Network: c.Network.Build()} ++ if err := shadowsocksr.ValidateServer(config); err != nil { ++ return nil, err ++ } ++ return config, nil ++} +--- a/infra/conf/xray.go ++++ b/infra/conf/xray.go +@@ -36,6 +36,7 @@ + "vless": func() interface{} { return new(VLessInboundConfig) }, + "vmess": func() interface{} { return new(VMessInboundConfig) }, + "trojan": func() interface{} { return new(TrojanServerConfig) }, ++ "shadowsocksr": func() interface{} { return new(ShadowsocksRServerConfig) }, + "anytls": func() interface{} { return new(AnyTLSServerConfig) }, + "wireguard": func() interface{} { return &WireGuardConfig{IsClient: false} }, + "hysteria": func() interface{} { return new(HysteriaServerConfig) }, +@@ -44,23 +45,24 @@ + }, "protocol", "settings") + + outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ +- "block": func() interface{} { return new(BlackholeConfig) }, +- "blackhole": func() interface{} { return new(BlackholeConfig) }, +- "loopback": func() interface{} { return new(LoopbackConfig) }, +- "direct": func() interface{} { return new(FreedomConfig) }, +- "freedom": func() interface{} { return new(FreedomConfig) }, +- "http": func() interface{} { return new(HTTPClientConfig) }, +- "shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) }, +- "socks": func() interface{} { return new(SocksClientConfig) }, +- "vless": func() interface{} { return new(VLessOutboundConfig) }, +- "vmess": func() interface{} { return new(VMessOutboundConfig) }, +- "trojan": func() interface{} { return new(TrojanClientConfig) }, +- "naive": func() interface{} { return new(NaiveClientConfig) }, +- "anytls": func() interface{} { return new(AnyTLSClientConfig) }, +- "hysteria": func() interface{} { return new(HysteriaClientConfig) }, +- "masque": func() interface{} { return new(MasqueClientConfig) }, +- "dns": func() interface{} { return new(DNSOutboundConfig) }, +- "wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} }, ++ "block": func() interface{} { return new(BlackholeConfig) }, ++ "blackhole": func() interface{} { return new(BlackholeConfig) }, ++ "loopback": func() interface{} { return new(LoopbackConfig) }, ++ "direct": func() interface{} { return new(FreedomConfig) }, ++ "freedom": func() interface{} { return new(FreedomConfig) }, ++ "http": func() interface{} { return new(HTTPClientConfig) }, ++ "shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) }, ++ "socks": func() interface{} { return new(SocksClientConfig) }, ++ "vless": func() interface{} { return new(VLessOutboundConfig) }, ++ "vmess": func() interface{} { return new(VMessOutboundConfig) }, ++ "trojan": func() interface{} { return new(TrojanClientConfig) }, ++ "naive": func() interface{} { return new(NaiveClientConfig) }, ++ "shadowsocksr": func() interface{} { return new(ShadowsocksRClientConfig) }, ++ "anytls": func() interface{} { return new(AnyTLSClientConfig) }, ++ "hysteria": func() interface{} { return new(HysteriaClientConfig) }, ++ "masque": func() interface{} { return new(MasqueClientConfig) }, ++ "dns": func() interface{} { return new(DNSOutboundConfig) }, ++ "wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} }, + }, "protocol", "settings") + ) + +--- a/main/distro/all/all.go ++++ b/main/distro/all/all.go +@@ -45,6 +45,7 @@ + _ "github.com/xtls/xray-core/proxy/masque" + _ "github.com/xtls/xray-core/proxy/naive" + _ "github.com/xtls/xray-core/proxy/shadowsocks" ++ _ "github.com/xtls/xray-core/proxy/shadowsocksr" + _ "github.com/xtls/xray-core/proxy/socks" + _ "github.com/xtls/xray-core/proxy/trojan" + _ "github.com/xtls/xray-core/proxy/vless/inbound" +--- a/proxy/freedom/freedom.go ++++ b/proxy/freedom/freedom.go +@@ -166,7 +166,7 @@ + if inbound == nil { + return nil + } +- if inbound.Name == "anytls" { ++ if inbound.Name == "anytls" || inbound.Name == "shadowsocksr" { + return defaultBlockPrivateRule + } + switch inbound.Name { +--- a/proxy/shadowsocksr/LICENSE ++++ b/proxy/shadowsocksr/LICENSE +@@ -0,0 +1,674 @@ ++ GNU GENERAL PUBLIC LICENSE ++ Version 3, 29 June 2007 ++ ++ Copyright (C) 2007 Free Software Foundation, Inc. ++ Everyone is permitted to copy and distribute verbatim copies ++ of this license document, but changing it is not allowed. ++ ++ Preamble ++ ++ The GNU General Public License is a free, copyleft license for ++software and other kinds of works. ++ ++ The licenses for most software and other practical works are designed ++to take away your freedom to share and change the works. By contrast, ++the GNU General Public License is intended to guarantee your freedom to ++share and change all versions of a program--to make sure it remains free ++software for all its users. We, the Free Software Foundation, use the ++GNU General Public License for most of our software; it applies also to ++any other work released this way by its authors. You can apply it to ++your programs, too. ++ ++ When we speak of free software, we are referring to freedom, not ++price. Our General Public Licenses are designed to make sure that you ++have the freedom to distribute copies of free software (and charge for ++them if you wish), that you receive source code or can get it if you ++want it, that you can change the software or use pieces of it in new ++free programs, and that you know you can do these things. ++ ++ To protect your rights, we need to prevent others from denying you ++these rights or asking you to surrender the rights. Therefore, you have ++certain responsibilities if you distribute copies of the software, or if ++you modify it: responsibilities to respect the freedom of others. ++ ++ For example, if you distribute copies of such a program, whether ++gratis or for a fee, you must pass on to the recipients the same ++freedoms that you received. You must make sure that they, too, receive ++or can get the source code. And you must show them these terms so they ++know their rights. ++ ++ Developers that use the GNU GPL protect your rights with two steps: ++(1) assert copyright on the software, and (2) offer you this License ++giving you legal permission to copy, distribute and/or modify it. ++ ++ For the developers' and authors' protection, the GPL clearly explains ++that there is no warranty for this free software. For both users' and ++authors' sake, the GPL requires that modified versions be marked as ++changed, so that their problems will not be attributed erroneously to ++authors of previous versions. ++ ++ Some devices are designed to deny users access to install or run ++modified versions of the software inside them, although the manufacturer ++can do so. This is fundamentally incompatible with the aim of ++protecting users' freedom to change the software. The systematic ++pattern of such abuse occurs in the area of products for individuals to ++use, which is precisely where it is most unacceptable. Therefore, we ++have designed this version of the GPL to prohibit the practice for those ++products. If such problems arise substantially in other domains, we ++stand ready to extend this provision to those domains in future versions ++of the GPL, as needed to protect the freedom of users. ++ ++ Finally, every program is threatened constantly by software patents. ++States should not allow patents to restrict development and use of ++software on general-purpose computers, but in those that do, we wish to ++avoid the special danger that patents applied to a free program could ++make it effectively proprietary. To prevent this, the GPL assures that ++patents cannot be used to render the program non-free. ++ ++ The precise terms and conditions for copying, distribution and ++modification follow. ++ ++ TERMS AND CONDITIONS ++ ++ 0. Definitions. ++ ++ "This License" refers to version 3 of the GNU General Public License. ++ ++ "Copyright" also means copyright-like laws that apply to other kinds of ++works, such as semiconductor masks. ++ ++ "The Program" refers to any copyrightable work licensed under this ++License. Each licensee is addressed as "you". "Licensees" and ++"recipients" may be individuals or organizations. ++ ++ To "modify" a work means to copy from or adapt all or part of the work ++in a fashion requiring copyright permission, other than the making of an ++exact copy. The resulting work is called a "modified version" of the ++earlier work or a work "based on" the earlier work. ++ ++ A "covered work" means either the unmodified Program or a work based ++on the Program. ++ ++ To "propagate" a work means to do anything with it that, without ++permission, would make you directly or secondarily liable for ++infringement under applicable copyright law, except executing it on a ++computer or modifying a private copy. Propagation includes copying, ++distribution (with or without modification), making available to the ++public, and in some countries other activities as well. ++ ++ To "convey" a work means any kind of propagation that enables other ++parties to make or receive copies. Mere interaction with a user through ++a computer network, with no transfer of a copy, is not conveying. ++ ++ An interactive user interface displays "Appropriate Legal Notices" ++to the extent that it includes a convenient and prominently visible ++feature that (1) displays an appropriate copyright notice, and (2) ++tells the user that there is no warranty for the work (except to the ++extent that warranties are provided), that licensees may convey the ++work under this License, and how to view a copy of this License. If ++the interface presents a list of user commands or options, such as a ++menu, a prominent item in the list meets this criterion. ++ ++ 1. Source Code. ++ ++ The "source code" for a work means the preferred form of the work ++for making modifications to it. "Object code" means any non-source ++form of a work. ++ ++ A "Standard Interface" means an interface that either is an official ++standard defined by a recognized standards body, or, in the case of ++interfaces specified for a particular programming language, one that ++is widely used among developers working in that language. ++ ++ The "System Libraries" of an executable work include anything, other ++than the work as a whole, that (a) is included in the normal form of ++packaging a Major Component, but which is not part of that Major ++Component, and (b) serves only to enable use of the work with that ++Major Component, or to implement a Standard Interface for which an ++implementation is available to the public in source code form. A ++"Major Component", in this context, means a major essential component ++(kernel, window system, and so on) of the specific operating system ++(if any) on which the executable work runs, or a compiler used to ++produce the work, or an object code interpreter used to run it. ++ ++ The "Corresponding Source" for a work in object code form means all ++the source code needed to generate, install, and (for an executable ++work) run the object code and to modify the work, including scripts to ++control those activities. However, it does not include the work's ++System Libraries, or general-purpose tools or generally available free ++programs which are used unmodified in performing those activities but ++which are not part of the work. For example, Corresponding Source ++includes interface definition files associated with source files for ++the work, and the source code for shared libraries and dynamically ++linked subprograms that the work is specifically designed to require, ++such as by intimate data communication or control flow between those ++subprograms and other parts of the work. ++ ++ The Corresponding Source need not include anything that users ++can regenerate automatically from other parts of the Corresponding ++Source. ++ ++ The Corresponding Source for a work in source code form is that ++same work. ++ ++ 2. Basic Permissions. ++ ++ All rights granted under this License are granted for the term of ++copyright on the Program, and are irrevocable provided the stated ++conditions are met. This License explicitly affirms your unlimited ++permission to run the unmodified Program. The output from running a ++covered work is covered by this License only if the output, given its ++content, constitutes a covered work. This License acknowledges your ++rights of fair use or other equivalent, as provided by copyright law. ++ ++ You may make, run and propagate covered works that you do not ++convey, without conditions so long as your license otherwise remains ++in force. You may convey covered works to others for the sole purpose ++of having them make modifications exclusively for you, or provide you ++with facilities for running those works, provided that you comply with ++the terms of this License in conveying all material for which you do ++not control copyright. Those thus making or running the covered works ++for you must do so exclusively on your behalf, under your direction ++and control, on terms that prohibit them from making any copies of ++your copyrighted material outside their relationship with you. ++ ++ Conveying under any other circumstances is permitted solely under ++the conditions stated below. Sublicensing is not allowed; section 10 ++makes it unnecessary. ++ ++ 3. Protecting Users' Legal Rights From Anti-Circumvention Law. ++ ++ No covered work shall be deemed part of an effective technological ++measure under any applicable law fulfilling obligations under article ++11 of the WIPO copyright treaty adopted on 20 December 1996, or ++similar laws prohibiting or restricting circumvention of such ++measures. ++ ++ When you convey a covered work, you waive any legal power to forbid ++circumvention of technological measures to the extent such circumvention ++is effected by exercising rights under this License with respect to ++the covered work, and you disclaim any intention to limit operation or ++modification of the work as a means of enforcing, against the work's ++users, your or third parties' legal rights to forbid circumvention of ++technological measures. ++ ++ 4. Conveying Verbatim Copies. ++ ++ You may convey verbatim copies of the Program's source code as you ++receive it, in any medium, provided that you conspicuously and ++appropriately publish on each copy an appropriate copyright notice; ++keep intact all notices stating that this License and any ++non-permissive terms added in accord with section 7 apply to the code; ++keep intact all notices of the absence of any warranty; and give all ++recipients a copy of this License along with the Program. ++ ++ You may charge any price or no price for each copy that you convey, ++and you may offer support or warranty protection for a fee. ++ ++ 5. Conveying Modified Source Versions. ++ ++ You may convey a work based on the Program, or the modifications to ++produce it from the Program, in the form of source code under the ++terms of section 4, provided that you also meet all of these conditions: ++ ++ a) The work must carry prominent notices stating that you modified ++ it, and giving a relevant date. ++ ++ b) The work must carry prominent notices stating that it is ++ released under this License and any conditions added under section ++ 7. This requirement modifies the requirement in section 4 to ++ "keep intact all notices". ++ ++ c) You must license the entire work, as a whole, under this ++ License to anyone who comes into possession of a copy. This ++ License will therefore apply, along with any applicable section 7 ++ additional terms, to the whole of the work, and all its parts, ++ regardless of how they are packaged. This License gives no ++ permission to license the work in any other way, but it does not ++ invalidate such permission if you have separately received it. ++ ++ d) If the work has interactive user interfaces, each must display ++ Appropriate Legal Notices; however, if the Program has interactive ++ interfaces that do not display Appropriate Legal Notices, your ++ work need not make them do so. ++ ++ A compilation of a covered work with other separate and independent ++works, which are not by their nature extensions of the covered work, ++and which are not combined with it such as to form a larger program, ++in or on a volume of a storage or distribution medium, is called an ++"aggregate" if the compilation and its resulting copyright are not ++used to limit the access or legal rights of the compilation's users ++beyond what the individual works permit. Inclusion of a covered work ++in an aggregate does not cause this License to apply to the other ++parts of the aggregate. ++ ++ 6. Conveying Non-Source Forms. ++ ++ You may convey a covered work in object code form under the terms ++of sections 4 and 5, provided that you also convey the ++machine-readable Corresponding Source under the terms of this License, ++in one of these ways: ++ ++ a) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by the ++ Corresponding Source fixed on a durable physical medium ++ customarily used for software interchange. ++ ++ b) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by a ++ written offer, valid for at least three years and valid for as ++ long as you offer spare parts or customer support for that product ++ model, to give anyone who possesses the object code either (1) a ++ copy of the Corresponding Source for all the software in the ++ product that is covered by this License, on a durable physical ++ medium customarily used for software interchange, for a price no ++ more than your reasonable cost of physically performing this ++ conveying of source, or (2) access to copy the ++ Corresponding Source from a network server at no charge. ++ ++ c) Convey individual copies of the object code with a copy of the ++ written offer to provide the Corresponding Source. This ++ alternative is allowed only occasionally and noncommercially, and ++ only if you received the object code with such an offer, in accord ++ with subsection 6b. ++ ++ d) Convey the object code by offering access from a designated ++ place (gratis or for a charge), and offer equivalent access to the ++ Corresponding Source in the same way through the same place at no ++ further charge. You need not require recipients to copy the ++ Corresponding Source along with the object code. If the place to ++ copy the object code is a network server, the Corresponding Source ++ may be on a different server (operated by you or a third party) ++ that supports equivalent copying facilities, provided you maintain ++ clear directions next to the object code saying where to find the ++ Corresponding Source. Regardless of what server hosts the ++ Corresponding Source, you remain obligated to ensure that it is ++ available for as long as needed to satisfy these requirements. ++ ++ e) Convey the object code using peer-to-peer transmission, provided ++ you inform other peers where the object code and Corresponding ++ Source of the work are being offered to the general public at no ++ charge under subsection 6d. ++ ++ A separable portion of the object code, whose source code is excluded ++from the Corresponding Source as a System Library, need not be ++included in conveying the object code work. ++ ++ A "User Product" is either (1) a "consumer product", which means any ++tangible personal property which is normally used for personal, family, ++or household purposes, or (2) anything designed or sold for incorporation ++into a dwelling. In determining whether a product is a consumer product, ++doubtful cases shall be resolved in favor of coverage. For a particular ++product received by a particular user, "normally used" refers to a ++typical or common use of that class of product, regardless of the status ++of the particular user or of the way in which the particular user ++actually uses, or expects or is expected to use, the product. A product ++is a consumer product regardless of whether the product has substantial ++commercial, industrial or non-consumer uses, unless such uses represent ++the only significant mode of use of the product. ++ ++ "Installation Information" for a User Product means any methods, ++procedures, authorization keys, or other information required to install ++and execute modified versions of a covered work in that User Product from ++a modified version of its Corresponding Source. The information must ++suffice to ensure that the continued functioning of the modified object ++code is in no case prevented or interfered with solely because ++modification has been made. ++ ++ If you convey an object code work under this section in, or with, or ++specifically for use in, a User Product, and the conveying occurs as ++part of a transaction in which the right of possession and use of the ++User Product is transferred to the recipient in perpetuity or for a ++fixed term (regardless of how the transaction is characterized), the ++Corresponding Source conveyed under this section must be accompanied ++by the Installation Information. But this requirement does not apply ++if neither you nor any third party retains the ability to install ++modified object code on the User Product (for example, the work has ++been installed in ROM). ++ ++ The requirement to provide Installation Information does not include a ++requirement to continue to provide support service, warranty, or updates ++for a work that has been modified or installed by the recipient, or for ++the User Product in which it has been modified or installed. Access to a ++network may be denied when the modification itself materially and ++adversely affects the operation of the network or violates the rules and ++protocols for communication across the network. ++ ++ Corresponding Source conveyed, and Installation Information provided, ++in accord with this section must be in a format that is publicly ++documented (and with an implementation available to the public in ++source code form), and must require no special password or key for ++unpacking, reading or copying. ++ ++ 7. Additional Terms. ++ ++ "Additional permissions" are terms that supplement the terms of this ++License by making exceptions from one or more of its conditions. ++Additional permissions that are applicable to the entire Program shall ++be treated as though they were included in this License, to the extent ++that they are valid under applicable law. If additional permissions ++apply only to part of the Program, that part may be used separately ++under those permissions, but the entire Program remains governed by ++this License without regard to the additional permissions. ++ ++ When you convey a copy of a covered work, you may at your option ++remove any additional permissions from that copy, or from any part of ++it. (Additional permissions may be written to require their own ++removal in certain cases when you modify the work.) You may place ++additional permissions on material, added by you to a covered work, ++for which you have or can give appropriate copyright permission. ++ ++ Notwithstanding any other provision of this License, for material you ++add to a covered work, you may (if authorized by the copyright holders of ++that material) supplement the terms of this License with terms: ++ ++ a) Disclaiming warranty or limiting liability differently from the ++ terms of sections 15 and 16 of this License; or ++ ++ b) Requiring preservation of specified reasonable legal notices or ++ author attributions in that material or in the Appropriate Legal ++ Notices displayed by works containing it; or ++ ++ c) Prohibiting misrepresentation of the origin of that material, or ++ requiring that modified versions of such material be marked in ++ reasonable ways as different from the original version; or ++ ++ d) Limiting the use for publicity purposes of names of licensors or ++ authors of the material; or ++ ++ e) Declining to grant rights under trademark law for use of some ++ trade names, trademarks, or service marks; or ++ ++ f) Requiring indemnification of licensors and authors of that ++ material by anyone who conveys the material (or modified versions of ++ it) with contractual assumptions of liability to the recipient, for ++ any liability that these contractual assumptions directly impose on ++ those licensors and authors. ++ ++ All other non-permissive additional terms are considered "further ++restrictions" within the meaning of section 10. If the Program as you ++received it, or any part of it, contains a notice stating that it is ++governed by this License along with a term that is a further ++restriction, you may remove that term. If a license document contains ++a further restriction but permits relicensing or conveying under this ++License, you may add to a covered work material governed by the terms ++of that license document, provided that the further restriction does ++not survive such relicensing or conveying. ++ ++ If you add terms to a covered work in accord with this section, you ++must place, in the relevant source files, a statement of the ++additional terms that apply to those files, or a notice indicating ++where to find the applicable terms. ++ ++ Additional terms, permissive or non-permissive, may be stated in the ++form of a separately written license, or stated as exceptions; ++the above requirements apply either way. ++ ++ 8. Termination. ++ ++ You may not propagate or modify a covered work except as expressly ++provided under this License. Any attempt otherwise to propagate or ++modify it is void, and will automatically terminate your rights under ++this License (including any patent licenses granted under the third ++paragraph of section 11). ++ ++ However, if you cease all violation of this License, then your ++license from a particular copyright holder is reinstated (a) ++provisionally, unless and until the copyright holder explicitly and ++finally terminates your license, and (b) permanently, if the copyright ++holder fails to notify you of the violation by some reasonable means ++prior to 60 days after the cessation. ++ ++ Moreover, your license from a particular copyright holder is ++reinstated permanently if the copyright holder notifies you of the ++violation by some reasonable means, this is the first time you have ++received notice of violation of this License (for any work) from that ++copyright holder, and you cure the violation prior to 30 days after ++your receipt of the notice. ++ ++ Termination of your rights under this section does not terminate the ++licenses of parties who have received copies or rights from you under ++this License. If your rights have been terminated and not permanently ++reinstated, you do not qualify to receive new licenses for the same ++material under section 10. ++ ++ 9. Acceptance Not Required for Having Copies. ++ ++ You are not required to accept this License in order to receive or ++run a copy of the Program. Ancillary propagation of a covered work ++occurring solely as a consequence of using peer-to-peer transmission ++to receive a copy likewise does not require acceptance. However, ++nothing other than this License grants you permission to propagate or ++modify any covered work. These actions infringe copyright if you do ++not accept this License. Therefore, by modifying or propagating a ++covered work, you indicate your acceptance of this License to do so. ++ ++ 10. Automatic Licensing of Downstream Recipients. ++ ++ Each time you convey a covered work, the recipient automatically ++receives a license from the original licensors, to run, modify and ++propagate that work, subject to this License. You are not responsible ++for enforcing compliance by third parties with this License. ++ ++ An "entity transaction" is a transaction transferring control of an ++organization, or substantially all assets of one, or subdividing an ++organization, or merging organizations. If propagation of a covered ++work results from an entity transaction, each party to that ++transaction who receives a copy of the work also receives whatever ++licenses to the work the party's predecessor in interest had or could ++give under the previous paragraph, plus a right to possession of the ++Corresponding Source of the work from the predecessor in interest, if ++the predecessor has it or can get it with reasonable efforts. ++ ++ You may not impose any further restrictions on the exercise of the ++rights granted or affirmed under this License. For example, you may ++not impose a license fee, royalty, or other charge for exercise of ++rights granted under this License, and you may not initiate litigation ++(including a cross-claim or counterclaim in a lawsuit) alleging that ++any patent claim is infringed by making, using, selling, offering for ++sale, or importing the Program or any portion of it. ++ ++ 11. Patents. ++ ++ A "contributor" is a copyright holder who authorizes use under this ++License of the Program or a work on which the Program is based. The ++work thus licensed is called the contributor's "contributor version". ++ ++ A contributor's "essential patent claims" are all patent claims ++owned or controlled by the contributor, whether already acquired or ++hereafter acquired, that would be infringed by some manner, permitted ++by this License, of making, using, or selling its contributor version, ++but do not include claims that would be infringed only as a ++consequence of further modification of the contributor version. For ++purposes of this definition, "control" includes the right to grant ++patent sublicenses in a manner consistent with the requirements of ++this License. ++ ++ Each contributor grants you a non-exclusive, worldwide, royalty-free ++patent license under the contributor's essential patent claims, to ++make, use, sell, offer for sale, import and otherwise run, modify and ++propagate the contents of its contributor version. ++ ++ In the following three paragraphs, a "patent license" is any express ++agreement or commitment, however denominated, not to enforce a patent ++(such as an express permission to practice a patent or covenant not to ++sue for patent infringement). To "grant" such a patent license to a ++party means to make such an agreement or commitment not to enforce a ++patent against the party. ++ ++ If you convey a covered work, knowingly relying on a patent license, ++and the Corresponding Source of the work is not available for anyone ++to copy, free of charge and under the terms of this License, through a ++publicly available network server or other readily accessible means, ++then you must either (1) cause the Corresponding Source to be so ++available, or (2) arrange to deprive yourself of the benefit of the ++patent license for this particular work, or (3) arrange, in a manner ++consistent with the requirements of this License, to extend the patent ++license to downstream recipients. "Knowingly relying" means you have ++actual knowledge that, but for the patent license, your conveying the ++covered work in a country, or your recipient's use of the covered work ++in a country, would infringe one or more identifiable patents in that ++country that you have reason to believe are valid. ++ ++ If, pursuant to or in connection with a single transaction or ++arrangement, you convey, or propagate by procuring conveyance of, a ++covered work, and grant a patent license to some of the parties ++receiving the covered work authorizing them to use, propagate, modify ++or convey a specific copy of the covered work, then the patent license ++you grant is automatically extended to all recipients of the covered ++work and works based on it. ++ ++ A patent license is "discriminatory" if it does not include within ++the scope of its coverage, prohibits the exercise of, or is ++conditioned on the non-exercise of one or more of the rights that are ++specifically granted under this License. You may not convey a covered ++work if you are a party to an arrangement with a third party that is ++in the business of distributing software, under which you make payment ++to the third party based on the extent of your activity of conveying ++the work, and under which the third party grants, to any of the ++parties who would receive the covered work from you, a discriminatory ++patent license (a) in connection with copies of the covered work ++conveyed by you (or copies made from those copies), or (b) primarily ++for and in connection with specific products or compilations that ++contain the covered work, unless you entered into that arrangement, ++or that patent license was granted, prior to 28 March 2007. ++ ++ Nothing in this License shall be construed as excluding or limiting ++any implied license or other defenses to infringement that may ++otherwise be available to you under applicable patent law. ++ ++ 12. No Surrender of Others' Freedom. ++ ++ If conditions are imposed on you (whether by court order, agreement or ++otherwise) that contradict the conditions of this License, they do not ++excuse you from the conditions of this License. If you cannot convey a ++covered work so as to satisfy simultaneously your obligations under this ++License and any other pertinent obligations, then as a consequence you may ++not convey it at all. For example, if you agree to terms that obligate you ++to collect a royalty for further conveying from those to whom you convey ++the Program, the only way you could satisfy both those terms and this ++License would be to refrain entirely from conveying the Program. ++ ++ 13. Use with the GNU Affero General Public License. ++ ++ Notwithstanding any other provision of this License, you have ++permission to link or combine any covered work with a work licensed ++under version 3 of the GNU Affero General Public License into a single ++combined work, and to convey the resulting work. The terms of this ++License will continue to apply to the part which is the covered work, ++but the special requirements of the GNU Affero General Public License, ++section 13, concerning interaction through a network will apply to the ++combination as such. ++ ++ 14. Revised Versions of this License. ++ ++ The Free Software Foundation may publish revised and/or new versions of ++the GNU General Public License from time to time. Such new versions will ++be similar in spirit to the present version, but may differ in detail to ++address new problems or concerns. ++ ++ Each version is given a distinguishing version number. If the ++Program specifies that a certain numbered version of the GNU General ++Public License "or any later version" applies to it, you have the ++option of following the terms and conditions either of that numbered ++version or of any later version published by the Free Software ++Foundation. If the Program does not specify a version number of the ++GNU General Public License, you may choose any version ever published ++by the Free Software Foundation. ++ ++ If the Program specifies that a proxy can decide which future ++versions of the GNU General Public License can be used, that proxy's ++public statement of acceptance of a version permanently authorizes you ++to choose that version for the Program. ++ ++ Later license versions may give you additional or different ++permissions. However, no additional obligations are imposed on any ++author or copyright holder as a result of your choosing to follow a ++later version. ++ ++ 15. Disclaimer of Warranty. ++ ++ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY ++APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT ++HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY ++OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ++THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM ++IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ++ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ++ ++ 16. Limitation of Liability. ++ ++ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING ++WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS ++THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY ++GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE ++USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF ++DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD ++PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), ++EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF ++SUCH DAMAGES. ++ ++ 17. Interpretation of Sections 15 and 16. ++ ++ If the disclaimer of warranty and limitation of liability provided ++above cannot be given local legal effect according to their terms, ++reviewing courts shall apply local law that most closely approximates ++an absolute waiver of all civil liability in connection with the ++Program, unless a warranty or assumption of liability accompanies a ++copy of the Program in return for a fee. ++ ++ END OF TERMS AND CONDITIONS ++ ++ How to Apply These Terms to Your New Programs ++ ++ If you develop a new program, and you want it to be of the greatest ++possible use to the public, the best way to achieve this is to make it ++free software which everyone can redistribute and change under these terms. ++ ++ To do so, attach the following notices to the program. It is safest ++to attach them to the start of each source file to most effectively ++state the exclusion of warranty; and each file should have at least ++the "copyright" line and a pointer to where the full notice is found. ++ ++ ++ Copyright (C) ++ ++ This program is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation, either version 3 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see . ++ ++Also add information on how to contact you by electronic and paper mail. ++ ++ If the program does terminal interaction, make it output a short ++notice like this when it starts in an interactive mode: ++ ++ Copyright (C) ++ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. ++ This is free software, and you are welcome to redistribute it ++ under certain conditions; type `show c' for details. ++ ++The hypothetical commands `show w' and `show c' should show the appropriate ++parts of the General Public License. Of course, your program's commands ++might be different; for a GUI interface, you would use an "about box". ++ ++ You should also get your employer (if you work as a programmer) or school, ++if any, to sign a "copyright disclaimer" for the program, if necessary. ++For more information on this, and how to apply and follow the GNU GPL, see ++. ++ ++ The GNU General Public License does not permit incorporating your program ++into proprietary programs. If your program is a subroutine library, you ++may consider it more useful to permit linking proprietary applications with ++the library. If this is what you want to do, use the GNU Lesser General ++Public License instead of this License. But first, please read ++. +--- a/proxy/shadowsocksr/cipher/cipher.go ++++ b/proxy/shadowsocksr/cipher/cipher.go +@@ -0,0 +1,110 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package cipher ++ ++import ( ++ "crypto/md5" ++ "errors" ++ "net" ++ "sort" ++ "strings" ++ ++ "github.com/xtls/xray-core/proxy/shadowsocksr/shadowstream" ++) ++ ++type Cipher interface { ++ StreamConnCipher ++ PacketConnCipher ++} ++ ++type StreamConnCipher interface { ++ StreamConn(net.Conn) net.Conn ++} ++ ++type PacketConnCipher interface { ++ PacketConn(net.PacketConn) net.PacketConn ++} ++ ++// ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns). ++var ErrCipherNotSupported = errors.New("cipher not supported") ++ ++// List of stream ciphers: key size in bytes and constructor ++var streamList = map[string]struct { ++ KeySize int ++ New func(key []byte) (shadowstream.Cipher, error) ++}{ ++ "RC4-MD5": {16, shadowstream.RC4MD5}, ++ "AES-128-CTR": {16, shadowstream.AESCTR}, ++ "AES-192-CTR": {24, shadowstream.AESCTR}, ++ "AES-256-CTR": {32, shadowstream.AESCTR}, ++ "AES-128-CFB": {16, shadowstream.AESCFB}, ++ "AES-192-CFB": {24, shadowstream.AESCFB}, ++ "AES-256-CFB": {32, shadowstream.AESCFB}, ++ "CHACHA20": {32, shadowstream.ChaCha20}, ++ "CHACHA20-IETF": {32, shadowstream.Chacha20IETF}, ++ "XCHACHA20": {32, shadowstream.Xchacha20}, ++} ++ ++// ListCipher returns a list of available cipher names sorted alphabetically. ++func ListCipher() []string { ++ var l []string ++ for k := range streamList { ++ l = append(l, k) ++ } ++ sort.Strings(l) ++ return l ++} ++ ++// PickCipher returns a Cipher of the given name. Derive key from password if given key is empty. ++func PickCipher(name string, key []byte, password string) (Cipher, error) { ++ name = strings.ToUpper(name) ++ ++ if name == "DUMMY" || name == "NONE" { ++ return &dummy{}, nil ++ } ++ ++ if choice, ok := streamList[name]; ok { ++ if len(key) == 0 { ++ key = Kdf(password, choice.KeySize) ++ } ++ if len(key) != choice.KeySize { ++ return nil, shadowstream.KeySizeError(choice.KeySize) ++ } ++ ciph, err := choice.New(key) ++ return &StreamCipher{Cipher: ciph, Key: key}, err ++ } ++ ++ return nil, ErrCipherNotSupported ++} ++ ++type StreamCipher struct { ++ shadowstream.Cipher ++ ++ Key []byte ++} ++ ++func (ciph *StreamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) } ++func (ciph *StreamCipher) PacketConn(c net.PacketConn) net.PacketConn { ++ return shadowstream.NewPacketConn(c, ciph) ++} ++ ++// dummy cipher does not encrypt ++ ++type dummy struct{} ++ ++func (dummy) StreamConn(c net.Conn) net.Conn { return c } ++func (dummy) PacketConn(c net.PacketConn) net.PacketConn { return c } ++ ++// key-derivation function from original Shadowsocks ++func Kdf(password string, keyLen int) []byte { ++ var b, prev []byte ++ h := md5.New() ++ for len(b) < keyLen { ++ h.Write(prev) ++ h.Write([]byte(password)) ++ b = h.Sum(b) ++ prev = b[len(b)-h.Size():] ++ h.Reset() ++ } ++ return b[:keyLen] ++} +--- a/proxy/shadowsocksr/client.go ++++ b/proxy/shadowsocksr/client.go +@@ -0,0 +1,136 @@ ++package shadowsocksr ++ ++import ( ++ "context" ++ "time" ++ ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/errors" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/common/signal" ++ "github.com/xtls/xray-core/common/task" ++ "github.com/xtls/xray-core/core" ++ "github.com/xtls/xray-core/features/policy" ++ C "github.com/xtls/xray-core/proxy/shadowsocksr/cipher" ++ O "github.com/xtls/xray-core/proxy/shadowsocksr/obfs" ++ P "github.com/xtls/xray-core/proxy/shadowsocksr/protocol" ++ "github.com/xtls/xray-core/proxy/shadowsocksr/shadowstream" ++ "github.com/xtls/xray-core/transport" ++ "github.com/xtls/xray-core/transport/internet" ++) ++ ++type Client struct { ++ server net.Destination ++ policy policy.Session ++ cipher C.Cipher ++ obfs O.Obfs ++ protocol P.Protocol ++} ++ ++// Validate checks the same cipher, protocol and obfuscation registry used at runtime. ++func Validate(config *ClientConfig) error { _, err := newClient(config); return err } ++func newClient(config *ClientConfig) (*Client, error) { ++ if config.Address == nil || config.Port == 0 || config.Port > 65535 || config.Password == "" { ++ return nil, errors.New("shadowsocksr: invalid server or password") ++ } ++ cipher, err := C.PickCipher(config.Method, nil, config.Password) ++ if err != nil { ++ return nil, err ++ } ++ key := C.Kdf(config.Password, 16) ++ ivSize := 0 ++ if stream, ok := cipher.(*C.StreamCipher); ok { ++ key = stream.Key ++ ivSize = stream.IVSize() ++ } ++ obfs, overhead, err := O.PickObfs(config.Obfs, &O.Base{Host: config.Address.AsAddress().String(), Port: int(config.Port), Key: key, IVSize: ivSize, Param: config.ObfsParam}) ++ if err != nil { ++ return nil, err ++ } ++ protocol, err := P.PickProtocol(config.Protocol, &P.Base{Key: key, Overhead: overhead, Param: config.ProtocolParam}) ++ if err != nil { ++ return nil, err ++ } ++ return &Client{server: net.TCPDestination(config.Address.AsAddress(), net.Port(config.Port)), cipher: cipher, obfs: obfs, protocol: protocol}, nil ++} ++func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { ++ c, err := newClient(config) ++ if err != nil { ++ return nil, err ++ } ++ c.policy = core.MustFromContext(ctx).GetFeature(policy.ManagerType()).(policy.Manager).ForLevel(config.Level) ++ return c, nil ++} ++func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { ++ outbounds := session.OutboundsFromContext(ctx) ++ if len(outbounds) == 0 { ++ return errors.New("shadowsocksr: missing target") ++ } ++ ob := outbounds[len(outbounds)-1] ++ target := ob.Target ++ if !target.IsValid() || (target.Network != net.Network_TCP && target.Network != net.Network_UDP) { ++ return errors.New("shadowsocksr: unsupported target") ++ } ++ ob.Name = "shadowsocksr" ++ ob.CanSpliceCopy = 3 ++ server := c.server ++ server.Network = target.Network ++ conn, err := dialer.Dial(ctx, server) ++ if err != nil { ++ return err ++ } ++ defer conn.Close() ++ var reader buf.Reader ++ var writer buf.Writer ++ if target.Network == net.Network_TCP { ++ conn.SetDeadline(time.Now().Add(c.policy.Timeouts.Handshake)) ++ stream := c.cipher.StreamConn(c.obfs.StreamConn(conn)) ++ var iv []byte ++ if sc, ok := stream.(*shadowstream.Conn); ok { ++ iv, err = sc.ObtainWriteIV() ++ if err != nil { ++ return err ++ } ++ } ++ stream = c.protocol.StreamConn(stream, iv) ++ if err = M.SocksaddrSerializer.WriteAddrPort(stream, socksaddr(target)); err != nil { ++ return err ++ } ++ conn.SetDeadline(time.Time{}) ++ reader = buf.NewReader(stream) ++ writer = buf.NewWriter(stream) ++ } else { ++ pc := c.protocol.PacketConn(c.cipher.PacketConn(&connectedPacketConn{Conn: conn})) ++ reader = &packetReader{conn: pc} ++ writer = &packetWriter{conn: pc, server: conn.RemoteAddr(), destination: target} ++ } ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ timer := signal.CancelAfterInactivity(ctx, cancel, c.policy.Timeouts.ConnectionIdle) ++ defer timer.SetTimeout(0) ++ request := func() error { ++ defer timer.SetTimeout(c.policy.Timeouts.DownlinkOnly) ++ return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)) ++ } ++ response := func() error { ++ defer timer.SetTimeout(c.policy.Timeouts.UplinkOnly) ++ return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) ++ } ++ if err = task.Run(ctx, request, task.OnSuccess(response, task.Close(link.Writer))); err != nil { ++ common.Interrupt(link.Reader) ++ common.Interrupt(link.Writer) ++ return errors.New("shadowsocksr: connection ends").Base(err) ++ } ++ return nil ++} ++func socksaddr(d net.Destination) M.Socksaddr { ++ return M.ParseSocksaddrHostPort(d.Address.String(), uint16(d.Port)) ++} ++func init() { ++ common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { ++ return NewClient(ctx, config.(*ClientConfig)) ++ })) ++} +--- a/proxy/shadowsocksr/config.pb.go ++++ b/proxy/shadowsocksr/config.pb.go +@@ -0,0 +1,311 @@ ++// Code generated by protoc-gen-go. DO NOT EDIT. ++// versions: ++// protoc-gen-go v1.36.12 ++// protoc v3.21.12 ++// source: proxy/shadowsocksr/config.proto ++ ++package shadowsocksr ++ ++import ( ++ net "github.com/xtls/xray-core/common/net" ++ protoreflect "google.golang.org/protobuf/reflect/protoreflect" ++ protoimpl "google.golang.org/protobuf/runtime/protoimpl" ++ reflect "reflect" ++ sync "sync" ++ unsafe "unsafe" ++) ++ ++const ( ++ // Verify that this generated code is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) ++ // Verify that runtime/protoimpl is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ++) ++ ++type ClientConfig struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` ++ Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` ++ Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` ++ Level uint32 `protobuf:"varint,4,opt,name=level,proto3" json:"level,omitempty"` ++ Method string `protobuf:"bytes,5,opt,name=method,proto3" json:"method,omitempty"` ++ Protocol string `protobuf:"bytes,6,opt,name=protocol,proto3" json:"protocol,omitempty"` ++ ProtocolParam string `protobuf:"bytes,7,opt,name=protocol_param,json=protocolParam,proto3" json:"protocol_param,omitempty"` ++ Obfs string `protobuf:"bytes,8,opt,name=obfs,proto3" json:"obfs,omitempty"` ++ ObfsParam string `protobuf:"bytes,9,opt,name=obfs_param,json=obfsParam,proto3" json:"obfs_param,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *ClientConfig) Reset() { ++ *x = ClientConfig{} ++ mi := &file_proxy_shadowsocksr_config_proto_msgTypes[0] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *ClientConfig) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*ClientConfig) ProtoMessage() {} ++ ++func (x *ClientConfig) ProtoReflect() protoreflect.Message { ++ mi := &file_proxy_shadowsocksr_config_proto_msgTypes[0] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. ++func (*ClientConfig) Descriptor() ([]byte, []int) { ++ return file_proxy_shadowsocksr_config_proto_rawDescGZIP(), []int{0} ++} ++ ++func (x *ClientConfig) GetAddress() *net.IPOrDomain { ++ if x != nil { ++ return x.Address ++ } ++ return nil ++} ++ ++func (x *ClientConfig) GetPort() uint32 { ++ if x != nil { ++ return x.Port ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetPassword() string { ++ if x != nil { ++ return x.Password ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetLevel() uint32 { ++ if x != nil { ++ return x.Level ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetMethod() string { ++ if x != nil { ++ return x.Method ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetProtocol() string { ++ if x != nil { ++ return x.Protocol ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetProtocolParam() string { ++ if x != nil { ++ return x.ProtocolParam ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetObfs() string { ++ if x != nil { ++ return x.Obfs ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetObfsParam() string { ++ if x != nil { ++ return x.ObfsParam ++ } ++ return "" ++} ++ ++type ServerConfig struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` ++ Level uint32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` ++ Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"` ++ Protocol string `protobuf:"bytes,4,opt,name=protocol,proto3" json:"protocol,omitempty"` ++ ProtocolParam string `protobuf:"bytes,5,opt,name=protocol_param,json=protocolParam,proto3" json:"protocol_param,omitempty"` ++ Obfs string `protobuf:"bytes,6,opt,name=obfs,proto3" json:"obfs,omitempty"` ++ ObfsParam string `protobuf:"bytes,7,opt,name=obfs_param,json=obfsParam,proto3" json:"obfs_param,omitempty"` ++ Network []net.Network `protobuf:"varint,8,rep,packed,name=network,proto3,enum=xray.common.net.Network" json:"network,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *ServerConfig) Reset() { ++ *x = ServerConfig{} ++ mi := &file_proxy_shadowsocksr_config_proto_msgTypes[1] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *ServerConfig) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*ServerConfig) ProtoMessage() {} ++ ++func (x *ServerConfig) ProtoReflect() protoreflect.Message { ++ mi := &file_proxy_shadowsocksr_config_proto_msgTypes[1] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead. ++func (*ServerConfig) Descriptor() ([]byte, []int) { ++ return file_proxy_shadowsocksr_config_proto_rawDescGZIP(), []int{1} ++} ++ ++func (x *ServerConfig) GetPassword() string { ++ if x != nil { ++ return x.Password ++ } ++ return "" ++} ++ ++func (x *ServerConfig) GetLevel() uint32 { ++ if x != nil { ++ return x.Level ++ } ++ return 0 ++} ++ ++func (x *ServerConfig) GetMethod() string { ++ if x != nil { ++ return x.Method ++ } ++ return "" ++} ++ ++func (x *ServerConfig) GetProtocol() string { ++ if x != nil { ++ return x.Protocol ++ } ++ return "" ++} ++ ++func (x *ServerConfig) GetProtocolParam() string { ++ if x != nil { ++ return x.ProtocolParam ++ } ++ return "" ++} ++ ++func (x *ServerConfig) GetObfs() string { ++ if x != nil { ++ return x.Obfs ++ } ++ return "" ++} ++ ++func (x *ServerConfig) GetObfsParam() string { ++ if x != nil { ++ return x.ObfsParam ++ } ++ return "" ++} ++ ++func (x *ServerConfig) GetNetwork() []net.Network { ++ if x != nil { ++ return x.Network ++ } ++ return nil ++} ++ ++var File_proxy_shadowsocksr_config_proto protoreflect.FileDescriptor ++ ++const file_proxy_shadowsocksr_config_proto_rawDesc = "" + ++ "\n" + ++ "\x1fproxy/shadowsocksr/config.proto\x12\x17xray.proxy.shadowsocksr\x1a\x18common/net/address.proto\x1a\x18common/net/network.proto\"\x99\x02\n" + ++ "\fClientConfig\x125\n" + ++ "\aaddress\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" + ++ "\x04port\x18\x02 \x01(\rR\x04port\x12\x1a\n" + ++ "\bpassword\x18\x03 \x01(\tR\bpassword\x12\x14\n" + ++ "\x05level\x18\x04 \x01(\rR\x05level\x12\x16\n" + ++ "\x06method\x18\x05 \x01(\tR\x06method\x12\x1a\n" + ++ "\bprotocol\x18\x06 \x01(\tR\bprotocol\x12%\n" + ++ "\x0eprotocol_param\x18\a \x01(\tR\rprotocolParam\x12\x12\n" + ++ "\x04obfs\x18\b \x01(\tR\x04obfs\x12\x1d\n" + ++ "\n" + ++ "obfs_param\x18\t \x01(\tR\tobfsParam\"\x82\x02\n" + ++ "\fServerConfig\x12\x1a\n" + ++ "\bpassword\x18\x01 \x01(\tR\bpassword\x12\x14\n" + ++ "\x05level\x18\x02 \x01(\rR\x05level\x12\x16\n" + ++ "\x06method\x18\x03 \x01(\tR\x06method\x12\x1a\n" + ++ "\bprotocol\x18\x04 \x01(\tR\bprotocol\x12%\n" + ++ "\x0eprotocol_param\x18\x05 \x01(\tR\rprotocolParam\x12\x12\n" + ++ "\x04obfs\x18\x06 \x01(\tR\x04obfs\x12\x1d\n" + ++ "\n" + ++ "obfs_param\x18\a \x01(\tR\tobfsParam\x122\n" + ++ "\anetwork\x18\b \x03(\x0e2\x18.xray.common.net.NetworkR\anetworkB.Z,github.com/xtls/xray-core/proxy/shadowsocksrb\x06proto3" ++ ++var ( ++ file_proxy_shadowsocksr_config_proto_rawDescOnce sync.Once ++ file_proxy_shadowsocksr_config_proto_rawDescData []byte ++) ++ ++func file_proxy_shadowsocksr_config_proto_rawDescGZIP() []byte { ++ file_proxy_shadowsocksr_config_proto_rawDescOnce.Do(func() { ++ file_proxy_shadowsocksr_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_shadowsocksr_config_proto_rawDesc), len(file_proxy_shadowsocksr_config_proto_rawDesc))) ++ }) ++ return file_proxy_shadowsocksr_config_proto_rawDescData ++} ++ ++var file_proxy_shadowsocksr_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) ++var file_proxy_shadowsocksr_config_proto_goTypes = []any{ ++ (*ClientConfig)(nil), // 0: xray.proxy.shadowsocksr.ClientConfig ++ (*ServerConfig)(nil), // 1: xray.proxy.shadowsocksr.ServerConfig ++ (*net.IPOrDomain)(nil), // 2: xray.common.net.IPOrDomain ++ (net.Network)(0), // 3: xray.common.net.Network ++} ++var file_proxy_shadowsocksr_config_proto_depIdxs = []int32{ ++ 2, // 0: xray.proxy.shadowsocksr.ClientConfig.address:type_name -> xray.common.net.IPOrDomain ++ 3, // 1: xray.proxy.shadowsocksr.ServerConfig.network:type_name -> xray.common.net.Network ++ 2, // [2:2] is the sub-list for method output_type ++ 2, // [2:2] is the sub-list for method input_type ++ 2, // [2:2] is the sub-list for extension type_name ++ 2, // [2:2] is the sub-list for extension extendee ++ 0, // [0:2] is the sub-list for field type_name ++} ++ ++func init() { file_proxy_shadowsocksr_config_proto_init() } ++func file_proxy_shadowsocksr_config_proto_init() { ++ if File_proxy_shadowsocksr_config_proto != nil { ++ return ++ } ++ type x struct{} ++ out := protoimpl.TypeBuilder{ ++ File: protoimpl.DescBuilder{ ++ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), ++ RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_shadowsocksr_config_proto_rawDesc), len(file_proxy_shadowsocksr_config_proto_rawDesc)), ++ NumEnums: 0, ++ NumMessages: 2, ++ NumExtensions: 0, ++ NumServices: 0, ++ }, ++ GoTypes: file_proxy_shadowsocksr_config_proto_goTypes, ++ DependencyIndexes: file_proxy_shadowsocksr_config_proto_depIdxs, ++ MessageInfos: file_proxy_shadowsocksr_config_proto_msgTypes, ++ }.Build() ++ File_proxy_shadowsocksr_config_proto = out.File ++ file_proxy_shadowsocksr_config_proto_goTypes = nil ++ file_proxy_shadowsocksr_config_proto_depIdxs = nil ++} +--- a/proxy/shadowsocksr/config.proto ++++ b/proxy/shadowsocksr/config.proto +@@ -0,0 +1,26 @@ ++syntax = "proto3"; ++package xray.proxy.shadowsocksr; ++option go_package = "github.com/xtls/xray-core/proxy/shadowsocksr"; ++import "common/net/address.proto"; ++import "common/net/network.proto"; ++message ClientConfig { ++ xray.common.net.IPOrDomain address = 1; ++ uint32 port = 2; ++ string password = 3; ++ uint32 level = 4; ++ string method = 5; ++ string protocol = 6; ++ string protocol_param = 7; ++ string obfs = 8; ++ string obfs_param = 9; ++} ++message ServerConfig { ++ string password = 1; ++ uint32 level = 2; ++ string method = 3; ++ string protocol = 4; ++ string protocol_param = 5; ++ string obfs = 6; ++ string obfs_param = 7; ++ repeated xray.common.net.Network network = 8; ++} +--- a/proxy/shadowsocksr/obfs/base.go ++++ b/proxy/shadowsocksr/obfs/base.go +@@ -0,0 +1,11 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package obfs ++ ++type Base struct { ++ Host string ++ Port int ++ Key []byte ++ IVSize int ++ Param string ++} +--- a/proxy/shadowsocksr/obfs/http_post.go ++++ b/proxy/shadowsocksr/obfs/http_post.go +@@ -0,0 +1,11 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package obfs ++ ++func init() { ++ register("http_post", newHTTPPost, 0) ++} ++ ++func newHTTPPost(b *Base) Obfs { ++ return &httpObfs{Base: b, post: true} ++} +--- a/proxy/shadowsocksr/obfs/http_simple.go ++++ b/proxy/shadowsocksr/obfs/http_simple.go +@@ -0,0 +1,416 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package obfs ++ ++import ( ++ "bufio" ++ "bytes" ++ "encoding/hex" ++ "fmt" ++ "io" ++ randv2 "math/rand/v2" ++ "net" ++ "strconv" ++ "strings" ++ ++ "github.com/xtls/xray-core/proxy/shadowsocksr/pool" ++) ++ ++func init() { ++ register("http_simple", newHTTPSimple, 0) ++} ++ ++type httpObfs struct { ++ *Base ++ post bool ++} ++ ++func newHTTPSimple(b *Base) Obfs { ++ return &httpObfs{Base: b} ++} ++ ++type httpConn struct { ++ net.Conn ++ *httpObfs ++ hasSentHeader bool ++ hasRecvHeader bool ++ buf []byte ++} ++ ++func (h *httpObfs) StreamConn(c net.Conn) net.Conn { ++ return &httpConn{Conn: c, httpObfs: h} ++} ++ ++func (c *httpConn) Read(b []byte) (int, error) { ++ if c.buf != nil { ++ n := copy(b, c.buf) ++ if n == len(c.buf) { ++ c.buf = nil ++ } else { ++ c.buf = c.buf[n:] ++ } ++ return n, nil ++ } ++ ++ if c.hasRecvHeader { ++ return c.Conn.Read(b) ++ } ++ ++ // TCP may fragment the HTTP camouflage header across arbitrary reads. ++ r := bufio.NewReader(c.Conn) ++ total := 0 ++ for { ++ line, err := r.ReadSlice('\n') ++ total += len(line) ++ if total > 32768 { ++ return 0, fmt.Errorf("SSR HTTP header too large") ++ } ++ if err != nil { ++ return 0, err ++ } ++ if bytes.Equal(line, []byte("\r\n")) { ++ break ++ } ++ } ++ c.hasRecvHeader = true ++ if n := r.Buffered(); n > 0 { ++ c.buf = make([]byte, n) ++ if _, err := io.ReadFull(r, c.buf); err != nil { ++ return 0, err ++ } ++ } ++ return c.Read(b) ++} ++ ++func (c *httpConn) Write(b []byte) (int, error) { ++ if c.hasSentHeader { ++ return c.Conn.Write(b) ++ } ++ // 30: head length ++ headLength := c.IVSize + 30 ++ ++ bLength := len(b) ++ headDataLength := bLength ++ if bLength-headLength > 64 { ++ headDataLength = headLength + randv2.IntN(65) ++ } ++ headData := b[:headDataLength] ++ b = b[headDataLength:] ++ ++ var body string ++ host := c.Host ++ if len(c.Param) > 0 { ++ pos := strings.Index(c.Param, "#") ++ if pos != -1 { ++ body = strings.ReplaceAll(c.Param[pos+1:], "\n", "\r\n") ++ body = strings.ReplaceAll(body, "\\n", "\r\n") ++ host = c.Param[:pos] ++ } else { ++ host = c.Param ++ } ++ } ++ hosts := strings.Split(host, ",") ++ host = hosts[randv2.IntN(len(hosts))] ++ ++ buf := pool.GetBuffer() ++ defer pool.PutBuffer(buf) ++ if c.post { ++ buf.WriteString("POST /") ++ } else { ++ buf.WriteString("GET /") ++ } ++ packURLEncodedHeadData(buf, headData) ++ buf.WriteString(" HTTP/1.1\r\nHost: " + host) ++ if c.Port != 80 { ++ buf.WriteString(":" + strconv.Itoa(c.Port)) ++ } ++ buf.WriteString("\r\n") ++ if len(body) > 0 { ++ buf.WriteString(body + "\r\n\r\n") ++ } else { ++ buf.WriteString("User-Agent: ") ++ buf.WriteString(userAgent[randv2.IntN(len(userAgent))]) ++ buf.WriteString("\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\n") ++ if c.post { ++ packBoundary(buf) ++ } ++ buf.WriteString("DNT: 1\r\nConnection: keep-alive\r\n\r\n") ++ } ++ buf.Write(b) ++ _, err := c.Conn.Write(buf.Bytes()) ++ if err != nil { ++ return 0, err ++ } ++ c.hasSentHeader = true ++ return bLength, nil ++} ++ ++func packURLEncodedHeadData(buf *bytes.Buffer, data []byte) { ++ dataLength := len(data) ++ for i := 0; i < dataLength; i++ { ++ buf.WriteRune('%') ++ buf.WriteString(hex.EncodeToString(data[i : i+1])) ++ } ++} ++ ++func packBoundary(buf *bytes.Buffer) { ++ buf.WriteString("Content-Type: multipart/form-data; boundary=") ++ set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ++ for i := 0; i < 32; i++ { ++ buf.WriteByte(set[randv2.IntN(62)]) ++ } ++ buf.WriteString("\r\n") ++} ++ ++var userAgent = []string{ ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", ++ "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", ++ "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", ++ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", ++ "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", ++ "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", ++ "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", ++ "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36", ++ "Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36", ++ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", ++ "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", ++ "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0", ++ "Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", ++ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", ++ "Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", ++ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", ++ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", ++ "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", ++ "Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", ++ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", ++ "Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", ++ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", ++ "Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1", ++ "Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", ++ "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", ++ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", ++ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36", ++ "Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", ++ "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", ++} +--- a/proxy/shadowsocksr/obfs/obfs.go ++++ b/proxy/shadowsocksr/obfs/obfs.go +@@ -0,0 +1,44 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package obfs ++ ++import ( ++ "errors" ++ "fmt" ++ "net" ++) ++ ++var ( ++ errTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number") ++ errTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data") ++ errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed") ++) ++ ++type authData struct { ++ clientID [32]byte ++} ++ ++type Obfs interface { ++ StreamConn(net.Conn) net.Conn ++} ++ ++type obfsCreator func(b *Base) Obfs ++ ++var obfsList = make(map[string]struct { ++ overhead int ++ new obfsCreator ++}) ++ ++func register(name string, c obfsCreator, o int) { ++ obfsList[name] = struct { ++ overhead int ++ new obfsCreator ++ }{overhead: o, new: c} ++} ++ ++func PickObfs(name string, b *Base) (Obfs, int, error) { ++ if choice, ok := obfsList[name]; ok { ++ return choice.new(b), choice.overhead, nil ++ } ++ return nil, 0, fmt.Errorf("Obfs %s not supported", name) ++} +--- a/proxy/shadowsocksr/obfs/plain.go ++++ b/proxy/shadowsocksr/obfs/plain.go +@@ -0,0 +1,17 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package obfs ++ ++import "net" ++ ++type plain struct{} ++ ++func init() { ++ register("plain", newPlain, 0) ++} ++ ++func newPlain(b *Base) Obfs { ++ return &plain{} ++} ++ ++func (p *plain) StreamConn(c net.Conn) net.Conn { return c } +--- a/proxy/shadowsocksr/obfs/random_head.go ++++ b/proxy/shadowsocksr/obfs/random_head.go +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package obfs ++ ++import ( ++ "crypto/rand" ++ "encoding/binary" ++ "hash/crc32" ++ randv2 "math/rand/v2" ++ "net" ++ "sync" ++ "sync/atomic" ++ ++ "github.com/xtls/xray-core/proxy/shadowsocksr/pool" ++) ++ ++func init() { ++ register("random_head", newRandomHead, 0) ++} ++ ++type randomHead struct { ++ *Base ++} ++ ++func newRandomHead(b *Base) Obfs { ++ return &randomHead{Base: b} ++} ++ ++type randomHeadConn struct { ++ net.Conn ++ *randomHead ++ hasSentHeader bool ++ rawTransSent bool ++ rawTransRecv atomic.Bool ++ writeMu sync.Mutex ++ buf []byte ++} ++ ++func (r *randomHead) StreamConn(c net.Conn) net.Conn { ++ return &randomHeadConn{Conn: c, randomHead: r} ++} ++ ++func (c *randomHeadConn) Read(b []byte) (int, error) { ++ if c.rawTransRecv.Load() { ++ return c.Conn.Read(b) ++ } ++ buf := pool.Get(pool.RelayBufferSize) ++ defer pool.Put(buf) ++ if _, err := c.Conn.Read(buf); err != nil { ++ return 0, err ++ } ++ c.rawTransRecv.Store(true) ++ _, err := c.Write(nil) ++ return 0, err ++} ++ ++func (c *randomHeadConn) Write(b []byte) (int, error) { ++ c.writeMu.Lock() ++ defer c.writeMu.Unlock() ++ if c.rawTransSent { ++ if len(b) == 0 { ++ return 0, nil ++ } ++ return c.Conn.Write(b) ++ } ++ c.buf = append(c.buf, b...) ++ if !c.hasSentHeader { ++ c.hasSentHeader = true ++ dataLength := randv2.IntN(96) + 4 ++ buf := pool.Get(dataLength + 4) ++ defer pool.Put(buf) ++ rand.Read(buf[:dataLength]) ++ binary.LittleEndian.PutUint32(buf[dataLength:], 0xffffffff-crc32.ChecksumIEEE(buf[:dataLength])) ++ _, err := c.Conn.Write(buf) ++ return len(b), err ++ } ++ if c.rawTransRecv.Load() { ++ _, err := c.Conn.Write(c.buf) ++ c.buf = nil ++ c.rawTransSent = true ++ return len(b), err ++ } ++ return len(b), nil ++} +--- a/proxy/shadowsocksr/obfs/server.go ++++ b/proxy/shadowsocksr/obfs/server.go +@@ -0,0 +1,257 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Server counterpart to Mihomo camouflage, checked against the libev server ++// HTTP/TLS implementations and shadowsocksrr's reference server. ++package obfs ++ ++import ( ++ "bufio" ++ "bytes" ++ "crypto/hmac" ++ "crypto/rand" ++ "encoding/binary" ++ "encoding/hex" ++ "errors" ++ "github.com/xtls/xray-core/proxy/shadowsocksr/protocol" ++ "github.com/xtls/xray-core/proxy/shadowsocksr/tools" ++ "hash/crc32" ++ "io" ++ "net" ++ "strings" ++ "time" ++) ++ ++var errServerHandshake = errors.New("shadowsocksr: invalid camouflage handshake") ++ ++type serverConn struct { ++ net.Conn ++ reader io.Reader ++ httpHeader bool ++ tls bool ++ decoded bytes.Buffer ++} ++ ++func (c *serverConn) Read(b []byte) (int, error) { ++ if len(b) == 0 { ++ return 0, nil ++ } ++ if !c.tls { ++ return c.reader.Read(b) ++ } ++ for c.decoded.Len() == 0 { ++ r, e := serverRecord(c.reader, false) ++ if e != nil { ++ return 0, e ++ } ++ if r[0] != 0x17 { ++ return 0, errServerHandshake ++ } ++ c.decoded.Write(r[5:]) ++ } ++ return c.decoded.Read(b) ++} ++func (c *serverConn) Write(b []byte) (int, error) { ++ var out bytes.Buffer ++ if c.httpHeader { ++ c.httpHeader = false ++ out.WriteString("HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Type: text/html\r\n\r\n") ++ } ++ if c.tls { ++ for p := b; len(p) > 0; { ++ n := len(p) ++ if n > 4096 { ++ n = 4096 ++ } ++ packData(&out, p[:n]) ++ p = p[n:] ++ } ++ } else { ++ out.Write(b) ++ } ++ n, e := c.Conn.Write(out.Bytes()) ++ if e == nil && n != out.Len() { ++ e = io.ErrShortWrite ++ } ++ if e != nil { ++ return 0, e ++ } ++ return len(b), nil ++} ++func serverRecord(r io.Reader, hello bool) ([]byte, error) { ++ var h [5]byte ++ if _, e := io.ReadFull(r, h[:]); e != nil { ++ return nil, e ++ } ++ if h[1] != 3 || (h[2] != 3 && !(hello && h[2] == 1)) { ++ return nil, errServerHandshake ++ } ++ n := int(binary.BigEndian.Uint16(h[3:])) ++ if n > 18432 { ++ return nil, errServerHandshake ++ } ++ b := make([]byte, 5+n) ++ copy(b, h[:]) ++ _, e := io.ReadFull(r, b[5:]) ++ return b, e ++} ++func AcceptServer(c net.Conn, name string, key []byte, param string, cache *protocol.ReplayCache, deadline time.Time) (net.Conn, error) { ++ switch name { ++ case "plain": ++ return c, nil ++ case "http_simple", "http_post": ++ r := bufio.NewReader(c) ++ var header bytes.Buffer ++ for { ++ line, e := r.ReadSlice('\n') ++ if e != nil { ++ return nil, e ++ } ++ header.Write(line) ++ if header.Len() > 32768 { ++ return nil, errServerHandshake ++ } ++ if bytes.Equal(line, []byte("\r\n")) { ++ break ++ } ++ } ++ lines := strings.Split(header.String(), "\r\n") ++ parts := strings.Split(lines[0], " ") ++ if len(parts) != 3 || (parts[0] != "GET" && parts[0] != "POST") || !strings.HasPrefix(parts[1], "/") { ++ return nil, errServerHandshake ++ } ++ // Camouflage carries the initial ciphertext as percent-encoded URL bytes. ++ path := strings.TrimPrefix(parts[1], "/") ++ var first []byte ++ for len(path) > 0 { ++ if len(path) < 3 || path[0] != '%' { ++ return nil, errServerHandshake ++ } ++ v, e := hex.DecodeString(path[1:3]) ++ if e != nil { ++ return nil, e ++ } ++ first = append(first, v[0]) ++ path = path[3:] ++ } ++ if len(first) == 0 { ++ return nil, errServerHandshake ++ } ++ if param != "" { ++ host := "" ++ for _, l := range lines[1:] { ++ if strings.HasPrefix(strings.ToLower(l), "host:") { ++ host = strings.TrimSpace(l[5:]) ++ if h, _, e := net.SplitHostPort(host); e == nil { ++ host = h ++ } ++ } ++ } ++ found := false ++ for _, h := range strings.Split(param, ",") { ++ if strings.EqualFold(strings.TrimSpace(h), host) { ++ found = true ++ } ++ } ++ if !found { ++ return nil, errServerHandshake ++ } ++ } ++ return &serverConn{Conn: c, reader: io.MultiReader(bytes.NewReader(first), r), httpHeader: true}, nil ++ case "random_head": ++ // This legacy header has no length field. A trailing zero CRC byte ++ // leaves the CRC residue unchanged, so a matching prefix is not a ++ // boundary. Read chunks and allow a short quiet interval to collect ++ // fragmented trailing bytes before replying (the peer waits for us). ++ var head [103]byte ++ length := 0 ++ valid := false ++ defer c.SetReadDeadline(deadline) ++ for length < len(head) { ++ if valid { ++ quiet := time.Now().Add(20 * time.Millisecond) ++ if !deadline.IsZero() && deadline.Before(quiet) { ++ quiet = deadline ++ } ++ c.SetReadDeadline(quiet) ++ } else { ++ c.SetReadDeadline(deadline) ++ } ++ n, e := c.Read(head[length:]) ++ length += n ++ valid = length >= 8 && crc32.ChecksumIEEE(head[:length]) == 0xffffffff ++ if e != nil { ++ if ne, ok := e.(net.Error); !ok || !ne.Timeout() || !valid { ++ return nil, e ++ } ++ break ++ } ++ } ++ if !valid { ++ return nil, errServerHandshake ++ } ++ c.SetReadDeadline(deadline) ++ reply := make([]byte, 16) ++ rand.Read(reply) ++ if _, e := c.Write(reply); e != nil { ++ return nil, e ++ } ++ return c, nil ++ case "tls1.2_ticket_auth", "tls1.2_ticket_fastauth": ++ rec, e := serverRecord(c, true) ++ if e != nil { ++ return nil, e ++ } ++ if len(rec) < 76 || rec[0] != 0x16 || rec[5] != 1 || rec[6] != 0 || int(binary.BigEndian.Uint16(rec[7:9])) != len(rec)-9 || rec[9] != 3 || rec[10] != 3 || rec[43] != 32 { ++ return nil, errServerHandshake ++ } ++ id := rec[44:76] ++ macKey := append(append([]byte{}, key...), id...) ++ verify := rec[11:43] ++ if !hmac.Equal(tools.HmacSHA1(macKey, verify[:22])[:10], verify[22:]) || !cache.Accept(verify[:22], int64(binary.BigEndian.Uint32(verify))) { ++ return nil, errServerHandshake ++ } ++ var body bytes.Buffer ++ body.Write([]byte{3, 3}) ++ auth := make([]byte, 22) ++ rand.Read(auth) ++ binary.BigEndian.PutUint32(auth, uint32(time.Now().Unix())) ++ body.Write(auth) ++ body.Write(tools.HmacSHA1(macKey, auth)[:10]) ++ body.WriteByte(32) ++ body.Write(id) ++ body.Write([]byte{0xc0, 0x2f, 0, 0, 5, 0xff, 1, 0, 1, 0}) ++ var out bytes.Buffer ++ out.Write([]byte{0x16, 3, 3}) ++ binary.Write(&out, binary.BigEndian, uint16(body.Len()+4)) ++ out.Write([]byte{2, 0}) ++ binary.Write(&out, binary.BigEndian, uint16(body.Len())) ++ out.Write(body.Bytes()) ++ out.Write([]byte{0x14, 3, 3, 0, 1, 1, 0x16, 3, 3, 0, 32}) ++ finish := make([]byte, 22) ++ rand.Read(finish) ++ out.Write(finish) ++ out.Write(tools.HmacSHA1(macKey, out.Bytes())[:10]) ++ if _, e = c.Write(out.Bytes()); e != nil { ++ return nil, e ++ } ++ change, e := serverRecord(c, false) ++ if e != nil { ++ return nil, e ++ } ++ if !bytes.Equal(change, []byte{0x14, 3, 3, 0, 1, 1}) { ++ return nil, errServerHandshake ++ } ++ fin, e := serverRecord(c, false) ++ if e != nil { ++ return nil, e ++ } ++ if fin[0] != 0x16 || len(fin) < 15 { ++ return nil, errServerHandshake ++ } ++ all := append(change, fin...) ++ if !hmac.Equal(tools.HmacSHA1(macKey, all[:len(all)-10])[:10], all[len(all)-10:]) { ++ return nil, errServerHandshake ++ } ++ return &serverConn{Conn: c, reader: c, tls: true}, nil ++ } ++ return nil, errServerHandshake ++} +--- a/proxy/shadowsocksr/obfs/tls1.2_ticket_auth.go ++++ b/proxy/shadowsocksr/obfs/tls1.2_ticket_auth.go +@@ -0,0 +1,264 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package obfs ++ ++import ( ++ "bytes" ++ "crypto/hmac" ++ "crypto/rand" ++ "encoding/binary" ++ "io" ++ randv2 "math/rand/v2" ++ "net" ++ "strings" ++ "sync" ++ "sync/atomic" ++ "time" ++ ++ "github.com/xtls/xray-core/proxy/shadowsocksr/pool" ++ "github.com/xtls/xray-core/proxy/shadowsocksr/tools" ++) ++ ++func init() { ++ register("tls1.2_ticket_auth", newTLS12Ticket, 5) ++ register("tls1.2_ticket_fastauth", newTLS12Ticket, 5) ++} ++ ++type tls12Ticket struct { ++ *Base ++ *authData ++} ++ ++func newTLS12Ticket(b *Base) Obfs { ++ r := &tls12Ticket{Base: b, authData: &authData{}} ++ rand.Read(r.clientID[:]) ++ return r ++} ++ ++type tls12TicketConn struct { ++ net.Conn ++ *tls12Ticket ++ handshakeStatus atomic.Int32 ++ writeMu sync.Mutex ++ decoded bytes.Buffer ++ sendBuf bytes.Buffer ++} ++ ++func (t *tls12Ticket) StreamConn(c net.Conn) net.Conn { ++ return &tls12TicketConn{Conn: c, tls12Ticket: t} ++} ++ ++// Read complete records so fragmented or coalesced TCP reads cannot corrupt ++// the handshake authentication boundary. ++func (c *tls12TicketConn) readRecord() ([]byte, error) { ++ var header [5]byte ++ if _, err := io.ReadFull(c.Conn, header[:]); err != nil { ++ return nil, err ++ } ++ if header[1] != 3 || header[2] != 3 { ++ return nil, errTLS12TicketAuthIncorrectMagicNumber ++ } ++ n := int(binary.BigEndian.Uint16(header[3:])) ++ if n > 18432 { ++ return nil, errTLS12TicketAuthTooShortData ++ } ++ record := make([]byte, 5+n) ++ copy(record, header[:]) ++ _, err := io.ReadFull(c.Conn, record[5:]) ++ return record, err ++} ++func (c *tls12TicketConn) Read(b []byte) (int, error) { ++ if len(b) == 0 { ++ return 0, nil ++ } ++ if c.handshakeStatus.Load() != 8 { ++ var handshake bytes.Buffer ++ changed := false ++ for i := 0; ; i++ { ++ if i >= 16 { ++ return 0, errTLS12TicketAuthTooShortData ++ } ++ record, err := c.readRecord() ++ if err != nil { ++ return 0, err ++ } ++ handshake.Write(record) ++ if handshake.Len() > 65536 { ++ return 0, errTLS12TicketAuthTooShortData ++ } ++ if record[0] == 0x16 && changed { ++ break ++ } ++ if record[0] == 0x14 { ++ changed = true ++ } else if record[0] != 0x16 { ++ return 0, errTLS12TicketAuthIncorrectMagicNumber ++ } ++ } ++ data := handshake.Bytes() ++ n := len(data) ++ if n < 76 { ++ return 0, errTLS12TicketAuthTooShortData ++ } ++ if !hmac.Equal(data[33:43], c.hmacSHA1(data[11:33])[:10]) || !hmac.Equal(data[n-10:], c.hmacSHA1(data[:n-10])[:10]) { ++ return 0, errTLS12TicketAuthHMACError ++ } ++ if _, err := c.Write(nil); err != nil { ++ return 0, err ++ } ++ } ++ for c.decoded.Len() == 0 { ++ record, err := c.readRecord() ++ if err != nil { ++ return 0, err ++ } ++ if record[0] != 0x17 { ++ return 0, errTLS12TicketAuthIncorrectMagicNumber ++ } ++ c.decoded.Write(record[5:]) ++ } ++ return c.decoded.Read(b) ++} ++ ++func (c *tls12TicketConn) Write(b []byte) (int, error) { ++ c.writeMu.Lock() ++ defer c.writeMu.Unlock() ++ length := len(b) ++ if c.handshakeStatus.Load() == 8 { ++ buf := pool.GetBuffer() ++ defer pool.PutBuffer(buf) ++ for len(b) > 2048 { ++ size := randv2.IntN(4096) + 100 ++ if len(b) < size { ++ size = len(b) ++ } ++ packData(buf, b[:size]) ++ b = b[size:] ++ } ++ if len(b) > 0 { ++ packData(buf, b) ++ } ++ _, err := c.Conn.Write(buf.Bytes()) ++ if err != nil { ++ return 0, err ++ } ++ return length, nil ++ } ++ ++ if len(b) > 0 { ++ packData(&c.sendBuf, b) ++ } ++ ++ if c.handshakeStatus.Load() == 0 { ++ c.handshakeStatus.Store(1) ++ ++ data := pool.GetBuffer() ++ defer pool.PutBuffer(data) ++ ++ data.Write([]byte{3, 3}) ++ c.packAuthData(data) ++ data.WriteByte(0x20) ++ data.Write(c.clientID[:]) ++ data.Write([]byte{0x00, 0x1c, 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13, 0xc0, 0x0a, 0xc0, 0x14, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x9c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0x0a}) ++ data.Write([]byte{0x1, 0x0}) ++ ++ ext := pool.GetBuffer() ++ defer pool.PutBuffer(ext) ++ ++ host := c.getHost() ++ ext.Write([]byte{0xff, 0x01, 0x00, 0x01, 0x00}) ++ packSNIData(ext, host) ++ ext.Write([]byte{0, 0x17, 0, 0}) ++ c.packTicketBuf(ext, host) ++ ext.Write([]byte{0x00, 0x0d, 0x00, 0x16, 0x00, 0x14, 0x06, 0x01, 0x06, 0x03, 0x05, 0x01, 0x05, 0x03, 0x04, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x03, 0x02, 0x01, 0x02, 0x03}) ++ ext.Write([]byte{0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00}) ++ ext.Write([]byte{0x00, 0x12, 0x00, 0x00}) ++ ext.Write([]byte{0x75, 0x50, 0x00, 0x00}) ++ ext.Write([]byte{0x00, 0x0b, 0x00, 0x02, 0x01, 0x00}) ++ ext.Write([]byte{0x00, 0x0a, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, 0x00, 0x18}) ++ ++ binary.Write(data, binary.BigEndian, uint16(ext.Len())) ++ data.ReadFrom(ext) ++ ++ ret := pool.GetBuffer() ++ defer pool.PutBuffer(ret) ++ ++ ret.Write([]byte{0x16, 3, 1}) ++ binary.Write(ret, binary.BigEndian, uint16(data.Len()+4)) ++ ret.Write([]byte{1, 0}) ++ binary.Write(ret, binary.BigEndian, uint16(data.Len())) ++ ret.ReadFrom(data) ++ ++ _, err := c.Conn.Write(ret.Bytes()) ++ if err != nil { ++ return 0, err ++ } ++ return length, nil ++ } else if c.handshakeStatus.Load() == 1 && len(b) == 0 { ++ buf := pool.GetBuffer() ++ defer pool.PutBuffer(buf) ++ ++ buf.Write([]byte{0x14, 3, 3, 0, 1, 1, 0x16, 3, 3, 0, 0x20}) ++ tools.AppendRandBytes(buf, 22) ++ buf.Write(c.hmacSHA1(buf.Bytes())[:10]) ++ buf.ReadFrom(&c.sendBuf) ++ ++ c.handshakeStatus.Store(8) ++ ++ _, err := c.Conn.Write(buf.Bytes()) ++ return 0, err ++ } ++ return length, nil ++} ++ ++func packData(buf *bytes.Buffer, data []byte) { ++ buf.Write([]byte{0x17, 3, 3}) ++ binary.Write(buf, binary.BigEndian, uint16(len(data))) ++ buf.Write(data) ++} ++ ++func (t *tls12Ticket) packAuthData(buf *bytes.Buffer) { ++ binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix())) ++ tools.AppendRandBytes(buf, 18) ++ buf.Write(t.hmacSHA1(buf.Bytes()[buf.Len()-22:])[:10]) ++} ++ ++func packSNIData(buf *bytes.Buffer, u string) { ++ len := uint16(len(u)) ++ buf.Write([]byte{0, 0}) ++ binary.Write(buf, binary.BigEndian, len+5) ++ binary.Write(buf, binary.BigEndian, len+3) ++ buf.WriteByte(0) ++ binary.Write(buf, binary.BigEndian, len) ++ buf.WriteString(u) ++} ++ ++func (c *tls12TicketConn) packTicketBuf(buf *bytes.Buffer, u string) { ++ length := 16 * (randv2.IntN(17) + 8) ++ buf.Write([]byte{0, 0x23}) ++ binary.Write(buf, binary.BigEndian, uint16(length)) ++ tools.AppendRandBytes(buf, length) ++} ++ ++func (t *tls12Ticket) hmacSHA1(data []byte) []byte { ++ key := pool.Get(len(t.Key) + 32) ++ defer pool.Put(key) ++ copy(key, t.Key) ++ copy(key[len(t.Key):], t.clientID[:]) ++ ++ sha1Data := tools.HmacSHA1(key, data) ++ return sha1Data[:10] ++} ++ ++func (t *tls12Ticket) getHost() string { ++ host := t.Param ++ if len(host) == 0 { ++ host = t.Host ++ } ++ if len(host) > 0 && host[len(host)-1] >= '0' && host[len(host)-1] <= '9' { ++ host = "" ++ } ++ hosts := strings.Split(host, ",") ++ host = hosts[randv2.IntN(len(hosts))] ++ return host ++} +--- a/proxy/shadowsocksr/packet.go ++++ b/proxy/shadowsocksr/packet.go +@@ -0,0 +1,62 @@ ++package shadowsocksr ++ ++import ( ++ "bytes" ++ "net" ++ ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/xtls/xray-core/common/buf" ++ N "github.com/xtls/xray-core/common/net" ++) ++ ++type connectedPacketConn struct{ net.Conn } ++ ++func (c *connectedPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { ++ n, e := c.Read(b) ++ return n, c.RemoteAddr(), e ++} ++func (c *connectedPacketConn) WriteTo(b []byte, _ net.Addr) (int, error) { return c.Write(b) } ++ ++type packetReader struct{ conn net.PacketConn } ++ ++func (r *packetReader) ReadMultiBuffer() (buf.MultiBuffer, error) { ++ packet := make([]byte, 65535) ++ n, _, err := r.conn.ReadFrom(packet) ++ if err != nil { ++ return nil, err ++ } ++ data := bytes.NewReader(packet[:n]) ++ address, err := M.SocksaddrSerializer.ReadAddrPort(data) ++ if err != nil { ++ return nil, err ++ } ++ b := buf.FromBytes(packet[n-data.Len() : n]) ++ d := N.UDPDestination(N.ParseAddress(address.AddrString()), N.Port(address.Port)) ++ b.UDP = &d ++ return buf.MultiBuffer{b}, nil ++} ++ ++type packetWriter struct { ++ conn net.PacketConn ++ server net.Addr ++ destination N.Destination ++} ++ ++func (w *packetWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { ++ defer buf.ReleaseMulti(mb) ++ for _, b := range mb { ++ d := w.destination ++ if b.UDP != nil { ++ d = *b.UDP ++ } ++ var packet bytes.Buffer ++ if err := M.SocksaddrSerializer.WriteAddrPort(&packet, socksaddr(d)); err != nil { ++ return err ++ } ++ packet.Write(b.Bytes()) ++ if _, err := w.conn.WriteTo(packet.Bytes(), w.server); err != nil { ++ return err ++ } ++ } ++ return nil ++} +--- a/proxy/shadowsocksr/pool/pool.go ++++ b/proxy/shadowsocksr/pool/pool.go +@@ -0,0 +1,31 @@ ++package pool ++ ++import ( ++ "bytes" ++ "sync" ++) ++ ++const RelayBufferSize = 32 * 1024 ++ ++var buffers = sync.Pool{New: func() any { return new(bytes.Buffer) }} ++var packets = sync.Pool{New: func() any { return make([]byte, 65536) }} ++ ++func Get(n int) []byte { ++ if n > 65536 { ++ return make([]byte, n) ++ } ++ return packets.Get().([]byte)[:n] ++} ++func Put(b []byte) { ++ if cap(b) == 65536 { ++ clear(b[:cap(b)]) ++ packets.Put(b[:cap(b)]) ++ } ++} ++func GetBuffer() *bytes.Buffer { return buffers.Get().(*bytes.Buffer) } ++func PutBuffer(b *bytes.Buffer) { ++ b.Reset() ++ if b.Cap() <= 65536 { ++ buffers.Put(b) ++ } ++} +--- a/proxy/shadowsocksr/protocol/auth_aes128_md5.go ++++ b/proxy/shadowsocksr/protocol/auth_aes128_md5.go +@@ -0,0 +1,20 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package protocol ++ ++import "github.com/xtls/xray-core/proxy/shadowsocksr/tools" ++ ++func init() { ++ register("auth_aes128_md5", newAuthAES128MD5, 9) ++} ++ ++func newAuthAES128MD5(b *Base) Protocol { ++ a := &authAES128{ ++ Base: b, ++ authData: &authData{}, ++ authAES128Function: &authAES128Function{salt: "auth_aes128_md5", hmac: tools.HmacMD5, hashDigest: tools.MD5Sum}, ++ userData: &userData{}, ++ } ++ a.initUserData() ++ return a ++} +--- a/proxy/shadowsocksr/protocol/auth_aes128_sha1.go ++++ b/proxy/shadowsocksr/protocol/auth_aes128_sha1.go +@@ -0,0 +1,286 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package protocol ++ ++import ( ++ "bytes" ++ "crypto/rand" ++ "encoding/binary" ++ "log" ++ "math" ++ randv2 "math/rand/v2" ++ "net" ++ "strconv" ++ "strings" ++ ++ "github.com/xtls/xray-core/proxy/shadowsocksr/pool" ++ "github.com/xtls/xray-core/proxy/shadowsocksr/tools" ++) ++ ++type ( ++ hmacMethod func(key, data []byte) []byte ++ hashDigestMethod func([]byte) []byte ++) ++ ++func init() { ++ register("auth_aes128_sha1", newAuthAES128SHA1, 9) ++} ++ ++type authAES128Function struct { ++ salt string ++ hmac hmacMethod ++ hashDigest hashDigestMethod ++} ++ ++type authAES128 struct { ++ *Base ++ *authData ++ *authAES128Function ++ *userData ++ iv []byte ++ hasSentHeader bool ++ rawTrans bool ++ packID uint32 ++ recvID uint32 ++} ++ ++func newAuthAES128SHA1(b *Base) Protocol { ++ a := &authAES128{ ++ Base: b, ++ authData: &authData{}, ++ authAES128Function: &authAES128Function{salt: "auth_aes128_sha1", hmac: tools.HmacSHA1, hashDigest: tools.SHA1Sum}, ++ userData: &userData{}, ++ } ++ a.initUserData() ++ return a ++} ++ ++func (a *authAES128) initUserData() { ++ params := strings.Split(a.Param, ":") ++ if len(params) > 1 { ++ if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { ++ binary.LittleEndian.PutUint32(a.userID[:], uint32(userID)) ++ a.userKey = a.hashDigest([]byte(params[1])) ++ } else { ++ log.Printf("Wrong protocol-param for %s, only digits are expected before ':'", a.salt) ++ } ++ } ++ if len(a.userKey) == 0 { ++ a.userKey = a.Key ++ rand.Read(a.userID[:]) ++ } ++} ++ ++func (a *authAES128) StreamConn(c net.Conn, iv []byte) net.Conn { ++ p := &authAES128{ ++ Base: a.Base, ++ authData: a.next(), ++ authAES128Function: a.authAES128Function, ++ userData: a.userData, ++ packID: 1, ++ recvID: 1, ++ } ++ p.iv = iv ++ return &Conn{Conn: c, Protocol: p} ++} ++ ++func (a *authAES128) PacketConn(c net.PacketConn) net.PacketConn { ++ p := &authAES128{ ++ Base: a.Base, ++ authAES128Function: a.authAES128Function, ++ userData: a.userData, ++ } ++ return &PacketConn{PacketConn: c, Protocol: p} ++} ++ ++func (a *authAES128) Decode(dst, src *bytes.Buffer) error { ++ if a.rawTrans { ++ dst.ReadFrom(src) ++ return nil ++ } ++ for src.Len() > 4 { ++ macKey := pool.Get(len(a.userKey) + 4) ++ defer pool.Put(macKey) ++ copy(macKey, a.userKey) ++ binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.recvID) ++ if !bytes.Equal(a.hmac(macKey, src.Bytes()[:2])[:2], src.Bytes()[2:4]) { ++ src.Reset() ++ return errAuthAES128MACError ++ } ++ ++ length := int(binary.LittleEndian.Uint16(src.Bytes()[:2])) ++ if length >= 8192 || length < 7 { ++ a.rawTrans = true ++ src.Reset() ++ return errAuthAES128LengthError ++ } ++ if length > src.Len() { ++ break ++ } ++ ++ if !bytes.Equal(a.hmac(macKey, src.Bytes()[:length-4])[:4], src.Bytes()[length-4:length]) { ++ a.rawTrans = true ++ src.Reset() ++ return errAuthAES128ChksumError ++ } ++ ++ a.recvID++ ++ ++ pos := int(src.Bytes()[4]) ++ if pos < 255 { ++ pos += 4 ++ } else { ++ pos = int(binary.LittleEndian.Uint16(src.Bytes()[5:7])) + 4 ++ } ++ if pos < 5 || pos > length-4 { ++ return errAuthAES128LengthError ++ } ++ dst.Write(src.Bytes()[pos : length-4]) ++ src.Next(length) ++ } ++ return nil ++} ++ ++func (a *authAES128) Encode(buf *bytes.Buffer, b []byte) error { ++ fullDataLength := len(b) ++ if !a.hasSentHeader { ++ dataLength := getDataLength(b) ++ a.packAuthData(buf, b[:dataLength]) ++ b = b[dataLength:] ++ a.hasSentHeader = true ++ } ++ for len(b) > 8100 { ++ a.packData(buf, b[:8100], fullDataLength) ++ b = b[8100:] ++ } ++ if len(b) > 0 { ++ a.packData(buf, b, fullDataLength) ++ } ++ return nil ++} ++ ++func (a *authAES128) DecodePacket(b []byte) ([]byte, error) { ++ if len(b) < 4 { ++ return nil, errAuthAES128LengthError ++ } ++ if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) { ++ return nil, errAuthAES128ChksumError ++ } ++ return b[:len(b)-4], nil ++} ++ ++func (a *authAES128) EncodePacket(buf *bytes.Buffer, b []byte) error { ++ buf.Write(b) ++ buf.Write(a.userID[:]) ++ buf.Write(a.hmac(a.userKey, buf.Bytes())[:4]) ++ return nil ++} ++ ++func (a *authAES128) packData(poolBuf *bytes.Buffer, data []byte, fullDataLength int) { ++ dataLength := len(data) ++ randDataLength := a.getRandDataLengthForPackData(dataLength, fullDataLength) ++ /* ++ 2: uint16 LittleEndian packedDataLength ++ 2: hmac of packedDataLength ++ 3: maxRandDataLengthPrefix (min:1) ++ 4: hmac of packedData except the last 4 bytes ++ */ ++ packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4 ++ if randDataLength < 128 { ++ packedDataLength -= 2 ++ } ++ ++ macKey := pool.Get(len(a.userKey) + 4) ++ defer pool.Put(macKey) ++ copy(macKey, a.userKey) ++ binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID) ++ a.packID++ ++ ++ binary.Write(poolBuf, binary.LittleEndian, uint16(packedDataLength)) ++ poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-2:])[:2]) ++ a.packRandData(poolBuf, randDataLength) ++ poolBuf.Write(data) ++ poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:])[:4]) ++} ++ ++func trapezoidRandom(max int, d float64) int { ++ base := randv2.Float64() ++ if d-0 > 1e-6 { ++ a := 1 - d ++ base = (math.Sqrt(a*a+4*d*base) - a) / (2 * d) ++ } ++ return int(base * float64(max)) ++} ++ ++func (a *authAES128) getRandDataLengthForPackData(dataLength, fullDataLength int) int { ++ if fullDataLength >= 32*1024-a.Overhead { ++ return 0 ++ } ++ // 1460: tcp_mss ++ revLength := 1460 - dataLength - 9 ++ if revLength == 0 { ++ return 0 ++ } ++ if revLength < 0 { ++ if revLength > -1460 { ++ return trapezoidRandom(revLength+1460, -0.3) ++ } ++ return randv2.IntN(32) ++ } ++ if dataLength > 900 { ++ return randv2.IntN(revLength) ++ } ++ return trapezoidRandom(revLength, -0.3) ++} ++ ++func (a *authAES128) packAuthData(poolBuf *bytes.Buffer, data []byte) { ++ if len(data) == 0 { ++ return ++ } ++ dataLength := len(data) ++ randDataLength := a.getRandDataLengthForPackAuthData(dataLength) ++ /* ++ 7: checkHead(1) and hmac of checkHead(6) ++ 4: userID ++ 16: encrypted data of authdata(12), uint16 BigEndian packedDataLength(2) and uint16 BigEndian randDataLength(2) ++ 4: hmac of userID and encrypted data ++ 4: hmac of packedAuthData except the last 4 bytes ++ */ ++ packedAuthDataLength := 7 + 4 + 16 + 4 + randDataLength + dataLength + 4 ++ ++ macKey := pool.Get(len(a.iv) + len(a.Key)) ++ defer pool.Put(macKey) ++ copy(macKey, a.iv) ++ copy(macKey[len(a.iv):], a.Key) ++ ++ poolBuf.WriteByte(byte(randv2.IntN(256))) ++ poolBuf.Write(a.hmac(macKey, poolBuf.Bytes())[:6]) ++ poolBuf.Write(a.userID[:]) ++ err := a.authData.putEncryptedData(poolBuf, a.userKey, [2]int{packedAuthDataLength, randDataLength}, a.salt) ++ if err != nil { ++ poolBuf.Reset() ++ return ++ } ++ poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[7:])[:4]) ++ tools.AppendRandBytes(poolBuf, randDataLength) ++ poolBuf.Write(data) ++ poolBuf.Write(a.hmac(a.userKey, poolBuf.Bytes())[:4]) ++} ++ ++func (a *authAES128) getRandDataLengthForPackAuthData(size int) int { ++ if size > 400 { ++ return randv2.IntN(512) ++ } ++ return randv2.IntN(1024) ++} ++ ++func (a *authAES128) packRandData(poolBuf *bytes.Buffer, size int) { ++ if size < 128 { ++ poolBuf.WriteByte(byte(size + 1)) ++ tools.AppendRandBytes(poolBuf, size) ++ return ++ } ++ poolBuf.WriteByte(255) ++ binary.Write(poolBuf, binary.LittleEndian, uint16(size+3)) ++ tools.AppendRandBytes(poolBuf, size) ++} +--- a/proxy/shadowsocksr/protocol/auth_chain_a.go ++++ b/proxy/shadowsocksr/protocol/auth_chain_a.go +@@ -0,0 +1,328 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package protocol ++ ++import ( ++ "bytes" ++ "crypto/cipher" ++ "crypto/rand" ++ "crypto/rc4" ++ "encoding/base64" ++ "encoding/binary" ++ "errors" ++ "log" ++ "net" ++ "strconv" ++ "strings" ++ ++ core "github.com/xtls/xray-core/proxy/shadowsocksr/cipher" ++ "github.com/xtls/xray-core/proxy/shadowsocksr/pool" ++ "github.com/xtls/xray-core/proxy/shadowsocksr/tools" ++) ++ ++func init() { ++ register("auth_chain_a", newAuthChainA, 4) ++} ++ ++type randDataLengthMethod func(int, []byte, *tools.XorShift128Plus) int ++ ++type authChainA struct { ++ *Base ++ *authData ++ *userData ++ iv []byte ++ salt string ++ hasSentHeader bool ++ serverSide bool ++ rawTrans bool ++ lastClientHash []byte ++ lastServerHash []byte ++ encrypter cipher.Stream ++ decrypter cipher.Stream ++ randomClient tools.XorShift128Plus ++ randomServer tools.XorShift128Plus ++ randDataLength randDataLengthMethod ++ packID uint32 ++ recvID uint32 ++} ++ ++func newAuthChainA(b *Base) Protocol { ++ a := &authChainA{ ++ Base: b, ++ authData: &authData{}, ++ userData: &userData{}, ++ salt: "auth_chain_a", ++ } ++ a.initUserData() ++ return a ++} ++ ++func (a *authChainA) initUserData() { ++ params := strings.Split(a.Param, ":") ++ if len(params) > 1 { ++ if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { ++ binary.LittleEndian.PutUint32(a.userID[:], uint32(userID)) ++ a.userKey = []byte(params[1]) ++ } else { ++ log.Printf("Wrong protocol-param for %s, only digits are expected before ':'", a.salt) ++ } ++ } ++ if len(a.userKey) == 0 { ++ a.userKey = a.Key ++ rand.Read(a.userID[:]) ++ } ++} ++ ++func (a *authChainA) StreamConn(c net.Conn, iv []byte) net.Conn { ++ p := &authChainA{ ++ Base: a.Base, ++ authData: a.next(), ++ userData: a.userData, ++ salt: a.salt, ++ packID: 1, ++ recvID: 1, ++ } ++ p.iv = iv ++ p.randDataLength = p.getRandLength ++ return &Conn{Conn: c, Protocol: p} ++} ++ ++func (a *authChainA) PacketConn(c net.PacketConn) net.PacketConn { ++ p := &authChainA{ ++ Base: a.Base, ++ salt: a.salt, ++ userData: a.userData, ++ } ++ return &PacketConn{PacketConn: c, Protocol: p} ++} ++ ++func (a *authChainA) Decode(dst, src *bytes.Buffer) error { ++ if a.rawTrans { ++ dst.ReadFrom(src) ++ return nil ++ } ++ for src.Len() > 4 { ++ macKey := pool.Get(len(a.userKey) + 4) ++ defer pool.Put(macKey) ++ copy(macKey, a.userKey) ++ binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.recvID) ++ ++ dataLength := int(binary.LittleEndian.Uint16(src.Bytes()[:2]) ^ binary.LittleEndian.Uint16(a.lastServerHash[14:16])) ++ randDataLength := a.randDataLength(dataLength, a.lastServerHash, &a.randomServer) ++ length := dataLength + randDataLength ++ // Temporary workaround for https://github.com/metacubex/mihomo/issues/1352 ++ if dataLength < 0 || randDataLength < 0 || length < 0 { ++ return errors.New("ssr crashing blocked") ++ } ++ ++ if length >= 4096 { ++ a.rawTrans = true ++ src.Reset() ++ return errAuthChainLengthError ++ } ++ ++ if 4+length > src.Len() { ++ break ++ } ++ ++ serverHash := tools.HmacMD5(macKey, src.Bytes()[:length+2]) ++ if !bytes.Equal(serverHash[:2], src.Bytes()[length+2:length+4]) { ++ a.rawTrans = true ++ src.Reset() ++ return errAuthChainChksumError ++ } ++ a.lastServerHash = serverHash ++ ++ pos := 2 ++ if dataLength > 0 && randDataLength > 0 { ++ pos += getRandStartPos(randDataLength, &a.randomServer) ++ } ++ // Temporary workaround for https://github.com/metacubex/mihomo/issues/1352 ++ if pos < 0 || dataLength < 0 || pos+dataLength > length+2 || (!a.serverSide && a.recvID == 1 && dataLength < 2) { ++ return errors.New("ssr crashing blocked") ++ } ++ ++ wantedData := src.Bytes()[pos : pos+dataLength] ++ a.decrypter.XORKeyStream(wantedData, wantedData) ++ if !a.serverSide && a.recvID == 1 { ++ dst.Write(wantedData[2:]) ++ } else { ++ dst.Write(wantedData) ++ } ++ a.recvID++ ++ src.Next(length + 4) ++ } ++ return nil ++} ++ ++func (a *authChainA) Encode(buf *bytes.Buffer, b []byte) error { ++ if a.serverSide && a.packID == 1 { ++ b = append([]byte{0xb4, 0x05}, b...) ++ } ++ if !a.hasSentHeader { ++ dataLength := getDataLength(b) ++ a.packAuthData(buf, b[:dataLength]) ++ b = b[dataLength:] ++ a.hasSentHeader = true ++ } ++ for len(b) > 2800 { ++ a.packData(buf, b[:2800]) ++ b = b[2800:] ++ } ++ if len(b) > 0 { ++ a.packData(buf, b) ++ } ++ return nil ++} ++ ++func (a *authChainA) DecodePacket(b []byte) ([]byte, error) { ++ if len(b) < 9 { ++ return nil, errAuthChainLengthError ++ } ++ if !bytes.Equal(tools.HmacMD5(a.userKey, b[:len(b)-1])[:1], b[len(b)-1:]) { ++ return nil, errAuthChainChksumError ++ } ++ md5Data := tools.HmacMD5(a.Key, b[len(b)-8:len(b)-1]) ++ ++ randDataLength := udpGetRandLength(md5Data, &a.randomServer) ++ ++ key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16) ++ rc4Cipher, err := rc4.NewCipher(key) ++ if err != nil { ++ return nil, err ++ } ++ if randDataLength > len(b)-8 { ++ return nil, errAuthChainLengthError ++ } ++ wantedData := b[:len(b)-8-randDataLength] ++ rc4Cipher.XORKeyStream(wantedData, wantedData) ++ return wantedData, nil ++} ++ ++func (a *authChainA) EncodePacket(buf *bytes.Buffer, b []byte) error { ++ authData := pool.Get(3) ++ defer pool.Put(authData) ++ rand.Read(authData) ++ ++ md5Data := tools.HmacMD5(a.Key, authData) ++ ++ randDataLength := udpGetRandLength(md5Data, &a.randomClient) ++ ++ key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16) ++ rc4Cipher, err := rc4.NewCipher(key) ++ if err != nil { ++ return err ++ } ++ rc4Cipher.XORKeyStream(b, b) ++ ++ buf.Write(b) ++ tools.AppendRandBytes(buf, randDataLength) ++ buf.Write(authData) ++ binary.Write(buf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(md5Data[:4])) ++ buf.Write(tools.HmacMD5(a.userKey, buf.Bytes())[:1]) ++ return nil ++} ++ ++func (a *authChainA) packAuthData(poolBuf *bytes.Buffer, data []byte) { ++ /* ++ dataLength := len(data) ++ 12: checkHead(4) and hmac of checkHead(8) ++ 4: uint32 LittleEndian uid (uid = userID ^ last client hash) ++ 16: encrypted data of authdata(12), uint16 LittleEndian overhead(2) and uint16 LittleEndian number zero(2) ++ 4: last server hash(4) ++ packedAuthDataLength := 12 + 4 + 16 + 4 + dataLength ++ */ ++ ++ macKey := pool.Get(len(a.iv) + len(a.Key)) ++ defer pool.Put(macKey) ++ copy(macKey, a.iv) ++ copy(macKey[len(a.iv):], a.Key) ++ ++ // check head ++ tools.AppendRandBytes(poolBuf, 4) ++ a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes()) ++ a.initRC4Cipher() ++ poolBuf.Write(a.lastClientHash[:8]) ++ // uid ++ binary.Write(poolBuf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(a.lastClientHash[8:12])) ++ // encrypted data ++ err := a.putEncryptedData(poolBuf, a.userKey, [2]int{a.Overhead, 0}, a.salt) ++ if err != nil { ++ poolBuf.Reset() ++ return ++ } ++ // last server hash ++ a.lastServerHash = tools.HmacMD5(a.userKey, poolBuf.Bytes()[12:]) ++ poolBuf.Write(a.lastServerHash[:4]) ++ // packed data ++ a.packData(poolBuf, data) ++} ++ ++func (a *authChainA) packData(poolBuf *bytes.Buffer, data []byte) { ++ a.encrypter.XORKeyStream(data, data) ++ ++ macKey := pool.Get(len(a.userKey) + 4) ++ defer pool.Put(macKey) ++ copy(macKey, a.userKey) ++ binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID) ++ a.packID++ ++ ++ length := uint16(len(data)) ^ binary.LittleEndian.Uint16(a.lastClientHash[14:16]) ++ ++ originalLength := poolBuf.Len() ++ binary.Write(poolBuf, binary.LittleEndian, length) ++ a.putMixedRandDataAndData(poolBuf, data) ++ a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes()[originalLength:]) ++ poolBuf.Write(a.lastClientHash[:2]) ++} ++ ++func (a *authChainA) putMixedRandDataAndData(poolBuf *bytes.Buffer, data []byte) { ++ randDataLength := a.randDataLength(len(data), a.lastClientHash, &a.randomClient) ++ if len(data) == 0 { ++ tools.AppendRandBytes(poolBuf, randDataLength) ++ return ++ } ++ if randDataLength > 0 { ++ startPos := getRandStartPos(randDataLength, &a.randomClient) ++ tools.AppendRandBytes(poolBuf, startPos) ++ poolBuf.Write(data) ++ tools.AppendRandBytes(poolBuf, randDataLength-startPos) ++ return ++ } ++ poolBuf.Write(data) ++} ++ ++func getRandStartPos(length int, random *tools.XorShift128Plus) int { ++ if length == 0 { ++ return 0 ++ } ++ return int(int64(random.Next()%8589934609) % int64(length)) ++} ++ ++func (a *authChainA) getRandLength(length int, lastHash []byte, random *tools.XorShift128Plus) int { ++ if length > 1440 { ++ return 0 ++ } ++ random.InitFromBinAndLength(lastHash, length) ++ if length > 1300 { ++ return int(random.Next() % 31) ++ } ++ if length > 900 { ++ return int(random.Next() % 127) ++ } ++ if length > 400 { ++ return int(random.Next() % 521) ++ } ++ return int(random.Next() % 1021) ++} ++ ++func (a *authChainA) initRC4Cipher() { ++ key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(a.lastClientHash), 16) ++ a.encrypter, _ = rc4.NewCipher(key) ++ a.decrypter, _ = rc4.NewCipher(key) ++} ++ ++func udpGetRandLength(lastHash []byte, random *tools.XorShift128Plus) int { ++ random.InitFromBin(lastHash) ++ return int(random.Next() % 127) ++} +--- a/proxy/shadowsocksr/protocol/auth_chain_b.go ++++ b/proxy/shadowsocksr/protocol/auth_chain_b.go +@@ -0,0 +1,99 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package protocol ++ ++import ( ++ "net" ++ "sort" ++ ++ "github.com/xtls/xray-core/proxy/shadowsocksr/tools" ++) ++ ++func init() { ++ register("auth_chain_b", newAuthChainB, 4) ++} ++ ++type authChainB struct { ++ *authChainA ++ dataSizeList []int ++ dataSizeList2 []int ++} ++ ++func newAuthChainB(b *Base) Protocol { ++ a := &authChainB{ ++ authChainA: &authChainA{ ++ Base: b, ++ authData: &authData{}, ++ userData: &userData{}, ++ salt: "auth_chain_b", ++ }, ++ } ++ a.initUserData() ++ return a ++} ++ ++func (a *authChainB) StreamConn(c net.Conn, iv []byte) net.Conn { ++ p := &authChainB{ ++ authChainA: &authChainA{ ++ Base: a.Base, ++ authData: a.next(), ++ userData: a.userData, ++ salt: a.salt, ++ packID: 1, ++ recvID: 1, ++ }, ++ } ++ p.iv = iv ++ p.randDataLength = p.getRandLength ++ p.initDataSize() ++ return &Conn{Conn: c, Protocol: p} ++} ++ ++func (a *authChainB) initDataSize() { ++ a.dataSizeList = a.dataSizeList[:0] ++ a.dataSizeList2 = a.dataSizeList2[:0] ++ ++ a.randomServer.InitFromBin(a.Key) ++ length := a.randomServer.Next()%8 + 4 ++ for ; length > 0; length-- { ++ a.dataSizeList = append(a.dataSizeList, int(a.randomServer.Next()%2340%2040%1440)) ++ } ++ sort.Ints(a.dataSizeList) ++ ++ length = a.randomServer.Next()%16 + 8 ++ for ; length > 0; length-- { ++ a.dataSizeList2 = append(a.dataSizeList2, int(a.randomServer.Next()%2340%2040%1440)) ++ } ++ sort.Ints(a.dataSizeList2) ++} ++ ++func (a *authChainB) getRandLength(length int, lashHash []byte, random *tools.XorShift128Plus) int { ++ if length >= 1440 { ++ return 0 ++ } ++ random.InitFromBinAndLength(lashHash, length) ++ pos := sort.Search(len(a.dataSizeList), func(i int) bool { return a.dataSizeList[i] >= length+a.Overhead }) ++ finalPos := pos + int(random.Next()%uint64(len(a.dataSizeList))) ++ if finalPos < len(a.dataSizeList) { ++ return a.dataSizeList[finalPos] - length - a.Overhead ++ } ++ ++ pos = sort.Search(len(a.dataSizeList2), func(i int) bool { return a.dataSizeList2[i] >= length+a.Overhead }) ++ finalPos = pos + int(random.Next()%uint64(len(a.dataSizeList2))) ++ if finalPos < len(a.dataSizeList2) { ++ return a.dataSizeList2[finalPos] - length - a.Overhead ++ } ++ if finalPos < pos+len(a.dataSizeList2)-1 { ++ return 0 ++ } ++ if length > 1300 { ++ return int(random.Next() % 31) ++ } ++ if length > 900 { ++ return int(random.Next() % 127) ++ } ++ if length > 400 { ++ return int(random.Next() % 521) ++ } ++ return int(random.Next() % 1021) ++} +--- a/proxy/shadowsocksr/protocol/auth_sha1_v4.go ++++ b/proxy/shadowsocksr/protocol/auth_sha1_v4.go +@@ -0,0 +1,187 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package protocol ++ ++import ( ++ "bytes" ++ "encoding/binary" ++ "hash/adler32" ++ "hash/crc32" ++ randv2 "math/rand/v2" ++ "net" ++ ++ "github.com/xtls/xray-core/proxy/shadowsocksr/pool" ++ "github.com/xtls/xray-core/proxy/shadowsocksr/tools" ++) ++ ++func init() { ++ register("auth_sha1_v4", newAuthSHA1V4, 7) ++} ++ ++type authSHA1V4 struct { ++ *Base ++ *authData ++ iv []byte ++ hasSentHeader bool ++ rawTrans bool ++} ++ ++func newAuthSHA1V4(b *Base) Protocol { ++ return &authSHA1V4{Base: b, authData: &authData{}} ++} ++ ++func (a *authSHA1V4) StreamConn(c net.Conn, iv []byte) net.Conn { ++ p := &authSHA1V4{Base: a.Base, authData: a.next()} ++ p.iv = iv ++ return &Conn{Conn: c, Protocol: p} ++} ++ ++func (a *authSHA1V4) PacketConn(c net.PacketConn) net.PacketConn { ++ return c ++} ++ ++func (a *authSHA1V4) Decode(dst, src *bytes.Buffer) error { ++ if a.rawTrans { ++ dst.ReadFrom(src) ++ return nil ++ } ++ for src.Len() > 4 { ++ if uint16(crc32.ChecksumIEEE(src.Bytes()[:2])&0xffff) != binary.LittleEndian.Uint16(src.Bytes()[2:4]) { ++ src.Reset() ++ return errAuthSHA1V4CRC32Error ++ } ++ ++ length := int(binary.BigEndian.Uint16(src.Bytes()[:2])) ++ if length >= 8192 || length < 7 { ++ a.rawTrans = true ++ src.Reset() ++ return errAuthSHA1V4LengthError ++ } ++ if length > src.Len() { ++ break ++ } ++ ++ if adler32.Checksum(src.Bytes()[:length-4]) != binary.LittleEndian.Uint32(src.Bytes()[length-4:length]) { ++ a.rawTrans = true ++ src.Reset() ++ return errAuthSHA1V4Adler32Error ++ } ++ ++ pos := int(src.Bytes()[4]) ++ if pos < 255 { ++ pos += 4 ++ } else { ++ pos = int(binary.BigEndian.Uint16(src.Bytes()[5:7])) + 4 ++ } ++ if pos < 5 || pos > length-4 { ++ return errAuthSHA1V4LengthError ++ } ++ dst.Write(src.Bytes()[pos : length-4]) ++ src.Next(length) ++ } ++ return nil ++} ++ ++func (a *authSHA1V4) Encode(buf *bytes.Buffer, b []byte) error { ++ if !a.hasSentHeader { ++ dataLength := getDataLength(b) ++ ++ a.packAuthData(buf, b[:dataLength]) ++ b = b[dataLength:] ++ ++ a.hasSentHeader = true ++ } ++ for len(b) > 8100 { ++ a.packData(buf, b[:8100]) ++ b = b[8100:] ++ } ++ if len(b) > 0 { ++ a.packData(buf, b) ++ } ++ ++ return nil ++} ++ ++func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, error) { return b, nil } ++ ++func (a *authSHA1V4) EncodePacket(buf *bytes.Buffer, b []byte) error { ++ buf.Write(b) ++ return nil ++} ++ ++func (a *authSHA1V4) packData(poolBuf *bytes.Buffer, data []byte) { ++ dataLength := len(data) ++ randDataLength := a.getRandDataLength(dataLength) ++ /* ++ 2: uint16 BigEndian packedDataLength ++ 2: uint16 LittleEndian crc32Data & 0xffff ++ 3: maxRandDataLengthPrefix (min:1) ++ 4: adler32Data ++ */ ++ packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4 ++ if randDataLength < 128 { ++ packedDataLength -= 2 ++ } ++ ++ binary.Write(poolBuf, binary.BigEndian, uint16(packedDataLength)) ++ binary.Write(poolBuf, binary.LittleEndian, uint16(crc32.ChecksumIEEE(poolBuf.Bytes()[poolBuf.Len()-2:])&0xffff)) ++ a.packRandData(poolBuf, randDataLength) ++ poolBuf.Write(data) ++ binary.Write(poolBuf, binary.LittleEndian, adler32.Checksum(poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:])) ++} ++ ++func (a *authSHA1V4) packAuthData(poolBuf *bytes.Buffer, data []byte) { ++ dataLength := len(data) ++ randDataLength := a.getRandDataLength(12 + dataLength) ++ /* ++ 2: uint16 BigEndian packedAuthDataLength ++ 4: uint32 LittleEndian crc32Data ++ 3: maxRandDataLengthPrefix (min: 1) ++ 12: authDataLength ++ 10: hmacSHA1DataLength ++ */ ++ packedAuthDataLength := 2 + 4 + 3 + randDataLength + 12 + dataLength + 10 ++ if randDataLength < 128 { ++ packedAuthDataLength -= 2 ++ } ++ ++ salt := []byte("auth_sha1_v4") ++ crcData := pool.Get(len(salt) + len(a.Key) + 2) ++ defer pool.Put(crcData) ++ binary.BigEndian.PutUint16(crcData, uint16(packedAuthDataLength)) ++ copy(crcData[2:], salt) ++ copy(crcData[2+len(salt):], a.Key) ++ ++ key := pool.Get(len(a.iv) + len(a.Key)) ++ defer pool.Put(key) ++ copy(key, a.iv) ++ copy(key[len(a.iv):], a.Key) ++ ++ poolBuf.Write(crcData[:2]) ++ binary.Write(poolBuf, binary.LittleEndian, crc32.ChecksumIEEE(crcData)) ++ a.packRandData(poolBuf, randDataLength) ++ a.putAuthData(poolBuf) ++ poolBuf.Write(data) ++ poolBuf.Write(tools.HmacSHA1(key, poolBuf.Bytes()[poolBuf.Len()-packedAuthDataLength+10:])[:10]) ++} ++ ++func (a *authSHA1V4) packRandData(poolBuf *bytes.Buffer, size int) { ++ if size < 128 { ++ poolBuf.WriteByte(byte(size + 1)) ++ tools.AppendRandBytes(poolBuf, size) ++ return ++ } ++ poolBuf.WriteByte(255) ++ binary.Write(poolBuf, binary.BigEndian, uint16(size+3)) ++ tools.AppendRandBytes(poolBuf, size) ++} ++ ++func (a *authSHA1V4) getRandDataLength(size int) int { ++ if size > 1200 { ++ return 0 ++ } ++ if size > 400 { ++ return randv2.IntN(256) ++ } ++ return randv2.IntN(512) ++} +--- a/proxy/shadowsocksr/protocol/base.go ++++ b/proxy/shadowsocksr/protocol/base.go +@@ -0,0 +1,80 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package protocol ++ ++import ( ++ "bytes" ++ "crypto/aes" ++ "crypto/cipher" ++ "crypto/rand" ++ "encoding/base64" ++ "encoding/binary" ++ "log" ++ randv2 "math/rand/v2" ++ "sync" ++ "time" ++ ++ core "github.com/xtls/xray-core/proxy/shadowsocksr/cipher" ++ "github.com/xtls/xray-core/proxy/shadowsocksr/pool" ++) ++ ++type Base struct { ++ Key []byte ++ Overhead int ++ Param string ++} ++ ++type userData struct { ++ userKey []byte ++ userID [4]byte ++} ++ ++type authData struct { ++ clientID [4]byte ++ connectionID uint32 ++ mutex sync.Mutex ++} ++ ++func (a *authData) next() *authData { ++ r := &authData{} ++ a.mutex.Lock() ++ defer a.mutex.Unlock() ++ if a.connectionID > 0xff000000 || a.connectionID == 0 { ++ rand.Read(a.clientID[:]) ++ a.connectionID = randv2.Uint32() & 0xffffff ++ } ++ a.connectionID++ ++ copy(r.clientID[:], a.clientID[:]) ++ r.connectionID = a.connectionID ++ return r ++} ++ ++func (a *authData) putAuthData(buf *bytes.Buffer) { ++ binary.Write(buf, binary.LittleEndian, uint32(time.Now().Unix())) ++ buf.Write(a.clientID[:]) ++ binary.Write(buf, binary.LittleEndian, a.connectionID) ++} ++ ++func (a *authData) putEncryptedData(b *bytes.Buffer, userKey []byte, paddings [2]int, salt string) error { ++ encrypt := pool.Get(16) ++ defer pool.Put(encrypt) ++ binary.LittleEndian.PutUint32(encrypt, uint32(time.Now().Unix())) ++ copy(encrypt[4:], a.clientID[:]) ++ binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) ++ binary.LittleEndian.PutUint16(encrypt[12:], uint16(paddings[0])) ++ binary.LittleEndian.PutUint16(encrypt[14:], uint16(paddings[1])) ++ ++ cipherKey := core.Kdf(base64.StdEncoding.EncodeToString(userKey)+salt, 16) ++ block, err := aes.NewCipher(cipherKey) ++ if err != nil { ++ log.Printf("New cipher error: %s", err.Error()) ++ return err ++ } ++ iv := bytes.Repeat([]byte{0}, 16) ++ cbcCipher := cipher.NewCBCEncrypter(block, iv) ++ ++ cbcCipher.CryptBlocks(encrypt, encrypt) ++ ++ b.Write(encrypt) ++ return nil ++} +--- a/proxy/shadowsocksr/protocol/origin.go ++++ b/proxy/shadowsocksr/protocol/origin.go +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package protocol ++ ++import ( ++ "bytes" ++ "net" ++) ++ ++type origin struct{} ++ ++func init() { register("origin", newOrigin, 0) } ++ ++func newOrigin(b *Base) Protocol { return &origin{} } ++ ++func (o *origin) StreamConn(c net.Conn, iv []byte) net.Conn { return c } ++ ++func (o *origin) PacketConn(c net.PacketConn) net.PacketConn { return c } ++ ++func (o *origin) Decode(dst, src *bytes.Buffer) error { ++ dst.ReadFrom(src) ++ return nil ++} ++ ++func (o *origin) Encode(buf *bytes.Buffer, b []byte) error { ++ buf.Write(b) ++ return nil ++} ++ ++func (o *origin) DecodePacket(b []byte) ([]byte, error) { return b, nil } ++ ++func (o *origin) EncodePacket(buf *bytes.Buffer, b []byte) error { ++ buf.Write(b) ++ return nil ++} +--- a/proxy/shadowsocksr/protocol/packet.go ++++ b/proxy/shadowsocksr/protocol/packet.go +@@ -0,0 +1,38 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package protocol ++ ++import ( ++ "net" ++ ++ "github.com/xtls/xray-core/proxy/shadowsocksr/pool" ++) ++ ++type PacketConn struct { ++ net.PacketConn ++ Protocol ++} ++ ++func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { ++ buf := pool.GetBuffer() ++ defer pool.PutBuffer(buf) ++ err := c.EncodePacket(buf, b) ++ if err != nil { ++ return 0, err ++ } ++ _, err = c.PacketConn.WriteTo(buf.Bytes(), addr) ++ return len(b), err ++} ++ ++func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { ++ n, addr, err := c.PacketConn.ReadFrom(b) ++ if err != nil { ++ return n, addr, err ++ } ++ decoded, err := c.DecodePacket(b[:n]) ++ if err != nil { ++ return n, addr, err ++ } ++ copy(b, decoded) ++ return len(decoded), addr, nil ++} +--- a/proxy/shadowsocksr/protocol/protocol.go ++++ b/proxy/shadowsocksr/protocol/protocol.go +@@ -0,0 +1,78 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package protocol ++ ++import ( ++ "bytes" ++ "errors" ++ "fmt" ++ randv2 "math/rand/v2" ++ "net" ++) ++ ++var ( ++ errAuthSHA1V4CRC32Error = errors.New("auth_sha1_v4 decode data wrong crc32") ++ errAuthSHA1V4LengthError = errors.New("auth_sha1_v4 decode data wrong length") ++ errAuthSHA1V4Adler32Error = errors.New("auth_sha1_v4 decode data wrong adler32") ++ errAuthAES128MACError = errors.New("auth_aes128 decode data wrong mac") ++ errAuthAES128LengthError = errors.New("auth_aes128 decode data wrong length") ++ errAuthAES128ChksumError = errors.New("auth_aes128 decode data wrong checksum") ++ errAuthChainLengthError = errors.New("auth_chain decode data wrong length") ++ errAuthChainChksumError = errors.New("auth_chain decode data wrong checksum") ++) ++ ++type Protocol interface { ++ StreamConn(net.Conn, []byte) net.Conn ++ PacketConn(net.PacketConn) net.PacketConn ++ Decode(dst, src *bytes.Buffer) error ++ Encode(buf *bytes.Buffer, b []byte) error ++ DecodePacket([]byte) ([]byte, error) ++ EncodePacket(buf *bytes.Buffer, b []byte) error ++} ++ ++type protocolCreator func(b *Base) Protocol ++ ++var protocolList = make(map[string]struct { ++ overhead int ++ new protocolCreator ++}) ++ ++func register(name string, c protocolCreator, o int) { ++ protocolList[name] = struct { ++ overhead int ++ new protocolCreator ++ }{overhead: o, new: c} ++} ++ ++func PickProtocol(name string, b *Base) (Protocol, error) { ++ if choice, ok := protocolList[name]; ok { ++ b.Overhead += choice.overhead ++ return choice.new(b), nil ++ } ++ return nil, fmt.Errorf("protocol %s not supported", name) ++} ++ ++func getHeadSize(b []byte, defaultValue int) int { ++ if len(b) < 2 { ++ return defaultValue ++ } ++ headType := b[0] & 7 ++ switch headType { ++ case 1: ++ return 7 ++ case 4: ++ return 19 ++ case 3: ++ return 4 + int(b[1]) ++ } ++ return defaultValue ++} ++ ++func getDataLength(b []byte) int { ++ bLength := len(b) ++ dataLength := getHeadSize(b, 30) + randv2.IntN(32) ++ if bLength < dataLength { ++ return bLength ++ } ++ return dataLength ++} +--- a/proxy/shadowsocksr/protocol/server.go ++++ b/proxy/shadowsocksr/protocol/server.go +@@ -0,0 +1,338 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Server framing complements the Mihomo client implementation. Wire format ++// checked against shadowsocksrr/shadowsocksr obfsplugin auth and auth_chain. ++package protocol ++ ++import ( ++ "crypto/aes" ++ "crypto/cipher" ++ "crypto/hmac" ++ "crypto/rand" ++ "crypto/rc4" ++ "encoding/base64" ++ "encoding/binary" ++ "errors" ++ "fmt" ++ core "github.com/xtls/xray-core/proxy/shadowsocksr/cipher" ++ "github.com/xtls/xray-core/proxy/shadowsocksr/tools" ++ "hash/crc32" ++ "io" ++ "net" ++ "strconv" ++ "strings" ++ "sync" ++ "time" ++) ++ ++var errServerAuth = errors.New("shadowsocksr: invalid authentication or framing") ++ ++// ReplayCache is per listener and bounded. Fail closed at capacity; entries ++// remain until their timestamp can no longer pass the authentication window. ++type ReplayCache struct { ++ mu sync.Mutex ++ entries map[string]int64 ++ sweep int64 ++} ++ ++func (r *ReplayCache) Accept(token []byte, timestamp int64) bool { ++ now := time.Now().Unix() ++ if timestamp < now-300 || timestamp > now+300 { ++ return false ++ } ++ r.mu.Lock() ++ defer r.mu.Unlock() ++ if r.entries == nil { ++ r.entries = make(map[string]int64) ++ } ++ if now > r.sweep { ++ for k, t := range r.entries { ++ if t < now { ++ delete(r.entries, k) ++ } ++ } ++ r.sweep = now + 30 ++ } ++ k := string(token) ++ if _, ok := r.entries[k]; ok { ++ return false ++ } ++ if len(r.entries) >= 65536 { ++ return false ++ } ++ r.entries[k] = timestamp + 301 ++ return true ++} ++ ++// ServerUsers accepts the traditional "max_clients#uid:password,..." form. ++// The max_clients prefix is checked but the listener uses a bounded replay cache. ++func ServerUsers(param string) (map[uint32][]byte, error) { ++ users := map[uint32][]byte{} ++ if param == "" { ++ return users, nil ++ } ++ if a, b, ok := strings.Cut(param, "#"); ok { ++ n, e := strconv.ParseUint(a, 10, 32) ++ if e != nil || n == 0 { ++ return nil, errServerAuth ++ } ++ param = b ++ } else if !strings.Contains(param, ":") { ++ n, e := strconv.ParseUint(param, 10, 32) ++ if e != nil || n == 0 { ++ return nil, errServerAuth ++ } ++ return users, nil ++ } ++ for _, v := range strings.Split(param, ",") { ++ id, p, ok := strings.Cut(v, ":") ++ n, e := strconv.ParseUint(id, 10, 32) ++ if !ok || e != nil || p == "" || users[uint32(n)] != nil { ++ return nil, errServerAuth ++ } ++ users[uint32(n)] = []byte(p) ++ } ++ return users, nil ++} ++func serverUser(key []byte, users map[uint32][]byte, id uint32, hash hashDigestMethod) ([]byte, error) { ++ if len(users) == 0 { ++ return key, nil ++ } ++ p, ok := users[id] ++ if !ok { ++ return nil, errServerAuth ++ } ++ if hash != nil { ++ return hash(p), nil ++ } ++ return p, nil ++} ++func authBlock(b, key []byte, salt string) []byte { ++ block, _ := aes.NewCipher(core.Kdf(base64.StdEncoding.EncodeToString(key)+salt, 16)) ++ out := make([]byte, 16) ++ cipher.NewCBCDecrypter(block, make([]byte, 16)).CryptBlocks(out, b) ++ return out ++} ++func acceptAuth(cache *ReplayCache, head, user []byte) bool { ++ token := append(append([]byte{}, user...), head[4:12]...) ++ return cache.Accept(token, int64(binary.LittleEndian.Uint32(head))) ++} ++func readN(c io.Reader, n int) ([]byte, error) { ++ b := make([]byte, n) ++ _, e := io.ReadFull(c, b) ++ return b, e ++} ++ ++// AcceptServer completes authentication before exposing a stream to routing. ++func AcceptServer(c net.Conn, name string, b *Base, iv []byte, users map[uint32][]byte, cache *ReplayCache) (net.Conn, error) { ++ if name == "origin" { ++ return c, nil ++ } ++ p, e := PickProtocol(name, b) ++ if e != nil { ++ return nil, e ++ } ++ var data []byte ++ switch a := p.(type) { ++ case *authSHA1V4: ++ head, e := readN(c, 7) ++ if e != nil { ++ return nil, e ++ } ++ n := int(binary.BigEndian.Uint16(head)) ++ if n < 29 || n >= 8192 { ++ return nil, errServerAuth ++ } ++ check := append(append(append([]byte{}, head[:2]...), []byte(name)...), b.Key...) ++ if binary.LittleEndian.Uint32(head[2:6]) != crc32.ChecksumIEEE(check) { ++ return nil, errServerAuth ++ } ++ rest, e := readN(c, n-7) ++ if e != nil { ++ return nil, e ++ } ++ head = append(head, rest...) ++ if !hmac.Equal(tools.HmacSHA1(append(append([]byte{}, iv...), b.Key...), head[:n-10])[:10], head[n-10:]) { ++ return nil, errServerAuth ++ } ++ pos := 6 + int(head[6]) ++ if head[6] == 255 { ++ pos = 6 + int(binary.BigEndian.Uint16(head[7:9])) ++ } ++ if pos < 7 || pos+12 > n-10 || !acceptAuth(cache, head[pos:pos+12], nil) { ++ return nil, errServerAuth ++ } ++ data = append([]byte{}, head[pos+12:n-10]...) ++ a.hasSentHeader = true ++ case *authAES128: ++ head, e := readN(c, 31) ++ if e != nil { ++ return nil, e ++ } ++ macKey := append(append([]byte{}, iv...), b.Key...) ++ if !hmac.Equal(a.hmac(macKey, head[:1])[:6], head[1:7]) || !hmac.Equal(a.hmac(macKey, head[7:27])[:4], head[27:31]) { ++ return nil, fmt.Errorf("%w: AES header MAC (prefix=%t body=%t)", errServerAuth, hmac.Equal(a.hmac(macKey, head[:1])[:6], head[1:7]), hmac.Equal(a.hmac(macKey, head[7:27])[:4], head[27:31])) ++ } ++ a.userKey, e = serverUser(b.Key, users, binary.LittleEndian.Uint32(head[7:11]), a.hashDigest) ++ if e != nil { ++ return nil, e ++ } ++ plain := authBlock(head[11:27], a.userKey, a.salt) ++ n := int(binary.LittleEndian.Uint16(plain[12:])) ++ padding := int(binary.LittleEndian.Uint16(plain[14:])) ++ if n < 35 || n >= 8192 || padding > n-35 { ++ return nil, fmt.Errorf("%w: AES header lengths", errServerAuth) ++ } ++ rest, e := readN(c, n-31) ++ if e != nil { ++ return nil, e ++ } ++ head = append(head, rest...) ++ if !hmac.Equal(a.hmac(a.userKey, head[:n-4])[:4], head[n-4:]) { ++ return nil, fmt.Errorf("%w: AES packet checksum", errServerAuth) ++ } ++ if !acceptAuth(cache, plain, head[7:11]) { ++ return nil, fmt.Errorf("%w: replay or timestamp", errServerAuth) ++ } ++ data = append([]byte{}, head[31+padding:n-4]...) ++ a.hasSentHeader = true ++ a.packID = 1 ++ a.recvID = 1 ++ case *authChainA: ++ if e = acceptChain(c, a, nil, iv, users, cache); e != nil { ++ return nil, e ++ } ++ case *authChainB: ++ if e = acceptChain(c, a.authChainA, a, iv, users, cache); e != nil { ++ return nil, e ++ } ++ default: ++ return nil, errServerAuth ++ } ++ conn := &Conn{Conn: c, Protocol: p} ++ conn.decoded.Write(data) ++ return conn, nil ++} ++func acceptChain(c net.Conn, a *authChainA, b *authChainB, iv []byte, users map[uint32][]byte, cache *ReplayCache) error { ++ head, e := readN(c, 36) ++ if e != nil { ++ return e ++ } ++ ch := tools.HmacMD5(append(append([]byte{}, iv...), a.Key...), head[:4]) ++ if !hmac.Equal(ch[:8], head[4:12]) { ++ return errServerAuth ++ } ++ id := binary.LittleEndian.Uint32(head[12:16]) ^ binary.LittleEndian.Uint32(ch[8:12]) ++ a.userKey, e = serverUser(a.Key, users, id, nil) ++ if e != nil { ++ return e ++ } ++ sh := tools.HmacMD5(a.userKey, head[12:32]) ++ if !hmac.Equal(sh[:4], head[32:36]) { ++ return errServerAuth ++ } ++ plain := authBlock(head[16:32], a.userKey, a.salt) ++ var uid [4]byte ++ binary.LittleEndian.PutUint32(uid[:], id) ++ overhead := int(binary.LittleEndian.Uint16(plain[12:])) ++ if overhead < 4 || overhead > 1024 || binary.LittleEndian.Uint16(plain[14:]) != 0 || !acceptAuth(cache, plain, uid[:]) { ++ return errServerAuth ++ } ++ a.Overhead = overhead ++ a.lastClientHash = ch ++ a.initRC4Cipher() ++ // Client-oriented codec fields become transmit/receive hashes on the server. ++ a.lastClientHash = sh ++ a.lastServerHash = ch ++ a.packID = 1 ++ a.recvID = 1 ++ a.hasSentHeader = true ++ a.serverSide = true ++ a.randDataLength = a.getRandLength ++ if b != nil { ++ b.initDataSize() ++ a.randDataLength = b.getRandLength ++ } ++ return nil ++} ++ ++// Packet decoding is stateless per datagram; return a response encoder bound to ++// the authenticated user so concurrent users cannot share mutable cipher state. ++func DecodeServerPacket(name string, key []byte, users map[uint32][]byte, b []byte) ([]byte, func([]byte) ([]byte, error), error) { ++ identity := func(p []byte) ([]byte, error) { return p, nil } ++ switch name { ++ case "origin", "auth_sha1_v4": ++ return b, identity, nil ++ case "auth_aes128_md5", "auth_aes128_sha1": ++ if len(b) < 8 { ++ return nil, nil, errServerAuth ++ } ++ hm := tools.HmacSHA1 ++ hash := tools.SHA1Sum ++ if name == "auth_aes128_md5" { ++ hm = tools.HmacMD5 ++ hash = tools.MD5Sum ++ } ++ uk, e := serverUser(key, users, binary.LittleEndian.Uint32(b[len(b)-8:]), hash) ++ if e != nil { ++ return nil, nil, e ++ } ++ if !hmac.Equal(hm(uk, b[:len(b)-4])[:4], b[len(b)-4:]) { ++ return nil, nil, errServerAuth ++ } ++ return b[:len(b)-8], func(p []byte) ([]byte, error) { return append(p, hm(key, p)[:4]...), nil }, nil ++ case "auth_chain_a", "auth_chain_b": ++ if len(b) < 8 { ++ return nil, nil, errServerAuth ++ } ++ digest := tools.HmacMD5(key, b[len(b)-8:len(b)-5]) ++ id := binary.LittleEndian.Uint32(b[len(b)-5:]) ^ binary.LittleEndian.Uint32(digest) ++ uk, e := serverUser(key, users, id, nil) ++ if e != nil { ++ return nil, nil, e ++ } ++ if !hmac.Equal(tools.HmacMD5(uk, b[:len(b)-1])[:1], b[len(b)-1:]) { ++ return nil, nil, errServerAuth ++ } ++ var random tools.XorShift128Plus ++ padding := udpGetRandLength(digest, &random) ++ if padding > len(b)-8 { ++ return nil, nil, errServerAuth ++ } ++ p := b[:len(b)-8-padding] ++ crypt, _ := rc4.NewCipher(core.Kdf(base64.StdEncoding.EncodeToString(uk)+base64.StdEncoding.EncodeToString(digest), 16)) ++ crypt.XORKeyStream(p, p) ++ return p, func(p []byte) ([]byte, error) { ++ auth := make([]byte, 7) ++ if _, e := rand.Read(auth); e != nil { ++ return nil, e ++ } ++ d := tools.HmacMD5(key, auth) ++ var r tools.XorShift128Plus ++ n := udpGetRandLength(d, &r) ++ out := make([]byte, len(p)+n) ++ crypt, _ := rc4.NewCipher(core.Kdf(base64.StdEncoding.EncodeToString(uk)+base64.StdEncoding.EncodeToString(d), 16)) ++ crypt.XORKeyStream(out, p) ++ rand.Read(out[len(p):]) ++ out = append(out, auth...) ++ return append(out, tools.HmacMD5(uk, out)[:1]...), nil ++ }, nil ++ } ++ return nil, nil, errServerAuth ++} ++ ++func ServerPacketUserID(name string, key, b []byte) (uint32, error) { ++ switch name { ++ case "auth_aes128_md5", "auth_aes128_sha1": ++ if len(b) < 8 { ++ return 0, errServerAuth ++ } ++ return binary.LittleEndian.Uint32(b[len(b)-8:]), nil ++ case "auth_chain_a", "auth_chain_b": ++ if len(b) < 8 { ++ return 0, errServerAuth ++ } ++ h := tools.HmacMD5(key, b[len(b)-8:len(b)-5]) ++ return binary.LittleEndian.Uint32(b[len(b)-5:]) ^ binary.LittleEndian.Uint32(h), nil ++ } ++ return 0, nil ++} +--- a/proxy/shadowsocksr/protocol/stream.go ++++ b/proxy/shadowsocksr/protocol/stream.go +@@ -0,0 +1,60 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package protocol ++ ++import ( ++ "bytes" ++ "io" ++ "net" ++ ++ "github.com/xtls/xray-core/proxy/shadowsocksr/pool" ++) ++ ++type Conn struct { ++ net.Conn ++ Protocol ++ decoded bytes.Buffer ++ underDecoded bytes.Buffer ++ readErr error ++} ++ ++func (c *Conn) Read(b []byte) (int, error) { ++ if len(b) == 0 { ++ return 0, nil ++ } ++ for c.decoded.Len() == 0 { ++ if c.readErr != nil { ++ if c.underDecoded.Len() > 0 && c.readErr == io.EOF { ++ return 0, io.ErrUnexpectedEOF ++ } ++ return 0, c.readErr ++ } ++ data := pool.Get(pool.RelayBufferSize) ++ n, err := c.Conn.Read(data) ++ c.readErr = err ++ c.underDecoded.Write(data[:n]) ++ pool.Put(data) ++ if err := c.Decode(&c.decoded, &c.underDecoded); err != nil { ++ return 0, err ++ } ++ } ++ return c.decoded.Read(b) ++} ++ ++func (c *Conn) Write(b []byte) (int, error) { ++ bLength := len(b) ++ buf := pool.GetBuffer() ++ defer pool.PutBuffer(buf) ++ err := c.Encode(buf, b) ++ if err != nil { ++ return 0, err ++ } ++ n, err := c.Conn.Write(buf.Bytes()) ++ if err == nil && n != buf.Len() { ++ err = io.ErrShortWrite ++ } ++ if err != nil { ++ return 0, err ++ } ++ return bLength, nil ++} +--- a/proxy/shadowsocksr/relay.go ++++ b/proxy/shadowsocksr/relay.go +@@ -0,0 +1,60 @@ ++package shadowsocksr ++ ++import ( ++ "context" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/signal" ++ "github.com/xtls/xray-core/features/policy" ++) ++ ++// Stop blocked reads and join both copy loops before releasing stream state. ++func relay(ctx context.Context, upR buf.Reader, upW buf.Writer, downR buf.Reader, downW buf.Writer, stopIO func(), p policy.Session) error { ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) ++ defer timer.SetTimeout(0) ++ results := make(chan error, 2) ++ go func() { ++ e := buf.Copy(upR, upW, buf.UpdateActivity(timer)) ++ if e == nil { ++ common.Close(upW) ++ } ++ timer.SetTimeout(p.Timeouts.DownlinkOnly) ++ results <- e ++ }() ++ go func() { ++ e := buf.Copy(downR, downW, buf.UpdateActivity(timer)) ++ if e == nil { ++ common.Close(downW) ++ } ++ timer.SetTimeout(p.Timeouts.UplinkOnly) ++ results <- e ++ }() ++ var first error ++ count := 0 ++ for count < 2 { ++ select { ++ case e := <-results: ++ count++ ++ if e != nil { ++ first = e ++ } ++ case <-ctx.Done(): ++ first = ctx.Err() ++ } ++ if first != nil || count == 2 { ++ break ++ } ++ } ++ stopIO() ++ common.Interrupt(upR) ++ common.Interrupt(upW) ++ common.Interrupt(downR) ++ common.Interrupt(downW) ++ for count < 2 { ++ <-results ++ count++ ++ } ++ return first ++} +--- a/proxy/shadowsocksr/server.go ++++ b/proxy/shadowsocksr/server.go +@@ -0,0 +1,240 @@ ++package shadowsocksr ++ ++import ( ++ "bytes" ++ "context" ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/errors" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/protocol" ++ udp_proto "github.com/xtls/xray-core/common/protocol/udp" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/core" ++ "github.com/xtls/xray-core/features/policy" ++ "github.com/xtls/xray-core/features/routing" ++ C "github.com/xtls/xray-core/proxy/shadowsocksr/cipher" ++ O "github.com/xtls/xray-core/proxy/shadowsocksr/obfs" ++ P "github.com/xtls/xray-core/proxy/shadowsocksr/protocol" ++ "github.com/xtls/xray-core/proxy/shadowsocksr/shadowstream" ++ "github.com/xtls/xray-core/transport/internet/stat" ++ "github.com/xtls/xray-core/transport/internet/udp" ++ "io" ++ "strings" ++ "time" ++) ++ ++type Server struct { ++ config *ServerConfig ++ cipher C.Cipher ++ key []byte ++ users map[uint32][]byte ++ overhead int ++ policy policy.Session ++ replay, obfsReplay P.ReplayCache ++} ++ ++func ValidateServer(c *ServerConfig) error { _, e := newServer(c); return e } ++func newServer(c *ServerConfig) (*Server, error) { ++ if c.Password == "" { ++ return nil, errors.New("shadowsocksr: password required") ++ } ++ for _, n := range c.Network { ++ if n != net.Network_TCP && n != net.Network_UDP { ++ return nil, errors.New("shadowsocksr: invalid network") ++ } ++ } ++ cipher, e := C.PickCipher(c.Method, nil, c.Password) ++ if e != nil { ++ return nil, e ++ } ++ key := C.Kdf(c.Password, 16) ++ ivSize := 0 ++ if sc, ok := cipher.(*C.StreamCipher); ok { ++ key = sc.Key ++ ivSize = sc.IVSize() ++ } ++ _, overhead, e := O.PickObfs(c.Obfs, &O.Base{Key: key, IVSize: ivSize}) ++ if e != nil { ++ return nil, e ++ } ++ if _, e = P.PickProtocol(c.Protocol, &P.Base{Key: key, Overhead: overhead}); e != nil { ++ return nil, e ++ } ++ users, e := P.ServerUsers(c.ProtocolParam) ++ if e != nil { ++ return nil, e ++ } ++ if len(users) > 0 && !strings.HasPrefix(c.Protocol, "auth_aes128_") && !strings.HasPrefix(c.Protocol, "auth_chain_") { ++ return nil, errors.New("shadowsocksr: protocol does not support users") ++ } ++ return &Server{config: c, cipher: cipher, key: key, users: users, overhead: overhead}, nil ++} ++func NewServer(ctx context.Context, c *ServerConfig) (*Server, error) { ++ s, e := newServer(c) ++ if e != nil { ++ return nil, e ++ } ++ s.policy = core.MustFromContext(ctx).GetFeature(policy.ManagerType()).(policy.Manager).ForLevel(c.Level) ++ return s, nil ++} ++func (s *Server) Network() []net.Network { ++ if len(s.config.Network) == 0 { ++ return []net.Network{net.Network_TCP} ++ } ++ return s.config.Network ++} ++func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, d routing.Dispatcher) error { ++ defer conn.Close() ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ done := make(chan struct{}) ++ stop := context.AfterFunc(ctx, func() { conn.Close(); close(done) }) ++ defer func() { ++ if !stop() { ++ <-done ++ } ++ }() ++ if in := session.InboundFromContext(ctx); in != nil { ++ cp := *in ++ cp.Name = "shadowsocksr" ++ cp.CanSpliceCopy = 3 ++ cp.User = &protocol.MemoryUser{Level: s.config.Level} ++ ctx = session.ContextWithInbound(ctx, &cp) ++ } ++ if network == net.Network_UDP { ++ return s.processUDP(ctx, conn, d) ++ } ++ if network != net.Network_TCP { ++ return errors.New("shadowsocksr: invalid network") ++ } ++ deadline := time.Now().Add(s.policy.Timeouts.Handshake) ++ conn.SetDeadline(deadline) ++ stream, e := O.AcceptServer(conn, s.config.Obfs, s.key, s.config.ObfsParam, &s.obfsReplay, deadline) ++ if e != nil { ++ return e ++ } ++ stream = s.cipher.StreamConn(stream) ++ var iv []byte ++ if sc, ok := stream.(*shadowstream.Conn); ok { ++ iv, e = sc.ObtainReadIV() ++ if e != nil { ++ return e ++ } ++ } ++ stream, e = P.AcceptServer(stream, s.config.Protocol, &P.Base{Key: s.key, Overhead: s.overhead}, iv, s.users, &s.replay) ++ if e != nil { ++ return e ++ } ++ addr, e := M.SocksaddrSerializer.ReadAddrPort(stream) ++ if e != nil { ++ return e ++ } ++ target := net.TCPDestination(net.ParseAddress(addr.AddrString()), net.Port(addr.Port)) ++ if !target.IsValid() || target.Port == 0 { ++ return errors.New("shadowsocksr: invalid destination") ++ } ++ conn.SetDeadline(time.Time{}) ++ link, e := d.Dispatch(ctx, target) ++ if e != nil { ++ return e ++ } ++ return relay(ctx, buf.NewReader(stream), link.Writer, link.Reader, buf.NewWriter(stream), func() { conn.Close() }, s.policy) ++} ++func (s *Server) processUDP(ctx context.Context, conn stat.Connection, d routing.Dispatcher) error { ++ var encoder func([]byte) ([]byte, error) ++ var userID uint32 ++ haveUser := false ++ // One UDP source is bound to its first authenticated user. The dispatcher ++ // preserves per-packet destinations; the response encoder is immutable. ++ var dispatch *udp.Dispatcher ++ defer func() { ++ if dispatch != nil { ++ dispatch.RemoveRay() ++ } ++ }() ++ reader := buf.NewPacketReader(conn) ++ var pending buf.MultiBuffer ++ defer func() { buf.ReleaseMulti(pending) }() ++ for { ++ conn.SetReadDeadline(time.Now().Add(s.policy.Timeouts.ConnectionIdle)) ++ var e error ++ if len(pending) == 0 { ++ pending, e = reader.ReadMultiBuffer() ++ } ++ if e != nil { ++ if e == io.EOF { ++ return nil ++ } ++ return e ++ } ++ if len(pending) == 0 { ++ continue ++ } ++ packet := append([]byte{}, pending[0].Bytes()...) ++ pending[0].Release() ++ pending = pending[1:] ++ if sc, ok := s.cipher.(*C.StreamCipher); ok { ++ packet, e = shadowstream.UnpackInplace(packet, sc) ++ if e != nil { ++ continue ++ } ++ } ++ uid, e := P.ServerPacketUserID(s.config.Protocol, s.key, packet) ++ if e != nil { ++ continue ++ } ++ if len(s.users) == 0 { ++ uid = 0 ++ } ++ if haveUser && uid != userID { ++ continue ++ } ++ payload, encode, e := P.DecodeServerPacket(s.config.Protocol, s.key, s.users, packet) ++ if e != nil { ++ continue ++ } ++ r := bytes.NewReader(payload) ++ addr, e := M.SocksaddrSerializer.ReadAddrPort(r) ++ if e != nil { ++ continue ++ } ++ target := net.UDPDestination(net.ParseAddress(addr.AddrString()), net.Port(addr.Port)) ++ if !target.IsValid() || target.Port == 0 { ++ continue ++ } ++ if !haveUser { ++ encoder = encode ++ userID = uid ++ haveUser = true ++ dispatch = udp.NewDispatcher(d, func(_ context.Context, p *udp_proto.Packet) { ++ defer p.Payload.Release() ++ var out bytes.Buffer ++ if e := M.SocksaddrSerializer.WriteAddrPort(&out, socksaddr(p.Source)); e != nil { ++ return ++ } ++ out.Write(p.Payload.Bytes()) ++ data, e := encoder(out.Bytes()) ++ if e != nil { ++ return ++ } ++ if sc, ok := s.cipher.(*C.StreamCipher); ok { ++ data, e = shadowstream.Pack(make([]byte, len(data)+sc.IVSize()), data, sc) ++ if e != nil { ++ return ++ } ++ } ++ conn.Write(data) ++ }) ++ } ++ b := buf.FromBytes(append([]byte{}, payload[len(payload)-r.Len():]...)) ++ b.UDP = &target ++ dispatch.Dispatch(ctx, target, b) ++ } ++} ++func init() { ++ common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, c interface{}) (interface{}, error) { ++ return NewServer(ctx, c.(*ServerConfig)) ++ })) ++} +--- a/proxy/shadowsocksr/shadowstream/chacha20.go ++++ b/proxy/shadowsocksr/shadowstream/chacha20.go +@@ -0,0 +1,57 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package shadowstream ++ ++import ( ++ "crypto/cipher" ++ ++ "github.com/metacubex/chacha" ++) ++ ++func newChaCha20(nonce, key []byte) cipher.Stream { ++ c, err := chacha.NewChaCha20IgnoreCounterOverflow(nonce, key) ++ if err != nil { ++ panic(err) // should never happen ++ } ++ return c ++} ++ ++type chacha20key []byte ++ ++func (k chacha20key) IVSize() int { return chacha.NonceSize } ++func (k chacha20key) Encrypter(iv []byte) cipher.Stream { return newChaCha20(iv, k) } ++func (k chacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } ++ ++func ChaCha20(key []byte) (Cipher, error) { ++ if len(key) != chacha.KeySize { ++ return nil, KeySizeError(chacha.KeySize) ++ } ++ return chacha20key(key), nil ++} ++ ++// IETF-variant of chacha20 ++type chacha20ietfkey []byte ++ ++func (k chacha20ietfkey) IVSize() int { return chacha.INonceSize } ++func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } ++func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream { return newChaCha20(iv, k) } ++ ++func Chacha20IETF(key []byte) (Cipher, error) { ++ if len(key) != chacha.KeySize { ++ return nil, KeySizeError(chacha.KeySize) ++ } ++ return chacha20ietfkey(key), nil ++} ++ ++type xchacha20key []byte ++ ++func (k xchacha20key) IVSize() int { return chacha.XNonceSize } ++func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } ++func (k xchacha20key) Encrypter(iv []byte) cipher.Stream { return newChaCha20(iv, k) } ++ ++func Xchacha20(key []byte) (Cipher, error) { ++ if len(key) != chacha.KeySize { ++ return nil, KeySizeError(chacha.KeySize) ++ } ++ return xchacha20key(key), nil ++} +--- a/proxy/shadowsocksr/shadowstream/cipher.go ++++ b/proxy/shadowsocksr/shadowstream/cipher.go +@@ -0,0 +1,77 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package shadowstream ++ ++import ( ++ "crypto/aes" ++ "crypto/cipher" ++ "crypto/md5" ++ "crypto/rc4" ++ "strconv" ++) ++ ++// Cipher generates a pair of stream ciphers for encryption and decryption. ++type Cipher interface { ++ IVSize() int ++ Encrypter(iv []byte) cipher.Stream ++ Decrypter(iv []byte) cipher.Stream ++} ++ ++type KeySizeError int ++ ++func (e KeySizeError) Error() string { ++ return "key size error: need " + strconv.Itoa(int(e)) + " bytes" ++} ++ ++// CTR mode ++type ctrStream struct{ cipher.Block } ++ ++func (b *ctrStream) IVSize() int { return b.BlockSize() } ++func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) } ++func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b.Block, iv) } ++ ++func AESCTR(key []byte) (Cipher, error) { ++ blk, err := aes.NewCipher(key) ++ if err != nil { ++ return nil, err ++ } ++ return &ctrStream{blk}, nil ++} ++ ++// CFB mode ++type cfbStream struct{ cipher.Block } ++ ++func (b *cfbStream) IVSize() int { return b.BlockSize() } ++func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b.Block, iv) } ++func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b.Block, iv) } ++ ++func AESCFB(key []byte) (Cipher, error) { ++ blk, err := aes.NewCipher(key) ++ if err != nil { ++ return nil, err ++ } ++ return &cfbStream{blk}, nil ++} ++ ++type rc4Md5Key []byte ++ ++func (k rc4Md5Key) IVSize() int { ++ return 16 ++} ++ ++func (k rc4Md5Key) Encrypter(iv []byte) cipher.Stream { ++ h := md5.New() ++ h.Write([]byte(k)) ++ h.Write(iv) ++ rc4key := h.Sum(nil) ++ c, _ := rc4.NewCipher(rc4key) ++ return c ++} ++ ++func (k rc4Md5Key) Decrypter(iv []byte) cipher.Stream { ++ return k.Encrypter(iv) ++} ++ ++func RC4MD5(key []byte) (Cipher, error) { ++ return rc4Md5Key(key), nil ++} +--- a/proxy/shadowsocksr/shadowstream/packet.go ++++ b/proxy/shadowsocksr/shadowstream/packet.go +@@ -0,0 +1,93 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package shadowstream ++ ++import ( ++ "crypto/rand" ++ "errors" ++ "io" ++ "net" ++ ++ "github.com/xtls/xray-core/proxy/shadowsocksr/pool" ++) ++ ++// ErrShortPacket means the packet is too short to be a valid encrypted packet. ++var ErrShortPacket = errors.New("short packet") ++ ++// Pack encrypts plaintext using stream cipher s and a random IV. ++// Returns a slice of dst containing random IV and ciphertext. ++// Ensure len(dst) >= s.IVSize() + len(plaintext). ++func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) { ++ if len(dst) < s.IVSize()+len(plaintext) { ++ return nil, io.ErrShortBuffer ++ } ++ iv := dst[:s.IVSize()] ++ _, err := rand.Read(iv) ++ if err != nil { ++ return nil, err ++ } ++ s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext) ++ return dst[:len(iv)+len(plaintext)], nil ++} ++ ++// UnpackInplace decrypts pkt using stream cipher s. ++// Returns a slice of pkt containing decrypted plaintext. ++// Note: The data in the input dst will be changed ++func UnpackInplace(pkt []byte, s Cipher) ([]byte, error) { ++ if len(pkt) < s.IVSize() { ++ return nil, ErrShortPacket ++ } ++ iv, dst := pkt[:s.IVSize()], pkt[s.IVSize():] ++ s.Decrypter(iv).XORKeyStream(dst, dst) ++ return dst, nil ++} ++ ++// Unpack decrypts pkt using stream cipher s. ++// Returns a slice of dst containing decrypted plaintext. ++func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) { ++ if len(pkt) < s.IVSize() { ++ return nil, ErrShortPacket ++ } ++ if len(dst) < len(pkt)-s.IVSize() { ++ return nil, io.ErrShortBuffer ++ } ++ iv := pkt[:s.IVSize()] ++ s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):]) ++ return dst[:len(pkt)-len(iv)], nil ++} ++ ++type PacketConn struct { ++ net.PacketConn ++ Cipher ++} ++ ++// NewPacketConn wraps an net.PacketConn with stream cipher encryption/decryption. ++func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn { ++ return &PacketConn{PacketConn: c, Cipher: ciph} ++} ++ ++const maxPacketSize = 64 * 1024 ++ ++func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { ++ buf := pool.Get(maxPacketSize) ++ defer pool.Put(buf) ++ buf, err := Pack(buf, b, c.Cipher) ++ if err != nil { ++ return 0, err ++ } ++ _, err = c.PacketConn.WriteTo(buf, addr) ++ return len(b), err ++} ++ ++func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { ++ n, addr, err := c.PacketConn.ReadFrom(b) ++ if err != nil { ++ return n, addr, err ++ } ++ bb, err := UnpackInplace(b[:n], c.Cipher) ++ if err != nil { ++ return n, addr, err ++ } ++ copy(b, bb) ++ return len(bb), addr, err ++} +--- a/proxy/shadowsocksr/shadowstream/stream.go ++++ b/proxy/shadowsocksr/shadowstream/stream.go +@@ -0,0 +1,199 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package shadowstream ++ ++import ( ++ "crypto/cipher" ++ "crypto/rand" ++ "io" ++ "net" ++) ++ ++const bufSize = 2048 ++ ++type Writer struct { ++ io.Writer ++ cipher.Stream ++ buf [bufSize]byte ++} ++ ++// NewWriter wraps an io.Writer with stream cipher encryption. ++func NewWriter(w io.Writer, s cipher.Stream) *Writer { return &Writer{Writer: w, Stream: s} } ++ ++func (w *Writer) Write(p []byte) (n int, err error) { ++ buf := w.buf[:] ++ for nw := 0; n < len(p) && err == nil; n += nw { ++ end := n + len(buf) ++ if end > len(p) { ++ end = len(p) ++ } ++ w.XORKeyStream(buf, p[n:end]) ++ nw, err = w.Writer.Write(buf[:end-n]) ++ if err == nil && nw != end-n { ++ err = io.ErrShortWrite ++ } ++ } ++ return ++} ++ ++func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) { ++ buf := w.buf[:] ++ for { ++ nr, er := r.Read(buf) ++ n += int64(nr) ++ b := buf[:nr] ++ w.XORKeyStream(b, b) ++ if _, err = w.Writer.Write(b); err != nil { ++ return ++ } ++ if er != nil { ++ if er != io.EOF { // ignore EOF as per io.ReaderFrom contract ++ err = er ++ } ++ return ++ } ++ } ++} ++ ++type Reader struct { ++ io.Reader ++ cipher.Stream ++ buf [bufSize]byte ++} ++ ++// NewReader wraps an io.Reader with stream cipher decryption. ++func NewReader(r io.Reader, s cipher.Stream) *Reader { return &Reader{Reader: r, Stream: s} } ++ ++func (r *Reader) Read(p []byte) (n int, err error) { ++ n, err = r.Reader.Read(p) ++ r.XORKeyStream(p[:n], p[:n]) ++ return ++} ++ ++func (r *Reader) WriteTo(w io.Writer) (n int64, err error) { ++ buf := r.buf[:] ++ for { ++ nr, er := r.Reader.Read(buf) ++ if nr > 0 { ++ r.XORKeyStream(buf, buf[:nr]) ++ nw, ew := w.Write(buf[:nr]) ++ n += int64(nw) ++ if ew != nil { ++ err = ew ++ return ++ } ++ } ++ if er != nil { ++ if er != io.EOF { // ignore EOF as per io.Copy contract (using src.WriteTo shortcut) ++ err = er ++ } ++ return ++ } ++ } ++} ++ ++// A Conn represents a Shadowsocks connection. It implements the net.Conn interface. ++type Conn struct { ++ net.Conn ++ Cipher ++ r *Reader ++ w *Writer ++ readIV []byte ++ writeIV []byte ++} ++ ++// NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption. ++func NewConn(c net.Conn, ciph Cipher) *Conn { return &Conn{Conn: c, Cipher: ciph} } ++ ++func (c *Conn) initReader() error { ++ if c.r == nil { ++ iv, err := c.ObtainReadIV() ++ if err != nil { ++ return err ++ } ++ c.r = NewReader(c.Conn, c.Decrypter(iv)) ++ } ++ return nil ++} ++ ++func (c *Conn) Read(b []byte) (int, error) { ++ if c.r == nil { ++ if err := c.initReader(); err != nil { ++ return 0, err ++ } ++ } ++ return c.r.Read(b) ++} ++ ++func (c *Conn) WriteTo(w io.Writer) (int64, error) { ++ if c.r == nil { ++ if err := c.initReader(); err != nil { ++ return 0, err ++ } ++ } ++ return c.r.WriteTo(w) ++} ++ ++func (c *Conn) initWriter() error { ++ if c.w == nil { ++ iv, err := c.ObtainWriteIV() ++ if err != nil { ++ return err ++ } ++ if _, err := c.Conn.Write(iv); err != nil { ++ return err ++ } ++ c.w = NewWriter(c.Conn, c.Encrypter(iv)) ++ } ++ return nil ++} ++ ++func (c *Conn) Write(b []byte) (int, error) { ++ if c.w == nil { ++ if err := c.initWriter(); err != nil { ++ return 0, err ++ } ++ } ++ return c.w.Write(b) ++} ++ ++func (c *Conn) ReadFrom(r io.Reader) (int64, error) { ++ if c.w == nil { ++ if err := c.initWriter(); err != nil { ++ return 0, err ++ } ++ } ++ return c.w.ReadFrom(r) ++} ++ ++func (c *Conn) ObtainWriteIV() ([]byte, error) { ++ if len(c.writeIV) == c.IVSize() { ++ return c.writeIV, nil ++ } ++ ++ iv := make([]byte, c.IVSize()) ++ ++ if _, err := rand.Read(iv); err != nil { ++ return nil, err ++ } ++ ++ c.writeIV = iv ++ ++ return iv, nil ++} ++ ++func (c *Conn) ObtainReadIV() ([]byte, error) { ++ if len(c.readIV) == c.IVSize() { ++ return c.readIV, nil ++ } ++ ++ iv := make([]byte, c.IVSize()) ++ ++ if _, err := io.ReadFull(c.Conn, iv); err != nil { ++ return nil, err ++ } ++ ++ c.readIV = iv ++ ++ return iv, nil ++} +--- a/proxy/shadowsocksr/tools/bufPool.go ++++ b/proxy/shadowsocksr/tools/bufPool.go +@@ -0,0 +1,13 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package tools ++ ++import ( ++ "bytes" ++ "crypto/rand" ++ "io" ++) ++ ++func AppendRandBytes(b *bytes.Buffer, length int) { ++ b.ReadFrom(io.LimitReader(rand.Reader, int64(length))) ++} +--- a/proxy/shadowsocksr/tools/crypto.go ++++ b/proxy/shadowsocksr/tools/crypto.go +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package tools ++ ++import ( ++ "crypto/hmac" ++ "crypto/md5" ++ "crypto/sha1" ++) ++ ++const HmacSHA1Len = 10 ++ ++func HmacMD5(key, data []byte) []byte { ++ hmacMD5 := hmac.New(md5.New, key) ++ hmacMD5.Write(data) ++ return hmacMD5.Sum(nil) ++} ++ ++func HmacSHA1(key, data []byte) []byte { ++ hmacSHA1 := hmac.New(sha1.New, key) ++ hmacSHA1.Write(data) ++ return hmacSHA1.Sum(nil) ++} ++ ++func MD5Sum(b []byte) []byte { ++ h := md5.New() ++ h.Write(b) ++ return h.Sum(nil) ++} ++ ++func SHA1Sum(b []byte) []byte { ++ h := sha1.New() ++ h.Write(b) ++ return h.Sum(nil) ++} +--- a/proxy/shadowsocksr/tools/random.go ++++ b/proxy/shadowsocksr/tools/random.go +@@ -0,0 +1,59 @@ ++// SPDX-License-Identifier: GPL-3.0-or-later ++// Adapted from MetaCubeX/mihomo v1.19.32. ++package tools ++ ++import ( ++ "encoding/binary" ++ ++ "github.com/xtls/xray-core/proxy/shadowsocksr/pool" ++) ++ ++// XorShift128Plus - a pseudorandom number generator ++type XorShift128Plus struct { ++ s [2]uint64 ++} ++ ++func (r *XorShift128Plus) Next() uint64 { ++ x := r.s[0] ++ y := r.s[1] ++ r.s[0] = y ++ x ^= x << 23 ++ x ^= y ^ (x >> 17) ^ (y >> 26) ++ r.s[1] = x ++ return x + y ++} ++ ++func (r *XorShift128Plus) InitFromBin(bin []byte) { ++ var full []byte ++ if len(bin) < 16 { ++ full := pool.Get(16)[:0] ++ defer pool.Put(full) ++ full = append(full, bin...) ++ for len(full) < 16 { ++ full = append(full, 0) ++ } ++ } else { ++ full = bin ++ } ++ r.s[0] = binary.LittleEndian.Uint64(full[:8]) ++ r.s[1] = binary.LittleEndian.Uint64(full[8:16]) ++} ++ ++func (r *XorShift128Plus) InitFromBinAndLength(bin []byte, length int) { ++ var full []byte ++ if len(bin) < 16 { ++ full := pool.Get(16)[:0] ++ defer pool.Put(full) ++ full = append(full, bin...) ++ for len(full) < 16 { ++ full = append(full, 0) ++ } ++ } ++ full = bin ++ binary.LittleEndian.PutUint16(full, uint16(length)) ++ r.s[0] = binary.LittleEndian.Uint64(full[:8]) ++ r.s[1] = binary.LittleEndian.Uint64(full[8:16]) ++ for i := 0; i < 4; i++ { ++ r.Next() ++ } ++} diff --git a/xray-core/patches/150-add-tuic-proxy.patch b/xray-core/patches/150-add-tuic-proxy.patch new file mode 100644 index 000000000..0166996e3 --- /dev/null +++ b/xray-core/patches/150-add-tuic-proxy.patch @@ -0,0 +1,2314 @@ +From: coolsnowwolf +Subject: [PATCH] proxy: add Mihomo TUIC v4/v5 inbound and outbound + +Use the pinned Mihomo v1.19.32 TUIC implementation and its QUIC/TLS stack. +Adapt authenticated streams and UDP associations to Xray routing. Support +v4 tokens and v5 UUID/password users, native datagrams and QUIC stream UDP, +congestion control, early handshakes, reusable clients and socket options. +Join packet copy loops before closing upstream packet state and preserve +Xray private-target restrictions on the TUIC inbound. + +Mihomo is GPL-3.0-or-later; include its license with the adapter. +Signed-off-by: coolsnowwolf + +--- a/go.mod ++++ b/go.mod +@@ -3,9 +3,11 @@ + go 1.27 + + require ( ++ github.com/andybalholm/brotli v1.2.3 // indirect + github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e + github.com/bufbuild/protocompile v0.14.1 + github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 ++ github.com/gofrs/uuid/v5 v5.4.0 + github.com/golang/mock v1.7.0-rc.1 + github.com/google/go-cmp v0.7.0 + github.com/google/uuid v1.6.0 +@@ -13,6 +15,9 @@ + github.com/klauspost/cpuid/v2 v2.4.2 + github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 + github.com/metacubex/chacha v0.1.5 ++ github.com/metacubex/mihomo v1.19.32 ++ github.com/metacubex/quic-go v0.61.1-0.20260727080200-2548683b76f4 ++ github.com/metacubex/tls v0.1.8 + github.com/miekg/dns v1.1.73 + github.com/pelletier/go-toml v1.9.5 + github.com/pion/stun/v3 v3.1.7 +@@ -40,10 +45,10 @@ + github.com/vishvananda/netlink v1.3.1 + github.com/xtls/reality v0.0.0-20261108101329-4bb46b2afd8b + github.com/yuin/gopher-lua v1.1.2 +- go4.org/netipx v0.0.0-20231129151722-fdeea329fbba ++ go4.org/netipx v0.0.0-20260823151212-3075585bcbeb + golang.org/x/crypto v0.58.0 + golang.org/x/crypto/x509roots/fallback v0.0.0-20261020153306-9f2b28d4331a +- golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 ++ golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e + golang.org/x/net v0.62.0 + golang.org/x/sync v0.24.0 + golang.org/x/sys v0.49.0 +@@ -59,6 +64,7 @@ + ) + + require ( ++ github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/ebitengine/purego v0.10.0 // indirect + github.com/google/gopacket v1.1.19 // indirect + github.com/huin/goupnp v1.2.0 // indirect +@@ -68,10 +74,24 @@ + github.com/koron/go-ssdp v0.0.4 // indirect + github.com/libp2p/go-netroute v0.2.1 // indirect + github.com/molecule-man/go-brrr v1.2.0 // indirect ++ github.com/metacubex/bart v0.29.0 // indirect ++ github.com/metacubex/blake3 v0.1.0 // indirect ++ github.com/metacubex/cpu v0.1.2 // indirect ++ github.com/metacubex/hkdf v0.1.0 // indirect ++ github.com/metacubex/hpke v0.1.0 // indirect ++ github.com/metacubex/http v0.1.8 // indirect ++ github.com/metacubex/mlkem v0.1.0 // indirect ++ github.com/metacubex/randv2 v0.2.0 // indirect ++ github.com/metacubex/sing v0.5.8 // indirect ++ github.com/metacubex/tfo-go v0.0.0-20260623020846-376a77860b8c // indirect ++ github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect ++ github.com/openacid/low v0.1.21 // indirect + github.com/pion/dtls/v3 v3.1.5 // indirect + github.com/pion/logging v0.2.4 // indirect + github.com/pion/transport/v4 v4.1.0 // indirect + github.com/quic-go/qpack v0.6.0 // indirect ++ github.com/samber/lo v1.53.0 // indirect ++ github.com/sirupsen/logrus v1.9.4 // indirect + github.com/vishvananda/netns v0.0.5 // indirect + github.com/wlynxg/anet v0.0.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect +--- a/go.sum ++++ b/go.sum +@@ -1,22 +1,31 @@ ++github.com/andybalholm/brotli v1.2.3 h1:8H1qwOkl2LPfjf3YezB90JnCliZb6SInJ/OJkEbA5NQ= ++github.com/andybalholm/brotli v1.2.3/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= + github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg= + github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8= ++github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= ++github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= ++github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= + github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= + github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= + github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= + github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= ++github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= + github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= + github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= + github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= + github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= + github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474= + github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= ++github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= ++github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= + github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= + github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= ++github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= + github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= + github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +-github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +-github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= ++github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= ++github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= + github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= + github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= + github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +@@ -47,10 +56,43 @@ + github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138/go.mod h1:TXQg5tfSy+bUjnhT5728j5j/MBj7keIYqqZ1+8k/ui8= + github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= + github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= ++github.com/metacubex/bart v0.29.0 h1:yVc/lLdDqn+7FShI0qUghStHl0/d3mn4EH/I5R5niKk= ++github.com/metacubex/bart v0.29.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI= ++github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cqY= ++github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk= + github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M= + github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= ++github.com/metacubex/cpu v0.1.2 h1:xJHPWLkGilSWm6vEvzz8Loe+UusODRkVnuXz96TWAZo= ++github.com/metacubex/cpu v0.1.2/go.mod h1:09VEt4dSRLR+bOA8l4w4NDuzGZ8n5dkMv7e8axgEeTU= ++github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA= ++github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4= ++github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ= ++github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U= ++github.com/metacubex/http v0.1.8 h1:d91onDan2ZXYE8W/uAy5oFmXHH2xi1L7LbGPV5EmCM0= ++github.com/metacubex/http v0.1.8/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg= ++github.com/metacubex/mihomo v1.19.32 h1:uD7ZC3P77isWD554NNvtee65L+99+/C5hyc+Lk8rVEk= ++github.com/metacubex/mihomo v1.19.32/go.mod h1:d7xWi5iEktkC9PqXkD/Vo4AEcb0Q92XikfLoySUDWls= ++github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I= ++github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ= ++github.com/metacubex/quic-go v0.61.1-0.20260727080200-2548683b76f4 h1:7oX6CpHVqoUXNyyt0MTuO5ywd1qVe1S1t3jXWMW7bYc= ++github.com/metacubex/quic-go v0.61.1-0.20260727080200-2548683b76f4/go.mod h1:KTmibo7vZo0gCxdFWrPEQ/u/RGt92mJkrXDE2Qg2/AU= ++github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= ++github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= ++github.com/metacubex/sing v0.5.8 h1:OwJDSPP+pza6Ywt7NMR4kEYq+UNrsIywSk/eAMlqxz8= ++github.com/metacubex/sing v0.5.8/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= ++github.com/metacubex/tfo-go v0.0.0-20260623020846-376a77860b8c h1:+2C1UshSLzaogLY/IDz1XhOLxjhs3rkhVHB4eCN9Zto= ++github.com/metacubex/tfo-go v0.0.0-20260623020846-376a77860b8c/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= ++github.com/metacubex/tls v0.1.8 h1:BHF2payjtxC7wdR3tnq8vl3FAgEQB6/pPjP2tJNTsP4= ++github.com/metacubex/tls v0.1.8/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM= ++github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk= ++github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4= + github.com/miekg/dns v1.1.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE= + github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ= ++github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0= ++github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo= ++github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0= ++github.com/openacid/must v0.1.3/go.mod h1:luPiXCuJlEo3UUFQngVQokV0MPGryeYvtCbQPs3U1+I= ++github.com/openacid/testkeys v0.1.6/go.mod h1:MfA7cACzBpbiwekivj8StqX0WIRmqlMsci1c37CA3Do= + github.com/molecule-man/go-brrr v1.2.0 h1:dOJU45BC3Gc2WXoxAW0rgCAgVuu2gruAOGncfieA0Jw= + github.com/molecule-man/go-brrr v1.2.0/go.mod h1:7ybW6/7gA3oKY45jOfVNjSJDtrr6ea4tzbsTkjmQDC4= + github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +@@ -67,6 +109,8 @@ + github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM= + github.com/pires/go-proxyproto v0.16.0 h1:9KYT/fM3wpjT8bdMEj/7FBcoiUG1KyzEjRYnPu+vz7A= + github.com/pires/go-proxyproto v0.16.0/go.mod h1:n7yhg8yIfNl3KgYDCwBzmeUHNpKTVnUhcWpc7rV4RW4= ++github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= ++github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= + github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0= + github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= + github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +@@ -111,8 +155,16 @@ + github.com/sagernet/sing v0.8.14/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA= + github.com/sagernet/sing-anytls v0.0.0-20260928104022-580984e4d8cb h1:n4sF2zv0CR1gcb4awh61t11t0FZmcP40x5enB8HARjE= + github.com/sagernet/sing-anytls v0.0.0-20260928104022-580984e4d8cb/go.mod h1:VRKLnj0ObEsN228h+zuzf91p+6sjtYE8SBaLPVsBy5o= ++github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM= ++github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= ++github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= ++github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= ++github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= + github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= + github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= ++github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= ++github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= ++github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= + github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= + github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= + github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= +@@ -129,16 +181,16 @@ + go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= + go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= + go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +-go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= +-go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= ++go4.org/netipx v0.0.0-20260823151212-3075585bcbeb h1:XBM4hvfwGAttkkiTIFfeigdfcL1xIfdKXqFdgiHGtDs= ++go4.org/netipx v0.0.0-20260823151212-3075585bcbeb/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= + golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54= + golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w= + golang.org/x/crypto/x509roots/fallback v0.0.0-20261020153306-9f2b28d4331a h1:V5oicZXS0oqZx30gY/apgSVD7YvNBfrIOdcMnlelJY4= + golang.org/x/crypto/x509roots/fallback v0.0.0-20261020153306-9f2b28d4331a/go.mod h1:HPze8vhfG6fO06AM+VSvxRm4E3+5Yk375mgrJ5M2z1E= +-golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +-golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= ++golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= ++golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= + golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= + golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= + golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +@@ -157,8 +209,8 @@ + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= + golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= + golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +-golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= + golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= ++golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= + golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= + golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +@@ -200,6 +252,7 @@ + gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= + gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= + gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= ++gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= + gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk= + gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q= + h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo= +--- a/infra/conf/transport_internet.go ++++ b/infra/conf/transport_internet.go +@@ -38,6 +38,8 @@ + return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3") + case "hysteria": + return "hysteria", nil ++ case "tuic": ++ return "tuic", nil + case "naive": + return "naive", nil + case "masque": +--- a/infra/conf/tuic.go ++++ b/infra/conf/tuic.go +@@ -0,0 +1,145 @@ ++package conf ++ ++import ( ++ "github.com/gofrs/uuid/v5" ++ "github.com/xtls/xray-core/common/errors" ++ P "github.com/xtls/xray-core/proxy/tuic" ++ T "github.com/xtls/xray-core/transport/internet/tuic" ++ "google.golang.org/protobuf/proto" ++ "strings" ++) ++ ++func tuicCC(s string) (string, error) { ++ if s == "" { ++ return "cubic", nil ++ } ++ switch s { ++ case "cubic", "new_reno", "bbr", "bbr_meta_v1", "bbr_meta_v2": ++ return s, nil ++ } ++ return "", errors.New("tuic: unsupported congestion control") ++} ++ ++type TUICClientConfig struct { ++ Address *Address `json:"address"` ++ Port uint16 `json:"port"` ++ Version uint32 `json:"version"` ++ Token string `json:"token"` ++ UUID string `json:"uuid"` ++ Password string `json:"password"` ++ ServerName string `json:"serverName"` ++ CertificateFile string `json:"certificateFile"` ++ AllowInsecure bool `json:"allowInsecure"` ++ ALPN []string `json:"alpn"` ++ CongestionControl string `json:"congestionControl"` ++ UDPRelayMode string `json:"udpRelayMode"` ++ ReduceRTT bool `json:"reduceRtt"` ++ FastOpen bool `json:"fastOpen"` ++ HeartbeatInterval uint32 `json:"heartbeatInterval"` ++ RequestTimeout uint32 `json:"requestTimeout"` ++ MaxOpenStreams uint32 `json:"maxOpenStreams"` ++ MaxUDPRelayPacketSize uint32 `json:"maxUdpRelayPacketSize"` ++ Level uint32 `json:"level"` ++} ++ ++func (c *TUICClientConfig) Build() (proto.Message, error) { ++ if c.Address == nil || c.Port == 0 { ++ return nil, errors.New("tuic: address and port required") ++ } ++ v := c.Version ++ if v == 0 { ++ v = 5 ++ if c.Token != "" { ++ v = 4 ++ } ++ } ++ if v == 4 { ++ if c.Token == "" || c.UUID != "" || c.Password != "" { ++ return nil, errors.New("tuic v4 requires token only") ++ } ++ } else if v == 5 { ++ if _, e := uuid.FromString(c.UUID); e != nil || c.Password == "" || c.Token != "" { ++ return nil, errors.New("tuic v5 requires UUID and password") ++ } ++ } else { ++ return nil, errors.New("tuic version must be 4 or 5") ++ } ++ cc, e := tuicCC(c.CongestionControl) ++ if e != nil { ++ return nil, e ++ } ++ mode := c.UDPRelayMode ++ if mode == "" { ++ mode = "native" ++ } ++ if mode != "native" && mode != "quic" { ++ return nil, errors.New("tuic: invalid UDP relay mode") ++ } ++ heartbeat := c.HeartbeatInterval ++ if heartbeat == 0 { ++ heartbeat = 10000 ++ } ++ timeout := c.RequestTimeout ++ if timeout == 0 { ++ timeout = 8000 ++ } ++ streams := c.MaxOpenStreams ++ if streams == 0 { ++ streams = 100 ++ } ++ if streams < 2 { ++ return nil, errors.New("tuic: maxOpenStreams must be at least 2") ++ } ++ size := c.MaxUDPRelayPacketSize ++ if size == 0 { ++ size = 1200 ++ } ++ if size < 256 || size > 1252 { ++ return nil, errors.New("tuic: UDP relay packet size must be 256..1252") ++ } ++ return &P.ClientConfig{Address: c.Address.Build(), Port: uint32(c.Port), Version: v, Token: c.Token, Uuid: c.UUID, Password: c.Password, ServerName: c.ServerName, CertificateFile: c.CertificateFile, AllowInsecure: c.AllowInsecure, Alpn: c.ALPN, CongestionControl: cc, UdpRelayMode: mode, ReduceRtt: c.ReduceRTT, FastOpen: c.FastOpen, HeartbeatInterval: heartbeat, RequestTimeout: timeout, MaxOpenStreams: streams, MaxUdpRelayPacketSize: size, Level: c.Level}, nil ++} ++ ++type TUICServerConfig struct { ++ Tokens []string `json:"tokens"` ++ Users map[string]string `json:"users"` ++ CertificateFile string `json:"certificateFile"` ++ KeyFile string `json:"keyFile"` ++ ALPN []string `json:"alpn"` ++ CongestionControl string `json:"congestionControl"` ++ AuthenticationTimeout uint32 `json:"authenticationTimeout"` ++ MaxUDPRelayPacketSize uint32 `json:"maxUdpRelayPacketSize"` ++ Level uint32 `json:"level"` ++} ++ ++func (c *TUICServerConfig) Build() (proto.Message, error) { ++ if len(c.Tokens)+len(c.Users) == 0 || c.CertificateFile == "" || c.KeyFile == "" { ++ return nil, errors.New("tuic: credentials and TLS certificate/key required") ++ } ++ for _, t := range c.Tokens { ++ if strings.TrimSpace(t) == "" { ++ return nil, errors.New("tuic: empty token") ++ } ++ } ++ for id, p := range c.Users { ++ if _, e := uuid.FromString(id); e != nil || p == "" { ++ return nil, errors.New("tuic: invalid user") ++ } ++ } ++ cc, e := tuicCC(c.CongestionControl) ++ if e != nil { ++ return nil, e ++ } ++ timeout := c.AuthenticationTimeout ++ if timeout == 0 { ++ timeout = 3000 ++ } ++ size := c.MaxUDPRelayPacketSize ++ if size == 0 { ++ size = 1200 ++ } ++ if size < 256 || size > 1252 { ++ return nil, errors.New("tuic: invalid UDP packet size") ++ } ++ return &T.Config{Tokens: c.Tokens, Users: c.Users, CertificateFile: c.CertificateFile, KeyFile: c.KeyFile, Alpn: c.ALPN, CongestionControl: cc, AuthenticationTimeout: timeout, MaxUdpRelayPacketSize: size, Level: c.Level}, nil ++} +--- a/infra/conf/xray.go ++++ b/infra/conf/xray.go +@@ -27,6 +27,7 @@ + var ( + inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ + "tunnel": func() interface{} { return new(DokodemoConfig) }, ++ "tuic": func() interface{} { return new(TUICServerConfig) }, + "naive": func() interface{} { return new(NaiveServerConfig) }, + "dokodemo-door": func() interface{} { return new(DokodemoConfig) }, + "http": func() interface{} { return new(HTTPServerConfig) }, +@@ -57,6 +58,7 @@ + "vmess": func() interface{} { return new(VMessOutboundConfig) }, + "trojan": func() interface{} { return new(TrojanClientConfig) }, + "naive": func() interface{} { return new(NaiveClientConfig) }, ++ "tuic": func() interface{} { return new(TUICClientConfig) }, + "shadowsocksr": func() interface{} { return new(ShadowsocksRClientConfig) }, + "anytls": func() interface{} { return new(AnyTLSClientConfig) }, + "hysteria": func() interface{} { return new(HysteriaClientConfig) }, +--- a/main/distro/all/all.go ++++ b/main/distro/all/all.go +@@ -48,6 +48,7 @@ + _ "github.com/xtls/xray-core/proxy/shadowsocksr" + _ "github.com/xtls/xray-core/proxy/socks" + _ "github.com/xtls/xray-core/proxy/trojan" ++ _ "github.com/xtls/xray-core/proxy/tuic" + _ "github.com/xtls/xray-core/proxy/vless/inbound" + _ "github.com/xtls/xray-core/proxy/vless/outbound" + _ "github.com/xtls/xray-core/proxy/vmess/inbound" +--- a/proxy/freedom/freedom.go ++++ b/proxy/freedom/freedom.go +@@ -172,7 +172,7 @@ + switch inbound.Name { + case "vless-reverse": + return defaultBlockAllRule +- case "vless", "vmess", "trojan", "hysteria", "wireguard", "masque", "naive": ++ case "vless", "vmess", "trojan", "hysteria", "wireguard", "masque", "naive", "tuic": + return defaultBlockPrivateRule + default: + if strings.HasPrefix(inbound.Name, "shadowsocks") { +--- a/proxy/tuic/LICENSE.mihomo ++++ b/proxy/tuic/LICENSE.mihomo +@@ -0,0 +1,674 @@ ++ GNU GENERAL PUBLIC LICENSE ++ Version 3, 29 June 2007 ++ ++ Copyright (C) 2007 Free Software Foundation, Inc. ++ Everyone is permitted to copy and distribute verbatim copies ++ of this license document, but changing it is not allowed. ++ ++ Preamble ++ ++ The GNU General Public License is a free, copyleft license for ++software and other kinds of works. ++ ++ The licenses for most software and other practical works are designed ++to take away your freedom to share and change the works. By contrast, ++the GNU General Public License is intended to guarantee your freedom to ++share and change all versions of a program--to make sure it remains free ++software for all its users. We, the Free Software Foundation, use the ++GNU General Public License for most of our software; it applies also to ++any other work released this way by its authors. You can apply it to ++your programs, too. ++ ++ When we speak of free software, we are referring to freedom, not ++price. Our General Public Licenses are designed to make sure that you ++have the freedom to distribute copies of free software (and charge for ++them if you wish), that you receive source code or can get it if you ++want it, that you can change the software or use pieces of it in new ++free programs, and that you know you can do these things. ++ ++ To protect your rights, we need to prevent others from denying you ++these rights or asking you to surrender the rights. Therefore, you have ++certain responsibilities if you distribute copies of the software, or if ++you modify it: responsibilities to respect the freedom of others. ++ ++ For example, if you distribute copies of such a program, whether ++gratis or for a fee, you must pass on to the recipients the same ++freedoms that you received. You must make sure that they, too, receive ++or can get the source code. And you must show them these terms so they ++know their rights. ++ ++ Developers that use the GNU GPL protect your rights with two steps: ++(1) assert copyright on the software, and (2) offer you this License ++giving you legal permission to copy, distribute and/or modify it. ++ ++ For the developers' and authors' protection, the GPL clearly explains ++that there is no warranty for this free software. For both users' and ++authors' sake, the GPL requires that modified versions be marked as ++changed, so that their problems will not be attributed erroneously to ++authors of previous versions. ++ ++ Some devices are designed to deny users access to install or run ++modified versions of the software inside them, although the manufacturer ++can do so. This is fundamentally incompatible with the aim of ++protecting users' freedom to change the software. The systematic ++pattern of such abuse occurs in the area of products for individuals to ++use, which is precisely where it is most unacceptable. Therefore, we ++have designed this version of the GPL to prohibit the practice for those ++products. If such problems arise substantially in other domains, we ++stand ready to extend this provision to those domains in future versions ++of the GPL, as needed to protect the freedom of users. ++ ++ Finally, every program is threatened constantly by software patents. ++States should not allow patents to restrict development and use of ++software on general-purpose computers, but in those that do, we wish to ++avoid the special danger that patents applied to a free program could ++make it effectively proprietary. To prevent this, the GPL assures that ++patents cannot be used to render the program non-free. ++ ++ The precise terms and conditions for copying, distribution and ++modification follow. ++ ++ TERMS AND CONDITIONS ++ ++ 0. Definitions. ++ ++ "This License" refers to version 3 of the GNU General Public License. ++ ++ "Copyright" also means copyright-like laws that apply to other kinds of ++works, such as semiconductor masks. ++ ++ "The Program" refers to any copyrightable work licensed under this ++License. Each licensee is addressed as "you". "Licensees" and ++"recipients" may be individuals or organizations. ++ ++ To "modify" a work means to copy from or adapt all or part of the work ++in a fashion requiring copyright permission, other than the making of an ++exact copy. The resulting work is called a "modified version" of the ++earlier work or a work "based on" the earlier work. ++ ++ A "covered work" means either the unmodified Program or a work based ++on the Program. ++ ++ To "propagate" a work means to do anything with it that, without ++permission, would make you directly or secondarily liable for ++infringement under applicable copyright law, except executing it on a ++computer or modifying a private copy. Propagation includes copying, ++distribution (with or without modification), making available to the ++public, and in some countries other activities as well. ++ ++ To "convey" a work means any kind of propagation that enables other ++parties to make or receive copies. Mere interaction with a user through ++a computer network, with no transfer of a copy, is not conveying. ++ ++ An interactive user interface displays "Appropriate Legal Notices" ++to the extent that it includes a convenient and prominently visible ++feature that (1) displays an appropriate copyright notice, and (2) ++tells the user that there is no warranty for the work (except to the ++extent that warranties are provided), that licensees may convey the ++work under this License, and how to view a copy of this License. If ++the interface presents a list of user commands or options, such as a ++menu, a prominent item in the list meets this criterion. ++ ++ 1. Source Code. ++ ++ The "source code" for a work means the preferred form of the work ++for making modifications to it. "Object code" means any non-source ++form of a work. ++ ++ A "Standard Interface" means an interface that either is an official ++standard defined by a recognized standards body, or, in the case of ++interfaces specified for a particular programming language, one that ++is widely used among developers working in that language. ++ ++ The "System Libraries" of an executable work include anything, other ++than the work as a whole, that (a) is included in the normal form of ++packaging a Major Component, but which is not part of that Major ++Component, and (b) serves only to enable use of the work with that ++Major Component, or to implement a Standard Interface for which an ++implementation is available to the public in source code form. A ++"Major Component", in this context, means a major essential component ++(kernel, window system, and so on) of the specific operating system ++(if any) on which the executable work runs, or a compiler used to ++produce the work, or an object code interpreter used to run it. ++ ++ The "Corresponding Source" for a work in object code form means all ++the source code needed to generate, install, and (for an executable ++work) run the object code and to modify the work, including scripts to ++control those activities. However, it does not include the work's ++System Libraries, or general-purpose tools or generally available free ++programs which are used unmodified in performing those activities but ++which are not part of the work. For example, Corresponding Source ++includes interface definition files associated with source files for ++the work, and the source code for shared libraries and dynamically ++linked subprograms that the work is specifically designed to require, ++such as by intimate data communication or control flow between those ++subprograms and other parts of the work. ++ ++ The Corresponding Source need not include anything that users ++can regenerate automatically from other parts of the Corresponding ++Source. ++ ++ The Corresponding Source for a work in source code form is that ++same work. ++ ++ 2. Basic Permissions. ++ ++ All rights granted under this License are granted for the term of ++copyright on the Program, and are irrevocable provided the stated ++conditions are met. This License explicitly affirms your unlimited ++permission to run the unmodified Program. The output from running a ++covered work is covered by this License only if the output, given its ++content, constitutes a covered work. This License acknowledges your ++rights of fair use or other equivalent, as provided by copyright law. ++ ++ You may make, run and propagate covered works that you do not ++convey, without conditions so long as your license otherwise remains ++in force. You may convey covered works to others for the sole purpose ++of having them make modifications exclusively for you, or provide you ++with facilities for running those works, provided that you comply with ++the terms of this License in conveying all material for which you do ++not control copyright. Those thus making or running the covered works ++for you must do so exclusively on your behalf, under your direction ++and control, on terms that prohibit them from making any copies of ++your copyrighted material outside their relationship with you. ++ ++ Conveying under any other circumstances is permitted solely under ++the conditions stated below. Sublicensing is not allowed; section 10 ++makes it unnecessary. ++ ++ 3. Protecting Users' Legal Rights From Anti-Circumvention Law. ++ ++ No covered work shall be deemed part of an effective technological ++measure under any applicable law fulfilling obligations under article ++11 of the WIPO copyright treaty adopted on 20 December 1996, or ++similar laws prohibiting or restricting circumvention of such ++measures. ++ ++ When you convey a covered work, you waive any legal power to forbid ++circumvention of technological measures to the extent such circumvention ++is effected by exercising rights under this License with respect to ++the covered work, and you disclaim any intention to limit operation or ++modification of the work as a means of enforcing, against the work's ++users, your or third parties' legal rights to forbid circumvention of ++technological measures. ++ ++ 4. Conveying Verbatim Copies. ++ ++ You may convey verbatim copies of the Program's source code as you ++receive it, in any medium, provided that you conspicuously and ++appropriately publish on each copy an appropriate copyright notice; ++keep intact all notices stating that this License and any ++non-permissive terms added in accord with section 7 apply to the code; ++keep intact all notices of the absence of any warranty; and give all ++recipients a copy of this License along with the Program. ++ ++ You may charge any price or no price for each copy that you convey, ++and you may offer support or warranty protection for a fee. ++ ++ 5. Conveying Modified Source Versions. ++ ++ You may convey a work based on the Program, or the modifications to ++produce it from the Program, in the form of source code under the ++terms of section 4, provided that you also meet all of these conditions: ++ ++ a) The work must carry prominent notices stating that you modified ++ it, and giving a relevant date. ++ ++ b) The work must carry prominent notices stating that it is ++ released under this License and any conditions added under section ++ 7. This requirement modifies the requirement in section 4 to ++ "keep intact all notices". ++ ++ c) You must license the entire work, as a whole, under this ++ License to anyone who comes into possession of a copy. This ++ License will therefore apply, along with any applicable section 7 ++ additional terms, to the whole of the work, and all its parts, ++ regardless of how they are packaged. This License gives no ++ permission to license the work in any other way, but it does not ++ invalidate such permission if you have separately received it. ++ ++ d) If the work has interactive user interfaces, each must display ++ Appropriate Legal Notices; however, if the Program has interactive ++ interfaces that do not display Appropriate Legal Notices, your ++ work need not make them do so. ++ ++ A compilation of a covered work with other separate and independent ++works, which are not by their nature extensions of the covered work, ++and which are not combined with it such as to form a larger program, ++in or on a volume of a storage or distribution medium, is called an ++"aggregate" if the compilation and its resulting copyright are not ++used to limit the access or legal rights of the compilation's users ++beyond what the individual works permit. Inclusion of a covered work ++in an aggregate does not cause this License to apply to the other ++parts of the aggregate. ++ ++ 6. Conveying Non-Source Forms. ++ ++ You may convey a covered work in object code form under the terms ++of sections 4 and 5, provided that you also convey the ++machine-readable Corresponding Source under the terms of this License, ++in one of these ways: ++ ++ a) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by the ++ Corresponding Source fixed on a durable physical medium ++ customarily used for software interchange. ++ ++ b) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by a ++ written offer, valid for at least three years and valid for as ++ long as you offer spare parts or customer support for that product ++ model, to give anyone who possesses the object code either (1) a ++ copy of the Corresponding Source for all the software in the ++ product that is covered by this License, on a durable physical ++ medium customarily used for software interchange, for a price no ++ more than your reasonable cost of physically performing this ++ conveying of source, or (2) access to copy the ++ Corresponding Source from a network server at no charge. ++ ++ c) Convey individual copies of the object code with a copy of the ++ written offer to provide the Corresponding Source. This ++ alternative is allowed only occasionally and noncommercially, and ++ only if you received the object code with such an offer, in accord ++ with subsection 6b. ++ ++ d) Convey the object code by offering access from a designated ++ place (gratis or for a charge), and offer equivalent access to the ++ Corresponding Source in the same way through the same place at no ++ further charge. You need not require recipients to copy the ++ Corresponding Source along with the object code. If the place to ++ copy the object code is a network server, the Corresponding Source ++ may be on a different server (operated by you or a third party) ++ that supports equivalent copying facilities, provided you maintain ++ clear directions next to the object code saying where to find the ++ Corresponding Source. Regardless of what server hosts the ++ Corresponding Source, you remain obligated to ensure that it is ++ available for as long as needed to satisfy these requirements. ++ ++ e) Convey the object code using peer-to-peer transmission, provided ++ you inform other peers where the object code and Corresponding ++ Source of the work are being offered to the general public at no ++ charge under subsection 6d. ++ ++ A separable portion of the object code, whose source code is excluded ++from the Corresponding Source as a System Library, need not be ++included in conveying the object code work. ++ ++ A "User Product" is either (1) a "consumer product", which means any ++tangible personal property which is normally used for personal, family, ++or household purposes, or (2) anything designed or sold for incorporation ++into a dwelling. In determining whether a product is a consumer product, ++doubtful cases shall be resolved in favor of coverage. For a particular ++product received by a particular user, "normally used" refers to a ++typical or common use of that class of product, regardless of the status ++of the particular user or of the way in which the particular user ++actually uses, or expects or is expected to use, the product. A product ++is a consumer product regardless of whether the product has substantial ++commercial, industrial or non-consumer uses, unless such uses represent ++the only significant mode of use of the product. ++ ++ "Installation Information" for a User Product means any methods, ++procedures, authorization keys, or other information required to install ++and execute modified versions of a covered work in that User Product from ++a modified version of its Corresponding Source. The information must ++suffice to ensure that the continued functioning of the modified object ++code is in no case prevented or interfered with solely because ++modification has been made. ++ ++ If you convey an object code work under this section in, or with, or ++specifically for use in, a User Product, and the conveying occurs as ++part of a transaction in which the right of possession and use of the ++User Product is transferred to the recipient in perpetuity or for a ++fixed term (regardless of how the transaction is characterized), the ++Corresponding Source conveyed under this section must be accompanied ++by the Installation Information. But this requirement does not apply ++if neither you nor any third party retains the ability to install ++modified object code on the User Product (for example, the work has ++been installed in ROM). ++ ++ The requirement to provide Installation Information does not include a ++requirement to continue to provide support service, warranty, or updates ++for a work that has been modified or installed by the recipient, or for ++the User Product in which it has been modified or installed. Access to a ++network may be denied when the modification itself materially and ++adversely affects the operation of the network or violates the rules and ++protocols for communication across the network. ++ ++ Corresponding Source conveyed, and Installation Information provided, ++in accord with this section must be in a format that is publicly ++documented (and with an implementation available to the public in ++source code form), and must require no special password or key for ++unpacking, reading or copying. ++ ++ 7. Additional Terms. ++ ++ "Additional permissions" are terms that supplement the terms of this ++License by making exceptions from one or more of its conditions. ++Additional permissions that are applicable to the entire Program shall ++be treated as though they were included in this License, to the extent ++that they are valid under applicable law. If additional permissions ++apply only to part of the Program, that part may be used separately ++under those permissions, but the entire Program remains governed by ++this License without regard to the additional permissions. ++ ++ When you convey a copy of a covered work, you may at your option ++remove any additional permissions from that copy, or from any part of ++it. (Additional permissions may be written to require their own ++removal in certain cases when you modify the work.) You may place ++additional permissions on material, added by you to a covered work, ++for which you have or can give appropriate copyright permission. ++ ++ Notwithstanding any other provision of this License, for material you ++add to a covered work, you may (if authorized by the copyright holders of ++that material) supplement the terms of this License with terms: ++ ++ a) Disclaiming warranty or limiting liability differently from the ++ terms of sections 15 and 16 of this License; or ++ ++ b) Requiring preservation of specified reasonable legal notices or ++ author attributions in that material or in the Appropriate Legal ++ Notices displayed by works containing it; or ++ ++ c) Prohibiting misrepresentation of the origin of that material, or ++ requiring that modified versions of such material be marked in ++ reasonable ways as different from the original version; or ++ ++ d) Limiting the use for publicity purposes of names of licensors or ++ authors of the material; or ++ ++ e) Declining to grant rights under trademark law for use of some ++ trade names, trademarks, or service marks; or ++ ++ f) Requiring indemnification of licensors and authors of that ++ material by anyone who conveys the material (or modified versions of ++ it) with contractual assumptions of liability to the recipient, for ++ any liability that these contractual assumptions directly impose on ++ those licensors and authors. ++ ++ All other non-permissive additional terms are considered "further ++restrictions" within the meaning of section 10. If the Program as you ++received it, or any part of it, contains a notice stating that it is ++governed by this License along with a term that is a further ++restriction, you may remove that term. If a license document contains ++a further restriction but permits relicensing or conveying under this ++License, you may add to a covered work material governed by the terms ++of that license document, provided that the further restriction does ++not survive such relicensing or conveying. ++ ++ If you add terms to a covered work in accord with this section, you ++must place, in the relevant source files, a statement of the ++additional terms that apply to those files, or a notice indicating ++where to find the applicable terms. ++ ++ Additional terms, permissive or non-permissive, may be stated in the ++form of a separately written license, or stated as exceptions; ++the above requirements apply either way. ++ ++ 8. Termination. ++ ++ You may not propagate or modify a covered work except as expressly ++provided under this License. Any attempt otherwise to propagate or ++modify it is void, and will automatically terminate your rights under ++this License (including any patent licenses granted under the third ++paragraph of section 11). ++ ++ However, if you cease all violation of this License, then your ++license from a particular copyright holder is reinstated (a) ++provisionally, unless and until the copyright holder explicitly and ++finally terminates your license, and (b) permanently, if the copyright ++holder fails to notify you of the violation by some reasonable means ++prior to 60 days after the cessation. ++ ++ Moreover, your license from a particular copyright holder is ++reinstated permanently if the copyright holder notifies you of the ++violation by some reasonable means, this is the first time you have ++received notice of violation of this License (for any work) from that ++copyright holder, and you cure the violation prior to 30 days after ++your receipt of the notice. ++ ++ Termination of your rights under this section does not terminate the ++licenses of parties who have received copies or rights from you under ++this License. If your rights have been terminated and not permanently ++reinstated, you do not qualify to receive new licenses for the same ++material under section 10. ++ ++ 9. Acceptance Not Required for Having Copies. ++ ++ You are not required to accept this License in order to receive or ++run a copy of the Program. Ancillary propagation of a covered work ++occurring solely as a consequence of using peer-to-peer transmission ++to receive a copy likewise does not require acceptance. However, ++nothing other than this License grants you permission to propagate or ++modify any covered work. These actions infringe copyright if you do ++not accept this License. Therefore, by modifying or propagating a ++covered work, you indicate your acceptance of this License to do so. ++ ++ 10. Automatic Licensing of Downstream Recipients. ++ ++ Each time you convey a covered work, the recipient automatically ++receives a license from the original licensors, to run, modify and ++propagate that work, subject to this License. You are not responsible ++for enforcing compliance by third parties with this License. ++ ++ An "entity transaction" is a transaction transferring control of an ++organization, or substantially all assets of one, or subdividing an ++organization, or merging organizations. If propagation of a covered ++work results from an entity transaction, each party to that ++transaction who receives a copy of the work also receives whatever ++licenses to the work the party's predecessor in interest had or could ++give under the previous paragraph, plus a right to possession of the ++Corresponding Source of the work from the predecessor in interest, if ++the predecessor has it or can get it with reasonable efforts. ++ ++ You may not impose any further restrictions on the exercise of the ++rights granted or affirmed under this License. For example, you may ++not impose a license fee, royalty, or other charge for exercise of ++rights granted under this License, and you may not initiate litigation ++(including a cross-claim or counterclaim in a lawsuit) alleging that ++any patent claim is infringed by making, using, selling, offering for ++sale, or importing the Program or any portion of it. ++ ++ 11. Patents. ++ ++ A "contributor" is a copyright holder who authorizes use under this ++License of the Program or a work on which the Program is based. The ++work thus licensed is called the contributor's "contributor version". ++ ++ A contributor's "essential patent claims" are all patent claims ++owned or controlled by the contributor, whether already acquired or ++hereafter acquired, that would be infringed by some manner, permitted ++by this License, of making, using, or selling its contributor version, ++but do not include claims that would be infringed only as a ++consequence of further modification of the contributor version. For ++purposes of this definition, "control" includes the right to grant ++patent sublicenses in a manner consistent with the requirements of ++this License. ++ ++ Each contributor grants you a non-exclusive, worldwide, royalty-free ++patent license under the contributor's essential patent claims, to ++make, use, sell, offer for sale, import and otherwise run, modify and ++propagate the contents of its contributor version. ++ ++ In the following three paragraphs, a "patent license" is any express ++agreement or commitment, however denominated, not to enforce a patent ++(such as an express permission to practice a patent or covenant not to ++sue for patent infringement). To "grant" such a patent license to a ++party means to make such an agreement or commitment not to enforce a ++patent against the party. ++ ++ If you convey a covered work, knowingly relying on a patent license, ++and the Corresponding Source of the work is not available for anyone ++to copy, free of charge and under the terms of this License, through a ++publicly available network server or other readily accessible means, ++then you must either (1) cause the Corresponding Source to be so ++available, or (2) arrange to deprive yourself of the benefit of the ++patent license for this particular work, or (3) arrange, in a manner ++consistent with the requirements of this License, to extend the patent ++license to downstream recipients. "Knowingly relying" means you have ++actual knowledge that, but for the patent license, your conveying the ++covered work in a country, or your recipient's use of the covered work ++in a country, would infringe one or more identifiable patents in that ++country that you have reason to believe are valid. ++ ++ If, pursuant to or in connection with a single transaction or ++arrangement, you convey, or propagate by procuring conveyance of, a ++covered work, and grant a patent license to some of the parties ++receiving the covered work authorizing them to use, propagate, modify ++or convey a specific copy of the covered work, then the patent license ++you grant is automatically extended to all recipients of the covered ++work and works based on it. ++ ++ A patent license is "discriminatory" if it does not include within ++the scope of its coverage, prohibits the exercise of, or is ++conditioned on the non-exercise of one or more of the rights that are ++specifically granted under this License. You may not convey a covered ++work if you are a party to an arrangement with a third party that is ++in the business of distributing software, under which you make payment ++to the third party based on the extent of your activity of conveying ++the work, and under which the third party grants, to any of the ++parties who would receive the covered work from you, a discriminatory ++patent license (a) in connection with copies of the covered work ++conveyed by you (or copies made from those copies), or (b) primarily ++for and in connection with specific products or compilations that ++contain the covered work, unless you entered into that arrangement, ++or that patent license was granted, prior to 28 March 2007. ++ ++ Nothing in this License shall be construed as excluding or limiting ++any implied license or other defenses to infringement that may ++otherwise be available to you under applicable patent law. ++ ++ 12. No Surrender of Others' Freedom. ++ ++ If conditions are imposed on you (whether by court order, agreement or ++otherwise) that contradict the conditions of this License, they do not ++excuse you from the conditions of this License. If you cannot convey a ++covered work so as to satisfy simultaneously your obligations under this ++License and any other pertinent obligations, then as a consequence you may ++not convey it at all. For example, if you agree to terms that obligate you ++to collect a royalty for further conveying from those to whom you convey ++the Program, the only way you could satisfy both those terms and this ++License would be to refrain entirely from conveying the Program. ++ ++ 13. Use with the GNU Affero General Public License. ++ ++ Notwithstanding any other provision of this License, you have ++permission to link or combine any covered work with a work licensed ++under version 3 of the GNU Affero General Public License into a single ++combined work, and to convey the resulting work. The terms of this ++License will continue to apply to the part which is the covered work, ++but the special requirements of the GNU Affero General Public License, ++section 13, concerning interaction through a network will apply to the ++combination as such. ++ ++ 14. Revised Versions of this License. ++ ++ The Free Software Foundation may publish revised and/or new versions of ++the GNU General Public License from time to time. Such new versions will ++be similar in spirit to the present version, but may differ in detail to ++address new problems or concerns. ++ ++ Each version is given a distinguishing version number. If the ++Program specifies that a certain numbered version of the GNU General ++Public License "or any later version" applies to it, you have the ++option of following the terms and conditions either of that numbered ++version or of any later version published by the Free Software ++Foundation. If the Program does not specify a version number of the ++GNU General Public License, you may choose any version ever published ++by the Free Software Foundation. ++ ++ If the Program specifies that a proxy can decide which future ++versions of the GNU General Public License can be used, that proxy's ++public statement of acceptance of a version permanently authorizes you ++to choose that version for the Program. ++ ++ Later license versions may give you additional or different ++permissions. However, no additional obligations are imposed on any ++author or copyright holder as a result of your choosing to follow a ++later version. ++ ++ 15. Disclaimer of Warranty. ++ ++ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY ++APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT ++HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY ++OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ++THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM ++IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ++ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ++ ++ 16. Limitation of Liability. ++ ++ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING ++WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS ++THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY ++GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE ++USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF ++DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD ++PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), ++EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF ++SUCH DAMAGES. ++ ++ 17. Interpretation of Sections 15 and 16. ++ ++ If the disclaimer of warranty and limitation of liability provided ++above cannot be given local legal effect according to their terms, ++reviewing courts shall apply local law that most closely approximates ++an absolute waiver of all civil liability in connection with the ++Program, unless a warranty or assumption of liability accompanies a ++copy of the Program in return for a fee. ++ ++ END OF TERMS AND CONDITIONS ++ ++ How to Apply These Terms to Your New Programs ++ ++ If you develop a new program, and you want it to be of the greatest ++possible use to the public, the best way to achieve this is to make it ++free software which everyone can redistribute and change under these terms. ++ ++ To do so, attach the following notices to the program. It is safest ++to attach them to the start of each source file to most effectively ++state the exclusion of warranty; and each file should have at least ++the "copyright" line and a pointer to where the full notice is found. ++ ++ ++ Copyright (C) ++ ++ This program is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation, either version 3 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see . ++ ++Also add information on how to contact you by electronic and paper mail. ++ ++ If the program does terminal interaction, make it output a short ++notice like this when it starts in an interactive mode: ++ ++ Copyright (C) ++ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. ++ This is free software, and you are welcome to redistribute it ++ under certain conditions; type `show c' for details. ++ ++The hypothetical commands `show w' and `show c' should show the appropriate ++parts of the General Public License. Of course, your program's commands ++might be different; for a GUI interface, you would use an "about box". ++ ++ You should also get your employer (if you work as a programmer) or school, ++if any, to sign a "copyright disclaimer" for the program, if necessary. ++For more information on this, and how to apply and follow the GNU GPL, see ++. ++ ++ The GNU General Public License does not permit incorporating your program ++into proprietary programs. If your program is a subroutine library, you ++may consider it more useful to permit linking proprietary applications with ++the library. If this is what you want to do, use the GNU Lesser General ++Public License instead of this License. But first, please read ++. +--- a/proxy/tuic/client.go ++++ b/proxy/tuic/client.go +@@ -0,0 +1,230 @@ ++package tuic ++ ++import ( ++ "context" ++ "crypto/x509" ++ stderrors "errors" ++ "github.com/gofrs/uuid/v5" ++ C "github.com/metacubex/mihomo/constant" ++ T "github.com/metacubex/mihomo/transport/tuic" ++ TC "github.com/metacubex/mihomo/transport/tuic/common" ++ Q "github.com/metacubex/quic-go" ++ TLS "github.com/metacubex/tls" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/errors" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/core" ++ "github.com/xtls/xray-core/features/policy" ++ "github.com/xtls/xray-core/transport" ++ "github.com/xtls/xray-core/transport/internet" ++ "net/netip" ++ "os" ++ "sync" ++ "time" ++) ++ ++type Client struct { ++ config *ClientConfig ++ tls *TLS.Config ++ policy policy.Session ++ mu sync.Mutex ++ clients []T.Client ++ udpClients map[T.Client]bool ++ closed bool ++ connections map[*Q.Conn]struct{} ++} ++ ++func NewClient(ctx context.Context, c *ClientConfig) (*Client, error) { ++ if c.Address == nil || c.Port == 0 || c.Port > 65535 { ++ return nil, errors.New("tuic: invalid server") ++ } ++ name := c.ServerName ++ if name == "" { ++ name = c.Address.AsAddress().String() ++ } ++ tls := &TLS.Config{ServerName: name, InsecureSkipVerify: c.AllowInsecure, NextProtos: c.Alpn, MinVersion: TLS.VersionTLS13} ++ if len(tls.NextProtos) == 0 { ++ tls.NextProtos = []string{"h3"} ++ } ++ if c.CertificateFile != "" { ++ pem, err := os.ReadFile(c.CertificateFile) ++ if err != nil { ++ return nil, err ++ } ++ roots, err := x509.SystemCertPool() ++ if err != nil { ++ roots = x509.NewCertPool() ++ } ++ if !roots.AppendCertsFromPEM(pem) { ++ return nil, errors.New("tuic: invalid CA") ++ } ++ tls.RootCAs = roots ++ } ++ return &Client{config: c, tls: tls, policy: core.MustFromContext(ctx).GetFeature(policy.ManagerType()).(policy.Manager).ForLevel(c.Level)}, nil ++} ++func (c *Client) Close() error { ++ c.mu.Lock() ++ defer c.mu.Unlock() ++ c.closed = true ++ for conn := range c.connections { ++ conn.CloseWithError(0, "outbound closed") ++ } ++ for _, v := range c.clients { ++ v.Close() ++ } ++ c.clients = nil ++ return nil ++} ++func (c *Client) newClient(dialer internet.Dialer, udp bool, force bool) (T.Client, error) { ++ c.mu.Lock() ++ defer c.mu.Unlock() ++ if c.closed { ++ return nil, errors.New("tuic: closed") ++ } ++ active := c.clients[:0] ++ for _, v := range c.clients { ++ if v.OpenStreams() == 0 && !v.LastVisited().IsZero() && time.Since(v.LastVisited()) > 10*time.Minute { ++ v.Close() ++ delete(c.udpClients, v) ++ } else { ++ active = append(active, v) ++ } ++ } ++ c.clients = active ++ for _, v := range c.clients { ++ if !force && c.udpClients[v] == udp && v.OpenStreams()+1 < int64(c.config.MaxOpenStreams) { ++ v.SetLastVisited(time.Now()) ++ return v, nil ++ } ++ } ++ if c.udpClients == nil { ++ c.udpClients = make(map[T.Client]bool) ++ } ++ conf := c.config ++ dial := func(ctx context.Context) (*Q.Conn, error) { ++ raw, err := dialer.Dial(ctx, net.UDPDestination(conf.Address.AsAddress(), net.Port(conf.Port))) ++ if err != nil { ++ return nil, err ++ } ++ pc := &connectedPacketConn{Conn: raw} ++ qc := &Q.Config{EnableDatagrams: true, MaxIncomingStreams: 1024, MaxIncomingUniStreams: 1024, KeepAlivePeriod: time.Duration(conf.HeartbeatInterval) * time.Millisecond, MaxDatagramFrameSize: 1400, InitialStreamReceiveWindow: TC.DefaultStreamReceiveWindow / 10, MaxStreamReceiveWindow: TC.DefaultStreamReceiveWindow, InitialConnectionReceiveWindow: TC.DefaultConnectionReceiveWindow / 10, MaxConnectionReceiveWindow: TC.DefaultConnectionReceiveWindow} ++ tr := &Q.Transport{Conn: pc} ++ var conn *Q.Conn ++ if conf.ReduceRtt { ++ conn, err = tr.DialEarly(ctx, raw.RemoteAddr(), c.tls, qc) ++ } else { ++ conn, err = tr.Dial(ctx, raw.RemoteAddr(), c.tls, qc) ++ } ++ if err != nil { ++ tr.Close() ++ raw.Close() ++ return nil, err ++ } ++ c.mu.Lock() ++ if c.closed { ++ c.mu.Unlock() ++ conn.CloseWithError(0, "closed") ++ tr.Close() ++ raw.Close() ++ return nil, errors.New("tuic: closed") ++ } ++ if c.connections == nil { ++ c.connections = make(map[*Q.Conn]struct{}) ++ } ++ c.connections[conn] = struct{}{} ++ c.mu.Unlock() ++ TC.SetCongestionController(conn, conf.CongestionControl, 32, "") ++ go func() { ++ <-conn.Context().Done() ++ c.mu.Lock() ++ delete(c.connections, conn) ++ c.mu.Unlock() ++ tr.Close() ++ raw.Close() ++ }() ++ return conn, nil ++ } ++ mode := T.NATIVE ++ if conf.UdpRelayMode == "quic" { ++ mode = T.QUIC ++ } ++ var v T.Client ++ if conf.Version == 4 { ++ v = T.NewClientV4(&T.ClientOptionV4{Token: T.GenTKN(conf.Token), UdpRelayMode: mode, RequestTimeout: time.Duration(conf.RequestTimeout) * time.Millisecond, FastOpen: conf.FastOpen, MaxOpenStreams: int64(conf.MaxOpenStreams), MaxUdpRelayPacketSize: int(conf.MaxUdpRelayPacketSize)}, udp, dial) ++ } else { ++ id, _ := uuid.FromString(conf.Uuid) ++ v = T.NewClientV5(&T.ClientOptionV5{Uuid: id, Password: conf.Password, UdpRelayMode: mode, MaxOpenStreams: int64(conf.MaxOpenStreams), MaxUdpRelayPacketSize: int(conf.MaxUdpRelayPacketSize)}, udp, dial) ++ } ++ v.SetLastVisited(time.Now()) ++ c.clients = append(c.clients, v) ++ c.udpClients[v] = udp ++ return v, nil ++} ++func metadata(d net.Destination) *C.Metadata { ++ m := &C.Metadata{DstPort: uint16(d.Port)} ++ if d.Address.Family().IsDomain() { ++ m.Host = d.Address.Domain() ++ } else { ++ m.DstIP, _ = netip.AddrFromSlice(d.Address.IP()) ++ } ++ return m ++} ++func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { ++ obs := session.OutboundsFromContext(ctx) ++ if len(obs) == 0 { ++ return errors.New("tuic: missing target") ++ } ++ ob := obs[len(obs)-1] ++ target := ob.Target ++ if !target.IsValid() || (target.Network != net.Network_TCP && target.Network != net.Network_UDP) { ++ return errors.New("tuic: invalid target") ++ } ++ ob.Name = "tuic" ++ ob.CanSpliceCopy = 3 ++ v, err := c.newClient(dialer, target.Network == net.Network_UDP, false) ++ if err != nil { ++ return err ++ } ++ var reader buf.Reader ++ var writer buf.Writer ++ var stopIO func() ++ if target.Network == net.Network_TCP { ++ conn, e := v.DialContext(ctx, metadata(target)) ++ if stderrors.Is(e, T.TooManyOpenStreams) { ++ v, e = c.newClient(dialer, false, true) ++ if e == nil { ++ conn, e = v.DialContext(ctx, metadata(target)) ++ } ++ } ++ if e != nil { ++ return e ++ } ++ defer conn.Close() ++ stopIO = func() { conn.SetDeadline(time.Now()) } ++ reader = buf.NewReader(conn) ++ writer = buf.NewWriter(conn) ++ } else { ++ pc, e := v.ListenPacket(ctx, metadata(target)) ++ if stderrors.Is(e, T.TooManyOpenStreams) { ++ v, e = c.newClient(dialer, true, true) ++ if e == nil { ++ pc, e = v.ListenPacket(ctx, metadata(target)) ++ } ++ } ++ if e != nil { ++ return e ++ } ++ defer pc.Close() ++ stopIO = func() { pc.SetReadDeadline(time.Now()) } ++ reader = &packetReader{conn: pc} ++ writer = &packetWriter{conn: pc, destination: target} ++ } ++ return relay(ctx, link.Reader, writer, reader, link.Writer, stopIO, c.policy) ++} ++func init() { ++ common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, c interface{}) (interface{}, error) { ++ return NewClient(ctx, c.(*ClientConfig)) ++ })) ++} +--- a/proxy/tuic/config.pb.go ++++ b/proxy/tuic/config.pb.go +@@ -0,0 +1,290 @@ ++// Code generated by protoc-gen-go. DO NOT EDIT. ++// versions: ++// protoc-gen-go v1.36.12 ++// protoc v3.21.12 ++// source: proxy/tuic/config.proto ++ ++package tuic ++ ++import ( ++ net "github.com/xtls/xray-core/common/net" ++ protoreflect "google.golang.org/protobuf/reflect/protoreflect" ++ protoimpl "google.golang.org/protobuf/runtime/protoimpl" ++ reflect "reflect" ++ sync "sync" ++ unsafe "unsafe" ++) ++ ++const ( ++ // Verify that this generated code is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) ++ // Verify that runtime/protoimpl is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ++) ++ ++type ClientConfig struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` ++ Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` ++ Version uint32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` ++ Token string `protobuf:"bytes,4,opt,name=token,proto3" json:"token,omitempty"` ++ Uuid string `protobuf:"bytes,5,opt,name=uuid,proto3" json:"uuid,omitempty"` ++ Password string `protobuf:"bytes,6,opt,name=password,proto3" json:"password,omitempty"` ++ ServerName string `protobuf:"bytes,7,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` ++ CertificateFile string `protobuf:"bytes,8,opt,name=certificate_file,json=certificateFile,proto3" json:"certificate_file,omitempty"` ++ AllowInsecure bool `protobuf:"varint,9,opt,name=allow_insecure,json=allowInsecure,proto3" json:"allow_insecure,omitempty"` ++ Alpn []string `protobuf:"bytes,10,rep,name=alpn,proto3" json:"alpn,omitempty"` ++ CongestionControl string `protobuf:"bytes,11,opt,name=congestion_control,json=congestionControl,proto3" json:"congestion_control,omitempty"` ++ UdpRelayMode string `protobuf:"bytes,12,opt,name=udp_relay_mode,json=udpRelayMode,proto3" json:"udp_relay_mode,omitempty"` ++ ReduceRtt bool `protobuf:"varint,13,opt,name=reduce_rtt,json=reduceRtt,proto3" json:"reduce_rtt,omitempty"` ++ FastOpen bool `protobuf:"varint,14,opt,name=fast_open,json=fastOpen,proto3" json:"fast_open,omitempty"` ++ HeartbeatInterval uint32 `protobuf:"varint,15,opt,name=heartbeat_interval,json=heartbeatInterval,proto3" json:"heartbeat_interval,omitempty"` ++ RequestTimeout uint32 `protobuf:"varint,16,opt,name=request_timeout,json=requestTimeout,proto3" json:"request_timeout,omitempty"` ++ MaxOpenStreams uint32 `protobuf:"varint,17,opt,name=max_open_streams,json=maxOpenStreams,proto3" json:"max_open_streams,omitempty"` ++ MaxUdpRelayPacketSize uint32 `protobuf:"varint,18,opt,name=max_udp_relay_packet_size,json=maxUdpRelayPacketSize,proto3" json:"max_udp_relay_packet_size,omitempty"` ++ Level uint32 `protobuf:"varint,19,opt,name=level,proto3" json:"level,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *ClientConfig) Reset() { ++ *x = ClientConfig{} ++ mi := &file_proxy_tuic_config_proto_msgTypes[0] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *ClientConfig) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*ClientConfig) ProtoMessage() {} ++ ++func (x *ClientConfig) ProtoReflect() protoreflect.Message { ++ mi := &file_proxy_tuic_config_proto_msgTypes[0] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. ++func (*ClientConfig) Descriptor() ([]byte, []int) { ++ return file_proxy_tuic_config_proto_rawDescGZIP(), []int{0} ++} ++ ++func (x *ClientConfig) GetAddress() *net.IPOrDomain { ++ if x != nil { ++ return x.Address ++ } ++ return nil ++} ++ ++func (x *ClientConfig) GetPort() uint32 { ++ if x != nil { ++ return x.Port ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetVersion() uint32 { ++ if x != nil { ++ return x.Version ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetToken() string { ++ if x != nil { ++ return x.Token ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetUuid() string { ++ if x != nil { ++ return x.Uuid ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetPassword() string { ++ if x != nil { ++ return x.Password ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetServerName() string { ++ if x != nil { ++ return x.ServerName ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetCertificateFile() string { ++ if x != nil { ++ return x.CertificateFile ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetAllowInsecure() bool { ++ if x != nil { ++ return x.AllowInsecure ++ } ++ return false ++} ++ ++func (x *ClientConfig) GetAlpn() []string { ++ if x != nil { ++ return x.Alpn ++ } ++ return nil ++} ++ ++func (x *ClientConfig) GetCongestionControl() string { ++ if x != nil { ++ return x.CongestionControl ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetUdpRelayMode() string { ++ if x != nil { ++ return x.UdpRelayMode ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetReduceRtt() bool { ++ if x != nil { ++ return x.ReduceRtt ++ } ++ return false ++} ++ ++func (x *ClientConfig) GetFastOpen() bool { ++ if x != nil { ++ return x.FastOpen ++ } ++ return false ++} ++ ++func (x *ClientConfig) GetHeartbeatInterval() uint32 { ++ if x != nil { ++ return x.HeartbeatInterval ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetRequestTimeout() uint32 { ++ if x != nil { ++ return x.RequestTimeout ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetMaxOpenStreams() uint32 { ++ if x != nil { ++ return x.MaxOpenStreams ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetMaxUdpRelayPacketSize() uint32 { ++ if x != nil { ++ return x.MaxUdpRelayPacketSize ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetLevel() uint32 { ++ if x != nil { ++ return x.Level ++ } ++ return 0 ++} ++ ++var File_proxy_tuic_config_proto protoreflect.FileDescriptor ++ ++const file_proxy_tuic_config_proto_rawDesc = "" + ++ "\n" + ++ "\x17proxy/tuic/config.proto\x12\x0fxray.proxy.tuic\x1a\x18common/net/address.proto\"\xa3\x05\n" + ++ "\fClientConfig\x125\n" + ++ "\aaddress\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" + ++ "\x04port\x18\x02 \x01(\rR\x04port\x12\x18\n" + ++ "\aversion\x18\x03 \x01(\rR\aversion\x12\x14\n" + ++ "\x05token\x18\x04 \x01(\tR\x05token\x12\x12\n" + ++ "\x04uuid\x18\x05 \x01(\tR\x04uuid\x12\x1a\n" + ++ "\bpassword\x18\x06 \x01(\tR\bpassword\x12\x1f\n" + ++ "\vserver_name\x18\a \x01(\tR\n" + ++ "serverName\x12)\n" + ++ "\x10certificate_file\x18\b \x01(\tR\x0fcertificateFile\x12%\n" + ++ "\x0eallow_insecure\x18\t \x01(\bR\rallowInsecure\x12\x12\n" + ++ "\x04alpn\x18\n" + ++ " \x03(\tR\x04alpn\x12-\n" + ++ "\x12congestion_control\x18\v \x01(\tR\x11congestionControl\x12$\n" + ++ "\x0eudp_relay_mode\x18\f \x01(\tR\fudpRelayMode\x12\x1d\n" + ++ "\n" + ++ "reduce_rtt\x18\r \x01(\bR\treduceRtt\x12\x1b\n" + ++ "\tfast_open\x18\x0e \x01(\bR\bfastOpen\x12-\n" + ++ "\x12heartbeat_interval\x18\x0f \x01(\rR\x11heartbeatInterval\x12'\n" + ++ "\x0frequest_timeout\x18\x10 \x01(\rR\x0erequestTimeout\x12(\n" + ++ "\x10max_open_streams\x18\x11 \x01(\rR\x0emaxOpenStreams\x128\n" + ++ "\x19max_udp_relay_packet_size\x18\x12 \x01(\rR\x15maxUdpRelayPacketSize\x12\x14\n" + ++ "\x05level\x18\x13 \x01(\rR\x05levelB&Z$github.com/xtls/xray-core/proxy/tuicb\x06proto3" ++ ++var ( ++ file_proxy_tuic_config_proto_rawDescOnce sync.Once ++ file_proxy_tuic_config_proto_rawDescData []byte ++) ++ ++func file_proxy_tuic_config_proto_rawDescGZIP() []byte { ++ file_proxy_tuic_config_proto_rawDescOnce.Do(func() { ++ file_proxy_tuic_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_tuic_config_proto_rawDesc), len(file_proxy_tuic_config_proto_rawDesc))) ++ }) ++ return file_proxy_tuic_config_proto_rawDescData ++} ++ ++var file_proxy_tuic_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) ++var file_proxy_tuic_config_proto_goTypes = []any{ ++ (*ClientConfig)(nil), // 0: xray.proxy.tuic.ClientConfig ++ (*net.IPOrDomain)(nil), // 1: xray.common.net.IPOrDomain ++} ++var file_proxy_tuic_config_proto_depIdxs = []int32{ ++ 1, // 0: xray.proxy.tuic.ClientConfig.address:type_name -> xray.common.net.IPOrDomain ++ 1, // [1:1] is the sub-list for method output_type ++ 1, // [1:1] is the sub-list for method input_type ++ 1, // [1:1] is the sub-list for extension type_name ++ 1, // [1:1] is the sub-list for extension extendee ++ 0, // [0:1] is the sub-list for field type_name ++} ++ ++func init() { file_proxy_tuic_config_proto_init() } ++func file_proxy_tuic_config_proto_init() { ++ if File_proxy_tuic_config_proto != nil { ++ return ++ } ++ type x struct{} ++ out := protoimpl.TypeBuilder{ ++ File: protoimpl.DescBuilder{ ++ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), ++ RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_tuic_config_proto_rawDesc), len(file_proxy_tuic_config_proto_rawDesc)), ++ NumEnums: 0, ++ NumMessages: 1, ++ NumExtensions: 0, ++ NumServices: 0, ++ }, ++ GoTypes: file_proxy_tuic_config_proto_goTypes, ++ DependencyIndexes: file_proxy_tuic_config_proto_depIdxs, ++ MessageInfos: file_proxy_tuic_config_proto_msgTypes, ++ }.Build() ++ File_proxy_tuic_config_proto = out.File ++ file_proxy_tuic_config_proto_goTypes = nil ++ file_proxy_tuic_config_proto_depIdxs = nil ++} +--- a/proxy/tuic/config.proto ++++ b/proxy/tuic/config.proto +@@ -0,0 +1,25 @@ ++syntax = "proto3"; ++package xray.proxy.tuic; ++option go_package = "github.com/xtls/xray-core/proxy/tuic"; ++import "common/net/address.proto"; ++message ClientConfig { ++ xray.common.net.IPOrDomain address = 1; ++ uint32 port = 2; ++ uint32 version = 3; ++ string token = 4; ++ string uuid = 5; ++ string password = 6; ++ string server_name = 7; ++ string certificate_file = 8; ++ bool allow_insecure = 9; ++ repeated string alpn = 10; ++ string congestion_control = 11; ++ string udp_relay_mode = 12; ++ bool reduce_rtt = 13; ++ bool fast_open = 14; ++ uint32 heartbeat_interval = 15; ++ uint32 request_timeout = 16; ++ uint32 max_open_streams = 17; ++ uint32 max_udp_relay_packet_size = 18; ++ uint32 level = 19; ++} +--- a/proxy/tuic/packet.go ++++ b/proxy/tuic/packet.go +@@ -0,0 +1,51 @@ ++package tuic ++ ++import ( ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/xtls/xray-core/common/buf" ++ N "github.com/xtls/xray-core/common/net" ++ "net" ++) ++ ++type connectedPacketConn struct{ net.Conn } ++ ++func (c *connectedPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { ++ n, e := c.Read(b) ++ return n, c.RemoteAddr(), e ++} ++func (c *connectedPacketConn) WriteTo(b []byte, _ net.Addr) (int, error) { return c.Write(b) } ++ ++type packetReader struct{ conn net.PacketConn } ++ ++func (r *packetReader) ReadMultiBuffer() (buf.MultiBuffer, error) { ++ data := make([]byte, 65535) ++ n, a, e := r.conn.ReadFrom(data) ++ if e != nil { ++ return nil, e ++ } ++ b := buf.FromBytes(data[:n]) ++ m := M.SocksaddrFromNet(a) ++ d := N.UDPDestination(N.ParseAddress(m.AddrString()), N.Port(m.Port)) ++ b.UDP = &d ++ return buf.MultiBuffer{b}, nil ++} ++ ++type packetWriter struct { ++ conn net.PacketConn ++ destination N.Destination ++} ++ ++func (w *packetWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { ++ defer buf.ReleaseMulti(mb) ++ for _, b := range mb { ++ d := w.destination ++ if b.UDP != nil { ++ d = *b.UDP ++ } ++ a := M.ParseSocksaddrHostPort(d.Address.String(), uint16(d.Port)) ++ if _, e := w.conn.WriteTo(b.Bytes(), a); e != nil { ++ return e ++ } ++ } ++ return nil ++} +--- a/proxy/tuic/relay.go ++++ b/proxy/tuic/relay.go +@@ -0,0 +1,60 @@ ++package tuic ++ ++import ( ++ "context" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/signal" ++ "github.com/xtls/xray-core/features/policy" ++) ++ ++// Stop blocked reads and join both copy loops before closing Mihomo packet state. ++func relay(ctx context.Context, upR buf.Reader, upW buf.Writer, downR buf.Reader, downW buf.Writer, stopIO func(), p policy.Session) error { ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) ++ defer timer.SetTimeout(0) ++ results := make(chan error, 2) ++ go func() { ++ e := buf.Copy(upR, upW, buf.UpdateActivity(timer)) ++ if e == nil { ++ common.Close(upW) ++ } ++ timer.SetTimeout(p.Timeouts.DownlinkOnly) ++ results <- e ++ }() ++ go func() { ++ e := buf.Copy(downR, downW, buf.UpdateActivity(timer)) ++ if e == nil { ++ common.Close(downW) ++ } ++ timer.SetTimeout(p.Timeouts.UplinkOnly) ++ results <- e ++ }() ++ var first error ++ count := 0 ++ for count < 2 { ++ select { ++ case e := <-results: ++ count++ ++ if e != nil { ++ first = e ++ } ++ case <-ctx.Done(): ++ first = ctx.Err() ++ } ++ if first != nil || count == 2 { ++ break ++ } ++ } ++ stopIO() ++ common.Interrupt(upR) ++ common.Interrupt(upW) ++ common.Interrupt(downR) ++ common.Interrupt(downW) ++ for count < 2 { ++ <-results ++ count++ ++ } ++ return first ++} +--- a/proxy/tuic/server.go ++++ b/proxy/tuic/server.go +@@ -0,0 +1,96 @@ ++package tuic ++ ++import ( ++ "context" ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/errors" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/protocol" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/common/signal" ++ "github.com/xtls/xray-core/common/task" ++ "github.com/xtls/xray-core/features/routing" ++ "github.com/xtls/xray-core/transport/internet" ++ "github.com/xtls/xray-core/transport/internet/stat" ++ T "github.com/xtls/xray-core/transport/internet/tuic" ++ "time" ++) ++ ++type Server struct{ config *T.Config } ++ ++func (s *Server) Network() []net.Network { return []net.Network{net.Network_TCP} } ++func (s *Server) Process(ctx context.Context, _ net.Network, conn stat.Connection, d routing.Dispatcher) error { ++ c, ok := stat.TryUnwrapStatsConn(conn).(*T.Conn) ++ if !ok { ++ return errors.New("tuic: requires TUIC transport") ++ } ++ defer c.Close() ++ if in := session.InboundFromContext(ctx); in != nil { ++ copy := *in ++ copy.Name = "tuic" ++ copy.User = &protocol.MemoryUser{Email: c.User, Level: s.config.Level} ++ ctx = session.ContextWithInbound(ctx, ©) ++ } ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ stop := context.AfterFunc(ctx, func() { c.Close() }) ++ defer stop() ++ timer := signal.CancelAfterInactivity(ctx, cancel, 5*time.Minute) ++ defer timer.SetTimeout(0) ++ link, err := d.Dispatch(ctx, c.Target) ++ if err != nil { ++ return err ++ } ++ var reader buf.Reader = buf.NewReader(c) ++ var writer buf.Writer = buf.NewWriter(c) ++ if c.Packets != nil { ++ reader = &inPacketReader{c} ++ writer = &inPacketWriter{c} ++ } ++ err = task.Run(ctx, func() error { return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) }, task.OnSuccess(func() error { return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)) }, task.Close(link.Writer))) ++ common.Interrupt(link.Reader) ++ common.Interrupt(link.Writer) ++ return err ++} ++ ++type inPacketReader struct{ c *T.Conn } ++ ++func (r *inPacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) { ++ p, e := r.c.ReadPacket() ++ if e != nil { ++ return nil, e ++ } ++ b := buf.FromBytes(append([]byte(nil), p.Packet.Data()...)) ++ p.Packet.Drop() ++ b.UDP = &p.Target ++ return buf.MultiBuffer{b}, nil ++} ++ ++type inPacketWriter struct{ c *T.Conn } ++ ++func (w *inPacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { ++ defer buf.ReleaseMulti(mb) ++ for _, b := range mb { ++ d := w.c.Target ++ if b.UDP != nil { ++ d = *b.UDP ++ } ++ a := M.ParseSocksaddrHostPort(d.Address.String(), uint16(d.Port)) ++ if e := w.c.WritePacket(b.Bytes(), a); e != nil { ++ return e ++ } ++ } ++ return nil ++} ++func init() { ++ common.Must(common.RegisterConfig((*T.Config)(nil), func(ctx context.Context, c interface{}) (interface{}, error) { ++ m, _ := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig) ++ if m == nil || m.ProtocolName != "tuic" { ++ return nil, errors.New("tuic inbound requires network tuic") ++ } ++ m.ProtocolSettings = c ++ return &Server{c.(*T.Config)}, nil ++ })) ++} +--- a/transport/internet/tuic/config.pb.go ++++ b/transport/internet/tuic/config.pb.go +@@ -0,0 +1,200 @@ ++// Code generated by protoc-gen-go. DO NOT EDIT. ++// versions: ++// protoc-gen-go v1.36.12 ++// protoc v3.21.12 ++// source: transport/internet/tuic/config.proto ++ ++package tuic ++ ++import ( ++ protoreflect "google.golang.org/protobuf/reflect/protoreflect" ++ protoimpl "google.golang.org/protobuf/runtime/protoimpl" ++ reflect "reflect" ++ sync "sync" ++ unsafe "unsafe" ++) ++ ++const ( ++ // Verify that this generated code is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) ++ // Verify that runtime/protoimpl is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ++) ++ ++type Config struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Tokens []string `protobuf:"bytes,1,rep,name=tokens,proto3" json:"tokens,omitempty"` ++ Users map[string]string `protobuf:"bytes,2,rep,name=users,proto3" json:"users,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` ++ CertificateFile string `protobuf:"bytes,3,opt,name=certificate_file,json=certificateFile,proto3" json:"certificate_file,omitempty"` ++ KeyFile string `protobuf:"bytes,4,opt,name=key_file,json=keyFile,proto3" json:"key_file,omitempty"` ++ Alpn []string `protobuf:"bytes,5,rep,name=alpn,proto3" json:"alpn,omitempty"` ++ CongestionControl string `protobuf:"bytes,6,opt,name=congestion_control,json=congestionControl,proto3" json:"congestion_control,omitempty"` ++ AuthenticationTimeout uint32 `protobuf:"varint,7,opt,name=authentication_timeout,json=authenticationTimeout,proto3" json:"authentication_timeout,omitempty"` ++ MaxUdpRelayPacketSize uint32 `protobuf:"varint,8,opt,name=max_udp_relay_packet_size,json=maxUdpRelayPacketSize,proto3" json:"max_udp_relay_packet_size,omitempty"` ++ Level uint32 `protobuf:"varint,9,opt,name=level,proto3" json:"level,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *Config) Reset() { ++ *x = Config{} ++ mi := &file_transport_internet_tuic_config_proto_msgTypes[0] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *Config) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*Config) ProtoMessage() {} ++ ++func (x *Config) ProtoReflect() protoreflect.Message { ++ mi := &file_transport_internet_tuic_config_proto_msgTypes[0] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use Config.ProtoReflect.Descriptor instead. ++func (*Config) Descriptor() ([]byte, []int) { ++ return file_transport_internet_tuic_config_proto_rawDescGZIP(), []int{0} ++} ++ ++func (x *Config) GetTokens() []string { ++ if x != nil { ++ return x.Tokens ++ } ++ return nil ++} ++ ++func (x *Config) GetUsers() map[string]string { ++ if x != nil { ++ return x.Users ++ } ++ return nil ++} ++ ++func (x *Config) GetCertificateFile() string { ++ if x != nil { ++ return x.CertificateFile ++ } ++ return "" ++} ++ ++func (x *Config) GetKeyFile() string { ++ if x != nil { ++ return x.KeyFile ++ } ++ return "" ++} ++ ++func (x *Config) GetAlpn() []string { ++ if x != nil { ++ return x.Alpn ++ } ++ return nil ++} ++ ++func (x *Config) GetCongestionControl() string { ++ if x != nil { ++ return x.CongestionControl ++ } ++ return "" ++} ++ ++func (x *Config) GetAuthenticationTimeout() uint32 { ++ if x != nil { ++ return x.AuthenticationTimeout ++ } ++ return 0 ++} ++ ++func (x *Config) GetMaxUdpRelayPacketSize() uint32 { ++ if x != nil { ++ return x.MaxUdpRelayPacketSize ++ } ++ return 0 ++} ++ ++func (x *Config) GetLevel() uint32 { ++ if x != nil { ++ return x.Level ++ } ++ return 0 ++} ++ ++var File_transport_internet_tuic_config_proto protoreflect.FileDescriptor ++ ++const file_transport_internet_tuic_config_proto_rawDesc = "" + ++ "\n" + ++ "$transport/internet/tuic/config.proto\x12\x1cxray.transport.internet.tuic\"\xb1\x03\n" + ++ "\x06Config\x12\x16\n" + ++ "\x06tokens\x18\x01 \x03(\tR\x06tokens\x12E\n" + ++ "\x05users\x18\x02 \x03(\v2/.xray.transport.internet.tuic.Config.UsersEntryR\x05users\x12)\n" + ++ "\x10certificate_file\x18\x03 \x01(\tR\x0fcertificateFile\x12\x19\n" + ++ "\bkey_file\x18\x04 \x01(\tR\akeyFile\x12\x12\n" + ++ "\x04alpn\x18\x05 \x03(\tR\x04alpn\x12-\n" + ++ "\x12congestion_control\x18\x06 \x01(\tR\x11congestionControl\x125\n" + ++ "\x16authentication_timeout\x18\a \x01(\rR\x15authenticationTimeout\x128\n" + ++ "\x19max_udp_relay_packet_size\x18\b \x01(\rR\x15maxUdpRelayPacketSize\x12\x14\n" + ++ "\x05level\x18\t \x01(\rR\x05level\x1a8\n" + ++ "\n" + ++ "UsersEntry\x12\x10\n" + ++ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + ++ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B3Z1github.com/xtls/xray-core/transport/internet/tuicb\x06proto3" ++ ++var ( ++ file_transport_internet_tuic_config_proto_rawDescOnce sync.Once ++ file_transport_internet_tuic_config_proto_rawDescData []byte ++) ++ ++func file_transport_internet_tuic_config_proto_rawDescGZIP() []byte { ++ file_transport_internet_tuic_config_proto_rawDescOnce.Do(func() { ++ file_transport_internet_tuic_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_tuic_config_proto_rawDesc), len(file_transport_internet_tuic_config_proto_rawDesc))) ++ }) ++ return file_transport_internet_tuic_config_proto_rawDescData ++} ++ ++var file_transport_internet_tuic_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) ++var file_transport_internet_tuic_config_proto_goTypes = []any{ ++ (*Config)(nil), // 0: xray.transport.internet.tuic.Config ++ nil, // 1: xray.transport.internet.tuic.Config.UsersEntry ++} ++var file_transport_internet_tuic_config_proto_depIdxs = []int32{ ++ 1, // 0: xray.transport.internet.tuic.Config.users:type_name -> xray.transport.internet.tuic.Config.UsersEntry ++ 1, // [1:1] is the sub-list for method output_type ++ 1, // [1:1] is the sub-list for method input_type ++ 1, // [1:1] is the sub-list for extension type_name ++ 1, // [1:1] is the sub-list for extension extendee ++ 0, // [0:1] is the sub-list for field type_name ++} ++ ++func init() { file_transport_internet_tuic_config_proto_init() } ++func file_transport_internet_tuic_config_proto_init() { ++ if File_transport_internet_tuic_config_proto != nil { ++ return ++ } ++ type x struct{} ++ out := protoimpl.TypeBuilder{ ++ File: protoimpl.DescBuilder{ ++ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), ++ RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_tuic_config_proto_rawDesc), len(file_transport_internet_tuic_config_proto_rawDesc)), ++ NumEnums: 0, ++ NumMessages: 2, ++ NumExtensions: 0, ++ NumServices: 0, ++ }, ++ GoTypes: file_transport_internet_tuic_config_proto_goTypes, ++ DependencyIndexes: file_transport_internet_tuic_config_proto_depIdxs, ++ MessageInfos: file_transport_internet_tuic_config_proto_msgTypes, ++ }.Build() ++ File_transport_internet_tuic_config_proto = out.File ++ file_transport_internet_tuic_config_proto_goTypes = nil ++ file_transport_internet_tuic_config_proto_depIdxs = nil ++} +--- a/transport/internet/tuic/config.proto ++++ b/transport/internet/tuic/config.proto +@@ -0,0 +1,14 @@ ++syntax = "proto3"; ++package xray.transport.internet.tuic; ++option go_package = "github.com/xtls/xray-core/transport/internet/tuic"; ++message Config { ++ repeated string tokens = 1; ++ map users = 2; ++ string certificate_file = 3; ++ string key_file = 4; ++ repeated string alpn = 5; ++ string congestion_control = 6; ++ uint32 authentication_timeout = 7; ++ uint32 max_udp_relay_packet_size = 8; ++ uint32 level = 9; ++} +--- a/transport/internet/tuic/hub.go ++++ b/transport/internet/tuic/hub.go +@@ -0,0 +1,218 @@ ++package tuic ++ ++import ( ++ "context" ++ "errors" ++ "github.com/gofrs/uuid/v5" ++ I "github.com/metacubex/mihomo/adapter/inbound" ++ C "github.com/metacubex/mihomo/constant" ++ S "github.com/metacubex/mihomo/transport/socks5" ++ T "github.com/metacubex/mihomo/transport/tuic" ++ Q "github.com/metacubex/quic-go" ++ TLS "github.com/metacubex/tls" ++ "github.com/xtls/xray-core/common" ++ N "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/transport/internet" ++ "io" ++ "net" ++ "sync" ++ "time" ++) ++ ++type Datagram struct { ++ Packet C.UDPPacket ++ Target N.Destination ++} ++type Conn struct { ++ net.Conn ++ Target N.Destination ++ Packets chan Datagram ++ ctx context.Context ++ cancel context.CancelFunc ++ local, remote net.Addr ++ User string ++ mu sync.Mutex ++ last C.UDPPacket ++ packetMu sync.Mutex ++} ++ ++func (c *Conn) ReadPacket() (Datagram, error) { ++ select { ++ case p := <-c.Packets: ++ c.mu.Lock() ++ c.last = p.Packet ++ c.mu.Unlock() ++ return p, nil ++ case <-c.ctx.Done(): ++ return Datagram{}, io.EOF ++ } ++} ++func (c *Conn) WritePacket(b []byte, a net.Addr) error { ++ c.mu.Lock() ++ p := c.last ++ c.mu.Unlock() ++ if p == nil { ++ return errors.New("tuic: no UDP association") ++ } ++ _, e := p.WriteBack(b, a) ++ return e ++} ++func (c *Conn) Close() error { ++ if c.cancel != nil { ++ c.packetMu.Lock() ++ defer c.packetMu.Unlock() ++ c.cancel() ++ for { ++ select { ++ case p := <-c.Packets: ++ p.Packet.Drop() ++ default: ++ return nil ++ } ++ } ++ } ++ return c.Conn.Close() ++} ++func (c *Conn) LocalAddr() net.Addr { ++ if c.Packets != nil { ++ return c.local ++ } ++ return c.Conn.LocalAddr() ++} ++func (c *Conn) RemoteAddr() net.Addr { ++ if c.Packets != nil { ++ if raw, ok := c.remote.(interface{ RawAddr() net.Addr }); ok { ++ return raw.RawAddr() ++ } ++ return c.remote ++ } ++ return c.Conn.RemoteAddr() ++} ++func (c *Conn) SetDeadline(t time.Time) error { ++ if c.Packets != nil { ++ return nil ++ } ++ return c.Conn.SetDeadline(t) ++} ++func (c *Conn) SetReadDeadline(t time.Time) error { ++ if c.Packets != nil { ++ return nil ++ } ++ return c.Conn.SetReadDeadline(t) ++} ++func (c *Conn) SetWriteDeadline(t time.Time) error { ++ if c.Packets != nil { ++ return nil ++ } ++ return c.Conn.SetWriteDeadline(t) ++} ++ ++type Listener struct { ++ server *T.Server ++ pc net.PacketConn ++ cancel context.CancelFunc ++} ++ ++func (l *Listener) Addr() net.Addr { return l.pc.LocalAddr() } ++func (l *Listener) Close() error { l.cancel(); return errors.Join(l.server.Close(), l.pc.Close()) } ++func target(a S.Addr, network N.Network) (N.Destination, error) { ++ d, e := N.ParseDestination("tcp:" + a.String()) ++ d.Network = network ++ return d, e ++} ++func Listen(ctx context.Context, address N.Address, port N.Port, settings *internet.MemoryStreamConfig, add internet.ConnHandler) (internet.Listener, error) { ++ c := settings.ProtocolSettings.(*Config) ++ cert, e := TLS.LoadX509KeyPair(c.CertificateFile, c.KeyFile) ++ if e != nil { ++ return nil, e ++ } ++ alpn := c.Alpn ++ if len(alpn) == 0 { ++ alpn = []string{"h3"} ++ } ++ pc, e := internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: address.IP(), Port: int(port)}, settings.SocketSettings) ++ if e != nil { ++ return nil, e ++ } ++ ctx, cancel := context.WithCancel(ctx) ++ var mu sync.Mutex ++ associations := map[string]*Conn{} ++ option := &T.ServerOption{TlsConfig: &TLS.Config{Certificates: []TLS.Certificate{cert}, NextProtos: alpn, MinVersion: TLS.VersionTLS13}, QuicConfig: &Q.Config{EnableDatagrams: true, Allow0RTT: true, MaxIncomingStreams: 1024, MaxIncomingUniStreams: 1024, MaxDatagramFrameSize: 1400}, CongestionController: c.CongestionControl, AuthenticationTimeout: time.Duration(c.AuthenticationTimeout) * time.Millisecond, MaxUdpRelayPacketSize: int(c.MaxUdpRelayPacketSize), Users: map[[16]byte]string{}} ++ for _, token := range c.Tokens { ++ option.Tokens = append(option.Tokens, T.GenTKN(token)) ++ } ++ for id, pass := range c.Users { ++ u, err := uuid.FromString(id) ++ if err != nil { ++ cancel() ++ pc.Close() ++ return nil, err ++ } ++ option.Users[u] = pass ++ } ++ user := func(a []I.Addition) string { m := &C.Metadata{}; I.ApplyAdditions(m, a...); return m.InUser } ++ option.HandleTcpFn = func(conn net.Conn, a S.Addr, adds ...I.Addition) error { ++ d, err := target(a, N.Network_TCP) ++ if err != nil { ++ conn.Close() ++ return err ++ } ++ add(&Conn{Conn: conn, Target: d, User: user(adds)}) ++ return nil ++ } ++ option.HandleUdpFn = func(a S.Addr, p C.UDPPacket, adds ...I.Addition) error { ++ d, err := target(a, N.Network_UDP) ++ if err != nil { ++ p.Drop() ++ return err ++ } ++ key := p.LocalAddr().String() + "|" + user(adds) ++ mu.Lock() ++ conn := associations[key] ++ if conn != nil && conn.ctx.Err() != nil { ++ delete(associations, key) ++ conn = nil ++ } ++ if conn == nil { ++ cc, stop := context.WithCancel(ctx) ++ conn = &Conn{Target: d, Packets: make(chan Datagram, 64), ctx: cc, cancel: stop, local: pc.LocalAddr(), remote: p.LocalAddr(), User: user(adds)} ++ associations[key] = conn ++ add(conn) ++ go func(cn *Conn) { ++ <-cn.ctx.Done() ++ mu.Lock() ++ if associations[key] == cn { ++ delete(associations, key) ++ } ++ mu.Unlock() ++ cn.Close() ++ }(conn) ++ } ++ mu.Unlock() ++ conn.packetMu.Lock() ++ if conn.ctx.Err() != nil { ++ p.Drop() ++ } else { ++ select { ++ case conn.Packets <- Datagram{p, d}: ++ default: ++ p.Drop() ++ } ++ } ++ conn.packetMu.Unlock() ++ return nil ++ } ++ server, err := T.NewServer(option, pc) ++ if err != nil { ++ cancel() ++ pc.Close() ++ return nil, err ++ } ++ l := &Listener{server: server, pc: pc, cancel: cancel} ++ go server.Serve() ++ return l, nil ++} ++func init() { ++ common.Must(internet.RegisterProtocolConfigCreator("tuic", func() interface{} { return &Config{} })) ++ common.Must(internet.RegisterTransportListener("tuic", Listen)) ++} diff --git a/xray-core/patches/160-add-snell-proxy.patch b/xray-core/patches/160-add-snell-proxy.patch new file mode 100644 index 000000000..63e365cf0 --- /dev/null +++ b/xray-core/patches/160-add-snell-proxy.patch @@ -0,0 +1,1642 @@ +From: coolsnowwolf +Subject: [PATCH] proxy: add Mihomo Snell inbound and outbound + +Reuse the pinned Mihomo v1.19.32 Snell encryption and framing implementation. +Support v1-v4 and v5's v4-compatible path, TCP, UDP for v3+, HTTP/TLS obfs, +and reusable TCP sessions. Adapt requests to Xray routing and socket options. +Join relay loops before recycling connections and retain private-target rules. + +Signed-off-by: coolsnowwolf + +--- a/go.mod ++++ b/go.mod +@@ -94,6 +94,7 @@ + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/vishvananda/netns v0.0.5 // indirect + github.com/wlynxg/anet v0.0.5 // indirect ++ gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/text v0.43.0 // indirect + golang.org/x/tools v0.51.0 // indirect +--- a/go.sum ++++ b/go.sum +@@ -177,6 +177,8 @@ + github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= + github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA= + github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8= ++gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4= ++gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs= + go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= + go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= + go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +--- a/infra/conf/snell.go ++++ b/infra/conf/snell.go +@@ -0,0 +1,72 @@ ++package conf ++ ++import ( ++ "github.com/xtls/xray-core/common/errors" ++ S "github.com/xtls/xray-core/proxy/snell" ++ "google.golang.org/protobuf/proto" ++) ++ ++type SnellClientConfig struct { ++ Address *Address `json:"address"` ++ Port uint16 `json:"port"` ++ PSK string `json:"psk"` ++ Version uint32 `json:"version"` ++ Obfs string `json:"obfs"` ++ ObfsHost string `json:"obfsHost"` ++ Reuse bool `json:"reuse"` ++ Level uint32 `json:"level"` ++} ++ ++func snellSettings(psk string, version uint32, obfs string) (uint32, error) { ++ if psk == "" { ++ return 0, errors.New("snell: psk required") ++ } ++ if version == 0 { ++ version = 4 ++ } ++ if version < 1 || version > 5 { ++ return 0, errors.New("snell: version must be 1..5") ++ } ++ switch obfs { ++ case "", "http", "tls": ++ default: ++ return 0, errors.New("snell: unsupported obfs") ++ } ++ return version, nil ++} ++func (c *SnellClientConfig) Build() (proto.Message, error) { ++ v, e := snellSettings(c.PSK, c.Version, c.Obfs) ++ if e != nil { ++ return nil, e ++ } ++ if c.Address == nil || c.Port == 0 { ++ return nil, errors.New("snell: address and port required") ++ } ++ if c.Reuse && v != 2 && v < 4 { ++ return nil, errors.New("snell: reuse requires version 2, 4 or 5") ++ } ++ host := c.ObfsHost ++ if host == "" { ++ host = "bing.com" ++ } ++ return &S.ClientConfig{Address: c.Address.Build(), Port: uint32(c.Port), Psk: c.PSK, Version: v, Obfs: c.Obfs, ObfsHost: host, Reuse: c.Reuse, Level: c.Level}, nil ++} ++ ++type SnellServerConfig struct { ++ PSK string `json:"psk"` ++ Version uint32 `json:"version"` ++ Obfs string `json:"obfs"` ++ UDP bool `json:"udp"` ++ Level uint32 `json:"level"` ++} ++ ++func (c *SnellServerConfig) Build() (proto.Message, error) { ++ v, e := snellSettings(c.PSK, c.Version, c.Obfs) ++ if e != nil { ++ return nil, e ++ } ++ if c.UDP && v < 3 { ++ return nil, errors.New("snell: UDP requires version 3 or later") ++ } ++ return &S.ServerConfig{Psk: c.PSK, Version: v, Obfs: c.Obfs, Udp: c.UDP, Level: c.Level}, nil ++} +--- a/infra/conf/xray.go ++++ b/infra/conf/xray.go +@@ -28,6 +28,7 @@ + inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ + "tunnel": func() interface{} { return new(DokodemoConfig) }, + "tuic": func() interface{} { return new(TUICServerConfig) }, ++ "snell": func() interface{} { return new(SnellServerConfig) }, + "naive": func() interface{} { return new(NaiveServerConfig) }, + "dokodemo-door": func() interface{} { return new(DokodemoConfig) }, + "http": func() interface{} { return new(HTTPServerConfig) }, +@@ -59,6 +60,7 @@ + "trojan": func() interface{} { return new(TrojanClientConfig) }, + "naive": func() interface{} { return new(NaiveClientConfig) }, + "tuic": func() interface{} { return new(TUICClientConfig) }, ++ "snell": func() interface{} { return new(SnellClientConfig) }, + "shadowsocksr": func() interface{} { return new(ShadowsocksRClientConfig) }, + "anytls": func() interface{} { return new(AnyTLSClientConfig) }, + "hysteria": func() interface{} { return new(HysteriaClientConfig) }, +--- a/main/distro/all/all.go ++++ b/main/distro/all/all.go +@@ -46,6 +46,7 @@ + _ "github.com/xtls/xray-core/proxy/naive" + _ "github.com/xtls/xray-core/proxy/shadowsocks" + _ "github.com/xtls/xray-core/proxy/shadowsocksr" ++ _ "github.com/xtls/xray-core/proxy/snell" + _ "github.com/xtls/xray-core/proxy/socks" + _ "github.com/xtls/xray-core/proxy/trojan" + _ "github.com/xtls/xray-core/proxy/tuic" +--- a/proxy/freedom/freedom.go ++++ b/proxy/freedom/freedom.go +@@ -172,7 +172,7 @@ + switch inbound.Name { + case "vless-reverse": + return defaultBlockAllRule +- case "vless", "vmess", "trojan", "hysteria", "wireguard", "masque", "naive", "tuic": ++ case "vless", "vmess", "trojan", "hysteria", "wireguard", "masque", "naive", "tuic", "snell": + return defaultBlockPrivateRule + default: + if strings.HasPrefix(inbound.Name, "shadowsocks") { +--- a/proxy/snell/LICENSE.mihomo ++++ b/proxy/snell/LICENSE.mihomo +@@ -0,0 +1,674 @@ ++ GNU GENERAL PUBLIC LICENSE ++ Version 3, 29 June 2007 ++ ++ Copyright (C) 2007 Free Software Foundation, Inc. ++ Everyone is permitted to copy and distribute verbatim copies ++ of this license document, but changing it is not allowed. ++ ++ Preamble ++ ++ The GNU General Public License is a free, copyleft license for ++software and other kinds of works. ++ ++ The licenses for most software and other practical works are designed ++to take away your freedom to share and change the works. By contrast, ++the GNU General Public License is intended to guarantee your freedom to ++share and change all versions of a program--to make sure it remains free ++software for all its users. We, the Free Software Foundation, use the ++GNU General Public License for most of our software; it applies also to ++any other work released this way by its authors. You can apply it to ++your programs, too. ++ ++ When we speak of free software, we are referring to freedom, not ++price. Our General Public Licenses are designed to make sure that you ++have the freedom to distribute copies of free software (and charge for ++them if you wish), that you receive source code or can get it if you ++want it, that you can change the software or use pieces of it in new ++free programs, and that you know you can do these things. ++ ++ To protect your rights, we need to prevent others from denying you ++these rights or asking you to surrender the rights. Therefore, you have ++certain responsibilities if you distribute copies of the software, or if ++you modify it: responsibilities to respect the freedom of others. ++ ++ For example, if you distribute copies of such a program, whether ++gratis or for a fee, you must pass on to the recipients the same ++freedoms that you received. You must make sure that they, too, receive ++or can get the source code. And you must show them these terms so they ++know their rights. ++ ++ Developers that use the GNU GPL protect your rights with two steps: ++(1) assert copyright on the software, and (2) offer you this License ++giving you legal permission to copy, distribute and/or modify it. ++ ++ For the developers' and authors' protection, the GPL clearly explains ++that there is no warranty for this free software. For both users' and ++authors' sake, the GPL requires that modified versions be marked as ++changed, so that their problems will not be attributed erroneously to ++authors of previous versions. ++ ++ Some devices are designed to deny users access to install or run ++modified versions of the software inside them, although the manufacturer ++can do so. This is fundamentally incompatible with the aim of ++protecting users' freedom to change the software. The systematic ++pattern of such abuse occurs in the area of products for individuals to ++use, which is precisely where it is most unacceptable. Therefore, we ++have designed this version of the GPL to prohibit the practice for those ++products. If such problems arise substantially in other domains, we ++stand ready to extend this provision to those domains in future versions ++of the GPL, as needed to protect the freedom of users. ++ ++ Finally, every program is threatened constantly by software patents. ++States should not allow patents to restrict development and use of ++software on general-purpose computers, but in those that do, we wish to ++avoid the special danger that patents applied to a free program could ++make it effectively proprietary. To prevent this, the GPL assures that ++patents cannot be used to render the program non-free. ++ ++ The precise terms and conditions for copying, distribution and ++modification follow. ++ ++ TERMS AND CONDITIONS ++ ++ 0. Definitions. ++ ++ "This License" refers to version 3 of the GNU General Public License. ++ ++ "Copyright" also means copyright-like laws that apply to other kinds of ++works, such as semiconductor masks. ++ ++ "The Program" refers to any copyrightable work licensed under this ++License. Each licensee is addressed as "you". "Licensees" and ++"recipients" may be individuals or organizations. ++ ++ To "modify" a work means to copy from or adapt all or part of the work ++in a fashion requiring copyright permission, other than the making of an ++exact copy. The resulting work is called a "modified version" of the ++earlier work or a work "based on" the earlier work. ++ ++ A "covered work" means either the unmodified Program or a work based ++on the Program. ++ ++ To "propagate" a work means to do anything with it that, without ++permission, would make you directly or secondarily liable for ++infringement under applicable copyright law, except executing it on a ++computer or modifying a private copy. Propagation includes copying, ++distribution (with or without modification), making available to the ++public, and in some countries other activities as well. ++ ++ To "convey" a work means any kind of propagation that enables other ++parties to make or receive copies. Mere interaction with a user through ++a computer network, with no transfer of a copy, is not conveying. ++ ++ An interactive user interface displays "Appropriate Legal Notices" ++to the extent that it includes a convenient and prominently visible ++feature that (1) displays an appropriate copyright notice, and (2) ++tells the user that there is no warranty for the work (except to the ++extent that warranties are provided), that licensees may convey the ++work under this License, and how to view a copy of this License. If ++the interface presents a list of user commands or options, such as a ++menu, a prominent item in the list meets this criterion. ++ ++ 1. Source Code. ++ ++ The "source code" for a work means the preferred form of the work ++for making modifications to it. "Object code" means any non-source ++form of a work. ++ ++ A "Standard Interface" means an interface that either is an official ++standard defined by a recognized standards body, or, in the case of ++interfaces specified for a particular programming language, one that ++is widely used among developers working in that language. ++ ++ The "System Libraries" of an executable work include anything, other ++than the work as a whole, that (a) is included in the normal form of ++packaging a Major Component, but which is not part of that Major ++Component, and (b) serves only to enable use of the work with that ++Major Component, or to implement a Standard Interface for which an ++implementation is available to the public in source code form. A ++"Major Component", in this context, means a major essential component ++(kernel, window system, and so on) of the specific operating system ++(if any) on which the executable work runs, or a compiler used to ++produce the work, or an object code interpreter used to run it. ++ ++ The "Corresponding Source" for a work in object code form means all ++the source code needed to generate, install, and (for an executable ++work) run the object code and to modify the work, including scripts to ++control those activities. However, it does not include the work's ++System Libraries, or general-purpose tools or generally available free ++programs which are used unmodified in performing those activities but ++which are not part of the work. For example, Corresponding Source ++includes interface definition files associated with source files for ++the work, and the source code for shared libraries and dynamically ++linked subprograms that the work is specifically designed to require, ++such as by intimate data communication or control flow between those ++subprograms and other parts of the work. ++ ++ The Corresponding Source need not include anything that users ++can regenerate automatically from other parts of the Corresponding ++Source. ++ ++ The Corresponding Source for a work in source code form is that ++same work. ++ ++ 2. Basic Permissions. ++ ++ All rights granted under this License are granted for the term of ++copyright on the Program, and are irrevocable provided the stated ++conditions are met. This License explicitly affirms your unlimited ++permission to run the unmodified Program. The output from running a ++covered work is covered by this License only if the output, given its ++content, constitutes a covered work. This License acknowledges your ++rights of fair use or other equivalent, as provided by copyright law. ++ ++ You may make, run and propagate covered works that you do not ++convey, without conditions so long as your license otherwise remains ++in force. You may convey covered works to others for the sole purpose ++of having them make modifications exclusively for you, or provide you ++with facilities for running those works, provided that you comply with ++the terms of this License in conveying all material for which you do ++not control copyright. Those thus making or running the covered works ++for you must do so exclusively on your behalf, under your direction ++and control, on terms that prohibit them from making any copies of ++your copyrighted material outside their relationship with you. ++ ++ Conveying under any other circumstances is permitted solely under ++the conditions stated below. Sublicensing is not allowed; section 10 ++makes it unnecessary. ++ ++ 3. Protecting Users' Legal Rights From Anti-Circumvention Law. ++ ++ No covered work shall be deemed part of an effective technological ++measure under any applicable law fulfilling obligations under article ++11 of the WIPO copyright treaty adopted on 20 December 1996, or ++similar laws prohibiting or restricting circumvention of such ++measures. ++ ++ When you convey a covered work, you waive any legal power to forbid ++circumvention of technological measures to the extent such circumvention ++is effected by exercising rights under this License with respect to ++the covered work, and you disclaim any intention to limit operation or ++modification of the work as a means of enforcing, against the work's ++users, your or third parties' legal rights to forbid circumvention of ++technological measures. ++ ++ 4. Conveying Verbatim Copies. ++ ++ You may convey verbatim copies of the Program's source code as you ++receive it, in any medium, provided that you conspicuously and ++appropriately publish on each copy an appropriate copyright notice; ++keep intact all notices stating that this License and any ++non-permissive terms added in accord with section 7 apply to the code; ++keep intact all notices of the absence of any warranty; and give all ++recipients a copy of this License along with the Program. ++ ++ You may charge any price or no price for each copy that you convey, ++and you may offer support or warranty protection for a fee. ++ ++ 5. Conveying Modified Source Versions. ++ ++ You may convey a work based on the Program, or the modifications to ++produce it from the Program, in the form of source code under the ++terms of section 4, provided that you also meet all of these conditions: ++ ++ a) The work must carry prominent notices stating that you modified ++ it, and giving a relevant date. ++ ++ b) The work must carry prominent notices stating that it is ++ released under this License and any conditions added under section ++ 7. This requirement modifies the requirement in section 4 to ++ "keep intact all notices". ++ ++ c) You must license the entire work, as a whole, under this ++ License to anyone who comes into possession of a copy. This ++ License will therefore apply, along with any applicable section 7 ++ additional terms, to the whole of the work, and all its parts, ++ regardless of how they are packaged. This License gives no ++ permission to license the work in any other way, but it does not ++ invalidate such permission if you have separately received it. ++ ++ d) If the work has interactive user interfaces, each must display ++ Appropriate Legal Notices; however, if the Program has interactive ++ interfaces that do not display Appropriate Legal Notices, your ++ work need not make them do so. ++ ++ A compilation of a covered work with other separate and independent ++works, which are not by their nature extensions of the covered work, ++and which are not combined with it such as to form a larger program, ++in or on a volume of a storage or distribution medium, is called an ++"aggregate" if the compilation and its resulting copyright are not ++used to limit the access or legal rights of the compilation's users ++beyond what the individual works permit. Inclusion of a covered work ++in an aggregate does not cause this License to apply to the other ++parts of the aggregate. ++ ++ 6. Conveying Non-Source Forms. ++ ++ You may convey a covered work in object code form under the terms ++of sections 4 and 5, provided that you also convey the ++machine-readable Corresponding Source under the terms of this License, ++in one of these ways: ++ ++ a) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by the ++ Corresponding Source fixed on a durable physical medium ++ customarily used for software interchange. ++ ++ b) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by a ++ written offer, valid for at least three years and valid for as ++ long as you offer spare parts or customer support for that product ++ model, to give anyone who possesses the object code either (1) a ++ copy of the Corresponding Source for all the software in the ++ product that is covered by this License, on a durable physical ++ medium customarily used for software interchange, for a price no ++ more than your reasonable cost of physically performing this ++ conveying of source, or (2) access to copy the ++ Corresponding Source from a network server at no charge. ++ ++ c) Convey individual copies of the object code with a copy of the ++ written offer to provide the Corresponding Source. This ++ alternative is allowed only occasionally and noncommercially, and ++ only if you received the object code with such an offer, in accord ++ with subsection 6b. ++ ++ d) Convey the object code by offering access from a designated ++ place (gratis or for a charge), and offer equivalent access to the ++ Corresponding Source in the same way through the same place at no ++ further charge. You need not require recipients to copy the ++ Corresponding Source along with the object code. If the place to ++ copy the object code is a network server, the Corresponding Source ++ may be on a different server (operated by you or a third party) ++ that supports equivalent copying facilities, provided you maintain ++ clear directions next to the object code saying where to find the ++ Corresponding Source. Regardless of what server hosts the ++ Corresponding Source, you remain obligated to ensure that it is ++ available for as long as needed to satisfy these requirements. ++ ++ e) Convey the object code using peer-to-peer transmission, provided ++ you inform other peers where the object code and Corresponding ++ Source of the work are being offered to the general public at no ++ charge under subsection 6d. ++ ++ A separable portion of the object code, whose source code is excluded ++from the Corresponding Source as a System Library, need not be ++included in conveying the object code work. ++ ++ A "User Product" is either (1) a "consumer product", which means any ++tangible personal property which is normally used for personal, family, ++or household purposes, or (2) anything designed or sold for incorporation ++into a dwelling. In determining whether a product is a consumer product, ++doubtful cases shall be resolved in favor of coverage. For a particular ++product received by a particular user, "normally used" refers to a ++typical or common use of that class of product, regardless of the status ++of the particular user or of the way in which the particular user ++actually uses, or expects or is expected to use, the product. A product ++is a consumer product regardless of whether the product has substantial ++commercial, industrial or non-consumer uses, unless such uses represent ++the only significant mode of use of the product. ++ ++ "Installation Information" for a User Product means any methods, ++procedures, authorization keys, or other information required to install ++and execute modified versions of a covered work in that User Product from ++a modified version of its Corresponding Source. The information must ++suffice to ensure that the continued functioning of the modified object ++code is in no case prevented or interfered with solely because ++modification has been made. ++ ++ If you convey an object code work under this section in, or with, or ++specifically for use in, a User Product, and the conveying occurs as ++part of a transaction in which the right of possession and use of the ++User Product is transferred to the recipient in perpetuity or for a ++fixed term (regardless of how the transaction is characterized), the ++Corresponding Source conveyed under this section must be accompanied ++by the Installation Information. But this requirement does not apply ++if neither you nor any third party retains the ability to install ++modified object code on the User Product (for example, the work has ++been installed in ROM). ++ ++ The requirement to provide Installation Information does not include a ++requirement to continue to provide support service, warranty, or updates ++for a work that has been modified or installed by the recipient, or for ++the User Product in which it has been modified or installed. Access to a ++network may be denied when the modification itself materially and ++adversely affects the operation of the network or violates the rules and ++protocols for communication across the network. ++ ++ Corresponding Source conveyed, and Installation Information provided, ++in accord with this section must be in a format that is publicly ++documented (and with an implementation available to the public in ++source code form), and must require no special password or key for ++unpacking, reading or copying. ++ ++ 7. Additional Terms. ++ ++ "Additional permissions" are terms that supplement the terms of this ++License by making exceptions from one or more of its conditions. ++Additional permissions that are applicable to the entire Program shall ++be treated as though they were included in this License, to the extent ++that they are valid under applicable law. If additional permissions ++apply only to part of the Program, that part may be used separately ++under those permissions, but the entire Program remains governed by ++this License without regard to the additional permissions. ++ ++ When you convey a copy of a covered work, you may at your option ++remove any additional permissions from that copy, or from any part of ++it. (Additional permissions may be written to require their own ++removal in certain cases when you modify the work.) You may place ++additional permissions on material, added by you to a covered work, ++for which you have or can give appropriate copyright permission. ++ ++ Notwithstanding any other provision of this License, for material you ++add to a covered work, you may (if authorized by the copyright holders of ++that material) supplement the terms of this License with terms: ++ ++ a) Disclaiming warranty or limiting liability differently from the ++ terms of sections 15 and 16 of this License; or ++ ++ b) Requiring preservation of specified reasonable legal notices or ++ author attributions in that material or in the Appropriate Legal ++ Notices displayed by works containing it; or ++ ++ c) Prohibiting misrepresentation of the origin of that material, or ++ requiring that modified versions of such material be marked in ++ reasonable ways as different from the original version; or ++ ++ d) Limiting the use for publicity purposes of names of licensors or ++ authors of the material; or ++ ++ e) Declining to grant rights under trademark law for use of some ++ trade names, trademarks, or service marks; or ++ ++ f) Requiring indemnification of licensors and authors of that ++ material by anyone who conveys the material (or modified versions of ++ it) with contractual assumptions of liability to the recipient, for ++ any liability that these contractual assumptions directly impose on ++ those licensors and authors. ++ ++ All other non-permissive additional terms are considered "further ++restrictions" within the meaning of section 10. If the Program as you ++received it, or any part of it, contains a notice stating that it is ++governed by this License along with a term that is a further ++restriction, you may remove that term. If a license document contains ++a further restriction but permits relicensing or conveying under this ++License, you may add to a covered work material governed by the terms ++of that license document, provided that the further restriction does ++not survive such relicensing or conveying. ++ ++ If you add terms to a covered work in accord with this section, you ++must place, in the relevant source files, a statement of the ++additional terms that apply to those files, or a notice indicating ++where to find the applicable terms. ++ ++ Additional terms, permissive or non-permissive, may be stated in the ++form of a separately written license, or stated as exceptions; ++the above requirements apply either way. ++ ++ 8. Termination. ++ ++ You may not propagate or modify a covered work except as expressly ++provided under this License. Any attempt otherwise to propagate or ++modify it is void, and will automatically terminate your rights under ++this License (including any patent licenses granted under the third ++paragraph of section 11). ++ ++ However, if you cease all violation of this License, then your ++license from a particular copyright holder is reinstated (a) ++provisionally, unless and until the copyright holder explicitly and ++finally terminates your license, and (b) permanently, if the copyright ++holder fails to notify you of the violation by some reasonable means ++prior to 60 days after the cessation. ++ ++ Moreover, your license from a particular copyright holder is ++reinstated permanently if the copyright holder notifies you of the ++violation by some reasonable means, this is the first time you have ++received notice of violation of this License (for any work) from that ++copyright holder, and you cure the violation prior to 30 days after ++your receipt of the notice. ++ ++ Termination of your rights under this section does not terminate the ++licenses of parties who have received copies or rights from you under ++this License. If your rights have been terminated and not permanently ++reinstated, you do not qualify to receive new licenses for the same ++material under section 10. ++ ++ 9. Acceptance Not Required for Having Copies. ++ ++ You are not required to accept this License in order to receive or ++run a copy of the Program. Ancillary propagation of a covered work ++occurring solely as a consequence of using peer-to-peer transmission ++to receive a copy likewise does not require acceptance. However, ++nothing other than this License grants you permission to propagate or ++modify any covered work. These actions infringe copyright if you do ++not accept this License. Therefore, by modifying or propagating a ++covered work, you indicate your acceptance of this License to do so. ++ ++ 10. Automatic Licensing of Downstream Recipients. ++ ++ Each time you convey a covered work, the recipient automatically ++receives a license from the original licensors, to run, modify and ++propagate that work, subject to this License. You are not responsible ++for enforcing compliance by third parties with this License. ++ ++ An "entity transaction" is a transaction transferring control of an ++organization, or substantially all assets of one, or subdividing an ++organization, or merging organizations. If propagation of a covered ++work results from an entity transaction, each party to that ++transaction who receives a copy of the work also receives whatever ++licenses to the work the party's predecessor in interest had or could ++give under the previous paragraph, plus a right to possession of the ++Corresponding Source of the work from the predecessor in interest, if ++the predecessor has it or can get it with reasonable efforts. ++ ++ You may not impose any further restrictions on the exercise of the ++rights granted or affirmed under this License. For example, you may ++not impose a license fee, royalty, or other charge for exercise of ++rights granted under this License, and you may not initiate litigation ++(including a cross-claim or counterclaim in a lawsuit) alleging that ++any patent claim is infringed by making, using, selling, offering for ++sale, or importing the Program or any portion of it. ++ ++ 11. Patents. ++ ++ A "contributor" is a copyright holder who authorizes use under this ++License of the Program or a work on which the Program is based. The ++work thus licensed is called the contributor's "contributor version". ++ ++ A contributor's "essential patent claims" are all patent claims ++owned or controlled by the contributor, whether already acquired or ++hereafter acquired, that would be infringed by some manner, permitted ++by this License, of making, using, or selling its contributor version, ++but do not include claims that would be infringed only as a ++consequence of further modification of the contributor version. For ++purposes of this definition, "control" includes the right to grant ++patent sublicenses in a manner consistent with the requirements of ++this License. ++ ++ Each contributor grants you a non-exclusive, worldwide, royalty-free ++patent license under the contributor's essential patent claims, to ++make, use, sell, offer for sale, import and otherwise run, modify and ++propagate the contents of its contributor version. ++ ++ In the following three paragraphs, a "patent license" is any express ++agreement or commitment, however denominated, not to enforce a patent ++(such as an express permission to practice a patent or covenant not to ++sue for patent infringement). To "grant" such a patent license to a ++party means to make such an agreement or commitment not to enforce a ++patent against the party. ++ ++ If you convey a covered work, knowingly relying on a patent license, ++and the Corresponding Source of the work is not available for anyone ++to copy, free of charge and under the terms of this License, through a ++publicly available network server or other readily accessible means, ++then you must either (1) cause the Corresponding Source to be so ++available, or (2) arrange to deprive yourself of the benefit of the ++patent license for this particular work, or (3) arrange, in a manner ++consistent with the requirements of this License, to extend the patent ++license to downstream recipients. "Knowingly relying" means you have ++actual knowledge that, but for the patent license, your conveying the ++covered work in a country, or your recipient's use of the covered work ++in a country, would infringe one or more identifiable patents in that ++country that you have reason to believe are valid. ++ ++ If, pursuant to or in connection with a single transaction or ++arrangement, you convey, or propagate by procuring conveyance of, a ++covered work, and grant a patent license to some of the parties ++receiving the covered work authorizing them to use, propagate, modify ++or convey a specific copy of the covered work, then the patent license ++you grant is automatically extended to all recipients of the covered ++work and works based on it. ++ ++ A patent license is "discriminatory" if it does not include within ++the scope of its coverage, prohibits the exercise of, or is ++conditioned on the non-exercise of one or more of the rights that are ++specifically granted under this License. You may not convey a covered ++work if you are a party to an arrangement with a third party that is ++in the business of distributing software, under which you make payment ++to the third party based on the extent of your activity of conveying ++the work, and under which the third party grants, to any of the ++parties who would receive the covered work from you, a discriminatory ++patent license (a) in connection with copies of the covered work ++conveyed by you (or copies made from those copies), or (b) primarily ++for and in connection with specific products or compilations that ++contain the covered work, unless you entered into that arrangement, ++or that patent license was granted, prior to 28 March 2007. ++ ++ Nothing in this License shall be construed as excluding or limiting ++any implied license or other defenses to infringement that may ++otherwise be available to you under applicable patent law. ++ ++ 12. No Surrender of Others' Freedom. ++ ++ If conditions are imposed on you (whether by court order, agreement or ++otherwise) that contradict the conditions of this License, they do not ++excuse you from the conditions of this License. If you cannot convey a ++covered work so as to satisfy simultaneously your obligations under this ++License and any other pertinent obligations, then as a consequence you may ++not convey it at all. For example, if you agree to terms that obligate you ++to collect a royalty for further conveying from those to whom you convey ++the Program, the only way you could satisfy both those terms and this ++License would be to refrain entirely from conveying the Program. ++ ++ 13. Use with the GNU Affero General Public License. ++ ++ Notwithstanding any other provision of this License, you have ++permission to link or combine any covered work with a work licensed ++under version 3 of the GNU Affero General Public License into a single ++combined work, and to convey the resulting work. The terms of this ++License will continue to apply to the part which is the covered work, ++but the special requirements of the GNU Affero General Public License, ++section 13, concerning interaction through a network will apply to the ++combination as such. ++ ++ 14. Revised Versions of this License. ++ ++ The Free Software Foundation may publish revised and/or new versions of ++the GNU General Public License from time to time. Such new versions will ++be similar in spirit to the present version, but may differ in detail to ++address new problems or concerns. ++ ++ Each version is given a distinguishing version number. If the ++Program specifies that a certain numbered version of the GNU General ++Public License "or any later version" applies to it, you have the ++option of following the terms and conditions either of that numbered ++version or of any later version published by the Free Software ++Foundation. If the Program does not specify a version number of the ++GNU General Public License, you may choose any version ever published ++by the Free Software Foundation. ++ ++ If the Program specifies that a proxy can decide which future ++versions of the GNU General Public License can be used, that proxy's ++public statement of acceptance of a version permanently authorizes you ++to choose that version for the Program. ++ ++ Later license versions may give you additional or different ++permissions. However, no additional obligations are imposed on any ++author or copyright holder as a result of your choosing to follow a ++later version. ++ ++ 15. Disclaimer of Warranty. ++ ++ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY ++APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT ++HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY ++OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ++THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM ++IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ++ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ++ ++ 16. Limitation of Liability. ++ ++ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING ++WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS ++THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY ++GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE ++USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF ++DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD ++PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), ++EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF ++SUCH DAMAGES. ++ ++ 17. Interpretation of Sections 15 and 16. ++ ++ If the disclaimer of warranty and limitation of liability provided ++above cannot be given local legal effect according to their terms, ++reviewing courts shall apply local law that most closely approximates ++an absolute waiver of all civil liability in connection with the ++Program, unless a warranty or assumption of liability accompanies a ++copy of the Program in return for a fee. ++ ++ END OF TERMS AND CONDITIONS ++ ++ How to Apply These Terms to Your New Programs ++ ++ If you develop a new program, and you want it to be of the greatest ++possible use to the public, the best way to achieve this is to make it ++free software which everyone can redistribute and change under these terms. ++ ++ To do so, attach the following notices to the program. It is safest ++to attach them to the start of each source file to most effectively ++state the exclusion of warranty; and each file should have at least ++the "copyright" line and a pointer to where the full notice is found. ++ ++ ++ Copyright (C) ++ ++ This program is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation, either version 3 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see . ++ ++Also add information on how to contact you by electronic and paper mail. ++ ++ If the program does terminal interaction, make it output a short ++notice like this when it starts in an interactive mode: ++ ++ Copyright (C) ++ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. ++ This is free software, and you are welcome to redistribute it ++ under certain conditions; type `show c' for details. ++ ++The hypothetical commands `show w' and `show c' should show the appropriate ++parts of the General Public License. Of course, your program's commands ++might be different; for a GUI interface, you would use an "about box". ++ ++ You should also get your employer (if you work as a programmer) or school, ++if any, to sign a "copyright disclaimer" for the program, if necessary. ++For more information on this, and how to apply and follow the GNU GPL, see ++. ++ ++ The GNU General Public License does not permit incorporating your program ++into proprietary programs. If your program is a subroutine library, you ++may consider it more useful to permit linking proprietary applications with ++the library. If this is what you want to do, use the GNU Lesser General ++Public License instead of this License. But first, please read ++. +--- a/proxy/snell/client.go ++++ b/proxy/snell/client.go +@@ -0,0 +1,176 @@ ++package snell ++ ++import ( ++ "context" ++ "io" ++ "sync" ++ "time" ++ ++ O "github.com/metacubex/mihomo/transport/simple-obfs" ++ S "github.com/metacubex/mihomo/transport/snell" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/errors" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/core" ++ "github.com/xtls/xray-core/features/policy" ++ "github.com/xtls/xray-core/transport" ++ "github.com/xtls/xray-core/transport/internet" ++) ++ ++type Client struct { ++ config *ClientConfig ++ policy policy.Session ++ mu sync.Mutex ++ pool *S.Pool ++ conns map[*trackedConn]struct{} ++ closed bool ++} ++type trackedConn struct { ++ net.Conn ++ owner *Client ++ once sync.Once ++} ++ ++func (c *trackedConn) Close() error { ++ var err error ++ c.once.Do(func() { err = c.Conn.Close(); c.owner.mu.Lock(); delete(c.owner.conns, c); c.owner.mu.Unlock() }) ++ return err ++} ++func (c *Client) Close() error { ++ c.mu.Lock() ++ c.closed = true ++ conns := make([]*trackedConn, 0, len(c.conns)) ++ for conn := range c.conns { ++ conns = append(conns, conn) ++ } ++ c.mu.Unlock() ++ for _, conn := range conns { ++ conn.Close() ++ } ++ return nil ++} ++func (c *Client) dial(ctx context.Context, dialer internet.Dialer) (*S.Snell, error) { ++ raw, e := dialer.Dial(ctx, net.TCPDestination(c.config.Address.AsAddress(), net.Port(c.config.Port))) ++ if e != nil { ++ return nil, e ++ } ++ tracked := &trackedConn{Conn: raw, owner: c} ++ c.mu.Lock() ++ if c.closed { ++ c.mu.Unlock() ++ raw.Close() ++ return nil, io.ErrClosedPipe ++ } ++ c.conns[tracked] = struct{}{} ++ c.mu.Unlock() ++ var conn net.Conn = tracked ++ switch c.config.Obfs { ++ case "http": ++ conn = O.NewHTTPObfs(conn, c.config.ObfsHost, net.Port(c.config.Port).String()) ++ case "tls": ++ conn = O.NewTLSObfs(conn, c.config.ObfsHost) ++ } ++ return S.StreamConn(conn, []byte(c.config.Psk), int(c.config.Version)), nil ++} ++ ++type closeWriter struct { ++ buf.Writer ++ close func() error ++} ++ ++func (w *closeWriter) Close() error { return w.close() } ++ ++func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { ++ obs := session.OutboundsFromContext(ctx) ++ if len(obs) == 0 { ++ return errors.New("snell: missing target") ++ } ++ ob := obs[len(obs)-1] ++ target := ob.Target ++ if !target.IsValid() || (target.Network != net.Network_TCP && target.Network != net.Network_UDP) { ++ return errors.New("snell: invalid target") ++ } ++ if target.Network == net.Network_UDP && c.config.Version < 3 { ++ return errors.New("snell: UDP requires version 3 or later") ++ } ++ if len(target.Address.String()) > 255 { ++ return errors.New("snell: target host too long") ++ } ++ ob.Name = "snell" ++ ob.CanSpliceCopy = 3 ++ var conn net.Conn ++ var err error ++ reuse := target.Network == net.Network_TCP && (c.config.Version == 2 || c.config.Reuse) ++ if reuse { ++ c.mu.Lock() ++ if c.closed { ++ c.mu.Unlock() ++ return io.ErrClosedPipe ++ } ++ if c.pool == nil { ++ c.pool = S.NewPool(func(ctx context.Context) (*S.Snell, error) { return c.dial(ctx, dialer) }) ++ } ++ pool := c.pool ++ c.mu.Unlock() ++ conn, err = pool.GetContext(ctx) ++ } else { ++ conn, err = c.dial(ctx, dialer) ++ } ++ if err != nil { ++ return err ++ } ++ defer conn.Close() ++ cancelDone := make(chan struct{}) ++ stop := context.AfterFunc(ctx, func() { conn.SetDeadline(time.Now()); close(cancelDone) }) ++ defer func() { ++ if !stop() { ++ <-cancelDone ++ } ++ }() ++ conn.SetDeadline(time.Now().Add(c.policy.Timeouts.Handshake)) ++ var reader buf.Reader ++ var writer buf.Writer ++ if target.Network == net.Network_TCP { ++ err = S.WriteHeaderWithReuse(conn, addressHost(target.Address), uint(target.Port), int(c.config.Version), reuse) ++ if err != nil { ++ return err ++ } ++ if pc, ok := conn.(*S.PoolConn); ok { ++ pc.MarkReusable() ++ } ++ reader = buf.NewReader(conn) ++ writer = buf.NewWriter(conn) ++ if pc, ok := conn.(*S.PoolConn); ok { ++ writer = &closeWriter{writer, pc.CloseWrite} ++ } ++ } else { ++ if err = S.WriteUDPHeader(conn, int(c.config.Version)); err != nil { ++ return err ++ } ++ if c.config.Version >= 4 { ++ if err = conn.(*S.Snell).ReadReply(); err != nil { ++ return err ++ } ++ } ++ pc := S.PacketConn(conn) ++ reader = &packetReader{pc} ++ writer = &packetWriter{pc, target} ++ } ++ conn.SetDeadline(time.Time{}) ++ return relay(ctx, link.Reader, writer, reader, link.Writer, func() { conn.SetReadDeadline(time.Now()) }, c.policy) ++} ++func init() { ++ common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, c interface{}) (interface{}, error) { ++ config := c.(*ClientConfig) ++ return &Client{config: config, policy: core.MustFromContext(ctx).GetFeature(policy.ManagerType()).(policy.Manager).ForLevel(config.Level), conns: make(map[*trackedConn]struct{})}, nil ++ })) ++} ++ ++func addressHost(a net.Address) string { ++ if a.Family().IsIP() { ++ return a.IP().String() ++ } ++ return a.Domain() ++} +--- a/proxy/snell/config.pb.go ++++ b/proxy/snell/config.pb.go +@@ -0,0 +1,271 @@ ++// Code generated by protoc-gen-go. DO NOT EDIT. ++// versions: ++// protoc-gen-go v1.36.12 ++// protoc v3.21.12 ++// source: proxy/snell/config.proto ++ ++package snell ++ ++import ( ++ net "github.com/xtls/xray-core/common/net" ++ protoreflect "google.golang.org/protobuf/reflect/protoreflect" ++ protoimpl "google.golang.org/protobuf/runtime/protoimpl" ++ reflect "reflect" ++ sync "sync" ++ unsafe "unsafe" ++) ++ ++const ( ++ // Verify that this generated code is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) ++ // Verify that runtime/protoimpl is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ++) ++ ++type ClientConfig struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` ++ Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` ++ Psk string `protobuf:"bytes,3,opt,name=psk,proto3" json:"psk,omitempty"` ++ Version uint32 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"` ++ Obfs string `protobuf:"bytes,5,opt,name=obfs,proto3" json:"obfs,omitempty"` ++ ObfsHost string `protobuf:"bytes,6,opt,name=obfs_host,json=obfsHost,proto3" json:"obfs_host,omitempty"` ++ Reuse bool `protobuf:"varint,7,opt,name=reuse,proto3" json:"reuse,omitempty"` ++ Level uint32 `protobuf:"varint,8,opt,name=level,proto3" json:"level,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *ClientConfig) Reset() { ++ *x = ClientConfig{} ++ mi := &file_proxy_snell_config_proto_msgTypes[0] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *ClientConfig) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*ClientConfig) ProtoMessage() {} ++ ++func (x *ClientConfig) ProtoReflect() protoreflect.Message { ++ mi := &file_proxy_snell_config_proto_msgTypes[0] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. ++func (*ClientConfig) Descriptor() ([]byte, []int) { ++ return file_proxy_snell_config_proto_rawDescGZIP(), []int{0} ++} ++ ++func (x *ClientConfig) GetAddress() *net.IPOrDomain { ++ if x != nil { ++ return x.Address ++ } ++ return nil ++} ++ ++func (x *ClientConfig) GetPort() uint32 { ++ if x != nil { ++ return x.Port ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetPsk() string { ++ if x != nil { ++ return x.Psk ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetVersion() uint32 { ++ if x != nil { ++ return x.Version ++ } ++ return 0 ++} ++ ++func (x *ClientConfig) GetObfs() string { ++ if x != nil { ++ return x.Obfs ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetObfsHost() string { ++ if x != nil { ++ return x.ObfsHost ++ } ++ return "" ++} ++ ++func (x *ClientConfig) GetReuse() bool { ++ if x != nil { ++ return x.Reuse ++ } ++ return false ++} ++ ++func (x *ClientConfig) GetLevel() uint32 { ++ if x != nil { ++ return x.Level ++ } ++ return 0 ++} ++ ++type ServerConfig struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Psk string `protobuf:"bytes,1,opt,name=psk,proto3" json:"psk,omitempty"` ++ Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` ++ Obfs string `protobuf:"bytes,3,opt,name=obfs,proto3" json:"obfs,omitempty"` ++ Udp bool `protobuf:"varint,4,opt,name=udp,proto3" json:"udp,omitempty"` ++ Level uint32 `protobuf:"varint,5,opt,name=level,proto3" json:"level,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *ServerConfig) Reset() { ++ *x = ServerConfig{} ++ mi := &file_proxy_snell_config_proto_msgTypes[1] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *ServerConfig) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*ServerConfig) ProtoMessage() {} ++ ++func (x *ServerConfig) ProtoReflect() protoreflect.Message { ++ mi := &file_proxy_snell_config_proto_msgTypes[1] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead. ++func (*ServerConfig) Descriptor() ([]byte, []int) { ++ return file_proxy_snell_config_proto_rawDescGZIP(), []int{1} ++} ++ ++func (x *ServerConfig) GetPsk() string { ++ if x != nil { ++ return x.Psk ++ } ++ return "" ++} ++ ++func (x *ServerConfig) GetVersion() uint32 { ++ if x != nil { ++ return x.Version ++ } ++ return 0 ++} ++ ++func (x *ServerConfig) GetObfs() string { ++ if x != nil { ++ return x.Obfs ++ } ++ return "" ++} ++ ++func (x *ServerConfig) GetUdp() bool { ++ if x != nil { ++ return x.Udp ++ } ++ return false ++} ++ ++func (x *ServerConfig) GetLevel() uint32 { ++ if x != nil { ++ return x.Level ++ } ++ return 0 ++} ++ ++var File_proxy_snell_config_proto protoreflect.FileDescriptor ++ ++const file_proxy_snell_config_proto_rawDesc = "" + ++ "\n" + ++ "\x18proxy/snell/config.proto\x12\x10xray.proxy.snell\x1a\x18common/net/address.proto\"\xe2\x01\n" + ++ "\fClientConfig\x125\n" + ++ "\aaddress\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" + ++ "\x04port\x18\x02 \x01(\rR\x04port\x12\x10\n" + ++ "\x03psk\x18\x03 \x01(\tR\x03psk\x12\x18\n" + ++ "\aversion\x18\x04 \x01(\rR\aversion\x12\x12\n" + ++ "\x04obfs\x18\x05 \x01(\tR\x04obfs\x12\x1b\n" + ++ "\tobfs_host\x18\x06 \x01(\tR\bobfsHost\x12\x14\n" + ++ "\x05reuse\x18\a \x01(\bR\x05reuse\x12\x14\n" + ++ "\x05level\x18\b \x01(\rR\x05level\"v\n" + ++ "\fServerConfig\x12\x10\n" + ++ "\x03psk\x18\x01 \x01(\tR\x03psk\x12\x18\n" + ++ "\aversion\x18\x02 \x01(\rR\aversion\x12\x12\n" + ++ "\x04obfs\x18\x03 \x01(\tR\x04obfs\x12\x10\n" + ++ "\x03udp\x18\x04 \x01(\bR\x03udp\x12\x14\n" + ++ "\x05level\x18\x05 \x01(\rR\x05levelB'Z%github.com/xtls/xray-core/proxy/snellb\x06proto3" ++ ++var ( ++ file_proxy_snell_config_proto_rawDescOnce sync.Once ++ file_proxy_snell_config_proto_rawDescData []byte ++) ++ ++func file_proxy_snell_config_proto_rawDescGZIP() []byte { ++ file_proxy_snell_config_proto_rawDescOnce.Do(func() { ++ file_proxy_snell_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_snell_config_proto_rawDesc), len(file_proxy_snell_config_proto_rawDesc))) ++ }) ++ return file_proxy_snell_config_proto_rawDescData ++} ++ ++var file_proxy_snell_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) ++var file_proxy_snell_config_proto_goTypes = []any{ ++ (*ClientConfig)(nil), // 0: xray.proxy.snell.ClientConfig ++ (*ServerConfig)(nil), // 1: xray.proxy.snell.ServerConfig ++ (*net.IPOrDomain)(nil), // 2: xray.common.net.IPOrDomain ++} ++var file_proxy_snell_config_proto_depIdxs = []int32{ ++ 2, // 0: xray.proxy.snell.ClientConfig.address:type_name -> xray.common.net.IPOrDomain ++ 1, // [1:1] is the sub-list for method output_type ++ 1, // [1:1] is the sub-list for method input_type ++ 1, // [1:1] is the sub-list for extension type_name ++ 1, // [1:1] is the sub-list for extension extendee ++ 0, // [0:1] is the sub-list for field type_name ++} ++ ++func init() { file_proxy_snell_config_proto_init() } ++func file_proxy_snell_config_proto_init() { ++ if File_proxy_snell_config_proto != nil { ++ return ++ } ++ type x struct{} ++ out := protoimpl.TypeBuilder{ ++ File: protoimpl.DescBuilder{ ++ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), ++ RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_snell_config_proto_rawDesc), len(file_proxy_snell_config_proto_rawDesc)), ++ NumEnums: 0, ++ NumMessages: 2, ++ NumExtensions: 0, ++ NumServices: 0, ++ }, ++ GoTypes: file_proxy_snell_config_proto_goTypes, ++ DependencyIndexes: file_proxy_snell_config_proto_depIdxs, ++ MessageInfos: file_proxy_snell_config_proto_msgTypes, ++ }.Build() ++ File_proxy_snell_config_proto = out.File ++ file_proxy_snell_config_proto_goTypes = nil ++ file_proxy_snell_config_proto_depIdxs = nil ++} +--- a/proxy/snell/config.proto ++++ b/proxy/snell/config.proto +@@ -0,0 +1,21 @@ ++syntax = "proto3"; ++package xray.proxy.snell; ++option go_package = "github.com/xtls/xray-core/proxy/snell"; ++import "common/net/address.proto"; ++message ClientConfig { ++ xray.common.net.IPOrDomain address = 1; ++ uint32 port = 2; ++ string psk = 3; ++ uint32 version = 4; ++ string obfs = 5; ++ string obfs_host = 6; ++ bool reuse = 7; ++ uint32 level = 8; ++} ++message ServerConfig { ++ string psk = 1; ++ uint32 version = 2; ++ string obfs = 3; ++ bool udp = 4; ++ uint32 level = 5; ++} +--- a/proxy/snell/packet.go ++++ b/proxy/snell/packet.go +@@ -0,0 +1,43 @@ ++package snell ++ ++import ( ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/xtls/xray-core/common/buf" ++ N "github.com/xtls/xray-core/common/net" ++ "net" ++) ++ ++type packetReader struct{ conn net.PacketConn } ++ ++func (r *packetReader) ReadMultiBuffer() (buf.MultiBuffer, error) { ++ data := make([]byte, 65535) ++ n, a, e := r.conn.ReadFrom(data) ++ if e != nil { ++ return nil, e ++ } ++ b := buf.FromBytes(data[:n]) ++ m := M.SocksaddrFromNet(a) ++ d := N.UDPDestination(N.ParseAddress(m.AddrString()), N.Port(m.Port)) ++ b.UDP = &d ++ return buf.MultiBuffer{b}, nil ++} ++ ++type packetWriter struct { ++ conn net.PacketConn ++ destination N.Destination ++} ++ ++func (w *packetWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { ++ defer buf.ReleaseMulti(mb) ++ for _, b := range mb { ++ d := w.destination ++ if b.UDP != nil { ++ d = *b.UDP ++ } ++ a := M.ParseSocksaddrHostPort(d.Address.String(), uint16(d.Port)) ++ if _, e := w.conn.WriteTo(b.Bytes(), a); e != nil { ++ return e ++ } ++ } ++ return nil ++} +--- a/proxy/snell/relay.go ++++ b/proxy/snell/relay.go +@@ -0,0 +1,60 @@ ++package snell ++ ++import ( ++ "context" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/signal" ++ "github.com/xtls/xray-core/features/policy" ++) ++ ++// Stop blocked reads and join both copy loops before closing Mihomo packet state. ++func relay(ctx context.Context, upR buf.Reader, upW buf.Writer, downR buf.Reader, downW buf.Writer, stopIO func(), p policy.Session) error { ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) ++ defer timer.SetTimeout(0) ++ results := make(chan error, 2) ++ go func() { ++ e := buf.Copy(upR, upW, buf.UpdateActivity(timer)) ++ if e == nil { ++ common.Close(upW) ++ } ++ timer.SetTimeout(p.Timeouts.DownlinkOnly) ++ results <- e ++ }() ++ go func() { ++ e := buf.Copy(downR, downW, buf.UpdateActivity(timer)) ++ if e == nil { ++ common.Close(downW) ++ } ++ timer.SetTimeout(p.Timeouts.UplinkOnly) ++ results <- e ++ }() ++ var first error ++ count := 0 ++ for count < 2 { ++ select { ++ case e := <-results: ++ count++ ++ if e != nil { ++ first = e ++ } ++ case <-ctx.Done(): ++ first = ctx.Err() ++ } ++ if first != nil || count == 2 { ++ break ++ } ++ } ++ stopIO() ++ common.Interrupt(upR) ++ common.Interrupt(upW) ++ common.Interrupt(downR) ++ common.Interrupt(downW) ++ for count < 2 { ++ <-results ++ count++ ++ } ++ return first ++} +--- a/proxy/snell/server.go ++++ b/proxy/snell/server.go +@@ -0,0 +1,231 @@ ++package snell ++ ++import ( ++ "context" ++ "encoding/binary" ++ stderrors "errors" ++ "io" ++ "sync" ++ "sync/atomic" ++ "time" ++ ++ A "github.com/metacubex/mihomo/transport/shadowsocks/shadowaead" ++ O "github.com/metacubex/mihomo/transport/simple-obfs" ++ S "github.com/metacubex/mihomo/transport/snell" ++ M "github.com/sagernet/sing/common/metadata" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/buf" ++ "github.com/xtls/xray-core/common/errors" ++ "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/protocol" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/core" ++ "github.com/xtls/xray-core/features/policy" ++ "github.com/xtls/xray-core/features/routing" ++ "github.com/xtls/xray-core/transport/internet/stat" ++) ++ ++type Server struct { ++ config *ServerConfig ++ policy policy.Session ++} ++ ++func (s *Server) Network() []net.Network { return []net.Network{net.Network_TCP} } ++func (s *Server) Process(ctx context.Context, _ net.Network, raw stat.Connection, d routing.Dispatcher) error { ++ var conn net.Conn = raw ++ switch s.config.Obfs { ++ case "http": ++ conn = O.NewHTTPObfsServer(conn) ++ case "tls": ++ conn = O.NewTLSObfsServer(conn) ++ } ++ stream := S.ServerStreamConn(conn, []byte(s.config.Psk), int(s.config.Version)) ++ defer stream.Close() ++ cancelDone := make(chan struct{}) ++ stop := context.AfterFunc(ctx, func() { stream.SetDeadline(time.Now()); close(cancelDone) }) ++ defer func() { ++ if !stop() { ++ <-cancelDone ++ } ++ }() ++ if in := session.InboundFromContext(ctx); in != nil { ++ cp := *in ++ cp.Name = "snell" ++ cp.CanSpliceCopy = 3 ++ cp.User = &protocol.MemoryUser{Level: s.config.Level} ++ ctx = session.ContextWithInbound(ctx, &cp) ++ } ++ for { ++ stream.SetDeadline(time.Now().Add(s.policy.Timeouts.Handshake)) ++ var head [2]byte ++ if _, e := io.ReadFull(stream, head[:]); e != nil { ++ return e ++ } ++ if head[0] != S.Version { ++ return errors.New("snell: invalid protocol version") ++ } ++ cmd := head[1] ++ if cmd == S.CommandPing { ++ _, e := stream.Write([]byte{S.CommandPong}) ++ return e ++ } ++ var size [1]byte ++ if _, e := io.ReadFull(stream, size[:]); e != nil { ++ return e ++ } ++ if _, e := io.CopyN(io.Discard, stream, int64(size[0])); e != nil { ++ return e ++ } ++ if cmd == S.CommandUDP { ++ if !s.config.Udp || s.config.Version < 3 { ++ return errors.New("snell: UDP disabled") ++ } ++ if _, e := stream.Write([]byte{S.CommandTunnel}); e != nil { ++ return e ++ } ++ stream.SetDeadline(time.Time{}) ++ pc := &serverPacketConn{Conn: stream, stream: stream} ++ reader := &packetReader{pc} ++ // Wait for the first target under the handshake timeout. ++ stream.SetReadDeadline(time.Now().Add(s.policy.Timeouts.Handshake)) ++ first, e := reader.ReadMultiBuffer() ++ if e != nil { ++ return e ++ } ++ stream.SetReadDeadline(time.Time{}) ++ target := *first[0].UDP ++ link, e := d.Dispatch(ctx, target) ++ if e != nil { ++ buf.ReleaseMulti(first) ++ return e ++ } ++ return relay(ctx, &firstReader{first: first, reader: reader}, link.Writer, link.Reader, &packetWriter{pc, target}, func() { ++ stream.SetReadDeadline(time.Now()) ++ stream.SetWriteDeadline(time.Now().Add(s.policy.Timeouts.Handshake)) ++ }, s.policy) ++ } ++ if cmd != S.CommandConnect && cmd != S.CommandConnectV2 { ++ return errors.New("snell: invalid command") ++ } ++ if _, e := io.ReadFull(stream, size[:]); e != nil { ++ return e ++ } ++ if size[0] == 0 { ++ return errors.New("snell: empty host") ++ } ++ host := make([]byte, int(size[0])) ++ if _, e := io.ReadFull(stream, host); e != nil { ++ return e ++ } ++ var port [2]byte ++ if _, e := io.ReadFull(stream, port[:]); e != nil { ++ return e ++ } ++ target := net.TCPDestination(net.ParseAddress(string(host)), net.Port(binary.BigEndian.Uint16(port[:]))) ++ if target.Port == 0 { ++ return errors.New("snell: invalid port") ++ } ++ requestCtx, cancel := context.WithCancel(ctx) ++ link, e := d.Dispatch(requestCtx, target) ++ if e != nil { ++ cancel() ++ return e ++ } ++ if _, e = stream.Write([]byte{S.CommandTunnel}); e != nil { ++ cancel() ++ common.Interrupt(link.Reader) ++ common.Interrupt(link.Writer) ++ return e ++ } ++ stream.SetDeadline(time.Time{}) ++ reuse := cmd == S.CommandConnectV2 ++ var writer buf.Writer = buf.NewWriter(stream) ++ ++ var endOnce sync.Once ++ var endErr error ++ endReply := func() error { endOnce.Do(func() { _, endErr = stream.Write(nil) }); return endErr } ++ if reuse { ++ writer = &closeWriter{writer, endReply} ++ } ++ request := &requestConn{Conn: stream} ++ e = relay(requestCtx, buf.NewReader(request), link.Writer, link.Reader, writer, func() { ++ stream.SetReadDeadline(time.Now()) ++ stream.SetWriteDeadline(time.Now().Add(s.policy.Timeouts.Handshake)) ++ }, s.policy) ++ cancel() ++ ++ if !reuse || !request.ended.Load() || ctx.Err() != nil { ++ return e ++ } ++ // Routing may finish on its downlink timeout after the peer's zero chunk. ++ // All copy loops have joined, so end this response before reading the next request. ++ stream.SetDeadline(time.Now().Add(s.policy.Timeouts.Handshake)) ++ if err := endReply(); err != nil { ++ return err ++ } ++ stream.SetDeadline(time.Time{}) ++ } ++} ++ ++type requestConn struct { ++ net.Conn ++ ended atomic.Bool ++} ++ ++func (c *requestConn) Read(p []byte) (int, error) { ++ n, e := c.Conn.Read(p) ++ if stderrors.Is(e, A.ErrZeroChunk) { ++ e = io.EOF ++ c.ended.Store(true) ++ } ++ return n, e ++} ++ ++type firstReader struct { ++ first buf.MultiBuffer ++ reader buf.Reader ++} ++ ++func (r *firstReader) ReadMultiBuffer() (buf.MultiBuffer, error) { ++ if r.first != nil { ++ b := r.first ++ r.first = nil ++ return b, nil ++ } ++ return r.reader.ReadMultiBuffer() ++} ++ ++type serverPacketConn struct { ++ net.Conn ++ stream *S.Snell ++} ++ ++func (c *serverPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { ++ data := make([]byte, 0x3fff) ++ n, e := c.stream.Read(data) ++ if e != nil { ++ return 0, nil, e ++ } ++ p, e := S.ParseUDPRequest(data[:n]) ++ if e != nil { ++ return 0, nil, e ++ } ++ host := p.Host ++ if p.Ip.IsValid() { ++ host = p.Ip.String() ++ } ++ if p.Port == 0 { ++ return 0, nil, errors.New("snell: invalid UDP port") ++ } ++ a := M.ParseSocksaddrHostPort(host, p.Port) ++ return copy(b, p.Payload), a, nil ++} ++func (c *serverPacketConn) WriteTo(b []byte, a net.Addr) (int, error) { ++ return S.WritePacketResponse(c.stream, a, b) ++} ++func init() { ++ common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, c interface{}) (interface{}, error) { ++ config := c.(*ServerConfig) ++ return &Server{config, core.MustFromContext(ctx).GetFeature(policy.ManagerType()).(policy.Manager).ForLevel(config.Level)}, nil ++ })) ++} diff --git a/xray-core/patches/170-add-shadowtls-transport.patch b/xray-core/patches/170-add-shadowtls-transport.patch new file mode 100644 index 000000000..217b46d75 --- /dev/null +++ b/xray-core/patches/170-add-shadowtls-transport.patch @@ -0,0 +1,2732 @@ +From: coolsnowwolf +Subject: [PATCH] transport: add Mihomo ShadowTLS inbound and outbound + +Port ShadowTLS v1/v2/v3 framing and authentication from Mihomo v1.19.32. +Add RAW/TCP security settings, metacubex uTLS client fingerprints, certificate +verification, multi-user v3 authentication and SNI-selected handshake servers. +Bound handshakes, cancel pending connections when listeners close, and retain +Xray routing and socket options. ShadowTLS does not encrypt inner payloads; +use an encrypted inner protocol. Native UDP is not wrapped by ShadowTLS. + +Signed-off-by: coolsnowwolf + +--- a/go.mod ++++ b/go.mod +@@ -18,6 +18,7 @@ + github.com/metacubex/mihomo v1.19.32 + github.com/metacubex/quic-go v0.61.1-0.20260727080200-2548683b76f4 + github.com/metacubex/tls v0.1.8 ++ github.com/metacubex/utls v1.8.8 + github.com/miekg/dns v1.1.73 + github.com/pelletier/go-toml v1.9.5 + github.com/pion/stun/v3 v3.1.7 +--- a/go.sum ++++ b/go.sum +@@ -84,6 +84,8 @@ + github.com/metacubex/tfo-go v0.0.0-20260623020846-376a77860b8c/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= + github.com/metacubex/tls v0.1.8 h1:BHF2payjtxC7wdR3tnq8vl3FAgEQB6/pPjP2tJNTsP4= + github.com/metacubex/tls v0.1.8/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM= ++github.com/metacubex/utls v1.8.8 h1:7fiMYahL+tT1I/vG1MWidv88r/3A5NnPrurjzApZGyQ= ++github.com/metacubex/utls v1.8.8/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= + github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk= + github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4= + github.com/miekg/dns v1.1.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE= +--- a/infra/conf/shadowtls.go ++++ b/infra/conf/shadowtls.go +@@ -0,0 +1,48 @@ ++package conf ++ ++import ( ++ "github.com/xtls/xray-core/transport/internet/shadowtls" ++ "os" ++) ++ ++type ShadowTLSUser struct { ++ Name string `json:"name"` ++ Password string `json:"password"` ++} ++type ShadowTLSConfig struct { ++ Version uint32 `json:"version"` ++ Password string `json:"password"` ++ ServerName string `json:"serverName"` ++ Fingerprint string `json:"fingerprint"` ++ AllowInsecure bool `json:"allowInsecure"` ++ ALPN []string `json:"alpn"` ++ CertificateFiles []string `json:"certificateFiles"` ++ StrictMode bool `json:"strictMode"` ++ Handshake string `json:"handshake"` ++ Users []ShadowTLSUser `json:"users"` ++ HandshakeForServerName map[string]string `json:"handshakeForServerName"` ++ WildcardSNI string `json:"wildcardSni"` ++ HandshakeTimeout uint32 `json:"handshakeTimeout"` ++} ++ ++func (c *ShadowTLSConfig) Build() (*shadowtls.Config, error) { ++ version := c.Version ++ if version == 0 { ++ version = 3 ++ } ++ cfg := &shadowtls.Config{Version: version, Password: c.Password, ServerName: c.ServerName, Fingerprint: c.Fingerprint, AllowInsecure: c.AllowInsecure, Alpn: c.ALPN, StrictMode: c.StrictMode, Handshake: c.Handshake, HandshakeForServerName: c.HandshakeForServerName, WildcardSni: c.WildcardSNI, HandshakeTimeout: c.HandshakeTimeout} ++ for _, u := range c.Users { ++ cfg.Users = append(cfg.Users, &shadowtls.User{Name: u.Name, Password: u.Password}) ++ } ++ for _, file := range c.CertificateFiles { ++ pem, err := os.ReadFile(file) ++ if err != nil { ++ return nil, err ++ } ++ cfg.Certificates = append(cfg.Certificates, pem) ++ } ++ if err := cfg.Validate(); err != nil { ++ return nil, err ++ } ++ return cfg, nil ++} +--- a/infra/conf/transport_internet.go ++++ b/infra/conf/transport_internet.go +@@ -52,6 +52,7 @@ + } + + type StreamConfig struct { ++ ShadowTLSSettings *ShadowTLSConfig `json:"shadowtlsSettings"` + Address *Address `json:"address"` + Port uint16 `json:"port"` + Method *TransportProtocol `json:"method"` +@@ -131,6 +132,20 @@ + } + tm := serial.ToTypedMessage(ts) + config.SecuritySettings = append(config.SecuritySettings, tm) ++ config.SecurityType = tm.Type ++ case "shadowtls", "shadow-tls": ++ if config.ProtocolName != "tcp" { ++ return nil, errors.New("ShadowTLS only supports RAW/TCP") ++ } ++ if c.ShadowTLSSettings == nil { ++ return nil, errors.New("ShadowTLS: empty shadowtlsSettings") ++ } ++ ts, err := c.ShadowTLSSettings.Build() ++ if err != nil { ++ return nil, err ++ } ++ tm := serial.ToTypedMessage(ts) ++ config.SecuritySettings = append(config.SecuritySettings, tm) + config.SecurityType = tm.Type + case "xtls": + return nil, errors.PrintRemovedFeatureError(`Legacy XTLS`, `xtls-rprx-vision with TLS or REALITY`) +--- a/transport/internet/shadowtls/LICENSE.mihomo ++++ b/transport/internet/shadowtls/LICENSE.mihomo +@@ -0,0 +1,674 @@ ++ GNU GENERAL PUBLIC LICENSE ++ Version 3, 29 June 2007 ++ ++ Copyright (C) 2007 Free Software Foundation, Inc. ++ Everyone is permitted to copy and distribute verbatim copies ++ of this license document, but changing it is not allowed. ++ ++ Preamble ++ ++ The GNU General Public License is a free, copyleft license for ++software and other kinds of works. ++ ++ The licenses for most software and other practical works are designed ++to take away your freedom to share and change the works. By contrast, ++the GNU General Public License is intended to guarantee your freedom to ++share and change all versions of a program--to make sure it remains free ++software for all its users. We, the Free Software Foundation, use the ++GNU General Public License for most of our software; it applies also to ++any other work released this way by its authors. You can apply it to ++your programs, too. ++ ++ When we speak of free software, we are referring to freedom, not ++price. Our General Public Licenses are designed to make sure that you ++have the freedom to distribute copies of free software (and charge for ++them if you wish), that you receive source code or can get it if you ++want it, that you can change the software or use pieces of it in new ++free programs, and that you know you can do these things. ++ ++ To protect your rights, we need to prevent others from denying you ++these rights or asking you to surrender the rights. Therefore, you have ++certain responsibilities if you distribute copies of the software, or if ++you modify it: responsibilities to respect the freedom of others. ++ ++ For example, if you distribute copies of such a program, whether ++gratis or for a fee, you must pass on to the recipients the same ++freedoms that you received. You must make sure that they, too, receive ++or can get the source code. And you must show them these terms so they ++know their rights. ++ ++ Developers that use the GNU GPL protect your rights with two steps: ++(1) assert copyright on the software, and (2) offer you this License ++giving you legal permission to copy, distribute and/or modify it. ++ ++ For the developers' and authors' protection, the GPL clearly explains ++that there is no warranty for this free software. For both users' and ++authors' sake, the GPL requires that modified versions be marked as ++changed, so that their problems will not be attributed erroneously to ++authors of previous versions. ++ ++ Some devices are designed to deny users access to install or run ++modified versions of the software inside them, although the manufacturer ++can do so. This is fundamentally incompatible with the aim of ++protecting users' freedom to change the software. The systematic ++pattern of such abuse occurs in the area of products for individuals to ++use, which is precisely where it is most unacceptable. Therefore, we ++have designed this version of the GPL to prohibit the practice for those ++products. If such problems arise substantially in other domains, we ++stand ready to extend this provision to those domains in future versions ++of the GPL, as needed to protect the freedom of users. ++ ++ Finally, every program is threatened constantly by software patents. ++States should not allow patents to restrict development and use of ++software on general-purpose computers, but in those that do, we wish to ++avoid the special danger that patents applied to a free program could ++make it effectively proprietary. To prevent this, the GPL assures that ++patents cannot be used to render the program non-free. ++ ++ The precise terms and conditions for copying, distribution and ++modification follow. ++ ++ TERMS AND CONDITIONS ++ ++ 0. Definitions. ++ ++ "This License" refers to version 3 of the GNU General Public License. ++ ++ "Copyright" also means copyright-like laws that apply to other kinds of ++works, such as semiconductor masks. ++ ++ "The Program" refers to any copyrightable work licensed under this ++License. Each licensee is addressed as "you". "Licensees" and ++"recipients" may be individuals or organizations. ++ ++ To "modify" a work means to copy from or adapt all or part of the work ++in a fashion requiring copyright permission, other than the making of an ++exact copy. The resulting work is called a "modified version" of the ++earlier work or a work "based on" the earlier work. ++ ++ A "covered work" means either the unmodified Program or a work based ++on the Program. ++ ++ To "propagate" a work means to do anything with it that, without ++permission, would make you directly or secondarily liable for ++infringement under applicable copyright law, except executing it on a ++computer or modifying a private copy. Propagation includes copying, ++distribution (with or without modification), making available to the ++public, and in some countries other activities as well. ++ ++ To "convey" a work means any kind of propagation that enables other ++parties to make or receive copies. Mere interaction with a user through ++a computer network, with no transfer of a copy, is not conveying. ++ ++ An interactive user interface displays "Appropriate Legal Notices" ++to the extent that it includes a convenient and prominently visible ++feature that (1) displays an appropriate copyright notice, and (2) ++tells the user that there is no warranty for the work (except to the ++extent that warranties are provided), that licensees may convey the ++work under this License, and how to view a copy of this License. If ++the interface presents a list of user commands or options, such as a ++menu, a prominent item in the list meets this criterion. ++ ++ 1. Source Code. ++ ++ The "source code" for a work means the preferred form of the work ++for making modifications to it. "Object code" means any non-source ++form of a work. ++ ++ A "Standard Interface" means an interface that either is an official ++standard defined by a recognized standards body, or, in the case of ++interfaces specified for a particular programming language, one that ++is widely used among developers working in that language. ++ ++ The "System Libraries" of an executable work include anything, other ++than the work as a whole, that (a) is included in the normal form of ++packaging a Major Component, but which is not part of that Major ++Component, and (b) serves only to enable use of the work with that ++Major Component, or to implement a Standard Interface for which an ++implementation is available to the public in source code form. A ++"Major Component", in this context, means a major essential component ++(kernel, window system, and so on) of the specific operating system ++(if any) on which the executable work runs, or a compiler used to ++produce the work, or an object code interpreter used to run it. ++ ++ The "Corresponding Source" for a work in object code form means all ++the source code needed to generate, install, and (for an executable ++work) run the object code and to modify the work, including scripts to ++control those activities. However, it does not include the work's ++System Libraries, or general-purpose tools or generally available free ++programs which are used unmodified in performing those activities but ++which are not part of the work. For example, Corresponding Source ++includes interface definition files associated with source files for ++the work, and the source code for shared libraries and dynamically ++linked subprograms that the work is specifically designed to require, ++such as by intimate data communication or control flow between those ++subprograms and other parts of the work. ++ ++ The Corresponding Source need not include anything that users ++can regenerate automatically from other parts of the Corresponding ++Source. ++ ++ The Corresponding Source for a work in source code form is that ++same work. ++ ++ 2. Basic Permissions. ++ ++ All rights granted under this License are granted for the term of ++copyright on the Program, and are irrevocable provided the stated ++conditions are met. This License explicitly affirms your unlimited ++permission to run the unmodified Program. The output from running a ++covered work is covered by this License only if the output, given its ++content, constitutes a covered work. This License acknowledges your ++rights of fair use or other equivalent, as provided by copyright law. ++ ++ You may make, run and propagate covered works that you do not ++convey, without conditions so long as your license otherwise remains ++in force. You may convey covered works to others for the sole purpose ++of having them make modifications exclusively for you, or provide you ++with facilities for running those works, provided that you comply with ++the terms of this License in conveying all material for which you do ++not control copyright. Those thus making or running the covered works ++for you must do so exclusively on your behalf, under your direction ++and control, on terms that prohibit them from making any copies of ++your copyrighted material outside their relationship with you. ++ ++ Conveying under any other circumstances is permitted solely under ++the conditions stated below. Sublicensing is not allowed; section 10 ++makes it unnecessary. ++ ++ 3. Protecting Users' Legal Rights From Anti-Circumvention Law. ++ ++ No covered work shall be deemed part of an effective technological ++measure under any applicable law fulfilling obligations under article ++11 of the WIPO copyright treaty adopted on 20 December 1996, or ++similar laws prohibiting or restricting circumvention of such ++measures. ++ ++ When you convey a covered work, you waive any legal power to forbid ++circumvention of technological measures to the extent such circumvention ++is effected by exercising rights under this License with respect to ++the covered work, and you disclaim any intention to limit operation or ++modification of the work as a means of enforcing, against the work's ++users, your or third parties' legal rights to forbid circumvention of ++technological measures. ++ ++ 4. Conveying Verbatim Copies. ++ ++ You may convey verbatim copies of the Program's source code as you ++receive it, in any medium, provided that you conspicuously and ++appropriately publish on each copy an appropriate copyright notice; ++keep intact all notices stating that this License and any ++non-permissive terms added in accord with section 7 apply to the code; ++keep intact all notices of the absence of any warranty; and give all ++recipients a copy of this License along with the Program. ++ ++ You may charge any price or no price for each copy that you convey, ++and you may offer support or warranty protection for a fee. ++ ++ 5. Conveying Modified Source Versions. ++ ++ You may convey a work based on the Program, or the modifications to ++produce it from the Program, in the form of source code under the ++terms of section 4, provided that you also meet all of these conditions: ++ ++ a) The work must carry prominent notices stating that you modified ++ it, and giving a relevant date. ++ ++ b) The work must carry prominent notices stating that it is ++ released under this License and any conditions added under section ++ 7. This requirement modifies the requirement in section 4 to ++ "keep intact all notices". ++ ++ c) You must license the entire work, as a whole, under this ++ License to anyone who comes into possession of a copy. This ++ License will therefore apply, along with any applicable section 7 ++ additional terms, to the whole of the work, and all its parts, ++ regardless of how they are packaged. This License gives no ++ permission to license the work in any other way, but it does not ++ invalidate such permission if you have separately received it. ++ ++ d) If the work has interactive user interfaces, each must display ++ Appropriate Legal Notices; however, if the Program has interactive ++ interfaces that do not display Appropriate Legal Notices, your ++ work need not make them do so. ++ ++ A compilation of a covered work with other separate and independent ++works, which are not by their nature extensions of the covered work, ++and which are not combined with it such as to form a larger program, ++in or on a volume of a storage or distribution medium, is called an ++"aggregate" if the compilation and its resulting copyright are not ++used to limit the access or legal rights of the compilation's users ++beyond what the individual works permit. Inclusion of a covered work ++in an aggregate does not cause this License to apply to the other ++parts of the aggregate. ++ ++ 6. Conveying Non-Source Forms. ++ ++ You may convey a covered work in object code form under the terms ++of sections 4 and 5, provided that you also convey the ++machine-readable Corresponding Source under the terms of this License, ++in one of these ways: ++ ++ a) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by the ++ Corresponding Source fixed on a durable physical medium ++ customarily used for software interchange. ++ ++ b) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by a ++ written offer, valid for at least three years and valid for as ++ long as you offer spare parts or customer support for that product ++ model, to give anyone who possesses the object code either (1) a ++ copy of the Corresponding Source for all the software in the ++ product that is covered by this License, on a durable physical ++ medium customarily used for software interchange, for a price no ++ more than your reasonable cost of physically performing this ++ conveying of source, or (2) access to copy the ++ Corresponding Source from a network server at no charge. ++ ++ c) Convey individual copies of the object code with a copy of the ++ written offer to provide the Corresponding Source. This ++ alternative is allowed only occasionally and noncommercially, and ++ only if you received the object code with such an offer, in accord ++ with subsection 6b. ++ ++ d) Convey the object code by offering access from a designated ++ place (gratis or for a charge), and offer equivalent access to the ++ Corresponding Source in the same way through the same place at no ++ further charge. You need not require recipients to copy the ++ Corresponding Source along with the object code. If the place to ++ copy the object code is a network server, the Corresponding Source ++ may be on a different server (operated by you or a third party) ++ that supports equivalent copying facilities, provided you maintain ++ clear directions next to the object code saying where to find the ++ Corresponding Source. Regardless of what server hosts the ++ Corresponding Source, you remain obligated to ensure that it is ++ available for as long as needed to satisfy these requirements. ++ ++ e) Convey the object code using peer-to-peer transmission, provided ++ you inform other peers where the object code and Corresponding ++ Source of the work are being offered to the general public at no ++ charge under subsection 6d. ++ ++ A separable portion of the object code, whose source code is excluded ++from the Corresponding Source as a System Library, need not be ++included in conveying the object code work. ++ ++ A "User Product" is either (1) a "consumer product", which means any ++tangible personal property which is normally used for personal, family, ++or household purposes, or (2) anything designed or sold for incorporation ++into a dwelling. In determining whether a product is a consumer product, ++doubtful cases shall be resolved in favor of coverage. For a particular ++product received by a particular user, "normally used" refers to a ++typical or common use of that class of product, regardless of the status ++of the particular user or of the way in which the particular user ++actually uses, or expects or is expected to use, the product. A product ++is a consumer product regardless of whether the product has substantial ++commercial, industrial or non-consumer uses, unless such uses represent ++the only significant mode of use of the product. ++ ++ "Installation Information" for a User Product means any methods, ++procedures, authorization keys, or other information required to install ++and execute modified versions of a covered work in that User Product from ++a modified version of its Corresponding Source. The information must ++suffice to ensure that the continued functioning of the modified object ++code is in no case prevented or interfered with solely because ++modification has been made. ++ ++ If you convey an object code work under this section in, or with, or ++specifically for use in, a User Product, and the conveying occurs as ++part of a transaction in which the right of possession and use of the ++User Product is transferred to the recipient in perpetuity or for a ++fixed term (regardless of how the transaction is characterized), the ++Corresponding Source conveyed under this section must be accompanied ++by the Installation Information. But this requirement does not apply ++if neither you nor any third party retains the ability to install ++modified object code on the User Product (for example, the work has ++been installed in ROM). ++ ++ The requirement to provide Installation Information does not include a ++requirement to continue to provide support service, warranty, or updates ++for a work that has been modified or installed by the recipient, or for ++the User Product in which it has been modified or installed. Access to a ++network may be denied when the modification itself materially and ++adversely affects the operation of the network or violates the rules and ++protocols for communication across the network. ++ ++ Corresponding Source conveyed, and Installation Information provided, ++in accord with this section must be in a format that is publicly ++documented (and with an implementation available to the public in ++source code form), and must require no special password or key for ++unpacking, reading or copying. ++ ++ 7. Additional Terms. ++ ++ "Additional permissions" are terms that supplement the terms of this ++License by making exceptions from one or more of its conditions. ++Additional permissions that are applicable to the entire Program shall ++be treated as though they were included in this License, to the extent ++that they are valid under applicable law. If additional permissions ++apply only to part of the Program, that part may be used separately ++under those permissions, but the entire Program remains governed by ++this License without regard to the additional permissions. ++ ++ When you convey a copy of a covered work, you may at your option ++remove any additional permissions from that copy, or from any part of ++it. (Additional permissions may be written to require their own ++removal in certain cases when you modify the work.) You may place ++additional permissions on material, added by you to a covered work, ++for which you have or can give appropriate copyright permission. ++ ++ Notwithstanding any other provision of this License, for material you ++add to a covered work, you may (if authorized by the copyright holders of ++that material) supplement the terms of this License with terms: ++ ++ a) Disclaiming warranty or limiting liability differently from the ++ terms of sections 15 and 16 of this License; or ++ ++ b) Requiring preservation of specified reasonable legal notices or ++ author attributions in that material or in the Appropriate Legal ++ Notices displayed by works containing it; or ++ ++ c) Prohibiting misrepresentation of the origin of that material, or ++ requiring that modified versions of such material be marked in ++ reasonable ways as different from the original version; or ++ ++ d) Limiting the use for publicity purposes of names of licensors or ++ authors of the material; or ++ ++ e) Declining to grant rights under trademark law for use of some ++ trade names, trademarks, or service marks; or ++ ++ f) Requiring indemnification of licensors and authors of that ++ material by anyone who conveys the material (or modified versions of ++ it) with contractual assumptions of liability to the recipient, for ++ any liability that these contractual assumptions directly impose on ++ those licensors and authors. ++ ++ All other non-permissive additional terms are considered "further ++restrictions" within the meaning of section 10. If the Program as you ++received it, or any part of it, contains a notice stating that it is ++governed by this License along with a term that is a further ++restriction, you may remove that term. If a license document contains ++a further restriction but permits relicensing or conveying under this ++License, you may add to a covered work material governed by the terms ++of that license document, provided that the further restriction does ++not survive such relicensing or conveying. ++ ++ If you add terms to a covered work in accord with this section, you ++must place, in the relevant source files, a statement of the ++additional terms that apply to those files, or a notice indicating ++where to find the applicable terms. ++ ++ Additional terms, permissive or non-permissive, may be stated in the ++form of a separately written license, or stated as exceptions; ++the above requirements apply either way. ++ ++ 8. Termination. ++ ++ You may not propagate or modify a covered work except as expressly ++provided under this License. Any attempt otherwise to propagate or ++modify it is void, and will automatically terminate your rights under ++this License (including any patent licenses granted under the third ++paragraph of section 11). ++ ++ However, if you cease all violation of this License, then your ++license from a particular copyright holder is reinstated (a) ++provisionally, unless and until the copyright holder explicitly and ++finally terminates your license, and (b) permanently, if the copyright ++holder fails to notify you of the violation by some reasonable means ++prior to 60 days after the cessation. ++ ++ Moreover, your license from a particular copyright holder is ++reinstated permanently if the copyright holder notifies you of the ++violation by some reasonable means, this is the first time you have ++received notice of violation of this License (for any work) from that ++copyright holder, and you cure the violation prior to 30 days after ++your receipt of the notice. ++ ++ Termination of your rights under this section does not terminate the ++licenses of parties who have received copies or rights from you under ++this License. If your rights have been terminated and not permanently ++reinstated, you do not qualify to receive new licenses for the same ++material under section 10. ++ ++ 9. Acceptance Not Required for Having Copies. ++ ++ You are not required to accept this License in order to receive or ++run a copy of the Program. Ancillary propagation of a covered work ++occurring solely as a consequence of using peer-to-peer transmission ++to receive a copy likewise does not require acceptance. However, ++nothing other than this License grants you permission to propagate or ++modify any covered work. These actions infringe copyright if you do ++not accept this License. Therefore, by modifying or propagating a ++covered work, you indicate your acceptance of this License to do so. ++ ++ 10. Automatic Licensing of Downstream Recipients. ++ ++ Each time you convey a covered work, the recipient automatically ++receives a license from the original licensors, to run, modify and ++propagate that work, subject to this License. You are not responsible ++for enforcing compliance by third parties with this License. ++ ++ An "entity transaction" is a transaction transferring control of an ++organization, or substantially all assets of one, or subdividing an ++organization, or merging organizations. If propagation of a covered ++work results from an entity transaction, each party to that ++transaction who receives a copy of the work also receives whatever ++licenses to the work the party's predecessor in interest had or could ++give under the previous paragraph, plus a right to possession of the ++Corresponding Source of the work from the predecessor in interest, if ++the predecessor has it or can get it with reasonable efforts. ++ ++ You may not impose any further restrictions on the exercise of the ++rights granted or affirmed under this License. For example, you may ++not impose a license fee, royalty, or other charge for exercise of ++rights granted under this License, and you may not initiate litigation ++(including a cross-claim or counterclaim in a lawsuit) alleging that ++any patent claim is infringed by making, using, selling, offering for ++sale, or importing the Program or any portion of it. ++ ++ 11. Patents. ++ ++ A "contributor" is a copyright holder who authorizes use under this ++License of the Program or a work on which the Program is based. The ++work thus licensed is called the contributor's "contributor version". ++ ++ A contributor's "essential patent claims" are all patent claims ++owned or controlled by the contributor, whether already acquired or ++hereafter acquired, that would be infringed by some manner, permitted ++by this License, of making, using, or selling its contributor version, ++but do not include claims that would be infringed only as a ++consequence of further modification of the contributor version. For ++purposes of this definition, "control" includes the right to grant ++patent sublicenses in a manner consistent with the requirements of ++this License. ++ ++ Each contributor grants you a non-exclusive, worldwide, royalty-free ++patent license under the contributor's essential patent claims, to ++make, use, sell, offer for sale, import and otherwise run, modify and ++propagate the contents of its contributor version. ++ ++ In the following three paragraphs, a "patent license" is any express ++agreement or commitment, however denominated, not to enforce a patent ++(such as an express permission to practice a patent or covenant not to ++sue for patent infringement). To "grant" such a patent license to a ++party means to make such an agreement or commitment not to enforce a ++patent against the party. ++ ++ If you convey a covered work, knowingly relying on a patent license, ++and the Corresponding Source of the work is not available for anyone ++to copy, free of charge and under the terms of this License, through a ++publicly available network server or other readily accessible means, ++then you must either (1) cause the Corresponding Source to be so ++available, or (2) arrange to deprive yourself of the benefit of the ++patent license for this particular work, or (3) arrange, in a manner ++consistent with the requirements of this License, to extend the patent ++license to downstream recipients. "Knowingly relying" means you have ++actual knowledge that, but for the patent license, your conveying the ++covered work in a country, or your recipient's use of the covered work ++in a country, would infringe one or more identifiable patents in that ++country that you have reason to believe are valid. ++ ++ If, pursuant to or in connection with a single transaction or ++arrangement, you convey, or propagate by procuring conveyance of, a ++covered work, and grant a patent license to some of the parties ++receiving the covered work authorizing them to use, propagate, modify ++or convey a specific copy of the covered work, then the patent license ++you grant is automatically extended to all recipients of the covered ++work and works based on it. ++ ++ A patent license is "discriminatory" if it does not include within ++the scope of its coverage, prohibits the exercise of, or is ++conditioned on the non-exercise of one or more of the rights that are ++specifically granted under this License. You may not convey a covered ++work if you are a party to an arrangement with a third party that is ++in the business of distributing software, under which you make payment ++to the third party based on the extent of your activity of conveying ++the work, and under which the third party grants, to any of the ++parties who would receive the covered work from you, a discriminatory ++patent license (a) in connection with copies of the covered work ++conveyed by you (or copies made from those copies), or (b) primarily ++for and in connection with specific products or compilations that ++contain the covered work, unless you entered into that arrangement, ++or that patent license was granted, prior to 28 March 2007. ++ ++ Nothing in this License shall be construed as excluding or limiting ++any implied license or other defenses to infringement that may ++otherwise be available to you under applicable patent law. ++ ++ 12. No Surrender of Others' Freedom. ++ ++ If conditions are imposed on you (whether by court order, agreement or ++otherwise) that contradict the conditions of this License, they do not ++excuse you from the conditions of this License. If you cannot convey a ++covered work so as to satisfy simultaneously your obligations under this ++License and any other pertinent obligations, then as a consequence you may ++not convey it at all. For example, if you agree to terms that obligate you ++to collect a royalty for further conveying from those to whom you convey ++the Program, the only way you could satisfy both those terms and this ++License would be to refrain entirely from conveying the Program. ++ ++ 13. Use with the GNU Affero General Public License. ++ ++ Notwithstanding any other provision of this License, you have ++permission to link or combine any covered work with a work licensed ++under version 3 of the GNU Affero General Public License into a single ++combined work, and to convey the resulting work. The terms of this ++License will continue to apply to the part which is the covered work, ++but the special requirements of the GNU Affero General Public License, ++section 13, concerning interaction through a network will apply to the ++combination as such. ++ ++ 14. Revised Versions of this License. ++ ++ The Free Software Foundation may publish revised and/or new versions of ++the GNU General Public License from time to time. Such new versions will ++be similar in spirit to the present version, but may differ in detail to ++address new problems or concerns. ++ ++ Each version is given a distinguishing version number. If the ++Program specifies that a certain numbered version of the GNU General ++Public License "or any later version" applies to it, you have the ++option of following the terms and conditions either of that numbered ++version or of any later version published by the Free Software ++Foundation. If the Program does not specify a version number of the ++GNU General Public License, you may choose any version ever published ++by the Free Software Foundation. ++ ++ If the Program specifies that a proxy can decide which future ++versions of the GNU General Public License can be used, that proxy's ++public statement of acceptance of a version permanently authorizes you ++to choose that version for the Program. ++ ++ Later license versions may give you additional or different ++permissions. However, no additional obligations are imposed on any ++author or copyright holder as a result of your choosing to follow a ++later version. ++ ++ 15. Disclaimer of Warranty. ++ ++ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY ++APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT ++HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY ++OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ++THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM ++IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ++ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ++ ++ 16. Limitation of Liability. ++ ++ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING ++WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS ++THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY ++GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE ++USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF ++DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD ++PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), ++EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF ++SUCH DAMAGES. ++ ++ 17. Interpretation of Sections 15 and 16. ++ ++ If the disclaimer of warranty and limitation of liability provided ++above cannot be given local legal effect according to their terms, ++reviewing courts shall apply local law that most closely approximates ++an absolute waiver of all civil liability in connection with the ++Program, unless a warranty or assumption of liability accompanies a ++copy of the Program in return for a fee. ++ ++ END OF TERMS AND CONDITIONS ++ ++ How to Apply These Terms to Your New Programs ++ ++ If you develop a new program, and you want it to be of the greatest ++possible use to the public, the best way to achieve this is to make it ++free software which everyone can redistribute and change under these terms. ++ ++ To do so, attach the following notices to the program. It is safest ++to attach them to the start of each source file to most effectively ++state the exclusion of warranty; and each file should have at least ++the "copyright" line and a pointer to where the full notice is found. ++ ++ ++ Copyright (C) ++ ++ This program is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation, either version 3 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see . ++ ++Also add information on how to contact you by electronic and paper mail. ++ ++ If the program does terminal interaction, make it output a short ++notice like this when it starts in an interactive mode: ++ ++ Copyright (C) ++ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. ++ This is free software, and you are welcome to redistribute it ++ under certain conditions; type `show c' for details. ++ ++The hypothetical commands `show w' and `show c' should show the appropriate ++parts of the General Public License. Of course, your program's commands ++might be different; for a GUI interface, you would use an "about box". ++ ++ You should also get your employer (if you work as a programmer) or school, ++if any, to sign a "copyright disclaimer" for the program, if necessary. ++For more information on this, and how to apply and follow the GNU GPL, see ++. ++ ++ The GNU General Public License does not permit incorporating your program ++into proprietary programs. If your program is a subroutine library, you ++may consider it more useful to permit linking proprietary applications with ++the library. If this is what you want to do, use the GNU Lesser General ++Public License instead of this License. But first, please read ++. +--- a/transport/internet/shadowtls/config.go ++++ b/transport/internet/shadowtls/config.go +@@ -0,0 +1,223 @@ ++package shadowtls ++ ++import ( ++ "context" ++ "crypto/x509" ++ "fmt" ++ "net" ++ "slices" ++ "strings" ++ "time" ++ ++ utls "github.com/metacubex/utls" ++ xnet "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/transport/internet" ++ protocol "github.com/xtls/xray-core/transport/internet/shadowtls/protocol" ++) ++ ++func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config { ++ if settings == nil { ++ return nil ++ } ++ c, _ := settings.SecuritySettings.(*Config) ++ return c ++} ++func (c *Config) Timeout() time.Duration { ++ if c.HandshakeTimeout == 0 { ++ return 15 * time.Second ++ } ++ return time.Duration(c.HandshakeTimeout) * time.Second ++} ++ ++func (c *Config) Validate() error { ++ if c.Version < 1 || c.Version > 3 { ++ return fmt.Errorf("shadowtls: version must be 1, 2 or 3") ++ } ++ if c.HandshakeTimeout > 300 { ++ return fmt.Errorf("shadowtls: handshakeTimeout must not exceed 300 seconds") ++ } ++ if (c.Version == 2 && c.Password == "") || (c.Version == 3 && c.Password == "" && len(c.Users) == 0) { ++ return fmt.Errorf("shadowtls: password or users required") ++ } ++ for _, u := range c.Users { ++ if u == nil || u.Password == "" { ++ return fmt.Errorf("shadowtls: empty user password") ++ } ++ } ++ if _, err := fingerprint(c.Fingerprint); err != nil { ++ return err ++ } ++ switch c.WildcardSni { ++ case "", "off", "authed", "all": ++ default: ++ return fmt.Errorf("shadowtls: invalid wildcardSni") ++ } ++ for _, addr := range c.HandshakeForServerName { ++ if err := checkAddress(addr); err != nil { ++ return err ++ } ++ } ++ for name := range c.HandshakeForServerName { ++ if name == "" { ++ return fmt.Errorf("shadowtls: empty handshake server name") ++ } ++ } ++ if c.Handshake != "" { ++ if err := checkAddress(c.Handshake); err != nil { ++ return err ++ } ++ } ++ _, err := c.roots() ++ return err ++} ++func checkAddress(addr string) error { ++ d, err := xnet.ParseDestination("tcp:" + addr) ++ if err != nil || d.Port == 0 || d.Address == nil { ++ return fmt.Errorf("shadowtls: invalid handshake address %q", addr) ++ } ++ return nil ++} ++func (c *Config) roots() (*x509.CertPool, error) { ++ if len(c.Certificates) == 0 { ++ return nil, nil ++ } ++ pool, err := x509.SystemCertPool() ++ if err != nil { ++ pool = x509.NewCertPool() ++ } ++ for _, pem := range c.Certificates { ++ if !pool.AppendCertsFromPEM(pem) { ++ return nil, fmt.Errorf("shadowtls: invalid CA certificate") ++ } ++ } ++ return pool, nil ++} ++func fingerprint(name string) (*utls.ClientHelloID, error) { ++ ids := map[string]utls.ClientHelloID{"chrome": utls.HelloChrome_Auto, "firefox": utls.HelloFirefox_Auto, "safari": utls.HelloSafari_Auto, "ios": utls.HelloIOS_Auto, "android": utls.HelloAndroid_11_OkHttp, "edge": utls.HelloEdge_Auto, "360": utls.Hello360_Auto, "qq": utls.HelloQQ_Auto, "chrome120": utls.HelloChrome_120, "firefox120": utls.HelloFirefox_120, "safari16": utls.HelloSafari_16_0, "randomized": utls.HelloRandomized} ++ switch name { ++ case "", "none", "golang": ++ return nil, nil ++ } ++ id, ok := ids[name] ++ if !ok { ++ return nil, fmt.Errorf("shadowtls: unknown fingerprint %q", name) ++ } ++ return &id, nil ++} ++ ++func (c *Config) Client(ctx context.Context, conn net.Conn, dest xnet.Destination) (net.Conn, error) { ++ if err := c.Validate(); err != nil { ++ return nil, err ++ } ++ if c.Version > 1 && c.Password == "" { ++ return nil, fmt.Errorf("shadowtls: outbound password required") ++ } ++ host := c.ServerName ++ if host == "" { ++ host = dest.Address.String() ++ } ++ roots, err := c.roots() ++ if err != nil { ++ return nil, err ++ } ++ fp, err := fingerprint(c.Fingerprint) ++ if err != nil { ++ return nil, err ++ } ++ alpn := c.Alpn ++ if len(alpn) == 0 { ++ alpn = protocol.DefaultALPN ++ } ++ client, err := protocol.NewClient(protocol.ClientConfig{Version: int(c.Version), Password: c.Password, StrictMode: c.StrictMode, TLSHandshake: func(ctx context.Context, raw net.Conn, generator protocol.TLSSessionIDGeneratorFunc) error { ++ tc := &utls.Config{ServerName: host, RootCAs: roots, InsecureSkipVerify: c.AllowInsecure, MinVersion: utls.VersionTLS12, NextProtos: alpn, SessionIDGenerator: generator} ++ if c.Version == 1 { ++ tc.MaxVersion = utls.VersionTLS12 ++ return utls.Client(raw, tc).HandshakeContext(ctx) ++ } ++ // V2 cannot authenticate a handshake using the post-quantum key share. ++ if c.Version == 2 { ++ tc.CurvePreferences = []utls.CurveID{utls.X25519, utls.CurveP256, utls.CurveP384} ++ } ++ if fp == nil { ++ return utls.Client(raw, tc).HandshakeContext(ctx) ++ } ++ uc := utls.UClient(raw, tc, *fp) ++ if err := uc.BuildHandshakeState(); err != nil { ++ return err ++ } ++ for _, extension := range uc.Extensions { ++ switch e := extension.(type) { ++ case *utls.ALPNExtension: ++ if len(c.Alpn) > 0 { ++ e.AlpnProtocols = append([]string(nil), alpn...) ++ } ++ case *utls.SupportedCurvesExtension: ++ if c.Version == 2 { ++ e.Curves = slices.DeleteFunc(e.Curves, func(v utls.CurveID) bool { return v == utls.X25519MLKEM768 }) ++ } ++ case *utls.KeyShareExtension: ++ if c.Version == 2 { ++ e.KeyShares = slices.DeleteFunc(e.KeyShares, func(v utls.KeyShare) bool { return v.Group == utls.X25519MLKEM768 }) ++ } ++ } ++ } ++ if err := uc.BuildHandshakeState(); err != nil { ++ return err ++ } ++ return uc.HandshakeContext(ctx) ++ }}) ++ if err != nil { ++ return nil, err ++ } ++ ctx, cancel := context.WithTimeout(ctx, c.Timeout()) ++ defer cancel() ++ stop := context.AfterFunc(ctx, func() { conn.Close() }) ++ result, err := client.DialContextConn(ctx, conn) ++ if !stop() && err == nil { ++ err = ctx.Err() ++ } ++ return result, err ++} ++ ++type handshakeConn struct { ++ net.Conn ++ stop func() bool ++} ++ ++func (c *handshakeConn) Close() error { c.stop(); return c.Conn.Close() } ++func (c *Config) ServerConfig(sockopt *internet.SocketConfig) (*protocol.ServerConfig, error) { ++ if err := c.Validate(); err != nil { ++ return nil, err ++ } ++ dial := func(ctx context.Context, network, addr string) (net.Conn, error) { ++ dest, err := xnet.ParseDestination("tcp:" + addr) ++ if err != nil { ++ return nil, err ++ } ++ conn, err := internet.DialSystem(ctx, dest, sockopt) ++ if err != nil { ++ return nil, err ++ } ++ return &handshakeConn{Conn: conn, stop: context.AfterFunc(ctx, func() { conn.Close() })}, nil ++ } ++ handshake := protocol.HandshakeConfig{Server: c.Handshake, DialContext: dial} ++ custom := make(map[string]protocol.HandshakeConfig, len(c.HandshakeForServerName)) ++ for name, addr := range c.HandshakeForServerName { ++ custom[name] = protocol.HandshakeConfig{Server: addr, DialContext: dial} ++ } ++ users := make([]protocol.User, 0, len(c.Users)+1) ++ for _, u := range c.Users { ++ users = append(users, protocol.User{Name: u.Name, Password: u.Password}) ++ } ++ if len(users) == 0 && c.Password != "" { ++ users = append(users, protocol.User{Password: c.Password}) ++ } ++ wildcard := protocol.WildcardSNIOff ++ switch strings.ToLower(c.WildcardSni) { ++ case "authed": ++ wildcard = protocol.WildcardSNIAuthed ++ case "all": ++ wildcard = protocol.WildcardSNIAll ++ } ++ return protocol.NewServerConfig(int(c.Version), c.Password, users, handshake, custom, c.StrictMode, wildcard) ++} +--- a/transport/internet/shadowtls/config.pb.go ++++ b/transport/internet/shadowtls/config.pb.go +@@ -0,0 +1,295 @@ ++// Code generated by protoc-gen-go. DO NOT EDIT. ++// versions: ++// protoc-gen-go v1.36.12 ++// protoc v3.21.12 ++// source: transport/internet/shadowtls/config.proto ++ ++package shadowtls ++ ++import ( ++ protoreflect "google.golang.org/protobuf/reflect/protoreflect" ++ protoimpl "google.golang.org/protobuf/runtime/protoimpl" ++ reflect "reflect" ++ sync "sync" ++ unsafe "unsafe" ++) ++ ++const ( ++ // Verify that this generated code is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) ++ // Verify that runtime/protoimpl is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ++) ++ ++type User struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` ++ Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *User) Reset() { ++ *x = User{} ++ mi := &file_transport_internet_shadowtls_config_proto_msgTypes[0] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *User) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*User) ProtoMessage() {} ++ ++func (x *User) ProtoReflect() protoreflect.Message { ++ mi := &file_transport_internet_shadowtls_config_proto_msgTypes[0] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use User.ProtoReflect.Descriptor instead. ++func (*User) Descriptor() ([]byte, []int) { ++ return file_transport_internet_shadowtls_config_proto_rawDescGZIP(), []int{0} ++} ++ ++func (x *User) GetName() string { ++ if x != nil { ++ return x.Name ++ } ++ return "" ++} ++ ++func (x *User) GetPassword() string { ++ if x != nil { ++ return x.Password ++ } ++ return "" ++} ++ ++type Config struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` ++ Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` ++ ServerName string `protobuf:"bytes,3,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` ++ Fingerprint string `protobuf:"bytes,4,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"` ++ AllowInsecure bool `protobuf:"varint,5,opt,name=allow_insecure,json=allowInsecure,proto3" json:"allow_insecure,omitempty"` ++ Alpn []string `protobuf:"bytes,6,rep,name=alpn,proto3" json:"alpn,omitempty"` ++ Certificates [][]byte `protobuf:"bytes,7,rep,name=certificates,proto3" json:"certificates,omitempty"` ++ StrictMode bool `protobuf:"varint,8,opt,name=strict_mode,json=strictMode,proto3" json:"strict_mode,omitempty"` ++ Handshake string `protobuf:"bytes,9,opt,name=handshake,proto3" json:"handshake,omitempty"` ++ Users []*User `protobuf:"bytes,10,rep,name=users,proto3" json:"users,omitempty"` ++ HandshakeForServerName map[string]string `protobuf:"bytes,11,rep,name=handshake_for_server_name,json=handshakeForServerName,proto3" json:"handshake_for_server_name,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` ++ WildcardSni string `protobuf:"bytes,12,opt,name=wildcard_sni,json=wildcardSni,proto3" json:"wildcard_sni,omitempty"` ++ HandshakeTimeout uint32 `protobuf:"varint,13,opt,name=handshake_timeout,json=handshakeTimeout,proto3" json:"handshake_timeout,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *Config) Reset() { ++ *x = Config{} ++ mi := &file_transport_internet_shadowtls_config_proto_msgTypes[1] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *Config) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*Config) ProtoMessage() {} ++ ++func (x *Config) ProtoReflect() protoreflect.Message { ++ mi := &file_transport_internet_shadowtls_config_proto_msgTypes[1] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use Config.ProtoReflect.Descriptor instead. ++func (*Config) Descriptor() ([]byte, []int) { ++ return file_transport_internet_shadowtls_config_proto_rawDescGZIP(), []int{1} ++} ++ ++func (x *Config) GetVersion() uint32 { ++ if x != nil { ++ return x.Version ++ } ++ return 0 ++} ++ ++func (x *Config) GetPassword() string { ++ if x != nil { ++ return x.Password ++ } ++ return "" ++} ++ ++func (x *Config) GetServerName() string { ++ if x != nil { ++ return x.ServerName ++ } ++ return "" ++} ++ ++func (x *Config) GetFingerprint() string { ++ if x != nil { ++ return x.Fingerprint ++ } ++ return "" ++} ++ ++func (x *Config) GetAllowInsecure() bool { ++ if x != nil { ++ return x.AllowInsecure ++ } ++ return false ++} ++ ++func (x *Config) GetAlpn() []string { ++ if x != nil { ++ return x.Alpn ++ } ++ return nil ++} ++ ++func (x *Config) GetCertificates() [][]byte { ++ if x != nil { ++ return x.Certificates ++ } ++ return nil ++} ++ ++func (x *Config) GetStrictMode() bool { ++ if x != nil { ++ return x.StrictMode ++ } ++ return false ++} ++ ++func (x *Config) GetHandshake() string { ++ if x != nil { ++ return x.Handshake ++ } ++ return "" ++} ++ ++func (x *Config) GetUsers() []*User { ++ if x != nil { ++ return x.Users ++ } ++ return nil ++} ++ ++func (x *Config) GetHandshakeForServerName() map[string]string { ++ if x != nil { ++ return x.HandshakeForServerName ++ } ++ return nil ++} ++ ++func (x *Config) GetWildcardSni() string { ++ if x != nil { ++ return x.WildcardSni ++ } ++ return "" ++} ++ ++func (x *Config) GetHandshakeTimeout() uint32 { ++ if x != nil { ++ return x.HandshakeTimeout ++ } ++ return 0 ++} ++ ++var File_transport_internet_shadowtls_config_proto protoreflect.FileDescriptor ++ ++const file_transport_internet_shadowtls_config_proto_rawDesc = "" + ++ "\n" + ++ ")transport/internet/shadowtls/config.proto\x12!xray.transport.internet.shadowtls\"6\n" + ++ "\x04User\x12\x12\n" + ++ "\x04name\x18\x01 \x01(\tR\x04name\x12\x1a\n" + ++ "\bpassword\x18\x02 \x01(\tR\bpassword\"\xfc\x04\n" + ++ "\x06Config\x12\x18\n" + ++ "\aversion\x18\x01 \x01(\rR\aversion\x12\x1a\n" + ++ "\bpassword\x18\x02 \x01(\tR\bpassword\x12\x1f\n" + ++ "\vserver_name\x18\x03 \x01(\tR\n" + ++ "serverName\x12 \n" + ++ "\vfingerprint\x18\x04 \x01(\tR\vfingerprint\x12%\n" + ++ "\x0eallow_insecure\x18\x05 \x01(\bR\rallowInsecure\x12\x12\n" + ++ "\x04alpn\x18\x06 \x03(\tR\x04alpn\x12\"\n" + ++ "\fcertificates\x18\a \x03(\fR\fcertificates\x12\x1f\n" + ++ "\vstrict_mode\x18\b \x01(\bR\n" + ++ "strictMode\x12\x1c\n" + ++ "\thandshake\x18\t \x01(\tR\thandshake\x12=\n" + ++ "\x05users\x18\n" + ++ " \x03(\v2'.xray.transport.internet.shadowtls.UserR\x05users\x12\x80\x01\n" + ++ "\x19handshake_for_server_name\x18\v \x03(\v2E.xray.transport.internet.shadowtls.Config.HandshakeForServerNameEntryR\x16handshakeForServerName\x12!\n" + ++ "\fwildcard_sni\x18\f \x01(\tR\vwildcardSni\x12+\n" + ++ "\x11handshake_timeout\x18\r \x01(\rR\x10handshakeTimeout\x1aI\n" + ++ "\x1bHandshakeForServerNameEntry\x12\x10\n" + ++ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + ++ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B8Z6github.com/xtls/xray-core/transport/internet/shadowtlsb\x06proto3" ++ ++var ( ++ file_transport_internet_shadowtls_config_proto_rawDescOnce sync.Once ++ file_transport_internet_shadowtls_config_proto_rawDescData []byte ++) ++ ++func file_transport_internet_shadowtls_config_proto_rawDescGZIP() []byte { ++ file_transport_internet_shadowtls_config_proto_rawDescOnce.Do(func() { ++ file_transport_internet_shadowtls_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_shadowtls_config_proto_rawDesc), len(file_transport_internet_shadowtls_config_proto_rawDesc))) ++ }) ++ return file_transport_internet_shadowtls_config_proto_rawDescData ++} ++ ++var file_transport_internet_shadowtls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) ++var file_transport_internet_shadowtls_config_proto_goTypes = []any{ ++ (*User)(nil), // 0: xray.transport.internet.shadowtls.User ++ (*Config)(nil), // 1: xray.transport.internet.shadowtls.Config ++ nil, // 2: xray.transport.internet.shadowtls.Config.HandshakeForServerNameEntry ++} ++var file_transport_internet_shadowtls_config_proto_depIdxs = []int32{ ++ 0, // 0: xray.transport.internet.shadowtls.Config.users:type_name -> xray.transport.internet.shadowtls.User ++ 2, // 1: xray.transport.internet.shadowtls.Config.handshake_for_server_name:type_name -> xray.transport.internet.shadowtls.Config.HandshakeForServerNameEntry ++ 2, // [2:2] is the sub-list for method output_type ++ 2, // [2:2] is the sub-list for method input_type ++ 2, // [2:2] is the sub-list for extension type_name ++ 2, // [2:2] is the sub-list for extension extendee ++ 0, // [0:2] is the sub-list for field type_name ++} ++ ++func init() { file_transport_internet_shadowtls_config_proto_init() } ++func file_transport_internet_shadowtls_config_proto_init() { ++ if File_transport_internet_shadowtls_config_proto != nil { ++ return ++ } ++ type x struct{} ++ out := protoimpl.TypeBuilder{ ++ File: protoimpl.DescBuilder{ ++ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), ++ RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_shadowtls_config_proto_rawDesc), len(file_transport_internet_shadowtls_config_proto_rawDesc)), ++ NumEnums: 0, ++ NumMessages: 3, ++ NumExtensions: 0, ++ NumServices: 0, ++ }, ++ GoTypes: file_transport_internet_shadowtls_config_proto_goTypes, ++ DependencyIndexes: file_transport_internet_shadowtls_config_proto_depIdxs, ++ MessageInfos: file_transport_internet_shadowtls_config_proto_msgTypes, ++ }.Build() ++ File_transport_internet_shadowtls_config_proto = out.File ++ file_transport_internet_shadowtls_config_proto_goTypes = nil ++ file_transport_internet_shadowtls_config_proto_depIdxs = nil ++} +--- a/transport/internet/shadowtls/config.proto ++++ b/transport/internet/shadowtls/config.proto +@@ -0,0 +1,19 @@ ++syntax = "proto3"; ++package xray.transport.internet.shadowtls; ++option go_package = "github.com/xtls/xray-core/transport/internet/shadowtls"; ++message User { string name = 1; string password = 2; } ++message Config { ++ uint32 version = 1; ++ string password = 2; ++ string server_name = 3; ++ string fingerprint = 4; ++ bool allow_insecure = 5; ++ repeated string alpn = 6; ++ repeated bytes certificates = 7; ++ bool strict_mode = 8; ++ string handshake = 9; ++ repeated User users = 10; ++ map handshake_for_server_name = 11; ++ string wildcard_sni = 12; ++ uint32 handshake_timeout = 13; ++} +--- a/transport/internet/shadowtls/protocol/client.go ++++ b/transport/internet/shadowtls/protocol/client.go +@@ -0,0 +1,103 @@ ++// Adapted from MetaCubeX/mihomo v1.19.32; GPL-3.0-or-later. ++package shadowtls ++ ++import ( ++ "context" ++ "crypto/hmac" ++ "crypto/sha1" ++ "errors" ++ "fmt" ++ "net" ++) ++ ++const Mode = "shadow-tls" ++ ++var ( ++ DefaultALPN = []string{"h2", "http/1.1"} ++ WsALPN = []string{"http/1.1"} ++) ++ ++type TLSSessionIDGeneratorFunc func(clientHello []byte, sessionID []byte) error ++ ++type TLSHandshakeFunc func( ++ ctx context.Context, ++ conn net.Conn, ++ sessionIDGenerator TLSSessionIDGeneratorFunc, // for shadow-tls version 3 ++) error ++ ++type ClientConfig struct { ++ Version int ++ Password string ++ StrictMode bool ++ TLSHandshake TLSHandshakeFunc ++} ++ ++type Client struct { ++ version int ++ password string ++ strictMode bool ++ tlsHandshake TLSHandshakeFunc ++} ++ ++func NewClient(config ClientConfig) (*Client, error) { ++ if err := checkVersion(config.Version); err != nil { ++ return nil, err ++ } ++ return &Client{ ++ version: config.Version, ++ password: config.Password, ++ strictMode: config.StrictMode, ++ tlsHandshake: config.TLSHandshake, ++ }, nil ++} ++ ++func (c *Client) SetHandshakeFunc(handshakeFunc TLSHandshakeFunc) { ++ c.tlsHandshake = handshakeFunc ++} ++ ++func (c *Client) DialContextConn(ctx context.Context, conn net.Conn) (net.Conn, error) { ++ if conn == nil || c.tlsHandshake == nil { ++ return nil, errors.New("shadow-tls: connection and TLS handshake are required") ++ } ++ switch c.version { ++ case 1: ++ if err := c.tlsHandshake(ctx, conn, nil); err != nil { ++ return nil, err ++ } ++ return conn, nil ++ case 2: ++ hashConn := newHashReadConn(conn, c.password) ++ if err := c.tlsHandshake(ctx, hashConn, nil); err != nil { ++ return nil, err ++ } ++ return newClientConn(hashConn), nil ++ case 3: ++ stream := newStreamWrapper(conn, c.password) ++ if err := c.tlsHandshake(ctx, stream, generateSessionID(c.password)); err != nil { ++ return nil, err ++ } ++ isTLS13, authorized, serverRandom, readHMAC := stream.Authorized() ++ if c.strictMode && !isTLS13 { ++ return nil, errors.New("shadow-tls: TLS 1.3 is not supported") ++ } ++ if !authorized { ++ return nil, errors.New("shadow-tls: traffic hijacked") ++ } ++ hmacAdd := hmac.New(sha1.New, []byte(c.password)) ++ _, _ = hmacAdd.Write(serverRandom) ++ _, _ = hmacAdd.Write([]byte("C")) ++ hmacVerify := hmac.New(sha1.New, []byte(c.password)) ++ _, _ = hmacVerify.Write(serverRandom) ++ _, _ = hmacVerify.Write([]byte("S")) ++ return newVerifiedConn(conn, hmacAdd, hmacVerify, readHMAC), nil ++ default: ++ panic("unreachable") ++ } ++} ++ ++func checkVersion(version int) error { ++ if version < 1 || version > 3 { ++ return fmt.Errorf("shadow-tls: unknown protocol version: %d", version) ++ } ++ return nil ++} +--- a/transport/internet/shadowtls/protocol/net.go ++++ b/transport/internet/shadowtls/protocol/net.go +@@ -0,0 +1,34 @@ ++package shadowtls ++ ++import ( ++ "bytes" ++ "context" ++ "io" ++ "net" ++) ++ ++type cachedConn struct { ++ net.Conn ++ prefix *bytes.Reader ++} ++ ++func newCachedConn(conn net.Conn, prefix []byte) net.Conn { ++ return &cachedConn{Conn: conn, prefix: bytes.NewReader(prefix)} ++} ++func (c *cachedConn) Read(b []byte) (int, error) { ++ if c.prefix.Len() > 0 { ++ return c.prefix.Read(b) ++ } ++ return c.Conn.Read(b) ++} ++func relayContext(ctx context.Context, a, b net.Conn) error { ++ stop := context.AfterFunc(ctx, func() { a.Close(); b.Close() }) ++ defer stop() ++ done := make(chan error, 1) ++ go func() { _, err := io.Copy(a, b); closeWrite(a); done <- err }() ++ _, err := io.Copy(b, a) ++ closeWrite(b) ++ return joinCopyErrors(err, <-done) ++} ++ ++func (c *cachedConn) Upstream() any { return c.Conn } +--- a/transport/internet/shadowtls/protocol/protocol.go ++++ b/transport/internet/shadowtls/protocol/protocol.go +@@ -0,0 +1,194 @@ ++// Adapted from MetaCubeX/mihomo v1.19.32; GPL-3.0-or-later. ++package shadowtls ++ ++import ( ++ "encoding/binary" ++ "errors" ++ "fmt" ++ "io" ++ "net" ++) ++ ++const ( ++ tlsRandomSize = 32 ++ tlsHeaderSize = 5 ++ tlsSessionIDSize = 32 ++ ++ clientHello = 1 ++ serverHello = 2 ++ ++ changeCipherSpec = 20 ++ alert = 21 ++ handshake = 22 ++ applicationData = 23 ++ ++ serverRandomIndex = tlsHeaderSize + 1 + 3 + 2 ++ sessionIDLengthIndex = tlsHeaderSize + 1 + 3 + 2 + tlsRandomSize ++ tlsHMACHeaderSize = tlsHeaderSize + hmacSize ++ hmacSize = 4 ++ maxTLSPlaintext = 16384 ++) ++ ++func readFrame(reader io.Reader) ([]byte, error) { ++ var header [tlsHeaderSize]byte ++ if _, err := io.ReadFull(reader, header[:]); err != nil { ++ return nil, err ++ } ++ length := int(binary.BigEndian.Uint16(header[3:])) ++ frame := make([]byte, tlsHeaderSize+length) ++ copy(frame, header[:]) ++ if _, err := io.ReadFull(reader, frame[tlsHeaderSize:]); err != nil { ++ return nil, err ++ } ++ return frame, nil ++} ++ ++func copyUntilHandshakeFinished(dst io.Writer, src io.Reader) error { ++ seenChangeCipherSpec := false ++ for { ++ frame, err := readFrame(src) ++ if err != nil { ++ return err ++ } ++ if _, err = dst.Write(frame); err != nil { ++ return err ++ } ++ if frame[0] != handshake { ++ if frame[0] != changeCipherSpec { ++ return fmt.Errorf("shadow-tls: unexpected TLS record type: %d", frame[0]) ++ } ++ if !seenChangeCipherSpec { ++ seenChangeCipherSpec = true ++ continue ++ } ++ } ++ if seenChangeCipherSpec { ++ return nil ++ } ++ } ++} ++ ++func extractServerName(frame []byte) (string, error) { ++ if len(frame) < tlsHeaderSize+4 || frame[0] != handshake || frame[tlsHeaderSize] != clientHello { ++ return "", errors.New("shadow-tls: invalid ClientHello") ++ } ++ helloLength := int(frame[tlsHeaderSize+1])<<16 | int(frame[tlsHeaderSize+2])<<8 | int(frame[tlsHeaderSize+3]) ++ hello := frame[tlsHeaderSize+4:] ++ if helloLength > len(hello) { ++ return "", io.ErrUnexpectedEOF ++ } ++ hello = hello[:helloLength] ++ if len(hello) < 2+tlsRandomSize+1 { ++ return "", io.ErrUnexpectedEOF ++ } ++ offset := 2 + tlsRandomSize ++ sessionIDLength := int(hello[offset]) ++ offset++ ++ if offset+sessionIDLength+2 > len(hello) { ++ return "", io.ErrUnexpectedEOF ++ } ++ offset += sessionIDLength ++ cipherSuitesLength := int(binary.BigEndian.Uint16(hello[offset:])) ++ offset += 2 ++ if offset+cipherSuitesLength+1 > len(hello) { ++ return "", io.ErrUnexpectedEOF ++ } ++ offset += cipherSuitesLength ++ compressionMethodsLength := int(hello[offset]) ++ offset++ ++ if offset+compressionMethodsLength == len(hello) { ++ return "", errors.New("shadow-tls: ClientHello has no SNI") ++ } ++ if offset+compressionMethodsLength+2 > len(hello) { ++ return "", io.ErrUnexpectedEOF ++ } ++ offset += compressionMethodsLength ++ extensionsLength := int(binary.BigEndian.Uint16(hello[offset:])) ++ offset += 2 ++ if offset+extensionsLength > len(hello) { ++ return "", io.ErrUnexpectedEOF ++ } ++ extensions := hello[offset : offset+extensionsLength] ++ for len(extensions) >= 4 { ++ extensionType := binary.BigEndian.Uint16(extensions) ++ extensionLength := int(binary.BigEndian.Uint16(extensions[2:])) ++ extensions = extensions[4:] ++ if extensionLength > len(extensions) { ++ return "", io.ErrUnexpectedEOF ++ } ++ if extensionType == 0 { ++ return parseServerNameExtension(extensions[:extensionLength]) ++ } ++ extensions = extensions[extensionLength:] ++ } ++ return "", errors.New("shadow-tls: ClientHello has no SNI") ++} ++ ++func parseServerNameExtension(extension []byte) (string, error) { ++ if len(extension) < 2 { ++ return "", io.ErrUnexpectedEOF ++ } ++ listLength := int(binary.BigEndian.Uint16(extension)) ++ if listLength > len(extension)-2 { ++ return "", io.ErrUnexpectedEOF ++ } ++ list := extension[2 : 2+listLength] ++ for len(list) >= 3 { ++ nameType := list[0] ++ nameLength := int(binary.BigEndian.Uint16(list[1:])) ++ list = list[3:] ++ if nameLength > len(list) { ++ return "", io.ErrUnexpectedEOF ++ } ++ if nameType == 0 && nameLength > 0 { ++ return string(list[:nameLength]), nil ++ } ++ list = list[nameLength:] ++ } ++ return "", errors.New("shadow-tls: ClientHello has no host name") ++} ++ ++func extractServerRandom(frame []byte) []byte { ++ if len(frame) < serverRandomIndex+tlsRandomSize || frame[0] != handshake || frame[tlsHeaderSize] != serverHello { ++ return nil ++ } ++ return append([]byte(nil), frame[serverRandomIndex:serverRandomIndex+tlsRandomSize]...) ++} ++ ++func isServerHelloSupportTLS13(frame []byte) bool { ++ if len(frame) <= sessionIDLengthIndex || frame[0] != handshake || frame[tlsHeaderSize] != serverHello { ++ return false ++ } ++ offset := sessionIDLengthIndex ++ sessionIDLength := int(frame[offset]) ++ offset++ ++ if offset+sessionIDLength+3+2 > len(frame) { ++ return false ++ } ++ offset += sessionIDLength + 3 ++ extensionsLength := int(binary.BigEndian.Uint16(frame[offset:])) ++ offset += 2 ++ if offset+extensionsLength > len(frame) { ++ return false ++ } ++ extensions := frame[offset : offset+extensionsLength] ++ for len(extensions) >= 4 { ++ extensionType := binary.BigEndian.Uint16(extensions) ++ extensionLength := int(binary.BigEndian.Uint16(extensions[2:])) ++ extensions = extensions[4:] ++ if extensionLength > len(extensions) { ++ return false ++ } ++ if extensionType == 43 { ++ return extensionLength == 2 && binary.BigEndian.Uint16(extensions[:2]) == 0x0304 ++ } ++ extensions = extensions[extensionLength:] ++ } ++ return false ++} ++ ++func writeBuffers(writer io.Writer, buffers ...[]byte) error { ++ netBuffers := net.Buffers(buffers) ++ _, err := netBuffers.WriteTo(writer) ++ return err ++} +--- a/transport/internet/shadowtls/protocol/server.go ++++ b/transport/internet/shadowtls/protocol/server.go +@@ -0,0 +1,397 @@ ++// Adapted from MetaCubeX/mihomo v1.19.32; GPL-3.0-or-later. ++package shadowtls ++ ++import ( ++ "context" ++ "crypto/hmac" ++ "crypto/sha1" ++ "errors" ++ "fmt" ++ "hash" ++ "io" ++ "net" ++ "os" ++ "strings" ++ "time" ++) ++ ++var ErrFallbackCompleted = errors.New("shadow-tls: connection relayed to fallback") ++ ++type WildcardSNI int ++ ++const ( ++ WildcardSNIOff WildcardSNI = iota ++ WildcardSNIAuthed ++ WildcardSNIAll ++) ++ ++type User struct { ++ Name string ++ Password string ++} ++ ++type HandshakeConfig struct { ++ Server string ++ DialContext func(ctx context.Context, network, address string) (net.Conn, error) ++} ++ ++type ServerConfig struct { ++ version int ++ password string // for protocol version 2 ++ users []User // for protocol version 3 ++ handshake HandshakeConfig ++ handshakeForServerName map[string]HandshakeConfig // for protocol version 2/3 ++ strictMode bool // for protocol version 3 ++ wildcardSNI WildcardSNI // for protocol version 3 ++} ++ ++func NewServerConfig( ++ version int, ++ password string, ++ users []User, ++ handshake HandshakeConfig, ++ handshakeForServerName map[string]HandshakeConfig, ++ strictMode bool, ++ wildcardSNI WildcardSNI, ++) (*ServerConfig, error) { ++ if err := checkVersion(version); err != nil { ++ return nil, err ++ } ++ if version == 3 && len(users) == 0 { ++ return nil, errors.New("shadow-tls: at least one user is required") ++ } ++ if wildcardSNI < WildcardSNIOff || wildcardSNI > WildcardSNIAll { ++ return nil, errors.New("shadow-tls: invalid wildcard SNI mode") ++ } ++ if !handshake.valid() && wildcardSNI == WildcardSNIOff { ++ return nil, errors.New("shadow-tls: missing default handshake information") ++ } ++ if version < 3 && !handshake.valid() { ++ return nil, errors.New("shadow-tls: missing default handshake information") ++ } ++ for serverName, handshakeConfig := range handshakeForServerName { ++ if serverName == "" || !handshakeConfig.valid() { ++ return nil, fmt.Errorf("shadow-tls: invalid handshake for server name %q", serverName) ++ } ++ } ++ return &ServerConfig{ ++ version: version, ++ password: password, ++ users: append([]User(nil), users...), ++ handshake: handshake, ++ handshakeForServerName: cloneHandshakeMap(handshakeForServerName), ++ strictMode: strictMode, ++ wildcardSNI: wildcardSNI, ++ }, nil ++} ++ ++func Server(ctx context.Context, conn net.Conn, config *ServerConfig) (net.Conn, error) { ++ if conn == nil || config == nil { ++ return nil, errors.New("shadow-tls: nil connection or server config") ++ } ++ var ( ++ serverConn net.Conn ++ user string ++ err error ++ ) ++ switch config.version { ++ case 1: ++ serverConn, user, err = config.serverV1(ctx, conn) ++ case 2: ++ serverConn, user, err = config.serverV2(ctx, conn) ++ case 3: ++ serverConn, user, err = config.serverV3(ctx, conn) ++ default: ++ panic("unreachable") ++ } ++ if err != nil || user == "" { ++ return serverConn, err ++ } ++ return &authenticatedConn{Conn: serverConn, user: user}, nil ++} ++ ++func (s *ServerConfig) serverV1(ctx context.Context, conn net.Conn) (net.Conn, string, error) { ++ handshakeConn, err := s.handshake.dial(ctx) ++ if err != nil { ++ return nil, "", fmt.Errorf("shadow-tls: dial handshake server: %w", err) ++ } ++ defer handshakeConn.Close() ++ if err = relayHandshake(conn, handshakeConn); err != nil { ++ return nil, "", fmt.Errorf("shadow-tls: relay handshake: %w", err) ++ } ++ return conn, "", nil ++} ++ ++func (s *ServerConfig) serverV2(ctx context.Context, conn net.Conn) (net.Conn, string, error) { ++ clientHello, err := readFrame(conn) ++ if err != nil { ++ return nil, "", fmt.Errorf("shadow-tls: read client handshake: %w", err) ++ } ++ handshakeConfig := s.selectHandshake(clientHello) ++ handshakeConn, err := handshakeConfig.dial(ctx) ++ if err != nil { ++ return nil, "", fmt.Errorf("shadow-tls: dial handshake server: %w", err) ++ } ++ hashConn := newHashWriteConn(conn, s.password) ++ serverCopyDone := make(chan error, 1) ++ go func() { ++ _, copyErr := io.Copy(hashConn, handshakeConn) ++ serverCopyDone <- copyErr ++ }() ++ ++ request, err := copyUntilHandshakeFinishedV2( ++ handshakeConn, ++ newCachedConn(conn, clientHello), ++ hashConn, ++ 2, ++ ) ++ if err == nil { ++ _ = handshakeConn.Close() ++ <-serverCopyDone ++ return newCachedConn(newConn(conn), request), "", nil ++ } ++ if errors.Is(err, os.ErrPermission) { ++ hashConn.Fallback() ++ clientCopyDone := make(chan error, 1) ++ go func() { ++ _, copyErr := io.Copy(handshakeConn, conn) ++ clientCopyDone <- copyErr ++ }() ++ var clientCopyErr, serverCopyErr error ++ select { ++ case clientCopyErr = <-clientCopyDone: ++ finishCopyDirection(handshakeConn, conn, clientCopyErr) ++ serverCopyErr = <-serverCopyDone ++ case serverCopyErr = <-serverCopyDone: ++ finishCopyDirection(conn, handshakeConn, serverCopyErr) ++ clientCopyErr = <-clientCopyDone ++ } ++ _ = handshakeConn.Close() ++ if relayErr := joinCopyErrors(clientCopyErr, serverCopyErr); relayErr != nil { ++ return nil, "", relayErr ++ } ++ return nil, "", ErrFallbackCompleted ++ } ++ _ = handshakeConn.Close() ++ <-serverCopyDone ++ return nil, "", err ++} ++ ++func (s *ServerConfig) serverV3(ctx context.Context, conn net.Conn) (net.Conn, string, error) { ++ clientHello, err := readFrame(conn) ++ if err != nil { ++ return nil, "", fmt.Errorf("shadow-tls: read client handshake: %w", err) ++ } ++ serverName, err := extractServerName(clientHello) ++ if err != nil { ++ return nil, "", fmt.Errorf("shadow-tls: extract server name: %w", err) ++ } ++ user, authErr := verifyClientHello(clientHello, s.users) ++ handshakeConfig := s.selectV3Handshake(serverName, authErr == nil) ++ if authErr != nil { ++ return nil, "", s.relayFallback(ctx, conn, clientHello, handshakeConfig) ++ } ++ ++ handshakeConn, err := handshakeConfig.dial(ctx) ++ if err != nil { ++ return nil, "", fmt.Errorf("shadow-tls: dial handshake server: %w", err) ++ } ++ defer handshakeConn.Close() ++ if _, err = handshakeConn.Write(clientHello); err != nil { ++ return nil, "", fmt.Errorf("shadow-tls: write client handshake: %w", err) ++ } ++ serverHello, err := readFrame(handshakeConn) ++ if err != nil { ++ return nil, "", fmt.Errorf("shadow-tls: read server handshake: %w", err) ++ } ++ if _, err = conn.Write(serverHello); err != nil { ++ return nil, "", fmt.Errorf("shadow-tls: write server handshake: %w", err) ++ } ++ serverRandom := extractServerRandom(serverHello) ++ if serverRandom == nil { ++ if err = relayContext(ctx, conn, handshakeConn); err != nil { ++ return nil, "", err ++ } ++ return nil, "", ErrFallbackCompleted ++ } ++ if s.strictMode && !isServerHelloSupportTLS13(serverHello) { ++ if err = relayContext(ctx, conn, handshakeConn); err != nil { ++ return nil, "", err ++ } ++ return nil, "", ErrFallbackCompleted ++ } ++ ++ hmacWrite := hmac.New(sha1.New, []byte(user.Password)) ++ _, _ = hmacWrite.Write(serverRandom) ++ hmacAdd := hmac.New(sha1.New, []byte(user.Password)) ++ _, _ = hmacAdd.Write(serverRandom) ++ _, _ = hmacAdd.Write([]byte("S")) ++ hmacVerify := hmac.New(sha1.New, []byte(user.Password)) ++ resetVerify := func() { hmacReset(hmacVerify, serverRandom, 'C') } ++ request, err := relayV3Handshake(conn, handshakeConn, user.Password, serverRandom, hmacWrite, hmacVerify, resetVerify) ++ if err != nil { ++ return nil, "", fmt.Errorf("shadow-tls: relay handshake: %w", err) ++ } ++ return newCachedConn(newVerifiedConn(conn, hmacAdd, hmacVerify, nil), request), user.Name, nil ++} ++ ++func (s *ServerConfig) relayFallback(ctx context.Context, conn net.Conn, prefix []byte, handshakeConfig HandshakeConfig) error { ++ handshakeConn, err := handshakeConfig.dial(ctx) ++ if err != nil { ++ return fmt.Errorf("shadow-tls: dial fallback server: %w", err) ++ } ++ defer handshakeConn.Close() ++ if err = relayContext(ctx, newCachedConn(conn, prefix), handshakeConn); err != nil { ++ return err ++ } ++ return ErrFallbackCompleted ++} ++ ++func (s *ServerConfig) selectHandshake(clientHello []byte) HandshakeConfig { ++ serverName, err := extractServerName(clientHello) ++ if err == nil { ++ if custom, found := s.handshakeForServerName[serverName]; found { ++ return custom ++ } ++ } ++ return s.handshake ++} ++ ++func (s *ServerConfig) selectV3Handshake(serverName string, authenticated bool) HandshakeConfig { ++ if custom, found := s.handshakeForServerName[serverName]; found { ++ return custom ++ } ++ handshakeConfig := s.handshake ++ if s.wildcardSNI == WildcardSNIAll || (authenticated && s.wildcardSNI == WildcardSNIAuthed) { ++ handshakeConfig.Server = net.JoinHostPort(serverName, "443") ++ } ++ return handshakeConfig ++} ++ ++type authenticatedConn struct { ++ net.Conn ++ user string ++} ++ ++func (c *authenticatedConn) Upstream() any { return c.Conn } ++ ++func (c HandshakeConfig) valid() bool { ++ return c.Server != "" && c.DialContext != nil ++} ++ ++func (c HandshakeConfig) dial(ctx context.Context) (net.Conn, error) { ++ if !c.valid() { ++ return nil, errors.New("shadow-tls: missing handshake information") ++ } ++ return c.DialContext(ctx, "tcp", c.Server) ++} ++ ++func cloneHandshakeMap(source map[string]HandshakeConfig) map[string]HandshakeConfig { ++ if source == nil { ++ return nil ++ } ++ cloned := make(map[string]HandshakeConfig, len(source)) ++ for key, value := range source { ++ cloned[key] = value ++ } ++ return cloned ++} ++ ++func relayHandshake(clientConn, serverConn net.Conn) error { ++ type result struct { ++ err error ++ } ++ results := make(chan result, 2) ++ go func() { ++ results <- result{err: copyUntilHandshakeFinished(serverConn, clientConn)} ++ }() ++ go func() { ++ results <- result{err: copyUntilHandshakeFinished(clientConn, serverConn)} ++ }() ++ first := <-results ++ if first.err != nil { ++ _ = clientConn.SetDeadline(time.Now()) ++ _ = serverConn.Close() ++ } ++ second := <-results ++ if first.err != nil { ++ return first.err ++ } ++ return second.err ++} ++ ++func relayV3Handshake(clientConn, serverConn net.Conn, password string, serverRandom []byte, hmacWrite, hmacVerify hash.Hash, resetVerify func()) ([]byte, error) { ++ type result struct { ++ request []byte ++ client bool ++ err error ++ } ++ results := make(chan result, 2) ++ go func() { ++ request, err := copyByFrameUntilHMACMatches(clientConn, serverConn, hmacVerify, resetVerify) ++ results <- result{request: request, client: true, err: err} ++ }() ++ go func() { ++ err := copyByFrameWithModification(serverConn, clientConn, password, serverRandom, hmacWrite) ++ results <- result{err: err} ++ }() ++ first := <-results ++ if first.client && first.err == nil { ++ _ = serverConn.Close() ++ <-results ++ return first.request, nil ++ } ++ _ = serverConn.Close() ++ _ = clientConn.SetDeadline(time.Now()) ++ second := <-results ++ if first.err != nil { ++ return nil, first.err ++ } ++ return nil, second.err ++} ++ ++func finishCopyDirection(dst, src net.Conn, err error) { ++ if err == nil { ++ closeWrite(dst) ++ return ++ } ++ _ = dst.Close() ++ _ = src.Close() ++} ++ ++func closeWrite(conn net.Conn) { ++ if closer, ok := conn.(interface{ CloseWrite() error }); ok { ++ _ = closer.CloseWrite() ++ return ++ } ++ _ = conn.Close() ++} ++ ++func joinCopyErrors(copyErrors ...error) error { ++ var result error ++ for _, err := range copyErrors { ++ if err == nil || errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) || errors.Is(err, os.ErrDeadlineExceeded) { ++ continue ++ } ++ if strings.Contains(err.Error(), "use of closed network connection") { ++ continue ++ } ++ result = errors.Join(result, err) ++ } ++ return result ++} ++ ++// UserFromConn returns the authenticated ShadowTLS v3 user through wrappers. ++func UserFromConn(conn net.Conn) (string, bool) { ++ for conn != nil { ++ if authenticated, ok := conn.(*authenticatedConn); ok { ++ return authenticated.user, true ++ } ++ wrapper, ok := conn.(interface{ Upstream() any }) ++ if !ok { ++ break ++ } ++ conn, _ = wrapper.Upstream().(net.Conn) ++ } ++ return "", false ++} +--- a/transport/internet/shadowtls/protocol/v2.go ++++ b/transport/internet/shadowtls/protocol/v2.go +@@ -0,0 +1,208 @@ ++// Adapted from MetaCubeX/mihomo v1.19.32; GPL-3.0-or-later. ++package shadowtls ++ ++import ( ++ "bytes" ++ "crypto/hmac" ++ "crypto/sha1" ++ "encoding/binary" ++ "fmt" ++ "hash" ++ "io" ++ "net" ++ "os" ++ "sync" ++) ++ ++type hashReadConn struct { ++ net.Conn ++ hmac hash.Hash ++} ++ ++func newHashReadConn(conn net.Conn, password string) *hashReadConn { ++ return &hashReadConn{Conn: conn, hmac: hmac.New(sha1.New, []byte(password))} ++} ++ ++func (c *hashReadConn) Read(p []byte) (int, error) { ++ n, err := c.Conn.Read(p) ++ if n > 0 { ++ _, _ = c.hmac.Write(p[:n]) ++ } ++ return n, err ++} ++ ++func (c *hashReadConn) Sum() []byte { ++ return append([]byte(nil), c.hmac.Sum(nil)[:8]...) ++} ++ ++type hashWriteConn struct { ++ net.Conn ++ mu sync.Mutex ++ hmac hash.Hash ++ hasContent bool ++ lastSum []byte ++} ++ ++func newHashWriteConn(conn net.Conn, password string) *hashWriteConn { ++ return &hashWriteConn{Conn: conn, hmac: hmac.New(sha1.New, []byte(password))} ++} ++ ++func (c *hashWriteConn) Write(p []byte) (int, error) { ++ c.mu.Lock() ++ defer c.mu.Unlock() ++ n, err := c.Conn.Write(p) ++ if c.hmac != nil && n > 0 { ++ if c.hasContent { ++ c.lastSum = append(c.lastSum[:0], c.hmac.Sum(nil)[:8]...) ++ } ++ _, _ = c.hmac.Write(p[:n]) ++ c.hasContent = true ++ } ++ return n, err ++} ++ ++func (c *hashWriteConn) sums() (current, previous []byte, hasContent bool) { ++ c.mu.Lock() ++ defer c.mu.Unlock() ++ if c.hmac != nil { ++ current = append([]byte(nil), c.hmac.Sum(nil)[:8]...) ++ } ++ previous = append([]byte(nil), c.lastSum...) ++ return current, previous, c.hasContent ++} ++ ++func (c *hashWriteConn) Fallback() { ++ c.mu.Lock() ++ c.hmac = nil ++ c.mu.Unlock() ++} ++ ++type shadowConn struct { ++ net.Conn ++ readRemaining int ++ readHeader [tlsHeaderSize]byte ++ readHeaderOffset int ++ writeMu sync.Mutex ++} ++ ++func newConn(conn net.Conn) *shadowConn { ++ return &shadowConn{Conn: conn} ++} ++ ++func (c *shadowConn) Read(p []byte) (int, error) { ++ if c.readRemaining > 0 { ++ if len(p) > c.readRemaining { ++ p = p[:c.readRemaining] ++ } ++ n, err := c.Conn.Read(p) ++ c.readRemaining -= n ++ return n, err ++ } ++ // Keep an incomplete header so a read deadline only interrupts the current Read. ++ n, err := io.ReadFull(c.Conn, c.readHeader[c.readHeaderOffset:]) ++ c.readHeaderOffset += n ++ if err != nil { ++ return 0, err ++ } ++ c.readHeaderOffset = 0 ++ if c.readHeader[0] != applicationData { ++ return 0, fmt.Errorf("shadow-tls: unexpected TLS record type: %d", c.readHeader[0]) ++ } ++ length := int(binary.BigEndian.Uint16(c.readHeader[3:])) ++ readLength := len(p) ++ if readLength > length { ++ readLength = length ++ } ++ n, err = c.Conn.Read(p[:readLength]) ++ c.readRemaining = length - n ++ return n, err ++} ++ ++func (c *shadowConn) Write(p []byte) (int, error) { ++ c.writeMu.Lock() ++ defer c.writeMu.Unlock() ++ total := len(p) ++ if total == 0 { ++ if err := c.writeRecord(nil); err != nil { ++ return 0, err ++ } ++ return 0, nil ++ } ++ for len(p) > 0 { ++ length := len(p) ++ if length > maxTLSPlaintext { ++ length = maxTLSPlaintext ++ } ++ if err := c.writeRecord(p[:length]); err != nil { ++ return total - len(p), err ++ } ++ p = p[length:] ++ } ++ return total, nil ++} ++ ++func (c *shadowConn) writeRecord(p []byte) error { ++ var header [tlsHeaderSize]byte ++ header[0] = applicationData ++ header[1] = 3 ++ header[2] = 3 ++ binary.BigEndian.PutUint16(header[3:], uint16(len(p))) ++ return writeBuffers(c.Conn, header[:], p) ++} ++ ++func (c *shadowConn) Upstream() any { return c.Conn } ++ ++type clientConn struct { ++ *shadowConn ++ mu sync.Mutex ++ hashConn *hashReadConn ++} ++ ++func newClientConn(hashConn *hashReadConn) *clientConn { ++ return &clientConn{shadowConn: newConn(hashConn.Conn), hashConn: hashConn} ++} ++ ++func (c *clientConn) Write(p []byte) (int, error) { ++ c.mu.Lock() ++ defer c.mu.Unlock() ++ if c.hashConn == nil { ++ return c.shadowConn.Write(p) ++ } ++ prefixed := make([]byte, 8+len(p)) ++ copy(prefixed, c.hashConn.Sum()) ++ copy(prefixed[8:], p) ++ c.hashConn = nil ++ if _, err := c.shadowConn.Write(prefixed); err != nil { ++ return 0, err ++ } ++ return len(p), nil ++} ++ ++func copyUntilHandshakeFinishedV2(dst net.Conn, src io.Reader, hashConn *hashWriteConn, fallbackAfter int) ([]byte, error) { ++ applicationDataCount := 0 ++ for { ++ frame, err := readFrame(src) ++ if err != nil { ++ return nil, err ++ } ++ if frame[0] == applicationData { ++ payload := frame[tlsHeaderSize:] ++ current, previous, hasContent := hashConn.sums() ++ if hasContent && len(payload) >= 8 { ++ if bytes.Equal(payload[:8], current) { ++ return append([]byte(nil), payload[8:]...), nil ++ } ++ if len(previous) > 0 && bytes.Equal(payload[:8], previous) { ++ return append([]byte(nil), payload[8:]...), nil ++ } ++ } ++ applicationDataCount++ ++ } ++ if _, err = dst.Write(frame); err != nil { ++ return nil, err ++ } ++ if applicationDataCount > fallbackAfter { ++ return nil, os.ErrPermission ++ } ++ } ++} +--- a/transport/internet/shadowtls/protocol/v3.go ++++ b/transport/internet/shadowtls/protocol/v3.go +@@ -0,0 +1,332 @@ ++// Adapted from MetaCubeX/mihomo v1.19.32; GPL-3.0-or-later. ++package shadowtls ++ ++import ( ++ "crypto/hmac" ++ "crypto/rand" ++ "crypto/sha1" ++ "crypto/sha256" ++ "encoding/binary" ++ "errors" ++ "fmt" ++ "hash" ++ "io" ++ "net" ++ "sync" ++) ++ ++func generateSessionID(password string) TLSSessionIDGeneratorFunc { ++ return func(clientHello []byte, sessionID []byte) error { ++ const sessionIDStart = 1 + 3 + 2 + tlsRandomSize + 1 ++ if len(clientHello) < sessionIDStart+tlsSessionIDSize || len(sessionID) < tlsSessionIDSize { ++ return errors.New("shadow-tls: unexpected ClientHello length") ++ } ++ if _, err := rand.Read(sessionID[:tlsSessionIDSize-hmacSize]); err != nil { ++ return err ++ } ++ for i := tlsSessionIDSize - hmacSize; i < tlsSessionIDSize; i++ { ++ sessionID[i] = 0 ++ } ++ h := hmac.New(sha1.New, []byte(password)) ++ _, _ = h.Write(clientHello[:sessionIDStart]) ++ _, _ = h.Write(sessionID[:tlsSessionIDSize]) ++ _, _ = h.Write(clientHello[sessionIDStart+tlsSessionIDSize:]) ++ copy(sessionID[tlsSessionIDSize-hmacSize:], h.Sum(nil)[:hmacSize]) ++ return nil ++ } ++} ++ ++type streamWrapper struct { ++ net.Conn ++ password string ++ pending []byte ++ serverRandom []byte ++ readHMAC hash.Hash ++ readHMACKey []byte ++ isTLS13 bool ++ authorized bool ++} ++ ++func newStreamWrapper(conn net.Conn, password string) *streamWrapper { ++ return &streamWrapper{Conn: conn, password: password} ++} ++ ++func (c *streamWrapper) Authorized() (bool, bool, []byte, hash.Hash) { ++ return c.isTLS13, c.authorized, c.serverRandom, c.readHMAC ++} ++ ++func (c *streamWrapper) Read(p []byte) (int, error) { ++ if len(c.pending) > 0 { ++ return c.readPending(p), nil ++ } ++ frame, err := readFrame(c.Conn) ++ if err != nil { ++ return 0, err ++ } ++ switch frame[0] { ++ case handshake: ++ // Mirror the server's one-shot seed: only initialize the HMAC chain on the first ++ // ServerHello so a TLS 1.3 HelloRetryRequest (RFC 8446 section 4.1.4) aligns both peers. ++ if len(frame) >= serverRandomIndex+tlsRandomSize && frame[tlsHeaderSize] == serverHello && c.readHMAC == nil { ++ c.serverRandom = append([]byte(nil), frame[serverRandomIndex:serverRandomIndex+tlsRandomSize]...) ++ c.readHMAC = hmac.New(sha1.New, []byte(c.password)) ++ _, _ = c.readHMAC.Write(c.serverRandom) ++ c.readHMACKey = kdf(c.password, c.serverRandom) ++ c.isTLS13 = isServerHelloSupportTLS13(frame) ++ c.authorized = !c.isTLS13 ++ } ++ case applicationData: ++ c.authorized = false ++ if len(frame) > tlsHMACHeaderSize && c.readHMAC != nil { ++ _, _ = c.readHMAC.Write(frame[tlsHMACHeaderSize:]) ++ if !hmac.Equal(c.readHMAC.Sum(nil)[:hmacSize], frame[tlsHeaderSize:tlsHMACHeaderSize]) { ++ return 0, errors.New("shadow-tls v3: HMAC mismatch, possible data corruption") ++ } ++ xorSlice(frame[tlsHMACHeaderSize:], c.readHMACKey) ++ copy(frame[hmacSize:hmacSize+tlsHeaderSize], frame[:tlsHeaderSize]) ++ frame = frame[hmacSize:] ++ binary.BigEndian.PutUint16(frame[3:5], uint16(len(frame)-tlsHeaderSize)) ++ c.authorized = true ++ } ++ } ++ c.pending = frame ++ return c.readPending(p), nil ++} ++ ++func (c *streamWrapper) readPending(p []byte) int { ++ n := copy(p, c.pending) ++ c.pending = c.pending[n:] ++ return n ++} ++ ++type verifiedConn struct { ++ net.Conn ++ writeMu sync.Mutex ++ hmacAdd hash.Hash ++ hmacVerify hash.Hash ++ hmacIgnore hash.Hash ++ pending []byte ++ readBuffer []byte ++ readOffset int ++} ++ ++func newVerifiedConn(conn net.Conn, hmacAdd, hmacVerify, hmacIgnore hash.Hash) *verifiedConn { ++ return &verifiedConn{ ++ Conn: conn, ++ hmacAdd: hmacAdd, ++ hmacVerify: hmacVerify, ++ hmacIgnore: hmacIgnore, ++ } ++} ++ ++func (c *verifiedConn) Read(p []byte) (int, error) { ++ if len(c.pending) > 0 { ++ return c.readPending(p), nil ++ } ++ for { ++ frame, err := c.readRecord() ++ if err != nil { ++ var netErr net.Error ++ if errors.As(err, &netErr) && netErr.Timeout() { ++ return 0, err ++ } ++ sendAlert(c.Conn) ++ return 0, err ++ } ++ switch frame[0] { ++ case alert: ++ return 0, fmt.Errorf("shadow-tls: remote alert: %w", net.ErrClosed) ++ case applicationData: ++ if c.hmacIgnore != nil { ++ if verifyApplicationData(frame, c.hmacIgnore, false) { ++ continue ++ } ++ c.hmacIgnore = nil ++ } ++ if !verifyApplicationData(frame, c.hmacVerify, true) { ++ sendAlert(c.Conn) ++ return 0, errors.New("shadow-tls: application data verification failed") ++ } ++ c.pending = frame[tlsHMACHeaderSize:] ++ return c.readPending(p), nil ++ default: ++ sendAlert(c.Conn) ++ return 0, fmt.Errorf("shadow-tls: unexpected TLS record type: %d", frame[0]) ++ } ++ } ++} ++ ++func (c *verifiedConn) readRecord() ([]byte, error) { ++ // Keep an incomplete record so a read deadline only interrupts the current Read. ++ if c.readBuffer == nil { ++ c.readBuffer = make([]byte, tlsHeaderSize) ++ } ++ if c.readOffset < tlsHeaderSize { ++ n, err := io.ReadFull(c.Conn, c.readBuffer[c.readOffset:tlsHeaderSize]) ++ c.readOffset += n ++ if err != nil { ++ return nil, err ++ } ++ length := int(binary.BigEndian.Uint16(c.readBuffer[3:])) ++ c.readBuffer = append(c.readBuffer, make([]byte, length)...) ++ } ++ if c.readOffset < len(c.readBuffer) { ++ n, err := io.ReadFull(c.Conn, c.readBuffer[c.readOffset:]) ++ c.readOffset += n ++ if err != nil { ++ return nil, err ++ } ++ } ++ frame := c.readBuffer ++ c.readBuffer = nil ++ c.readOffset = 0 ++ return frame, nil ++} ++ ++func (c *verifiedConn) readPending(p []byte) int { ++ n := copy(p, c.pending) ++ c.pending = c.pending[n:] ++ return n ++} ++ ++func (c *verifiedConn) Write(p []byte) (int, error) { ++ c.writeMu.Lock() ++ defer c.writeMu.Unlock() ++ total := len(p) ++ for len(p) > 0 { ++ length := len(p) ++ if length > maxTLSPlaintext { ++ length = maxTLSPlaintext ++ } ++ if err := c.writeRecord(p[:length]); err != nil { ++ return total - len(p), err ++ } ++ p = p[length:] ++ } ++ return total, nil ++} ++ ++func (c *verifiedConn) writeRecord(p []byte) error { ++ var header [tlsHMACHeaderSize]byte ++ header[0] = applicationData ++ header[1] = 3 ++ header[2] = 3 ++ binary.BigEndian.PutUint16(header[3:tlsHeaderSize], uint16(hmacSize+len(p))) ++ _, _ = c.hmacAdd.Write(p) ++ hmacHash := c.hmacAdd.Sum(nil)[:hmacSize] ++ _, _ = c.hmacAdd.Write(hmacHash) ++ copy(header[tlsHeaderSize:], hmacHash) ++ return writeBuffers(c.Conn, header[:], p) ++} ++ ++func (c *verifiedConn) Upstream() any { return c.Conn } ++ ++func verifyApplicationData(frame []byte, h hash.Hash, update bool) bool { ++ if len(frame) < tlsHMACHeaderSize || frame[0] != applicationData || frame[1] != 3 || frame[2] != 3 { ++ return false ++ } ++ _, _ = h.Write(frame[tlsHMACHeaderSize:]) ++ hmacHash := h.Sum(nil)[:hmacSize] ++ if update { ++ _, _ = h.Write(hmacHash) ++ } ++ return hmac.Equal(frame[tlsHeaderSize:tlsHMACHeaderSize], hmacHash) ++} ++ ++func verifyClientHello(frame []byte, users []User) (*User, error) { ++ const minLength = tlsHeaderSize + 1 + 3 + 2 + tlsRandomSize + 1 + tlsSessionIDSize ++ const hmacIndex = sessionIDLengthIndex + 1 + tlsSessionIDSize - hmacSize ++ if len(frame) < minLength { ++ return nil, io.ErrUnexpectedEOF ++ } ++ if frame[0] != handshake || frame[tlsHeaderSize] != clientHello { ++ return nil, errors.New("shadow-tls: unexpected ClientHello record") ++ } ++ if frame[sessionIDLengthIndex] != tlsSessionIDSize { ++ return nil, errors.New("shadow-tls: unexpected session ID length") ++ } ++ for i := range users { ++ user := &users[i] ++ h := hmac.New(sha1.New, []byte(user.Password)) ++ _, _ = h.Write(frame[tlsHeaderSize:hmacIndex]) ++ _, _ = h.Write([]byte{0, 0, 0, 0}) ++ _, _ = h.Write(frame[hmacIndex+hmacSize:]) ++ if hmac.Equal(frame[hmacIndex:hmacIndex+hmacSize], h.Sum(nil)[:hmacSize]) { ++ return user, nil ++ } ++ } ++ return nil, errors.New("shadow-tls: ClientHello HMAC mismatch") ++} ++ ++func copyByFrameUntilHMACMatches(conn, handshakeConn net.Conn, hmacVerify hash.Hash, hmacReset func()) ([]byte, error) { ++ for { ++ frame, err := readFrame(conn) ++ if err != nil { ++ return nil, fmt.Errorf("shadow-tls: read client record: %w", err) ++ } ++ if len(frame) > tlsHMACHeaderSize && frame[0] == applicationData { ++ hmacReset() ++ _, _ = hmacVerify.Write(frame[tlsHMACHeaderSize:]) ++ hmacHash := hmacVerify.Sum(nil)[:hmacSize] ++ if hmac.Equal(hmacHash, frame[tlsHeaderSize:tlsHMACHeaderSize]) { ++ hmacReset() ++ _, _ = hmacVerify.Write(frame[tlsHMACHeaderSize:]) ++ _, _ = hmacVerify.Write(frame[tlsHeaderSize:tlsHMACHeaderSize]) ++ return append([]byte(nil), frame[tlsHMACHeaderSize:]...), nil ++ } ++ } ++ if _, err = handshakeConn.Write(frame); err != nil { ++ return nil, fmt.Errorf("shadow-tls: write client record: %w", err) ++ } ++ } ++} ++ ++func copyByFrameWithModification(conn, handshakeConn net.Conn, password string, serverRandom []byte, hmacWrite hash.Hash) error { ++ writeKey := kdf(password, serverRandom) ++ for { ++ frame, err := readFrame(conn) ++ if err != nil { ++ return fmt.Errorf("shadow-tls: read server record: %w", err) ++ } ++ if frame[0] != applicationData { ++ if _, err = handshakeConn.Write(frame); err != nil { ++ return fmt.Errorf("shadow-tls: write server record: %w", err) ++ } ++ continue ++ } ++ xorSlice(frame[tlsHeaderSize:], writeKey) ++ _, _ = hmacWrite.Write(frame[tlsHeaderSize:]) ++ binary.BigEndian.PutUint16(frame[3:5], uint16(len(frame)-tlsHeaderSize+hmacSize)) ++ hmacHash := hmacWrite.Sum(nil)[:hmacSize] ++ if err = writeBuffers(handshakeConn, frame[:tlsHeaderSize], hmacHash, frame[tlsHeaderSize:]); err != nil { ++ return fmt.Errorf("shadow-tls: write modified server record: %w", err) ++ } ++ } ++} ++ ++func kdf(password string, serverRandom []byte) []byte { ++ hasher := sha256.New() ++ _, _ = hasher.Write([]byte(password)) ++ _, _ = hasher.Write(serverRandom) ++ return hasher.Sum(nil) ++} ++ ++func xorSlice(data, key []byte) { ++ for i := range data { ++ data[i] ^= key[i%len(key)] ++ } ++} ++ ++func sendAlert(writer io.Writer) { ++ const recordSize = 31 ++ record := [recordSize]byte{alert, 3, 3, 0, recordSize - tlsHeaderSize} ++ if _, err := rand.Read(record[tlsHeaderSize:]); err == nil { ++ _, _ = writer.Write(record[:]) ++ } ++} ++ ++func hmacReset(h hash.Hash, serverRandom []byte, side byte) { ++ h.Reset() ++ _, _ = h.Write(serverRandom) ++ _, _ = h.Write([]byte{side}) ++} +--- a/transport/internet/tcp/dialer.go ++++ b/transport/internet/tcp/dialer.go +@@ -12,6 +12,7 @@ + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/reality" ++ "github.com/xtls/xray-core/transport/internet/shadowtls" + "github.com/xtls/xray-core/transport/internet/stat" + "github.com/xtls/xray-core/transport/internet/tls" + ) +@@ -98,6 +99,14 @@ + } + } + ++ if config := shadowtls.ConfigFromStreamSettings(streamSettings); config != nil { ++ raw := conn ++ conn, err = config.Client(ctx, raw, dest) ++ if err != nil { ++ raw.Close() ++ return nil, err ++ } ++ } + tcpSettings := streamSettings.ProtocolSettings.(*Config) + if tcpSettings.HeaderSettings != nil { + headerConfig, err := tcpSettings.HeaderSettings.GetInstance() +--- a/transport/internet/tcp/hub.go ++++ b/transport/internet/tcp/hub.go +@@ -12,12 +12,18 @@ + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/reality" ++ "github.com/xtls/xray-core/transport/internet/shadowtls" ++ shadowprotocol "github.com/xtls/xray-core/transport/internet/shadowtls/protocol" + "github.com/xtls/xray-core/transport/internet/stat" + "github.com/xtls/xray-core/transport/internet/tls" + ) + + // Listener is an internet.Listener that listens for TCP connections. + type Listener struct { ++ shadowConfig *shadowprotocol.ServerConfig ++ shadowTimeout time.Duration ++ ctx context.Context ++ cancel context.CancelFunc + listener net.Listener + tlsConfig *gotls.Config + realityConfig *goreality.Config +@@ -31,6 +37,15 @@ + l := &Listener{ + addConn: handler, + } ++ ++ if config := shadowtls.ConfigFromStreamSettings(streamSettings); config != nil { ++ var err error ++ l.shadowConfig, err = config.ServerConfig(streamSettings.SocketSettings) ++ if err != nil { ++ return nil, err ++ } ++ l.shadowTimeout = config.Timeout() ++ } + tcpSettings := streamSettings.ProtocolSettings.(*Config) + l.config = tcpSettings + if l.config != nil { +@@ -83,6 +98,7 @@ + l.authConfig = auth + } + ++ l.ctx, l.cancel = context.WithCancel(ctx) + go l.keepAccepting() + return l, nil + } +@@ -102,7 +118,19 @@ + continue + } + +- go func() { ++ go func(conn net.Conn) { ++ if v.shadowConfig != nil { ++ ctx, cancel := context.WithTimeout(v.ctx, v.shadowTimeout) ++ stop := context.AfterFunc(ctx, func() { conn.Close() }) ++ wrapped, handshakeErr := shadowprotocol.Server(ctx, conn, v.shadowConfig) ++ stopped := stop() ++ cancel() ++ if handshakeErr != nil || !stopped { ++ conn.Close() ++ return ++ } ++ conn = wrapped ++ } + if v.tlsConfig != nil { + conn = tls.Server(conn, v.tlsConfig) + } else if v.realityConfig != nil { +@@ -115,7 +143,7 @@ + conn = v.authConfig.Server(conn) + } + v.addConn(stat.Connection(conn)) +- }() ++ }(conn) + } + } + +@@ -126,6 +154,7 @@ + + // Close implements internet.Listener.Close. + func (v *Listener) Close() error { ++ v.cancel() + return v.listener.Close() + } + diff --git a/xray-core/patches/180-add-kcptun-transport.patch b/xray-core/patches/180-add-kcptun-transport.patch new file mode 100644 index 000000000..d9148ec3c --- /dev/null +++ b/xray-core/patches/180-add-kcptun-transport.patch @@ -0,0 +1,2009 @@ +From: coolsnowwolf +Subject: [PATCH] transport: add Mihomo-compatible Kcptun inbound and outbound + +Port Mihomo v1.19.32 Kcptun settings, cipher selection, FEC, compression and +smux v1/v2 as a separate UDP-based reliable stream transport. Preserve mKCP. +Own client pools per outbound and destination, reclaim expired sessions and +close pooled sockets with their outbound. Track and close accepted server +sessions and adapt stream addresses for Xray protocols. Use Xray socket +options, outbound gateway selection and finalmask packet wrappers. + +Signed-off-by: coolsnowwolf + +--- a/app/proxyman/outbound/handler.go ++++ b/app/proxyman/outbound/handler.go +@@ -329,6 +329,7 @@ + + // Close implements common.Closable. + func (h *Handler) Close() error { ++ common.Close(h.streamSettings) + common.Close(h.mux) + common.Close(h.proxy) + return nil +--- a/go.mod ++++ b/go.mod +@@ -9,14 +9,17 @@ + github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 + github.com/gofrs/uuid/v5 v5.4.0 + github.com/golang/mock v1.7.0-rc.1 ++ github.com/golang/snappy v1.0.0 + github.com/google/go-cmp v0.7.0 + github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.3 + github.com/klauspost/cpuid/v2 v2.4.2 + github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 + github.com/metacubex/chacha v0.1.5 ++ github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 + github.com/metacubex/mihomo v1.19.32 + github.com/metacubex/quic-go v0.61.1-0.20260727080200-2548683b76f4 ++ github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 + github.com/metacubex/tls v0.1.8 + github.com/metacubex/utls v1.8.8 + github.com/miekg/dns v1.1.73 +@@ -72,6 +75,7 @@ + github.com/jackpal/go-nat-pmp v1.0.2 // indirect + github.com/juju/ratelimit v1.0.2 // indirect + github.com/klauspost/compress v1.20.1 // indirect ++ github.com/klauspost/reedsolomon v1.12.3 // indirect + github.com/koron/go-ssdp v0.0.4 // indirect + github.com/libp2p/go-netroute v0.2.1 // indirect + github.com/molecule-man/go-brrr v1.2.0 // indirect +@@ -98,6 +102,7 @@ + gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/text v0.43.0 // indirect ++ golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.51.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 // indirect + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect +--- a/go.sum ++++ b/go.sum +@@ -24,6 +24,8 @@ + github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= + github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= + github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= ++github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= ++github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= + github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= + github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= + github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +@@ -46,6 +48,8 @@ + github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= + github.com/klauspost/cpuid/v2 v2.4.2 h1:xRw7IZEsAgVCp6trS/DgWbTAqpixhmywgVgBjYGg1bQ= + github.com/klauspost/cpuid/v2 v2.4.2/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= ++github.com/klauspost/reedsolomon v1.12.3 h1:tzUznbfc3OFwJaTebv/QdhnFf2Xvb7gZ24XaHLBPmdc= ++github.com/klauspost/reedsolomon v1.12.3/go.mod h1:3K5rXwABAvzGeR01r6pWZieUALXO/Tq7bFKGIb4m4WI= + github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= + github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= + github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +@@ -70,6 +74,8 @@ + github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U= + github.com/metacubex/http v0.1.8 h1:d91onDan2ZXYE8W/uAy5oFmXHH2xi1L7LbGPV5EmCM0= + github.com/metacubex/http v0.1.8/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg= ++github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI= ++github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc= + github.com/metacubex/mihomo v1.19.32 h1:uD7ZC3P77isWD554NNvtee65L+99+/C5hyc+Lk8rVEk= + github.com/metacubex/mihomo v1.19.32/go.mod h1:d7xWi5iEktkC9PqXkD/Vo4AEcb0Q92XikfLoySUDWls= + github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I= +@@ -80,6 +86,8 @@ + github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= + github.com/metacubex/sing v0.5.8 h1:OwJDSPP+pza6Ywt7NMR4kEYq+UNrsIywSk/eAMlqxz8= + github.com/metacubex/sing v0.5.8/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= ++github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2BhiAPgbJygiWhesPlfGmF+9Vw6ARdk= ++github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg= + github.com/metacubex/tfo-go v0.0.0-20260623020846-376a77860b8c h1:+2C1UshSLzaogLY/IDz1XhOLxjhs3rkhVHB4eCN9Zto= + github.com/metacubex/tfo-go v0.0.0-20260623020846-376a77860b8c/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= + github.com/metacubex/tls v0.1.8 h1:BHF2payjtxC7wdR3tnq8vl3FAgEQB6/pPjP2tJNTsP4= +--- a/infra/conf/kcptun.go ++++ b/infra/conf/kcptun.go +@@ -0,0 +1,13 @@ ++package conf ++ ++import "github.com/xtls/xray-core/transport/internet/kcptun" ++ ++type KcptunConfig kcptun.Config ++ ++func (c *KcptunConfig) Build() (*kcptun.Config, error) { ++ cfg := (*kcptun.Config)(c) ++ if _, err := cfg.Build(); err != nil { ++ return nil, err ++ } ++ return cfg, nil ++} +--- a/infra/conf/transport_internet.go ++++ b/infra/conf/transport_internet.go +@@ -21,6 +21,8 @@ + return "tcp", nil + case "xhttp", "splithttp": + return "splithttp", nil ++ case "kcptun": ++ return "kcptun", nil + case "kcp", "mkcp": + return "mkcp", nil + case "grpc": +@@ -52,6 +54,7 @@ + } + + type StreamConfig struct { ++ KcptunSettings *KcptunConfig `json:"kcptunSettings"` + ShadowTLSSettings *ShadowTLSConfig `json:"shadowtlsSettings"` + Address *Address `json:"address"` + Port uint16 `json:"port"` +@@ -95,6 +98,9 @@ + config.ProtocolName = protocol + } + ++ if config.ProtocolName == "kcptun" && c.Security != "" && c.Security != "none" { ++ return nil, errors.New("Kcptun does not support additional stream security") ++ } + var xdriveH3 bool + switch strings.ToLower(c.Security) { + case "", "none": +@@ -179,6 +185,17 @@ + Settings: serial.ToTypedMessage(hs), + }) + } ++ if config.ProtocolName == "kcptun" { ++ settings := c.KcptunSettings ++ if settings == nil { ++ settings = &KcptunConfig{} ++ } ++ ts, err := settings.Build() ++ if err != nil { ++ return nil, err ++ } ++ config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ProtocolName: "kcptun", Settings: serial.ToTypedMessage(ts)}) ++ } + if c.KCPSettings != nil { + ts, err := c.KCPSettings.Build() + if err != nil { +--- a/main/distro/all/all.go ++++ b/main/distro/all/all.go +@@ -60,6 +60,7 @@ + _ "github.com/xtls/xray-core/transport/internet/grpc" + _ "github.com/xtls/xray-core/transport/internet/httpupgrade" + _ "github.com/xtls/xray-core/transport/internet/kcp" ++ _ "github.com/xtls/xray-core/transport/internet/kcptun" + _ "github.com/xtls/xray-core/transport/internet/masque" + _ "github.com/xtls/xray-core/transport/internet/reality" + _ "github.com/xtls/xray-core/transport/internet/splithttp" +--- a/transport/internet/kcptun/LICENSE.kcptun ++++ b/transport/internet/kcptun/LICENSE.kcptun +@@ -0,0 +1,9 @@ ++The MIT License (MIT) ++ ++Copyright (c) 2016 xtaci ++ ++Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: ++ ++The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. ++ ++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--- a/transport/internet/kcptun/LICENSE.mihomo ++++ b/transport/internet/kcptun/LICENSE.mihomo +@@ -0,0 +1,674 @@ ++ GNU GENERAL PUBLIC LICENSE ++ Version 3, 29 June 2007 ++ ++ Copyright (C) 2007 Free Software Foundation, Inc. ++ Everyone is permitted to copy and distribute verbatim copies ++ of this license document, but changing it is not allowed. ++ ++ Preamble ++ ++ The GNU General Public License is a free, copyleft license for ++software and other kinds of works. ++ ++ The licenses for most software and other practical works are designed ++to take away your freedom to share and change the works. By contrast, ++the GNU General Public License is intended to guarantee your freedom to ++share and change all versions of a program--to make sure it remains free ++software for all its users. We, the Free Software Foundation, use the ++GNU General Public License for most of our software; it applies also to ++any other work released this way by its authors. You can apply it to ++your programs, too. ++ ++ When we speak of free software, we are referring to freedom, not ++price. Our General Public Licenses are designed to make sure that you ++have the freedom to distribute copies of free software (and charge for ++them if you wish), that you receive source code or can get it if you ++want it, that you can change the software or use pieces of it in new ++free programs, and that you know you can do these things. ++ ++ To protect your rights, we need to prevent others from denying you ++these rights or asking you to surrender the rights. Therefore, you have ++certain responsibilities if you distribute copies of the software, or if ++you modify it: responsibilities to respect the freedom of others. ++ ++ For example, if you distribute copies of such a program, whether ++gratis or for a fee, you must pass on to the recipients the same ++freedoms that you received. You must make sure that they, too, receive ++or can get the source code. And you must show them these terms so they ++know their rights. ++ ++ Developers that use the GNU GPL protect your rights with two steps: ++(1) assert copyright on the software, and (2) offer you this License ++giving you legal permission to copy, distribute and/or modify it. ++ ++ For the developers' and authors' protection, the GPL clearly explains ++that there is no warranty for this free software. For both users' and ++authors' sake, the GPL requires that modified versions be marked as ++changed, so that their problems will not be attributed erroneously to ++authors of previous versions. ++ ++ Some devices are designed to deny users access to install or run ++modified versions of the software inside them, although the manufacturer ++can do so. This is fundamentally incompatible with the aim of ++protecting users' freedom to change the software. The systematic ++pattern of such abuse occurs in the area of products for individuals to ++use, which is precisely where it is most unacceptable. Therefore, we ++have designed this version of the GPL to prohibit the practice for those ++products. If such problems arise substantially in other domains, we ++stand ready to extend this provision to those domains in future versions ++of the GPL, as needed to protect the freedom of users. ++ ++ Finally, every program is threatened constantly by software patents. ++States should not allow patents to restrict development and use of ++software on general-purpose computers, but in those that do, we wish to ++avoid the special danger that patents applied to a free program could ++make it effectively proprietary. To prevent this, the GPL assures that ++patents cannot be used to render the program non-free. ++ ++ The precise terms and conditions for copying, distribution and ++modification follow. ++ ++ TERMS AND CONDITIONS ++ ++ 0. Definitions. ++ ++ "This License" refers to version 3 of the GNU General Public License. ++ ++ "Copyright" also means copyright-like laws that apply to other kinds of ++works, such as semiconductor masks. ++ ++ "The Program" refers to any copyrightable work licensed under this ++License. Each licensee is addressed as "you". "Licensees" and ++"recipients" may be individuals or organizations. ++ ++ To "modify" a work means to copy from or adapt all or part of the work ++in a fashion requiring copyright permission, other than the making of an ++exact copy. The resulting work is called a "modified version" of the ++earlier work or a work "based on" the earlier work. ++ ++ A "covered work" means either the unmodified Program or a work based ++on the Program. ++ ++ To "propagate" a work means to do anything with it that, without ++permission, would make you directly or secondarily liable for ++infringement under applicable copyright law, except executing it on a ++computer or modifying a private copy. Propagation includes copying, ++distribution (with or without modification), making available to the ++public, and in some countries other activities as well. ++ ++ To "convey" a work means any kind of propagation that enables other ++parties to make or receive copies. Mere interaction with a user through ++a computer network, with no transfer of a copy, is not conveying. ++ ++ An interactive user interface displays "Appropriate Legal Notices" ++to the extent that it includes a convenient and prominently visible ++feature that (1) displays an appropriate copyright notice, and (2) ++tells the user that there is no warranty for the work (except to the ++extent that warranties are provided), that licensees may convey the ++work under this License, and how to view a copy of this License. If ++the interface presents a list of user commands or options, such as a ++menu, a prominent item in the list meets this criterion. ++ ++ 1. Source Code. ++ ++ The "source code" for a work means the preferred form of the work ++for making modifications to it. "Object code" means any non-source ++form of a work. ++ ++ A "Standard Interface" means an interface that either is an official ++standard defined by a recognized standards body, or, in the case of ++interfaces specified for a particular programming language, one that ++is widely used among developers working in that language. ++ ++ The "System Libraries" of an executable work include anything, other ++than the work as a whole, that (a) is included in the normal form of ++packaging a Major Component, but which is not part of that Major ++Component, and (b) serves only to enable use of the work with that ++Major Component, or to implement a Standard Interface for which an ++implementation is available to the public in source code form. A ++"Major Component", in this context, means a major essential component ++(kernel, window system, and so on) of the specific operating system ++(if any) on which the executable work runs, or a compiler used to ++produce the work, or an object code interpreter used to run it. ++ ++ The "Corresponding Source" for a work in object code form means all ++the source code needed to generate, install, and (for an executable ++work) run the object code and to modify the work, including scripts to ++control those activities. However, it does not include the work's ++System Libraries, or general-purpose tools or generally available free ++programs which are used unmodified in performing those activities but ++which are not part of the work. For example, Corresponding Source ++includes interface definition files associated with source files for ++the work, and the source code for shared libraries and dynamically ++linked subprograms that the work is specifically designed to require, ++such as by intimate data communication or control flow between those ++subprograms and other parts of the work. ++ ++ The Corresponding Source need not include anything that users ++can regenerate automatically from other parts of the Corresponding ++Source. ++ ++ The Corresponding Source for a work in source code form is that ++same work. ++ ++ 2. Basic Permissions. ++ ++ All rights granted under this License are granted for the term of ++copyright on the Program, and are irrevocable provided the stated ++conditions are met. This License explicitly affirms your unlimited ++permission to run the unmodified Program. The output from running a ++covered work is covered by this License only if the output, given its ++content, constitutes a covered work. This License acknowledges your ++rights of fair use or other equivalent, as provided by copyright law. ++ ++ You may make, run and propagate covered works that you do not ++convey, without conditions so long as your license otherwise remains ++in force. You may convey covered works to others for the sole purpose ++of having them make modifications exclusively for you, or provide you ++with facilities for running those works, provided that you comply with ++the terms of this License in conveying all material for which you do ++not control copyright. Those thus making or running the covered works ++for you must do so exclusively on your behalf, under your direction ++and control, on terms that prohibit them from making any copies of ++your copyrighted material outside their relationship with you. ++ ++ Conveying under any other circumstances is permitted solely under ++the conditions stated below. Sublicensing is not allowed; section 10 ++makes it unnecessary. ++ ++ 3. Protecting Users' Legal Rights From Anti-Circumvention Law. ++ ++ No covered work shall be deemed part of an effective technological ++measure under any applicable law fulfilling obligations under article ++11 of the WIPO copyright treaty adopted on 20 December 1996, or ++similar laws prohibiting or restricting circumvention of such ++measures. ++ ++ When you convey a covered work, you waive any legal power to forbid ++circumvention of technological measures to the extent such circumvention ++is effected by exercising rights under this License with respect to ++the covered work, and you disclaim any intention to limit operation or ++modification of the work as a means of enforcing, against the work's ++users, your or third parties' legal rights to forbid circumvention of ++technological measures. ++ ++ 4. Conveying Verbatim Copies. ++ ++ You may convey verbatim copies of the Program's source code as you ++receive it, in any medium, provided that you conspicuously and ++appropriately publish on each copy an appropriate copyright notice; ++keep intact all notices stating that this License and any ++non-permissive terms added in accord with section 7 apply to the code; ++keep intact all notices of the absence of any warranty; and give all ++recipients a copy of this License along with the Program. ++ ++ You may charge any price or no price for each copy that you convey, ++and you may offer support or warranty protection for a fee. ++ ++ 5. Conveying Modified Source Versions. ++ ++ You may convey a work based on the Program, or the modifications to ++produce it from the Program, in the form of source code under the ++terms of section 4, provided that you also meet all of these conditions: ++ ++ a) The work must carry prominent notices stating that you modified ++ it, and giving a relevant date. ++ ++ b) The work must carry prominent notices stating that it is ++ released under this License and any conditions added under section ++ 7. This requirement modifies the requirement in section 4 to ++ "keep intact all notices". ++ ++ c) You must license the entire work, as a whole, under this ++ License to anyone who comes into possession of a copy. This ++ License will therefore apply, along with any applicable section 7 ++ additional terms, to the whole of the work, and all its parts, ++ regardless of how they are packaged. This License gives no ++ permission to license the work in any other way, but it does not ++ invalidate such permission if you have separately received it. ++ ++ d) If the work has interactive user interfaces, each must display ++ Appropriate Legal Notices; however, if the Program has interactive ++ interfaces that do not display Appropriate Legal Notices, your ++ work need not make them do so. ++ ++ A compilation of a covered work with other separate and independent ++works, which are not by their nature extensions of the covered work, ++and which are not combined with it such as to form a larger program, ++in or on a volume of a storage or distribution medium, is called an ++"aggregate" if the compilation and its resulting copyright are not ++used to limit the access or legal rights of the compilation's users ++beyond what the individual works permit. Inclusion of a covered work ++in an aggregate does not cause this License to apply to the other ++parts of the aggregate. ++ ++ 6. Conveying Non-Source Forms. ++ ++ You may convey a covered work in object code form under the terms ++of sections 4 and 5, provided that you also convey the ++machine-readable Corresponding Source under the terms of this License, ++in one of these ways: ++ ++ a) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by the ++ Corresponding Source fixed on a durable physical medium ++ customarily used for software interchange. ++ ++ b) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by a ++ written offer, valid for at least three years and valid for as ++ long as you offer spare parts or customer support for that product ++ model, to give anyone who possesses the object code either (1) a ++ copy of the Corresponding Source for all the software in the ++ product that is covered by this License, on a durable physical ++ medium customarily used for software interchange, for a price no ++ more than your reasonable cost of physically performing this ++ conveying of source, or (2) access to copy the ++ Corresponding Source from a network server at no charge. ++ ++ c) Convey individual copies of the object code with a copy of the ++ written offer to provide the Corresponding Source. This ++ alternative is allowed only occasionally and noncommercially, and ++ only if you received the object code with such an offer, in accord ++ with subsection 6b. ++ ++ d) Convey the object code by offering access from a designated ++ place (gratis or for a charge), and offer equivalent access to the ++ Corresponding Source in the same way through the same place at no ++ further charge. You need not require recipients to copy the ++ Corresponding Source along with the object code. If the place to ++ copy the object code is a network server, the Corresponding Source ++ may be on a different server (operated by you or a third party) ++ that supports equivalent copying facilities, provided you maintain ++ clear directions next to the object code saying where to find the ++ Corresponding Source. Regardless of what server hosts the ++ Corresponding Source, you remain obligated to ensure that it is ++ available for as long as needed to satisfy these requirements. ++ ++ e) Convey the object code using peer-to-peer transmission, provided ++ you inform other peers where the object code and Corresponding ++ Source of the work are being offered to the general public at no ++ charge under subsection 6d. ++ ++ A separable portion of the object code, whose source code is excluded ++from the Corresponding Source as a System Library, need not be ++included in conveying the object code work. ++ ++ A "User Product" is either (1) a "consumer product", which means any ++tangible personal property which is normally used for personal, family, ++or household purposes, or (2) anything designed or sold for incorporation ++into a dwelling. In determining whether a product is a consumer product, ++doubtful cases shall be resolved in favor of coverage. For a particular ++product received by a particular user, "normally used" refers to a ++typical or common use of that class of product, regardless of the status ++of the particular user or of the way in which the particular user ++actually uses, or expects or is expected to use, the product. A product ++is a consumer product regardless of whether the product has substantial ++commercial, industrial or non-consumer uses, unless such uses represent ++the only significant mode of use of the product. ++ ++ "Installation Information" for a User Product means any methods, ++procedures, authorization keys, or other information required to install ++and execute modified versions of a covered work in that User Product from ++a modified version of its Corresponding Source. The information must ++suffice to ensure that the continued functioning of the modified object ++code is in no case prevented or interfered with solely because ++modification has been made. ++ ++ If you convey an object code work under this section in, or with, or ++specifically for use in, a User Product, and the conveying occurs as ++part of a transaction in which the right of possession and use of the ++User Product is transferred to the recipient in perpetuity or for a ++fixed term (regardless of how the transaction is characterized), the ++Corresponding Source conveyed under this section must be accompanied ++by the Installation Information. But this requirement does not apply ++if neither you nor any third party retains the ability to install ++modified object code on the User Product (for example, the work has ++been installed in ROM). ++ ++ The requirement to provide Installation Information does not include a ++requirement to continue to provide support service, warranty, or updates ++for a work that has been modified or installed by the recipient, or for ++the User Product in which it has been modified or installed. Access to a ++network may be denied when the modification itself materially and ++adversely affects the operation of the network or violates the rules and ++protocols for communication across the network. ++ ++ Corresponding Source conveyed, and Installation Information provided, ++in accord with this section must be in a format that is publicly ++documented (and with an implementation available to the public in ++source code form), and must require no special password or key for ++unpacking, reading or copying. ++ ++ 7. Additional Terms. ++ ++ "Additional permissions" are terms that supplement the terms of this ++License by making exceptions from one or more of its conditions. ++Additional permissions that are applicable to the entire Program shall ++be treated as though they were included in this License, to the extent ++that they are valid under applicable law. If additional permissions ++apply only to part of the Program, that part may be used separately ++under those permissions, but the entire Program remains governed by ++this License without regard to the additional permissions. ++ ++ When you convey a copy of a covered work, you may at your option ++remove any additional permissions from that copy, or from any part of ++it. (Additional permissions may be written to require their own ++removal in certain cases when you modify the work.) You may place ++additional permissions on material, added by you to a covered work, ++for which you have or can give appropriate copyright permission. ++ ++ Notwithstanding any other provision of this License, for material you ++add to a covered work, you may (if authorized by the copyright holders of ++that material) supplement the terms of this License with terms: ++ ++ a) Disclaiming warranty or limiting liability differently from the ++ terms of sections 15 and 16 of this License; or ++ ++ b) Requiring preservation of specified reasonable legal notices or ++ author attributions in that material or in the Appropriate Legal ++ Notices displayed by works containing it; or ++ ++ c) Prohibiting misrepresentation of the origin of that material, or ++ requiring that modified versions of such material be marked in ++ reasonable ways as different from the original version; or ++ ++ d) Limiting the use for publicity purposes of names of licensors or ++ authors of the material; or ++ ++ e) Declining to grant rights under trademark law for use of some ++ trade names, trademarks, or service marks; or ++ ++ f) Requiring indemnification of licensors and authors of that ++ material by anyone who conveys the material (or modified versions of ++ it) with contractual assumptions of liability to the recipient, for ++ any liability that these contractual assumptions directly impose on ++ those licensors and authors. ++ ++ All other non-permissive additional terms are considered "further ++restrictions" within the meaning of section 10. If the Program as you ++received it, or any part of it, contains a notice stating that it is ++governed by this License along with a term that is a further ++restriction, you may remove that term. If a license document contains ++a further restriction but permits relicensing or conveying under this ++License, you may add to a covered work material governed by the terms ++of that license document, provided that the further restriction does ++not survive such relicensing or conveying. ++ ++ If you add terms to a covered work in accord with this section, you ++must place, in the relevant source files, a statement of the ++additional terms that apply to those files, or a notice indicating ++where to find the applicable terms. ++ ++ Additional terms, permissive or non-permissive, may be stated in the ++form of a separately written license, or stated as exceptions; ++the above requirements apply either way. ++ ++ 8. Termination. ++ ++ You may not propagate or modify a covered work except as expressly ++provided under this License. Any attempt otherwise to propagate or ++modify it is void, and will automatically terminate your rights under ++this License (including any patent licenses granted under the third ++paragraph of section 11). ++ ++ However, if you cease all violation of this License, then your ++license from a particular copyright holder is reinstated (a) ++provisionally, unless and until the copyright holder explicitly and ++finally terminates your license, and (b) permanently, if the copyright ++holder fails to notify you of the violation by some reasonable means ++prior to 60 days after the cessation. ++ ++ Moreover, your license from a particular copyright holder is ++reinstated permanently if the copyright holder notifies you of the ++violation by some reasonable means, this is the first time you have ++received notice of violation of this License (for any work) from that ++copyright holder, and you cure the violation prior to 30 days after ++your receipt of the notice. ++ ++ Termination of your rights under this section does not terminate the ++licenses of parties who have received copies or rights from you under ++this License. If your rights have been terminated and not permanently ++reinstated, you do not qualify to receive new licenses for the same ++material under section 10. ++ ++ 9. Acceptance Not Required for Having Copies. ++ ++ You are not required to accept this License in order to receive or ++run a copy of the Program. Ancillary propagation of a covered work ++occurring solely as a consequence of using peer-to-peer transmission ++to receive a copy likewise does not require acceptance. However, ++nothing other than this License grants you permission to propagate or ++modify any covered work. These actions infringe copyright if you do ++not accept this License. Therefore, by modifying or propagating a ++covered work, you indicate your acceptance of this License to do so. ++ ++ 10. Automatic Licensing of Downstream Recipients. ++ ++ Each time you convey a covered work, the recipient automatically ++receives a license from the original licensors, to run, modify and ++propagate that work, subject to this License. You are not responsible ++for enforcing compliance by third parties with this License. ++ ++ An "entity transaction" is a transaction transferring control of an ++organization, or substantially all assets of one, or subdividing an ++organization, or merging organizations. If propagation of a covered ++work results from an entity transaction, each party to that ++transaction who receives a copy of the work also receives whatever ++licenses to the work the party's predecessor in interest had or could ++give under the previous paragraph, plus a right to possession of the ++Corresponding Source of the work from the predecessor in interest, if ++the predecessor has it or can get it with reasonable efforts. ++ ++ You may not impose any further restrictions on the exercise of the ++rights granted or affirmed under this License. For example, you may ++not impose a license fee, royalty, or other charge for exercise of ++rights granted under this License, and you may not initiate litigation ++(including a cross-claim or counterclaim in a lawsuit) alleging that ++any patent claim is infringed by making, using, selling, offering for ++sale, or importing the Program or any portion of it. ++ ++ 11. Patents. ++ ++ A "contributor" is a copyright holder who authorizes use under this ++License of the Program or a work on which the Program is based. The ++work thus licensed is called the contributor's "contributor version". ++ ++ A contributor's "essential patent claims" are all patent claims ++owned or controlled by the contributor, whether already acquired or ++hereafter acquired, that would be infringed by some manner, permitted ++by this License, of making, using, or selling its contributor version, ++but do not include claims that would be infringed only as a ++consequence of further modification of the contributor version. For ++purposes of this definition, "control" includes the right to grant ++patent sublicenses in a manner consistent with the requirements of ++this License. ++ ++ Each contributor grants you a non-exclusive, worldwide, royalty-free ++patent license under the contributor's essential patent claims, to ++make, use, sell, offer for sale, import and otherwise run, modify and ++propagate the contents of its contributor version. ++ ++ In the following three paragraphs, a "patent license" is any express ++agreement or commitment, however denominated, not to enforce a patent ++(such as an express permission to practice a patent or covenant not to ++sue for patent infringement). To "grant" such a patent license to a ++party means to make such an agreement or commitment not to enforce a ++patent against the party. ++ ++ If you convey a covered work, knowingly relying on a patent license, ++and the Corresponding Source of the work is not available for anyone ++to copy, free of charge and under the terms of this License, through a ++publicly available network server or other readily accessible means, ++then you must either (1) cause the Corresponding Source to be so ++available, or (2) arrange to deprive yourself of the benefit of the ++patent license for this particular work, or (3) arrange, in a manner ++consistent with the requirements of this License, to extend the patent ++license to downstream recipients. "Knowingly relying" means you have ++actual knowledge that, but for the patent license, your conveying the ++covered work in a country, or your recipient's use of the covered work ++in a country, would infringe one or more identifiable patents in that ++country that you have reason to believe are valid. ++ ++ If, pursuant to or in connection with a single transaction or ++arrangement, you convey, or propagate by procuring conveyance of, a ++covered work, and grant a patent license to some of the parties ++receiving the covered work authorizing them to use, propagate, modify ++or convey a specific copy of the covered work, then the patent license ++you grant is automatically extended to all recipients of the covered ++work and works based on it. ++ ++ A patent license is "discriminatory" if it does not include within ++the scope of its coverage, prohibits the exercise of, or is ++conditioned on the non-exercise of one or more of the rights that are ++specifically granted under this License. You may not convey a covered ++work if you are a party to an arrangement with a third party that is ++in the business of distributing software, under which you make payment ++to the third party based on the extent of your activity of conveying ++the work, and under which the third party grants, to any of the ++parties who would receive the covered work from you, a discriminatory ++patent license (a) in connection with copies of the covered work ++conveyed by you (or copies made from those copies), or (b) primarily ++for and in connection with specific products or compilations that ++contain the covered work, unless you entered into that arrangement, ++or that patent license was granted, prior to 28 March 2007. ++ ++ Nothing in this License shall be construed as excluding or limiting ++any implied license or other defenses to infringement that may ++otherwise be available to you under applicable patent law. ++ ++ 12. No Surrender of Others' Freedom. ++ ++ If conditions are imposed on you (whether by court order, agreement or ++otherwise) that contradict the conditions of this License, they do not ++excuse you from the conditions of this License. If you cannot convey a ++covered work so as to satisfy simultaneously your obligations under this ++License and any other pertinent obligations, then as a consequence you may ++not convey it at all. For example, if you agree to terms that obligate you ++to collect a royalty for further conveying from those to whom you convey ++the Program, the only way you could satisfy both those terms and this ++License would be to refrain entirely from conveying the Program. ++ ++ 13. Use with the GNU Affero General Public License. ++ ++ Notwithstanding any other provision of this License, you have ++permission to link or combine any covered work with a work licensed ++under version 3 of the GNU Affero General Public License into a single ++combined work, and to convey the resulting work. The terms of this ++License will continue to apply to the part which is the covered work, ++but the special requirements of the GNU Affero General Public License, ++section 13, concerning interaction through a network will apply to the ++combination as such. ++ ++ 14. Revised Versions of this License. ++ ++ The Free Software Foundation may publish revised and/or new versions of ++the GNU General Public License from time to time. Such new versions will ++be similar in spirit to the present version, but may differ in detail to ++address new problems or concerns. ++ ++ Each version is given a distinguishing version number. If the ++Program specifies that a certain numbered version of the GNU General ++Public License "or any later version" applies to it, you have the ++option of following the terms and conditions either of that numbered ++version or of any later version published by the Free Software ++Foundation. If the Program does not specify a version number of the ++GNU General Public License, you may choose any version ever published ++by the Free Software Foundation. ++ ++ If the Program specifies that a proxy can decide which future ++versions of the GNU General Public License can be used, that proxy's ++public statement of acceptance of a version permanently authorizes you ++to choose that version for the Program. ++ ++ Later license versions may give you additional or different ++permissions. However, no additional obligations are imposed on any ++author or copyright holder as a result of your choosing to follow a ++later version. ++ ++ 15. Disclaimer of Warranty. ++ ++ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY ++APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT ++HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY ++OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ++THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM ++IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ++ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ++ ++ 16. Limitation of Liability. ++ ++ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING ++WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS ++THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY ++GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE ++USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF ++DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD ++PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), ++EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF ++SUCH DAMAGES. ++ ++ 17. Interpretation of Sections 15 and 16. ++ ++ If the disclaimer of warranty and limitation of liability provided ++above cannot be given local legal effect according to their terms, ++reviewing courts shall apply local law that most closely approximates ++an absolute waiver of all civil liability in connection with the ++Program, unless a warranty or assumption of liability accompanies a ++copy of the Program in return for a fee. ++ ++ END OF TERMS AND CONDITIONS ++ ++ How to Apply These Terms to Your New Programs ++ ++ If you develop a new program, and you want it to be of the greatest ++possible use to the public, the best way to achieve this is to make it ++free software which everyone can redistribute and change under these terms. ++ ++ To do so, attach the following notices to the program. It is safest ++to attach them to the start of each source file to most effectively ++state the exclusion of warranty; and each file should have at least ++the "copyright" line and a pointer to where the full notice is found. ++ ++ ++ Copyright (C) ++ ++ This program is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation, either version 3 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see . ++ ++Also add information on how to contact you by electronic and paper mail. ++ ++ If the program does terminal interaction, make it output a short ++notice like this when it starts in an interactive mode: ++ ++ Copyright (C) ++ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. ++ This is free software, and you are welcome to redistribute it ++ under certain conditions; type `show c' for details. ++ ++The hypothetical commands `show w' and `show c' should show the appropriate ++parts of the General Public License. Of course, your program's commands ++might be different; for a GUI interface, you would use an "about box". ++ ++ You should also get your employer (if you work as a programmer) or school, ++if any, to sign a "copyright disclaimer" for the program, if necessary. ++For more information on this, and how to apply and follow the GNU GPL, see ++. ++ ++ The GNU General Public License does not permit incorporating your program ++into proprietary programs. If your program is a subroutine library, you ++may consider it more useful to permit linking proprietary applications with ++the library. If this is what you want to do, use the GNU Lesser General ++Public License instead of this License. But first, please read ++. +--- a/transport/internet/kcptun/config.go ++++ b/transport/internet/kcptun/config.go +@@ -0,0 +1,43 @@ ++package kcptun ++ ++import ( ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/transport/internet" ++ protocol "github.com/xtls/xray-core/transport/internet/kcptun/protocol" ++) ++ ++const protocolName = "kcptun" ++ ++func (c *Config) Build() (protocol.Config, error) { ++ cfg := protocol.Config{ ++ Key: c.Key, ++ Crypt: c.Crypt, ++ Mode: c.Mode, ++ Conn: int(c.Conn), ++ AutoExpire: int(c.Autoexpire), ++ ScavengeTTL: int(c.Scavengettl), ++ MTU: int(c.Mtu), ++ RateLimit: int(c.Ratelimit), ++ SndWnd: int(c.Sndwnd), ++ RcvWnd: int(c.Rcvwnd), ++ DataShard: int(c.Datashard), ++ ParityShard: int(c.Parityshard), ++ DSCP: int(c.Dscp), ++ NoComp: c.Nocomp, ++ AckNodelay: c.Acknodelay, ++ NoDelay: int(c.Nodelay), ++ Interval: int(c.Interval), ++ Resend: int(c.Resend), ++ NoCongestion: int(c.Nc), ++ SockBuf: int(c.Sockbuf), ++ SmuxVer: int(c.Smuxver), ++ SmuxBuf: int(c.Smuxbuf), ++ FrameSize: int(c.Framesize), ++ StreamBuf: int(c.Streambuf), ++ KeepAlive: int(c.Keepalive), ++ } ++ return protocol.Normalize(cfg) ++} ++func init() { ++ common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} { return new(Config) })) ++} +--- a/transport/internet/kcptun/config.pb.go ++++ b/transport/internet/kcptun/config.pb.go +@@ -0,0 +1,343 @@ ++// Code generated by protoc-gen-go. DO NOT EDIT. ++// versions: ++// protoc-gen-go v1.36.12 ++// protoc v3.21.12 ++// source: transport/internet/kcptun/config.proto ++ ++package kcptun ++ ++import ( ++ protoreflect "google.golang.org/protobuf/reflect/protoreflect" ++ protoimpl "google.golang.org/protobuf/runtime/protoimpl" ++ reflect "reflect" ++ sync "sync" ++ unsafe "unsafe" ++) ++ ++const ( ++ // Verify that this generated code is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) ++ // Verify that runtime/protoimpl is sufficiently up-to-date. ++ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ++) ++ ++type Config struct { ++ state protoimpl.MessageState `protogen:"open.v1"` ++ Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` ++ Crypt string `protobuf:"bytes,2,opt,name=crypt,proto3" json:"crypt,omitempty"` ++ Mode string `protobuf:"bytes,3,opt,name=mode,proto3" json:"mode,omitempty"` ++ Conn int32 `protobuf:"varint,4,opt,name=conn,proto3" json:"conn,omitempty"` ++ Autoexpire int32 `protobuf:"varint,5,opt,name=autoexpire,proto3" json:"autoexpire,omitempty"` ++ Scavengettl int32 `protobuf:"varint,6,opt,name=scavengettl,proto3" json:"scavengettl,omitempty"` ++ Mtu int32 `protobuf:"varint,7,opt,name=mtu,proto3" json:"mtu,omitempty"` ++ Ratelimit int32 `protobuf:"varint,8,opt,name=ratelimit,proto3" json:"ratelimit,omitempty"` ++ Sndwnd int32 `protobuf:"varint,9,opt,name=sndwnd,proto3" json:"sndwnd,omitempty"` ++ Rcvwnd int32 `protobuf:"varint,10,opt,name=rcvwnd,proto3" json:"rcvwnd,omitempty"` ++ Datashard int32 `protobuf:"varint,11,opt,name=datashard,proto3" json:"datashard,omitempty"` ++ Parityshard int32 `protobuf:"varint,12,opt,name=parityshard,proto3" json:"parityshard,omitempty"` ++ Dscp int32 `protobuf:"varint,13,opt,name=dscp,proto3" json:"dscp,omitempty"` ++ Nocomp bool `protobuf:"varint,14,opt,name=nocomp,proto3" json:"nocomp,omitempty"` ++ Acknodelay bool `protobuf:"varint,15,opt,name=acknodelay,proto3" json:"acknodelay,omitempty"` ++ Nodelay int32 `protobuf:"varint,16,opt,name=nodelay,proto3" json:"nodelay,omitempty"` ++ Interval int32 `protobuf:"varint,17,opt,name=interval,proto3" json:"interval,omitempty"` ++ Resend int32 `protobuf:"varint,18,opt,name=resend,proto3" json:"resend,omitempty"` ++ Nc int32 `protobuf:"varint,19,opt,name=nc,proto3" json:"nc,omitempty"` ++ Sockbuf int32 `protobuf:"varint,20,opt,name=sockbuf,proto3" json:"sockbuf,omitempty"` ++ Smuxver int32 `protobuf:"varint,21,opt,name=smuxver,proto3" json:"smuxver,omitempty"` ++ Smuxbuf int32 `protobuf:"varint,22,opt,name=smuxbuf,proto3" json:"smuxbuf,omitempty"` ++ Framesize int32 `protobuf:"varint,23,opt,name=framesize,proto3" json:"framesize,omitempty"` ++ Streambuf int32 `protobuf:"varint,24,opt,name=streambuf,proto3" json:"streambuf,omitempty"` ++ Keepalive int32 `protobuf:"varint,25,opt,name=keepalive,proto3" json:"keepalive,omitempty"` ++ unknownFields protoimpl.UnknownFields ++ sizeCache protoimpl.SizeCache ++} ++ ++func (x *Config) Reset() { ++ *x = Config{} ++ mi := &file_transport_internet_kcptun_config_proto_msgTypes[0] ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ ms.StoreMessageInfo(mi) ++} ++ ++func (x *Config) String() string { ++ return protoimpl.X.MessageStringOf(x) ++} ++ ++func (*Config) ProtoMessage() {} ++ ++func (x *Config) ProtoReflect() protoreflect.Message { ++ mi := &file_transport_internet_kcptun_config_proto_msgTypes[0] ++ if x != nil { ++ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ++ if ms.LoadMessageInfo() == nil { ++ ms.StoreMessageInfo(mi) ++ } ++ return ms ++ } ++ return mi.MessageOf(x) ++} ++ ++// Deprecated: Use Config.ProtoReflect.Descriptor instead. ++func (*Config) Descriptor() ([]byte, []int) { ++ return file_transport_internet_kcptun_config_proto_rawDescGZIP(), []int{0} ++} ++ ++func (x *Config) GetKey() string { ++ if x != nil { ++ return x.Key ++ } ++ return "" ++} ++ ++func (x *Config) GetCrypt() string { ++ if x != nil { ++ return x.Crypt ++ } ++ return "" ++} ++ ++func (x *Config) GetMode() string { ++ if x != nil { ++ return x.Mode ++ } ++ return "" ++} ++ ++func (x *Config) GetConn() int32 { ++ if x != nil { ++ return x.Conn ++ } ++ return 0 ++} ++ ++func (x *Config) GetAutoexpire() int32 { ++ if x != nil { ++ return x.Autoexpire ++ } ++ return 0 ++} ++ ++func (x *Config) GetScavengettl() int32 { ++ if x != nil { ++ return x.Scavengettl ++ } ++ return 0 ++} ++ ++func (x *Config) GetMtu() int32 { ++ if x != nil { ++ return x.Mtu ++ } ++ return 0 ++} ++ ++func (x *Config) GetRatelimit() int32 { ++ if x != nil { ++ return x.Ratelimit ++ } ++ return 0 ++} ++ ++func (x *Config) GetSndwnd() int32 { ++ if x != nil { ++ return x.Sndwnd ++ } ++ return 0 ++} ++ ++func (x *Config) GetRcvwnd() int32 { ++ if x != nil { ++ return x.Rcvwnd ++ } ++ return 0 ++} ++ ++func (x *Config) GetDatashard() int32 { ++ if x != nil { ++ return x.Datashard ++ } ++ return 0 ++} ++ ++func (x *Config) GetParityshard() int32 { ++ if x != nil { ++ return x.Parityshard ++ } ++ return 0 ++} ++ ++func (x *Config) GetDscp() int32 { ++ if x != nil { ++ return x.Dscp ++ } ++ return 0 ++} ++ ++func (x *Config) GetNocomp() bool { ++ if x != nil { ++ return x.Nocomp ++ } ++ return false ++} ++ ++func (x *Config) GetAcknodelay() bool { ++ if x != nil { ++ return x.Acknodelay ++ } ++ return false ++} ++ ++func (x *Config) GetNodelay() int32 { ++ if x != nil { ++ return x.Nodelay ++ } ++ return 0 ++} ++ ++func (x *Config) GetInterval() int32 { ++ if x != nil { ++ return x.Interval ++ } ++ return 0 ++} ++ ++func (x *Config) GetResend() int32 { ++ if x != nil { ++ return x.Resend ++ } ++ return 0 ++} ++ ++func (x *Config) GetNc() int32 { ++ if x != nil { ++ return x.Nc ++ } ++ return 0 ++} ++ ++func (x *Config) GetSockbuf() int32 { ++ if x != nil { ++ return x.Sockbuf ++ } ++ return 0 ++} ++ ++func (x *Config) GetSmuxver() int32 { ++ if x != nil { ++ return x.Smuxver ++ } ++ return 0 ++} ++ ++func (x *Config) GetSmuxbuf() int32 { ++ if x != nil { ++ return x.Smuxbuf ++ } ++ return 0 ++} ++ ++func (x *Config) GetFramesize() int32 { ++ if x != nil { ++ return x.Framesize ++ } ++ return 0 ++} ++ ++func (x *Config) GetStreambuf() int32 { ++ if x != nil { ++ return x.Streambuf ++ } ++ return 0 ++} ++ ++func (x *Config) GetKeepalive() int32 { ++ if x != nil { ++ return x.Keepalive ++ } ++ return 0 ++} ++ ++var File_transport_internet_kcptun_config_proto protoreflect.FileDescriptor ++ ++const file_transport_internet_kcptun_config_proto_rawDesc = "" + ++ "\n" + ++ "&transport/internet/kcptun/config.proto\x12\x1exray.transport.internet.kcptun\"\x8c\x05\n" + ++ "\x06Config\x12\x10\n" + ++ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + ++ "\x05crypt\x18\x02 \x01(\tR\x05crypt\x12\x12\n" + ++ "\x04mode\x18\x03 \x01(\tR\x04mode\x12\x12\n" + ++ "\x04conn\x18\x04 \x01(\x05R\x04conn\x12\x1e\n" + ++ "\n" + ++ "autoexpire\x18\x05 \x01(\x05R\n" + ++ "autoexpire\x12 \n" + ++ "\vscavengettl\x18\x06 \x01(\x05R\vscavengettl\x12\x10\n" + ++ "\x03mtu\x18\a \x01(\x05R\x03mtu\x12\x1c\n" + ++ "\tratelimit\x18\b \x01(\x05R\tratelimit\x12\x16\n" + ++ "\x06sndwnd\x18\t \x01(\x05R\x06sndwnd\x12\x16\n" + ++ "\x06rcvwnd\x18\n" + ++ " \x01(\x05R\x06rcvwnd\x12\x1c\n" + ++ "\tdatashard\x18\v \x01(\x05R\tdatashard\x12 \n" + ++ "\vparityshard\x18\f \x01(\x05R\vparityshard\x12\x12\n" + ++ "\x04dscp\x18\r \x01(\x05R\x04dscp\x12\x16\n" + ++ "\x06nocomp\x18\x0e \x01(\bR\x06nocomp\x12\x1e\n" + ++ "\n" + ++ "acknodelay\x18\x0f \x01(\bR\n" + ++ "acknodelay\x12\x18\n" + ++ "\anodelay\x18\x10 \x01(\x05R\anodelay\x12\x1a\n" + ++ "\binterval\x18\x11 \x01(\x05R\binterval\x12\x16\n" + ++ "\x06resend\x18\x12 \x01(\x05R\x06resend\x12\x0e\n" + ++ "\x02nc\x18\x13 \x01(\x05R\x02nc\x12\x18\n" + ++ "\asockbuf\x18\x14 \x01(\x05R\asockbuf\x12\x18\n" + ++ "\asmuxver\x18\x15 \x01(\x05R\asmuxver\x12\x18\n" + ++ "\asmuxbuf\x18\x16 \x01(\x05R\asmuxbuf\x12\x1c\n" + ++ "\tframesize\x18\x17 \x01(\x05R\tframesize\x12\x1c\n" + ++ "\tstreambuf\x18\x18 \x01(\x05R\tstreambuf\x12\x1c\n" + ++ "\tkeepalive\x18\x19 \x01(\x05R\tkeepaliveB5Z3github.com/xtls/xray-core/transport/internet/kcptunb\x06proto3" ++ ++var ( ++ file_transport_internet_kcptun_config_proto_rawDescOnce sync.Once ++ file_transport_internet_kcptun_config_proto_rawDescData []byte ++) ++ ++func file_transport_internet_kcptun_config_proto_rawDescGZIP() []byte { ++ file_transport_internet_kcptun_config_proto_rawDescOnce.Do(func() { ++ file_transport_internet_kcptun_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_kcptun_config_proto_rawDesc), len(file_transport_internet_kcptun_config_proto_rawDesc))) ++ }) ++ return file_transport_internet_kcptun_config_proto_rawDescData ++} ++ ++var file_transport_internet_kcptun_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) ++var file_transport_internet_kcptun_config_proto_goTypes = []any{ ++ (*Config)(nil), // 0: xray.transport.internet.kcptun.Config ++} ++var file_transport_internet_kcptun_config_proto_depIdxs = []int32{ ++ 0, // [0:0] is the sub-list for method output_type ++ 0, // [0:0] is the sub-list for method input_type ++ 0, // [0:0] is the sub-list for extension type_name ++ 0, // [0:0] is the sub-list for extension extendee ++ 0, // [0:0] is the sub-list for field type_name ++} ++ ++func init() { file_transport_internet_kcptun_config_proto_init() } ++func file_transport_internet_kcptun_config_proto_init() { ++ if File_transport_internet_kcptun_config_proto != nil { ++ return ++ } ++ type x struct{} ++ out := protoimpl.TypeBuilder{ ++ File: protoimpl.DescBuilder{ ++ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), ++ RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_kcptun_config_proto_rawDesc), len(file_transport_internet_kcptun_config_proto_rawDesc)), ++ NumEnums: 0, ++ NumMessages: 1, ++ NumExtensions: 0, ++ NumServices: 0, ++ }, ++ GoTypes: file_transport_internet_kcptun_config_proto_goTypes, ++ DependencyIndexes: file_transport_internet_kcptun_config_proto_depIdxs, ++ MessageInfos: file_transport_internet_kcptun_config_proto_msgTypes, ++ }.Build() ++ File_transport_internet_kcptun_config_proto = out.File ++ file_transport_internet_kcptun_config_proto_goTypes = nil ++ file_transport_internet_kcptun_config_proto_depIdxs = nil ++} +--- a/transport/internet/kcptun/config.proto ++++ b/transport/internet/kcptun/config.proto +@@ -0,0 +1,30 @@ ++syntax = "proto3"; ++package xray.transport.internet.kcptun; ++option go_package = "github.com/xtls/xray-core/transport/internet/kcptun"; ++message Config { ++ string key = 1; ++ string crypt = 2; ++ string mode = 3; ++ int32 conn = 4; ++ int32 autoexpire = 5; ++ int32 scavengettl = 6; ++ int32 mtu = 7; ++ int32 ratelimit = 8; ++ int32 sndwnd = 9; ++ int32 rcvwnd = 10; ++ int32 datashard = 11; ++ int32 parityshard = 12; ++ int32 dscp = 13; ++ bool nocomp = 14; ++ bool acknodelay = 15; ++ int32 nodelay = 16; ++ int32 interval = 17; ++ int32 resend = 18; ++ int32 nc = 19; ++ int32 sockbuf = 20; ++ int32 smuxver = 21; ++ int32 smuxbuf = 22; ++ int32 framesize = 23; ++ int32 streambuf = 24; ++ int32 keepalive = 25; ++} +--- a/transport/internet/kcptun/dialer.go ++++ b/transport/internet/kcptun/dialer.go +@@ -0,0 +1,60 @@ ++package kcptun ++ ++import ( ++ "context" ++ "github.com/xtls/xray-core/common" ++ xnet "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/common/session" ++ "github.com/xtls/xray-core/transport/internet" ++ protocol "github.com/xtls/xray-core/transport/internet/kcptun/protocol" ++ "github.com/xtls/xray-core/transport/internet/stat" ++ "io" ++ "net" ++) ++ ++type connectedPacket struct{ net.Conn } ++ ++func (c *connectedPacket) ReadFrom(b []byte) (int, net.Addr, error) { ++ n, err := c.Read(b) ++ return n, c.RemoteAddr(), err ++} ++func (c *connectedPacket) WriteTo(b []byte, _ net.Addr) (int, error) { return c.Write(b) } ++ ++func Dial(ctx context.Context, dest xnet.Destination, settings *internet.MemoryStreamConfig) (stat.Connection, error) { ++ cfg, err := settings.ProtocolSettings.(*Config).Build() ++ if err != nil { ++ return nil, err ++ } ++ dest.Network = xnet.Network_UDP ++ key := "kcptun:" + dest.String() ++ if outbounds := session.OutboundsFromContext(ctx); len(outbounds) > 0 { ++ if gateway := outbounds[len(outbounds)-1].Gateway; gateway != nil { ++ key += "/" + gateway.String() ++ } ++ } ++ owner, err := settings.TransportClient(key, func() (io.Closer, error) { return protocol.NewClient(cfg) }) ++ if err != nil { ++ return nil, err ++ } ++ stream, err := owner.(*protocol.Client).OpenStream(ctx, func(ctx context.Context) (net.PacketConn, net.Addr, error) { ++ var conn net.Conn ++ var err error ++ if settings.FinalMask != nil { ++ conn, err = settings.FinalMask.DialUDP(ctx, dest) ++ } else { ++ conn, err = internet.DialSystem(ctx, dest, settings.SocketSettings) ++ } ++ if err != nil { ++ return nil, nil, err ++ } ++ if pc, ok := conn.(*xnet.PacketConnWrapper); ok { ++ return pc.PacketConn, pc.Dest, nil ++ } ++ return &connectedPacket{Conn: conn}, conn.RemoteAddr(), nil ++ }) ++ if err != nil { ++ return nil, err ++ } ++ return &streamConn{Conn: stream}, nil ++} ++func init() { common.Must(internet.RegisterTransportDialer(protocolName, Dial)) } +--- a/transport/internet/kcptun/hub.go ++++ b/transport/internet/kcptun/hub.go +@@ -0,0 +1,38 @@ ++package kcptun ++ ++import ( ++ "context" ++ "github.com/xtls/xray-core/common" ++ "github.com/xtls/xray-core/common/errors" ++ xnet "github.com/xtls/xray-core/common/net" ++ "github.com/xtls/xray-core/transport/internet" ++ protocol "github.com/xtls/xray-core/transport/internet/kcptun/protocol" ++ "net" ++) ++ ++func Listen(ctx context.Context, address xnet.Address, port xnet.Port, settings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) { ++ if address.Family().IsDomain() || port == 0 { ++ return nil, errors.New("kcptun: UDP listener requires an IP and port") ++ } ++ cfg, err := settings.ProtocolSettings.(*Config).Build() ++ if err != nil { ++ return nil, err ++ } ++ var pc net.PacketConn ++ addr := &net.UDPAddr{IP: address.IP(), Port: int(port)} ++ if settings.FinalMask != nil { ++ pc, err = settings.FinalMask.ListenPacket(ctx, addr) ++ } else { ++ pc, err = internet.ListenSystemPacket(ctx, addr, settings.SocketSettings) ++ } ++ if err != nil { ++ return nil, err ++ } ++ server, err := protocol.NewServer(pc, cfg, func(conn net.Conn) { handler(&streamConn{Conn: conn}) }) ++ if err != nil { ++ return nil, err ++ } ++ errors.LogInfo(ctx, "listening Kcptun on ", server.Addr()) ++ return server, nil ++} ++func init() { common.Must(internet.RegisterTransportListener(protocolName, Listen)) } +--- a/transport/internet/kcptun/protocol/client.go ++++ b/transport/internet/kcptun/protocol/client.go +@@ -0,0 +1,135 @@ ++// Adapted from MetaCubeX/mihomo v1.19.32; GPL-3.0-or-later. ++package kcptun ++ ++import ( ++ "context" ++ kcp "github.com/metacubex/kcp-go" ++ "github.com/metacubex/smux" ++ "math/rand/v2" ++ "net" ++ "sync" ++ "time" ++) ++ ++type DialFn func(context.Context) (net.PacketConn, net.Addr, error) ++type timedSession struct { ++ session *smux.Session ++ expiry time.Time ++} ++type Client struct { ++ config Config ++ block kcp.BlockCrypt ++ ctx context.Context ++ cancel context.CancelFunc ++ done chan struct{} ++ mu sync.Mutex ++ slots []timedSession ++ sessions map[*smux.Session]time.Time ++ next int ++} ++ ++func NewClient(cfg Config) (*Client, error) { ++ cfg, err := Normalize(cfg) ++ if err != nil { ++ return nil, err ++ } ++ ctx, cancel := context.WithCancel(context.Background()) ++ c := &Client{config: cfg, block: cfg.NewBlock(), ctx: ctx, cancel: cancel, done: make(chan struct{}), slots: make([]timedSession, cfg.Conn), sessions: make(map[*smux.Session]time.Time)} ++ go c.scavenge() ++ return c, nil ++} ++func (c *Client) Close() error { c.cancel(); <-c.done; return nil } ++func (c *Client) scavenge() { ++ ticker := time.NewTicker(time.Second) ++ defer ticker.Stop() ++ defer close(c.done) ++ for { ++ select { ++ case <-c.ctx.Done(): ++ c.mu.Lock() ++ for session := range c.sessions { ++ session.Close() ++ } ++ clear(c.sessions) ++ c.mu.Unlock() ++ return ++ case now := <-ticker.C: ++ c.mu.Lock() ++ for session, expiry := range c.sessions { ++ if session.IsClosed() || (!expiry.IsZero() && now.After(expiry)) { ++ session.Close() ++ delete(c.sessions, session) ++ } ++ } ++ c.mu.Unlock() ++ } ++ } ++} ++func (c *Client) createConn(ctx context.Context, dial DialFn) (*smux.Session, error) { ++ pc, addr, err := dial(ctx) ++ if err != nil { ++ return nil, err ++ } ++ conn, err := kcp.NewConn4(rand.Uint32(), addr, c.block, c.config.DataShard, c.config.ParityShard, true, pc) ++ if err != nil { ++ pc.Close() ++ return nil, err ++ } ++ c.config.Apply(conn) ++ _ = conn.SetDSCP(c.config.DSCP) ++ _ = conn.SetReadBuffer(c.config.SockBuf) ++ _ = conn.SetWriteBuffer(c.config.SockBuf) ++ var raw net.Conn = conn ++ if !c.config.NoComp { ++ raw = NewCompStream(raw) ++ } ++ mux, err := smux.Client(raw, c.config.SmuxConfig()) ++ if err != nil { ++ raw.Close() ++ return nil, err ++ } ++ return mux, nil ++} ++func (c *Client) OpenStream(ctx context.Context, dial DialFn) (*smux.Stream, error) { ++ if err := ctx.Err(); err != nil { ++ return nil, err ++ } ++ ctx, cancel := context.WithCancel(ctx) ++ defer cancel() ++ stop := context.AfterFunc(c.ctx, cancel) ++ defer stop() ++ c.mu.Lock() ++ if c.ctx.Err() != nil { ++ c.mu.Unlock() ++ return nil, net.ErrClosed ++ } ++ idx := c.next ++ c.next = (c.next + 1) % len(c.slots) ++ slot := &c.slots[idx] ++ if slot.session == nil || slot.session.IsClosed() || (c.config.AutoExpire > 0 && time.Now().After(slot.expiry)) { ++ mux, err := c.createConn(ctx, dial) ++ if err != nil { ++ c.mu.Unlock() ++ return nil, err ++ } ++ slot.session = mux ++ expiry := time.Time{} ++ if c.config.AutoExpire > 0 { ++ slot.expiry = time.Now().Add(time.Duration(c.config.AutoExpire) * time.Second) ++ expiry = slot.expiry.Add(time.Duration(c.config.ScavengeTTL) * time.Second) ++ } ++ c.sessions[mux] = expiry ++ } ++ mux := slot.session ++ c.mu.Unlock() ++ stream, err := mux.OpenStream() ++ if err != nil { ++ mux.Close() ++ return nil, err ++ } ++ if err := ctx.Err(); err != nil { ++ stream.Close() ++ return nil, err ++ } ++ return stream, nil ++} +--- a/transport/internet/kcptun/protocol/common.go ++++ b/transport/internet/kcptun/protocol/common.go +@@ -0,0 +1,147 @@ ++// Adapted from MetaCubeX/mihomo v1.19.32; GPL-3.0-or-later. ++package kcptun ++ ++import ( ++ "crypto/sha1" ++ ++ "github.com/metacubex/kcp-go" ++ "golang.org/x/crypto/pbkdf2" ++) ++ ++const ( ++ // SALT is use for pbkdf2 key expansion ++ SALT = "kcp-go" ++ // maximum supported smux version ++ maxSmuxVer = 2 ++ // scavenger check period ++ scavengePeriod = 5 ++) ++ ++type Config struct { ++ Key string `json:"key"` ++ Crypt string `json:"crypt"` ++ Mode string `json:"mode"` ++ Conn int `json:"conn"` ++ AutoExpire int `json:"autoexpire"` ++ ScavengeTTL int `json:"scavengettl"` ++ MTU int `json:"mtu"` ++ RateLimit int `json:"ratelimit"` ++ SndWnd int `json:"sndwnd"` ++ RcvWnd int `json:"rcvwnd"` ++ DataShard int `json:"datashard"` ++ ParityShard int `json:"parityshard"` ++ DSCP int `json:"dscp"` ++ NoComp bool `json:"nocomp"` ++ AckNodelay bool `json:"acknodelay"` ++ NoDelay int `json:"nodelay"` ++ Interval int `json:"interval"` ++ Resend int `json:"resend"` ++ NoCongestion int `json:"nc"` ++ SockBuf int `json:"sockbuf"` ++ SmuxVer int `json:"smuxver"` ++ SmuxBuf int `json:"smuxbuf"` ++ FrameSize int `json:"framesize"` ++ StreamBuf int `json:"streambuf"` ++ KeepAlive int `json:"keepalive"` ++} ++ ++func (config *Config) FillDefaults() { ++ if config.Key == "" { ++ config.Key = "it's a secrect" ++ } ++ if config.Crypt == "" { ++ config.Crypt = "aes" ++ } ++ if config.Mode == "" { ++ config.Mode = "fast" ++ } ++ if config.Conn == 0 { ++ config.Conn = 1 ++ } ++ if config.ScavengeTTL == 0 { ++ config.ScavengeTTL = 600 ++ } ++ if config.MTU == 0 { ++ config.MTU = 1350 ++ } ++ if config.SndWnd == 0 { ++ config.SndWnd = 128 ++ } ++ if config.RcvWnd == 0 { ++ config.RcvWnd = 512 ++ } ++ if config.DataShard == 0 { ++ config.DataShard = 10 ++ } ++ if config.ParityShard == 0 { ++ config.ParityShard = 3 ++ } ++ if config.Interval == 0 { ++ config.Interval = 50 ++ } ++ if config.SockBuf == 0 { ++ config.SockBuf = 4194304 ++ } ++ if config.SmuxVer == 0 { ++ config.SmuxVer = 1 ++ } ++ if config.SmuxBuf == 0 { ++ config.SmuxBuf = 4194304 ++ } ++ if config.FrameSize == 0 { ++ config.FrameSize = 8192 ++ } ++ if config.StreamBuf == 0 { ++ config.StreamBuf = 2097152 ++ } ++ if config.KeepAlive == 0 { ++ config.KeepAlive = 10 ++ } ++ switch config.Mode { ++ case "normal": ++ config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 0, 40, 2, 1 ++ case "fast": ++ config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 0, 30, 2, 1 ++ case "fast2": ++ config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 1, 20, 2, 1 ++ case "fast3": ++ config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 1, 10, 2, 1 ++ } ++ ++} ++ ++func (config *Config) NewBlock() (block kcp.BlockCrypt) { ++ pass := pbkdf2.Key([]byte(config.Key), []byte(SALT), 4096, 32, sha1.New) ++ switch config.Crypt { ++ case "null": ++ block = nil ++ case "tea": ++ block, _ = kcp.NewTEABlockCrypt(pass[:16]) ++ case "xor": ++ block, _ = kcp.NewSimpleXORBlockCrypt(pass) ++ case "none": ++ block, _ = kcp.NewNoneBlockCrypt(pass) ++ case "aes-128": ++ block, _ = kcp.NewAESBlockCrypt(pass[:16]) ++ case "aes-192": ++ block, _ = kcp.NewAESBlockCrypt(pass[:24]) ++ case "blowfish": ++ block, _ = kcp.NewBlowfishBlockCrypt(pass) ++ case "twofish": ++ block, _ = kcp.NewTwofishBlockCrypt(pass) ++ case "cast5": ++ block, _ = kcp.NewCast5BlockCrypt(pass[:16]) ++ case "3des": ++ block, _ = kcp.NewTripleDESBlockCrypt(pass[:24]) ++ case "xtea": ++ block, _ = kcp.NewXTEABlockCrypt(pass[:16]) ++ case "salsa20": ++ block, _ = kcp.NewSalsa20BlockCrypt(pass) ++ case "aes-128-gcm": ++ block, _ = kcp.NewAESGCMCrypt(pass[:16]) ++ default: ++ config.Crypt = "aes" ++ block, _ = kcp.NewAESBlockCrypt(pass) ++ } ++ return ++} +--- a/transport/internet/kcptun/protocol/comp.go ++++ b/transport/internet/kcptun/protocol/comp.go +@@ -0,0 +1,64 @@ ++// Adapted from MetaCubeX/mihomo v1.19.32; GPL-3.0-or-later. ++package kcptun ++ ++import ( ++ "net" ++ "time" ++ ++ "github.com/golang/snappy" ++) ++ ++// CompStream is a net.Conn wrapper that compresses data using snappy ++type CompStream struct { ++ conn net.Conn ++ w *snappy.Writer ++ r *snappy.Reader ++} ++ ++func (c *CompStream) Read(p []byte) (n int, err error) { ++ return c.r.Read(p) ++} ++ ++func (c *CompStream) Write(p []byte) (n int, err error) { ++ if _, err := c.w.Write(p); err != nil { ++ return 0, err ++ } ++ ++ if err := c.w.Flush(); err != nil { ++ return 0, err ++ } ++ return len(p), err ++} ++ ++func (c *CompStream) Close() error { ++ return c.conn.Close() ++} ++ ++func (c *CompStream) LocalAddr() net.Addr { ++ return c.conn.LocalAddr() ++} ++ ++func (c *CompStream) RemoteAddr() net.Addr { ++ return c.conn.RemoteAddr() ++} ++ ++func (c *CompStream) SetDeadline(t time.Time) error { ++ return c.conn.SetDeadline(t) ++} ++ ++func (c *CompStream) SetReadDeadline(t time.Time) error { ++ return c.conn.SetReadDeadline(t) ++} ++ ++func (c *CompStream) SetWriteDeadline(t time.Time) error { ++ return c.conn.SetWriteDeadline(t) ++} ++ ++// NewCompStream creates a new stream that compresses data using snappy ++func NewCompStream(conn net.Conn) *CompStream { ++ c := new(CompStream) ++ c.conn = conn ++ c.w = snappy.NewBufferedWriter(conn) ++ c.r = snappy.NewReader(conn) ++ return c ++} +--- a/transport/internet/kcptun/protocol/doc.go ++++ b/transport/internet/kcptun/protocol/doc.go +@@ -0,0 +1,6 @@ ++// Adapted from MetaCubeX/mihomo v1.19.32; GPL-3.0-or-later. ++// Package kcptun copy and modify from: ++// https://github.com/xtaci/kcptun/tree/f54f35175bed6ddda4e47aa35c9d7ae8b7e7eb85 ++// adopt for mihomo ++// without SM4,QPP,tcpraw support ++package kcptun +--- a/transport/internet/kcptun/protocol/server.go ++++ b/transport/internet/kcptun/protocol/server.go +@@ -0,0 +1,95 @@ ++// Adapted from MetaCubeX/mihomo v1.19.32; GPL-3.0-or-later. ++package kcptun ++ ++import ( ++ kcp "github.com/metacubex/kcp-go" ++ "github.com/metacubex/smux" ++ "net" ++ "sync" ++) ++ ++type Server struct { ++ config Config ++ listener *kcp.Listener ++ pc net.PacketConn ++ mu sync.Mutex ++ closed bool ++ connections map[*kcp.UDPSession]struct{} ++ wg sync.WaitGroup ++ once sync.Once ++} ++ ++func NewServer(pc net.PacketConn, cfg Config, handler func(net.Conn)) (*Server, error) { ++ cfg, err := Normalize(cfg) ++ if err != nil { ++ pc.Close() ++ return nil, err ++ } ++ listener, err := kcp.ServeConn(cfg.NewBlock(), cfg.DataShard, cfg.ParityShard, pc) ++ if err != nil { ++ pc.Close() ++ return nil, err ++ } ++ _ = listener.SetDSCP(cfg.DSCP) ++ _ = listener.SetReadBuffer(cfg.SockBuf) ++ _ = listener.SetWriteBuffer(cfg.SockBuf) ++ s := &Server{config: cfg, listener: listener, pc: pc, connections: make(map[*kcp.UDPSession]struct{})} ++ s.wg.Add(1) ++ go s.serve(handler) ++ return s, nil ++} ++func (s *Server) Addr() net.Addr { return s.pc.LocalAddr() } ++func (s *Server) Close() error { ++ s.once.Do(func() { ++ s.mu.Lock() ++ s.closed = true ++ s.listener.Close() ++ s.pc.Close() ++ for conn := range s.connections { ++ conn.Close() ++ } ++ s.mu.Unlock() ++ }) ++ s.wg.Wait() ++ return nil ++} ++func (s *Server) serve(handler func(net.Conn)) { ++ defer s.wg.Done() ++ for { ++ conn, err := s.listener.AcceptKCP() ++ if err != nil { ++ return ++ } ++ s.mu.Lock() ++ if s.closed { ++ s.mu.Unlock() ++ conn.Close() ++ return ++ } ++ s.connections[conn] = struct{}{} ++ s.wg.Add(1) ++ s.mu.Unlock() ++ go s.serveConn(conn, handler) ++ } ++} ++func (s *Server) serveConn(conn *kcp.UDPSession, handler func(net.Conn)) { ++ defer s.wg.Done() ++ defer func() { conn.Close(); s.mu.Lock(); delete(s.connections, conn); s.mu.Unlock() }() ++ s.config.Apply(conn) ++ var raw net.Conn = conn ++ if !s.config.NoComp { ++ raw = NewCompStream(raw) ++ } ++ mux, err := smux.Server(raw, s.config.SmuxConfig()) ++ if err != nil { ++ return ++ } ++ defer mux.Close() ++ for { ++ stream, err := mux.AcceptStream() ++ if err != nil { ++ return ++ } ++ go handler(stream) ++ } ++} +--- a/transport/internet/kcptun/protocol/settings.go ++++ b/transport/internet/kcptun/protocol/settings.go +@@ -0,0 +1,69 @@ ++package kcptun ++ ++import ( ++ "fmt" ++ kcp "github.com/metacubex/kcp-go" ++ "github.com/metacubex/smux" ++ "time" ++) ++ ++// Normalize preserves Mihomo defaults and rejects invalid settings before opening sockets. ++func Normalize(c Config) (Config, error) { ++ c.FillDefaults() ++ switch c.Crypt { ++ case "aes", "aes-128", "aes-192", "aes-128-gcm", "null", "none", "tea", "xor", "blowfish", "twofish", "cast5", "3des", "xtea", "salsa20": ++ default: ++ return c, fmt.Errorf("kcptun: unknown crypt %q", c.Crypt) ++ } ++ switch c.Mode { ++ case "normal", "fast", "fast2", "fast3", "manual": ++ default: ++ return c, fmt.Errorf("kcptun: unknown mode %q", c.Mode) ++ } ++ if c.Conn < 1 || c.Conn > 256 { ++ return c, fmt.Errorf("kcptun: conn must be 1..256") ++ } ++ if c.MTU < 256 || c.MTU > 1500 { ++ return c, fmt.Errorf("kcptun: mtu must be 256..1500") ++ } ++ if !(c.DataShard == -1 && c.ParityShard == -1) && (c.DataShard < 1 || c.ParityShard < 1 || c.DataShard+c.ParityShard > 256) { ++ return c, fmt.Errorf("kcptun: invalid FEC shard counts") ++ } ++ if c.SndWnd < 1 || c.RcvWnd < 1 || c.SndWnd > 65535 || c.RcvWnd > 65535 { ++ return c, fmt.Errorf("kcptun: invalid windows") ++ } ++ if c.AutoExpire < 0 || c.ScavengeTTL < 0 || c.RateLimit < 0 || c.SockBuf < 1 || c.SockBuf > 1<<28 || c.DSCP < 0 || c.DSCP > 63 { ++ return c, fmt.Errorf("kcptun: invalid socket or expiry settings") ++ } ++ if c.NoDelay < 0 || c.NoDelay > 1 || c.NoCongestion < 0 || c.NoCongestion > 1 || c.Interval < 1 || c.Interval > 5000 || c.Resend < 0 || c.Resend > 2 { ++ return c, fmt.Errorf("kcptun: invalid manual timing") ++ } ++ if c.KeepAlive < 1 || c.KeepAlive > 3600 || c.SmuxBuf > 1<<28 { ++ return c, fmt.Errorf("kcptun: invalid smux limits") ++ } ++ if err := smux.VerifyConfig(c.SmuxConfig()); err != nil { ++ return c, fmt.Errorf("kcptun: %w", err) ++ } ++ return c, nil ++} ++func (c *Config) SmuxConfig() *smux.Config { ++ cfg := smux.DefaultConfig() ++ cfg.Version = c.SmuxVer ++ cfg.MaxReceiveBuffer = c.SmuxBuf ++ cfg.MaxStreamBuffer = c.StreamBuf ++ cfg.MaxFrameSize = c.FrameSize ++ cfg.KeepAliveInterval = time.Duration(c.KeepAlive) * time.Second ++ if cfg.KeepAliveInterval >= cfg.KeepAliveTimeout { ++ cfg.KeepAliveTimeout = 3 * cfg.KeepAliveInterval ++ } ++ return cfg ++} ++func (c *Config) Apply(conn *kcp.UDPSession) { ++ conn.SetStreamMode(true) ++ conn.SetWriteDelay(false) ++ conn.SetNoDelay(c.NoDelay, c.Interval, c.Resend, c.NoCongestion) ++ conn.SetWindowSize(c.SndWnd, c.RcvWnd) ++ conn.SetMtu(c.MTU) ++ conn.SetACKNoDelay(c.AckNodelay) ++ conn.SetRateLimit(uint32(c.RateLimit)) ++} +--- a/transport/internet/kcptun/stream.go ++++ b/transport/internet/kcptun/stream.go +@@ -0,0 +1,15 @@ ++package kcptun ++ ++import "net" ++ ++// Kcptun carries a reliable stream; Xray's stream protocols expect TCP addresses. ++type streamConn struct{ net.Conn } ++ ++func tcpAddr(addr net.Addr) net.Addr { ++ if udp, ok := addr.(*net.UDPAddr); ok { ++ return &net.TCPAddr{IP: udp.IP, Port: udp.Port, Zone: udp.Zone} ++ } ++ return addr ++} ++func (c *streamConn) LocalAddr() net.Addr { return tcpAddr(c.Conn.LocalAddr()) } ++func (c *streamConn) RemoteAddr() net.Addr { return tcpAddr(c.Conn.RemoteAddr()) } +--- a/transport/internet/memory_settings.go ++++ b/transport/internet/memory_settings.go +@@ -12,6 +12,7 @@ + + // MemoryStreamConfig is a parsed form of StreamConfig. It is used to reduce the number of Protobuf parses. + type MemoryStreamConfig struct { ++ clients transportClients + Destination *net.Destination + ProtocolName string + ProtocolSettings interface{} +--- a/transport/internet/transport_clients.go ++++ b/transport/internet/transport_clients.go +@@ -0,0 +1,57 @@ ++package internet ++ ++import ( ++ "errors" ++ "io" ++ "sync" ++) ++ ++// transportClients owns pooled transport connections for a single outbound. ++// Keeping ownership here prevents sharing credentials or sockets across outbounds. ++type transportClients struct { ++ mu sync.Mutex ++ closed bool ++ values map[string]io.Closer ++} ++ ++func (s *MemoryStreamConfig) TransportClient(key string, create func() (io.Closer, error)) (io.Closer, error) { ++ s.clients.mu.Lock() ++ defer s.clients.mu.Unlock() ++ if s.clients.closed { ++ return nil, io.ErrClosedPipe ++ } ++ if value := s.clients.values[key]; value != nil { ++ return value, nil ++ } ++ if len(s.clients.values) >= 256 { ++ return nil, errors.New("too many transport client destinations") ++ } ++ value, err := create() ++ if err != nil { ++ return nil, err ++ } ++ if s.clients.values == nil { ++ s.clients.values = make(map[string]io.Closer) ++ } ++ s.clients.values[key] = value ++ return value, nil ++} ++ ++func (s *MemoryStreamConfig) Close() error { ++ if s == nil { ++ return nil ++ } ++ s.clients.mu.Lock() ++ s.clients.closed = true ++ values := s.clients.values ++ s.clients.values = nil ++ s.clients.mu.Unlock() ++ var result error ++ for _, value := range values { ++ result = errors.Join(result, value.Close()) ++ } ++ if s.DownloadSettings != nil { ++ result = errors.Join(result, s.DownloadSettings.Close()) ++ } ++ return result ++} diff --git a/xray-core/patches/190-an7581-eip93-aes-gcm.patch b/xray-core/patches/190-an7581-eip93-aes-gcm.patch new file mode 100644 index 000000000..0fcff739a --- /dev/null +++ b/xray-core/patches/190-an7581-eip93-aes-gcm.patch @@ -0,0 +1,515 @@ +From: coolsnowwolf +Subject: [PATCH] build: add AN7581-only EIP93 hybrid AES-GCM overlay + +Keep the shared Go toolchain unchanged. Select the AF_ALG backend only on +AN7581 without ARM AES instructions, with per-key verification and software +fallback. AN7583 remains on the normal path pending separate measurements. + +Signed-off-by: coolsnowwolf + +--- a/tools/eip93/eip93_linux_arm64.go ++++ b/tools/eip93/eip93_linux_arm64.go +@@ -0,0 +1,294 @@ ++// Copyright 2026 coolsnowwolf ++// SPDX-License-Identifier: BSD-3-Clause ++//go:build linux && arm64 && !purego ++ ++package gcm ++ ++import ( ++ "bytes" ++ "crypto/internal/fips140" ++ "crypto/internal/fips140/aes" ++ "crypto/internal/fips140deps/cpu" ++ "encoding/binary" ++ "os" ++ "runtime" ++ "sync" ++ "sync/atomic" ++ "syscall" ++ "unsafe" ++) ++ ++const eip93SOL = 279 ++const eip93Min = 4096 ++const eip93Max = 32768 ++const eip93MaxSockets = 128 ++ ++var eip93Detected sync.Once ++var eip93Allowed bool ++var eip93Sockets atomic.Int32 ++var eip93Notice sync.Once ++ ++func eip93Compatible(data []byte) bool { ++ if len(data) == 0 || data[len(data)-1] != 0 { ++ return false ++ } ++ found := false ++ for _, s := range bytes.Split(data, []byte{0}) { ++ switch string(s) { ++ case "airoha,an7583", "airoha,en7583": ++ return false ++ case "airoha,an7581", "airoha,en7581": ++ found = true ++ } ++ } ++ return found ++} ++ ++func eip93Enabled() bool { ++ eip93Detected.Do(func() { ++ if fips140.Enabled || cpu.ARM64HasAES || os.Getenv("XRAY_EIP93") == "off" { ++ return ++ } ++ for _, path := range []string{"/sys/firmware/devicetree/base/compatible", "/proc/device-tree/compatible"} { ++ b, err := os.ReadFile(path) ++ if err == nil { ++ eip93Allowed = eip93Compatible(b) ++ return ++ } ++ } ++ }) ++ return eip93Allowed ++} ++ ++type eip93State struct { ++ mu sync.Mutex ++ key [32]byte ++ keyLen int ++ fd int ++ disabled bool ++ input, output []byte ++} ++ ++func newEIP93State(g *GCM) *eip93State { ++ if g.nonceSize != 12 || g.tagSize != 16 || !eip93Enabled() { ++ return nil ++ } ++ // Only the generic ARM64 schedule is used: its first Nk big-endian words ++ // are the original AES key. Never interpret an instruction-specific schedule. ++ schedule := aes.EncryptionKeySchedule(&g.cipher) ++ keyLen := (len(schedule)/4 - 7) * 4 ++ if keyLen != 16 && keyLen != 24 && keyLen != 32 { ++ return nil ++ } ++ s := &eip93State{keyLen: keyLen, fd: -1} ++ for i := 0; i < keyLen/4; i++ { ++ binary.BigEndian.PutUint32(s.key[i*4:], schedule[i]) ++ } ++ runtime.SetFinalizer(s, func(s *eip93State) { s.close(); clear(s.key[:]) }) ++ return s ++} ++ ++func (s *eip93State) close() { ++ if s.fd >= 0 { ++ syscall.Close(s.fd) ++ s.fd = -1 ++ eip93Sockets.Add(-1) ++ } ++ clear(s.input) ++ clear(s.output) ++} ++ ++func eip93Cmsg(kind int32, data []byte) []byte { ++ b := make([]byte, syscall.CmsgSpace(len(data))) ++ h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0])) ++ h.Level = eip93SOL ++ h.Type = kind ++ h.SetLen(syscall.CmsgLen(len(data))) ++ copy(b[syscall.CmsgLen(0):], data) ++ return b ++} ++func eip93U32(v uint32) []byte { b := make([]byte, 4); binary.LittleEndian.PutUint32(b, v); return b } ++ ++// Linux 5.4 uses the CryptoAPI GHASH driver; Linux 7.3 uses the library. ++func eip93Bind(bind func(string) syscall.Errno) syscall.Errno { ++ var errno syscall.Errno ++ for _, name := range []string{ ++ "gcm_base(ctr(aes-eip93),ghash-generic)", ++ "gcm_base(ctr(aes-eip93),ghash-lib)", ++ } { ++ errno = bind(name) ++ if errno == 0 { ++ return 0 ++ } ++ } ++ return errno ++} ++ ++func (s *eip93State) connect() error { ++ if eip93Sockets.Add(1) > eip93MaxSockets { ++ eip93Sockets.Add(-1) ++ return syscall.EMFILE ++ } ++ success := false ++ defer func() { ++ if !success { ++ eip93Sockets.Add(-1) ++ } ++ }() ++ fd, err := syscall.Socket(38, syscall.SOCK_SEQPACKET|syscall.SOCK_CLOEXEC, 0) ++ if err != nil { ++ return err ++ } ++ defer syscall.Close(fd) ++ var sa [88]byte ++ binary.LittleEndian.PutUint16(sa[:], 38) ++ copy(sa[2:16], "aead") ++ // Try both exact EIP93 compositions even when a kernel policy or driver ++ // rejects the first one. Never substitute a generic software CTR driver. ++ errno := eip93Bind(func(name string) syscall.Errno { ++ clear(sa[24:]) ++ copy(sa[24:], name) ++ _, _, errno := syscall.Syscall(syscall.SYS_BIND, uintptr(fd), uintptr(unsafe.Pointer(&sa[0])), uintptr(len(sa))) ++ return errno ++ }) ++ if errno != 0 { ++ return errno ++ } ++ _, _, errno = syscall.Syscall6(syscall.SYS_SETSOCKOPT, uintptr(fd), eip93SOL, 1, uintptr(unsafe.Pointer(&s.key[0])), uintptr(s.keyLen), 0) ++ runtime.KeepAlive(s) ++ if errno != 0 { ++ return errno ++ } ++ _, _, errno = syscall.Syscall6(syscall.SYS_SETSOCKOPT, uintptr(fd), eip93SOL, 5, 0, 16, 0) ++ if errno != 0 { ++ return errno ++ } ++ op, _, errno := syscall.Syscall6(syscall.SYS_ACCEPT4, uintptr(fd), 0, 0, syscall.SOCK_CLOEXEC, 0, 0) ++ if errno != 0 { ++ return errno ++ } ++ for _, option := range []int{syscall.SO_RCVTIMEO, syscall.SO_SNDTIMEO} { ++ if err = syscall.SetsockoptTimeval(int(op), syscall.SOL_SOCKET, option, &syscall.Timeval{Sec: 1}); err != nil { ++ syscall.Close(int(op)) ++ return err ++ } ++ } ++ s.fd = int(op) ++ success = true ++ clear(s.key[:]) ++ return nil ++} ++ ++// exchange never exposes plaintext until the kernel has authenticated it. ++// It also never modifies the caller's buffers before a complete operation, ++// so a syscall failure can safely fall back to the original implementation. ++func (s *eip93State) exchange(nonce, src, aad []byte, decrypt bool) ([]byte, error) { ++ inLen := len(aad) + len(src) ++ outLen := inLen + 16 ++ if decrypt { ++ outLen = inLen - 16 ++ } ++ if cap(s.input) < inLen { ++ s.input = make([]byte, inLen) ++ } else { ++ s.input = s.input[:inLen] ++ } ++ if cap(s.output) < outLen { ++ s.output = make([]byte, outLen) ++ } else { ++ s.output = s.output[:outLen] ++ } ++ copy(s.input, aad) ++ copy(s.input[len(aad):], src) ++ op := uint32(1) ++ if decrypt { ++ op = 0 ++ } ++ ctl := eip93Cmsg(3, eip93U32(op)) ++ iv := append(eip93U32(12), nonce...) ++ ctl = append(ctl, eip93Cmsg(2, iv)...) ++ ctl = append(ctl, eip93Cmsg(4, eip93U32(uint32(len(aad))))...) ++ n, err := syscall.SendmsgN(s.fd, s.input, ctl, nil, 0) ++ if err != nil { ++ return nil, err ++ } ++ if n != inLen { ++ return nil, syscall.EIO ++ } ++ n, err = syscall.Read(s.fd, s.output) ++ if err != nil { ++ return nil, err ++ } ++ if n != outLen { ++ return nil, syscall.EIO ++ } ++ return s.output[len(aad):], nil ++} ++ ++func (s *eip93State) selftest(g *GCM) bool { ++ var nonce [12]byte ++ n, _, errno := syscall.Syscall(syscall.SYS_GETRANDOM, uintptr(unsafe.Pointer(&nonce[0])), uintptr(len(nonce)), 0) ++ if errno != 0 || n != uintptr(len(nonce)) { ++ return false ++ } ++ aad := []byte("EIP93 selftest") ++ plain := make([]byte, 257) ++ for i := range plain { ++ plain[i] = byte(i * 7) ++ } ++ expected := make([]byte, len(plain)+16) ++ sealGeneric(expected, g, nonce[:], plain, aad) ++ got, err := s.exchange(nonce[:], plain, aad, false) ++ if err != nil || !bytes.Equal(got, expected) { ++ return false ++ } ++ got, err = s.exchange(nonce[:], expected, aad, true) ++ if err != nil || !bytes.Equal(got, plain) { ++ return false ++ } ++ expected[len(expected)-1] ^= 1 ++ _, err = s.exchange(nonce[:], expected, aad, true) ++ return err == syscall.EBADMSG ++} ++ ++// Return handled=false for unsupported sizes and operational failures. ++// Authentication failures are handled=true, valid=false, never successes. ++func (s *eip93State) crypt(g *GCM, out, nonce, src, aad []byte, decrypt bool) (handled, valid bool) { ++ n := len(src) ++ if decrypt { ++ n -= 16 ++ } ++ if n < eip93Min || n > eip93Max || len(aad) > 256 || len(nonce) != 12 { ++ return false, false ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ defer runtime.KeepAlive(s) ++ if s.disabled { ++ return false, false ++ } ++ if s.fd < 0 { ++ if s.connect() != nil || !s.selftest(g) { ++ s.close() ++ clear(s.key[:]) ++ s.disabled = true ++ return false, false ++ } ++ eip93Notice.Do(func() { println("Xray EIP93: AN7581 AES-GCM hybrid backend enabled (AF_ALG CTR + CPU GHASH)") }) ++ } ++ result, err := s.exchange(nonce, src, aad, decrypt) ++ if decrypt && err == syscall.EBADMSG { ++ clear(s.input) ++ clear(s.output) ++ return true, false ++ } ++ if err != nil { ++ s.close() ++ s.disabled = true ++ return false, false ++ } ++ copy(out, result) ++ clear(s.input) ++ clear(s.output) ++ return true, true ++} +--- a/tools/eip93/eip93_linux_arm64_test.go ++++ b/tools/eip93/eip93_linux_arm64_test.go +@@ -0,0 +1,153 @@ ++// Copyright 2026 coolsnowwolf ++// SPDX-License-Identifier: BSD-3-Clause ++//go:build linux && arm64 && !purego ++ ++package gcm ++ ++import ( ++ "bytes" ++ "crypto/internal/fips140/aes" ++ "sync" ++ "syscall" ++ "testing" ++) ++ ++func TestEIP93DTGate(t *testing.T) { ++ for _, tc := range []struct { ++ dt string ++ want bool ++ }{ ++ {"nokia,xg-040g-md-ubi\x00airoha,an7581\x00", true}, ++ {"airoha,en7581\x00", true}, {"airoha,an7583\x00", false}, ++ {"airoha,an7581\x00airoha,en7583\x00", false}, ++ {"airoha,an7581", false}, {"", false}, {"vendor,an7581\x00", false}, {"airoha,an75810\x00", false}, ++ } { ++ if eip93Compatible([]byte(tc.dt)) != tc.want { ++ t.Fatalf("gate %q", tc.dt) ++ } ++ } ++} ++ ++func TestEIP93Hardware(t *testing.T) { ++ if !eip93Enabled() { ++ t.Skip("requires AN7581 with EIP93 backend enabled") ++ } ++ for _, keyLen := range []int{16, 24, 32} { ++ key := make([]byte, keyLen) ++ for i := range key { ++ key[i] = byte(i + 1) ++ } ++ block, err := aes.New(key) ++ if err != nil { ++ t.Fatal(err) ++ } ++ g, err := New(block, 12, 16) ++ if err != nil { ++ t.Fatal(err) ++ } ++ if g.eip93 == nil { ++ t.Fatal("backend not selected") ++ } ++ for _, n := range []int{0, 1, 15, 16, 17, 4095, 4096, 4097, 16384, 16385, 32768, 32769} { ++ plain := make([]byte, n) ++ for i := range plain { ++ plain[i] = byte(i * 7) ++ } ++ nonce := make([]byte, 12) ++ nonce[0] = byte(n) ++ nonce[1] = byte(n >> 8) ++ aad := []byte("authentication data") ++ want := make([]byte, n+16) ++ sealGeneric(want, g, nonce, plain, aad) ++ got := g.Seal(nil, nonce, plain, aad) ++ if !bytes.Equal(got, want) { ++ t.Fatalf("seal key=%d n=%d", keyLen, n) ++ } ++ buf := append([]byte(nil), got...) ++ out, err := g.Open(buf[:0], nonce, buf, aad) ++ if err != nil || !bytes.Equal(out, plain) { ++ t.Fatalf("inplace open key=%d n=%d err=%v", keyLen, n, err) ++ } ++ buf = make([]byte, n, n+16) ++ copy(buf, plain) ++ got = g.Seal(buf[:0], nonce, buf, aad) ++ if !bytes.Equal(got, want) { ++ t.Fatalf("inplace seal key=%d n=%d", keyLen, n) ++ } ++ got[len(got)-1] ^= 1 ++ out, err = g.Open(nil, nonce, got, aad) ++ if err == nil || out != nil { ++ t.Fatal("corrupt tag accepted") ++ } ++ } ++ if g.eip93.fd < 0 || g.eip93.disabled { ++ t.Fatal("hardware failed or fell back") ++ } ++ var wg sync.WaitGroup ++ for worker := 0; worker < 8; worker++ { ++ wg.Add(1) ++ go func(id int) { ++ defer wg.Done() ++ for i := 0; i < 20; i++ { ++ nonce := make([]byte, 12) ++ nonce[0] = byte(id + 64) ++ nonce[1] = byte(i) ++ plain := bytes.Repeat([]byte{byte(id)}, 8192) ++ got := g.Seal(nil, nonce, plain, nil) ++ out, err := g.Open(nil, nonce, got, nil) ++ if err != nil || !bytes.Equal(out, plain) { ++ t.Error("concurrent roundtrip") ++ } ++ } ++ }(worker) ++ } ++ wg.Wait() ++ // A closed operation fd must cause a safe software fallback, without ++ // returning partial plaintext or leaking the global socket reservation. ++ syscall.Close(g.eip93.fd) ++ plain := make([]byte, 8192) ++ nonce := make([]byte, 12) ++ want := make([]byte, len(plain)+16) ++ sealGeneric(want, g, nonce, plain, nil) ++ if got := g.Seal(nil, nonce, plain, nil); !bytes.Equal(got, want) || !g.eip93.disabled { ++ t.Fatal("closed-fd fallback") ++ } ++ for _, nt := range [][2]int{{8, 16}, {12, 12}} { ++ other, err := New(block, nt[0], nt[1]) ++ if err != nil || other.eip93 != nil { ++ t.Fatal("nonstandard GCM must use software") ++ } ++ } ++ } ++ if n := eip93Sockets.Load(); n != 0 { ++ t.Fatalf("socket reservations leaked: %d", n) ++ } ++} ++ ++func TestEIP93BindFallback(t *testing.T) { ++ for _, first := range []syscall.Errno{0, syscall.ENOENT, syscall.EINVAL, syscall.ENODEV, syscall.EACCES, syscall.EPERM} { ++ for _, second := range []syscall.Errno{0, syscall.ENOENT} { ++ calls := 0 ++ err := eip93Bind(func(name string) syscall.Errno { ++ calls++ ++ if calls == 1 { ++ if name != "gcm_base(ctr(aes-eip93),ghash-generic)" { ++ t.Fatal(name) ++ } ++ return first ++ } ++ if name != "gcm_base(ctr(aes-eip93),ghash-lib)" { ++ t.Fatal(name) ++ } ++ return second ++ }) ++ want, count := second, 2 ++ if first == 0 { ++ want, count = 0, 1 ++ } ++ if err != want || calls != count { ++ t.Fatalf("first=%v second=%v: err=%v calls=%d", first, second, err, calls) ++ } ++ } ++ } ++} +--- a/tools/eip93/eip93_stub.go ++++ b/tools/eip93/eip93_stub.go +@@ -0,0 +1,12 @@ ++// Copyright 2026 coolsnowwolf ++// SPDX-License-Identifier: BSD-3-Clause ++//go:build !linux || !arm64 || purego ++ ++package gcm ++ ++type eip93State struct{} ++ ++func newEIP93State(g *GCM) *eip93State { return nil } ++func (*eip93State) crypt(g *GCM, out, nonce, src, aad []byte, decrypt bool) (bool, bool) { ++ return false, false ++} +--- a/tools/eip93/overlay.py ++++ b/tools/eip93/overlay.py +@@ -0,0 +1,35 @@ ++#!/usr/bin/env python3 ++"""Generate an Xray-only Go overlay; never modify the shared Go toolchain.""" ++import argparse ++import json ++from pathlib import Path ++import subprocess ++ ++parser=argparse.ArgumentParser() ++parser.add_argument('--go',required=True) ++parser.add_argument('--out',required=True) ++args=parser.parse_args() ++root=Path(subprocess.check_output([args.go,'env','GOROOT'],text=True).strip()).resolve() ++source=root/'src/crypto/internal/fips140/aes/gcm' ++out=Path(args.out).resolve();out.mkdir(parents=True,exist_ok=True) ++replacements={} ++def change(name,edits): ++ text=(source/name).read_text() ++ for old,new in edits: ++ if text.count(old)!=1: ++ raise SystemExit('EIP93 overlay: unsupported Go source in '+name+'; review before enabling acceleration') ++ text=text.replace(old,new,1) ++ dst=out/name;dst.write_text(text) ++ replacements[str(source/name)]=str(dst) ++change('gcm.go',[('\tgcmPlatformData\n','\tgcmPlatformData\n\teip93 *eip93State\n')]) ++change('gcm_asm.go',[ ++ ('func initGCM(g *GCM) {\n\tif !supportsAESGCM {\n\t\treturn', ++ 'func initGCM(g *GCM) {\n\tif !supportsAESGCM {\n\t\tg.eip93 = newEIP93State(g)\n\t\treturn'), ++ ('\t\tsealGeneric(out, g, nonce, plaintext, data)', ++ '\t\tif g.eip93 != nil {\n\t\t\tif handled, _ := g.eip93.crypt(g, out, nonce, plaintext, data, false); handled { return }\n\t\t}\n\t\tsealGeneric(out, g, nonce, plaintext, data)'), ++ ('\t\treturn openGeneric(out, g, nonce, ciphertext, data)', ++ '\t\tif g.eip93 != nil {\n\t\t\tif handled, valid := g.eip93.crypt(g, out, nonce, ciphertext, data, true); handled {\n\t\t\t\tif !valid { return errOpen }; return nil\n\t\t\t}\n\t\t}\n\t\treturn openGeneric(out, g, nonce, ciphertext, data)')]) ++for name in ['eip93_linux_arm64.go','eip93_stub.go','eip93_linux_arm64_test.go']: ++ dst=out/name;dst.write_text((Path(__file__).parent/name).read_text()) ++ replacements[str(source/name)]=str(dst) ++(out/'overlay.json').write_text(json.dumps({'Replace':replacements},indent=2)+'\n')