mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-14 12:38:03 +08:00
🌈 Sync 2026-11-14 02:21:00
This commit is contained in:
1 parent
f1cab8a933
commit
24333bdf44
22 files changed
+18341
-65
No files matched your search
@@ -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
|
||||
|
||||
|
||||
+27
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
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
|
||||
@@ -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)?
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2026 Amr Fateem <amr.fateem@yahoo.com>
|
||||
|
||||
case "$1" in
|
||||
v2raya-core)
|
||||
/usr/bin/v2raya_core version | grep -F "$2"
|
||||
;;
|
||||
*)
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
+13
-8
@@ -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))
|
||||
@@ -2,6 +2,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021 Tianling Shen <cnsztl@immortalwrt.org>
|
||||
# Copyright (C) 2021 Tianling Shen <cnsztl@immortalwrt.org>
|
||||
# Copyright (C) 2026 Amr Fateem <amr.fateem@yahoo.com>
|
||||
#
|
||||
# procd init script for v2rayA.
|
||||
|
||||
Binary file not shown.
@@ -1,13 +0,0 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2026 Amr Fateem <amr.fateem@yahoo.com>
|
||||
|
||||
case "$1" in
|
||||
v2raya)
|
||||
/usr/bin/v2raya --version | grep -F "$2"
|
||||
;;
|
||||
*)
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
+83
-23
@@ -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 <cnsztl@immortalwrt.org>
|
||||
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))
|
||||
@@ -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/`.
|
||||
@@ -0,0 +1,154 @@
|
||||
From: coolsnowwolf <coolsnowwolf@gmail.com>
|
||||
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 <coolsnowwolf@gmail.com>
|
||||
|
||||
--- 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,
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,669 @@
|
||||
From: coolsnowwolf <coolsnowwolf@gmail.com>
|
||||
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 <coolsnowwolf@gmail.com>
|
||||
|
||||
--- 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)
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,515 @@
|
||||
From: coolsnowwolf <coolsnowwolf@gmail.com>
|
||||
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 <coolsnowwolf@gmail.com>
|
||||
|
||||
--- a/tools/eip93/eip93_linux_arm64.go
|
||||
+++ b/tools/eip93/eip93_linux_arm64.go
|
||||
@@ -0,0 +1,294 @@
|
||||
+// Copyright 2026 coolsnowwolf <coolsnowwolf@gmail.com>
|
||||
+// 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 <coolsnowwolf@gmail.com>
|
||||
+// 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 <coolsnowwolf@gmail.com>
|
||||
+// 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')
|
||||
Reference in new issue
Block a user