Update On Mon Apr 7 20:36:19 CEST 2025

This commit is contained in:
github-action[bot]
2025-04-07 20:36:19 +02:00
parent abfa3f6b2c
commit cc70b50b0e
48 changed files with 562 additions and 245 deletions

1
.github/update.log vendored
View File

@@ -965,3 +965,4 @@ Update On Thu Apr 3 20:36:49 CEST 2025
Update On Fri Apr 4 20:36:13 CEST 2025 Update On Fri Apr 4 20:36:13 CEST 2025
Update On Sat Apr 5 20:32:49 CEST 2025 Update On Sat Apr 5 20:32:49 CEST 2025
Update On Sun Apr 6 20:34:52 CEST 2025 Update On Sun Apr 6 20:34:52 CEST 2025
Update On Mon Apr 7 20:36:09 CEST 2025

View File

@@ -2980,9 +2980,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.0" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide", "miniz_oxide",

View File

@@ -53,7 +53,7 @@
"@csstools/normalize.css": "12.1.1", "@csstools/normalize.css": "12.1.1",
"@emotion/babel-plugin": "11.13.5", "@emotion/babel-plugin": "11.13.5",
"@emotion/react": "11.14.0", "@emotion/react": "11.14.0",
"@iconify/json": "2.2.323", "@iconify/json": "2.2.324",
"@monaco-editor/react": "4.7.0", "@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.71.10", "@tanstack/react-query": "5.71.10",
"@tanstack/react-router": "1.114.34", "@tanstack/react-router": "1.114.34",

View File

@@ -2,7 +2,7 @@
"manifest_version": 1, "manifest_version": 1,
"latest": { "latest": {
"mihomo": "v1.19.4", "mihomo": "v1.19.4",
"mihomo_alpha": "alpha-2a08c44", "mihomo_alpha": "alpha-9e8f4ad",
"clash_rs": "v0.7.6", "clash_rs": "v0.7.6",
"clash_premium": "2023-09-05-gdcc8d87", "clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.7.6-alpha+sha.5af4aa5" "clash_rs_alpha": "0.7.6-alpha+sha.5af4aa5"
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
} }
}, },
"updated_at": "2025-04-05T22:20:47.286Z" "updated_at": "2025-04-06T22:20:38.947Z"
} }

View File

@@ -333,8 +333,8 @@ importers:
specifier: 11.14.0 specifier: 11.14.0
version: 11.14.0(@types/react@19.0.12)(react@19.1.0) version: 11.14.0(@types/react@19.0.12)(react@19.1.0)
'@iconify/json': '@iconify/json':
specifier: 2.2.323 specifier: 2.2.324
version: 2.2.323 version: 2.2.324
'@monaco-editor/react': '@monaco-editor/react':
specifier: 4.7.0 specifier: 4.7.0
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -1680,8 +1680,8 @@ packages:
'@vue/compiler-sfc': '@vue/compiler-sfc':
optional: true optional: true
'@iconify/json@2.2.323': '@iconify/json@2.2.324':
resolution: {integrity: sha512-PtRN4hK9OkT2nlEa76A5QT54E6/SOukceKQkOZv9mk44UOlaS/9fhJFNUEA+FBAXEPcnnCQb2nVui+IAn7xTSw==} resolution: {integrity: sha512-7rx2pY2NH4zn/7q04zFiiD3o7eQ8ZV0F0nf7Rkn2DyI272OWzDMw5goSULOyDdiW9sdfBLeZod/TRxEilaNNsA==}
'@iconify/types@2.0.0': '@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -9551,7 +9551,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@iconify/json@2.2.323': '@iconify/json@2.2.324':
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
pathe: 1.1.2 pathe: 1.1.2
@@ -9679,7 +9679,7 @@ snapshots:
'@babel/runtime': 7.26.10 '@babel/runtime': 7.26.10
'@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@mui/types': 7.2.24(@types/react@19.0.12) '@mui/types': 7.2.24(@types/react@19.0.12)
'@mui/utils': 6.4.8(@types/react@19.0.12)(react@19.1.0) '@mui/utils': 6.4.9(@types/react@19.0.12)(react@19.1.0)
'@popperjs/core': 2.11.8 '@popperjs/core': 2.11.8
clsx: 2.1.1 clsx: 2.1.1
prop-types: 15.8.1 prop-types: 15.8.1

View File

@@ -3,13 +3,9 @@ import { getClashInfo } from "./cmds";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
let axiosIns: AxiosInstance = null!; let instancePromise: Promise<AxiosInstance> = null!;
/// initialize some information
/// enable force update axiosIns
export const getAxios = async (force: boolean = false) => {
if (axiosIns && !force) return axiosIns;
async function getInstancePromise() {
let server = ""; let server = "";
let secret = ""; let secret = "";
@@ -26,13 +22,22 @@ export const getAxios = async (force: boolean = false) => {
if (info?.secret) secret = info?.secret; if (info?.secret) secret = info?.secret;
} catch {} } catch {}
axiosIns = axios.create({ const axiosIns = axios.create({
baseURL: `http://${server}`, baseURL: `http://${server}`,
headers: secret ? { Authorization: `Bearer ${secret}` } : {}, headers: secret ? { Authorization: `Bearer ${secret}` } : {},
timeout: 15000, timeout: 15000,
}); });
axiosIns.interceptors.response.use((r) => r.data); axiosIns.interceptors.response.use((r) => r.data);
return axiosIns; return axiosIns;
}
/// initialize some information
/// enable force update axiosIns
export const getAxios = async (force: boolean = false) => {
if (!instancePromise || force) {
instancePromise = getInstancePromise();
}
return instancePromise;
}; };
/// Get Version /// Get Version

View File

@@ -1,4 +1,4 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT) // SPDX-License-Identifier: GPL-2.0-or-later OR MIT
/dts-v1/; /dts-v1/;
@@ -43,38 +43,21 @@
}; };
}; };
leds { gpio-leds {
compatible = "gpio-leds"; compatible = "gpio-leds";
led_sys_red: led-0 { led_sys_red: led-0 {
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_RED>; color = <LED_COLOR_ID_RED>;
function = LED_FUNCTION_STATUS;
gpios = <&pio 11 GPIO_ACTIVE_LOW>; gpios = <&pio 11 GPIO_ACTIVE_LOW>;
}; };
led_sys_green: led-1 { led_sys_green: led-1 {
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_GREEN>; color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_STATUS;
gpios = <&pio 10 GPIO_ACTIVE_LOW>; gpios = <&pio 10 GPIO_ACTIVE_LOW>;
}; };
}; };
usb_vbus: regulator-usb {
compatible = "regulator-fixed";
regulator-name = "usb-vbus";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
gpios = <&pio 9 GPIO_ACTIVE_LOW>;
regulator-boot-on;
};
};
&uart0 {
status = "okay";
};
&watchdog {
status = "okay";
}; };
&eth { &eth {
@@ -87,6 +70,9 @@
reg = <0>; reg = <0>;
phy-mode = "2500base-x"; phy-mode = "2500base-x";
phy-handle = <&phy1>; phy-handle = <&phy1>;
nvmem-cells = <&macaddr_factory_4 2>;
nvmem-cell-names = "mac-address";
}; };
gmac1: mac@1 { gmac1: mac@1 {
@@ -94,11 +80,14 @@
reg = <1>; reg = <1>;
phy-mode = "gmii"; phy-mode = "gmii";
phy-handle = <&int_gbe_phy>; phy-handle = <&int_gbe_phy>;
nvmem-cells = <&macaddr_factory_4 3>;
nvmem-cell-names = "mac-address";
}; };
}; };
&mdio_bus { &mdio_bus {
phy1: phy@1 { phy1: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c45"; compatible = "ethernet-phy-ieee802.3-c45";
reg = <1>; reg = <1>;
reset-assert-us = <100000>; reset-assert-us = <100000>;
@@ -110,6 +99,49 @@
}; };
}; };
&mmc0 {
bus-width = <8>;
cap-mmc-highspeed;
max-frequency = <52000000>;
non-removable;
pinctrl-names = "default", "state_uhs";
pinctrl-0 = <&mmc0_pins_default>;
pinctrl-1 = <&mmc0_pins_uhs>;
vmmc-supply = <&reg_3p3v>;
status = "okay";
card@0 {
compatible = "mmc-card";
reg = <0>;
block {
compatible = "block-device";
partitions {
block-partition-factory {
partname = "factory";
nvmem-layout {
compatible = "fixed-layout";
#address-cells = <1>;
#size-cells = <1>;
eeprom_factory_0: eeprom@0 {
reg = <0x0 0x1000>;
};
macaddr_factory_4: macaddr@4 {
compatible = "mac-base";
reg = <0x4 0x6>;
#nvmem-cell-cells = <1>;
};
};
};
};
};
};
};
&pio { &pio {
mmc0_pins_default: mmc0-pins-default { mmc0_pins_default: mmc0-pins-default {
mux { mux {
@@ -125,27 +157,24 @@
}; };
}; };
&uart0 {
status = "okay";
};
&usb_phy { &usb_phy {
status = "okay"; status = "okay";
}; };
&watchdog {
status = "okay";
};
&wifi {
nvmem-cells = <&eeprom_factory_0>;
nvmem-cell-names = "eeprom";
status = "okay";
};
&xhci { &xhci {
status = "okay"; status = "okay";
vbus-supply = <&usb_vbus>;
};
&wifi {
status = "okay";
};
&mmc0 {
pinctrl-names = "default", "state_uhs";
pinctrl-0 = <&mmc0_pins_default>;
pinctrl-1 = <&mmc0_pins_uhs>;
bus-width = <8>;
cap-mmc-highspeed;
max-frequency = <52000000>;
vmmc-supply = <&reg_3p3v>;
non-removable;
status = "okay";
}; };

View File

@@ -128,11 +128,6 @@ mediatek_setup_macs()
lan_mac=$(macaddr_add "$wan_mac" 1) lan_mac=$(macaddr_add "$wan_mac" 1)
label_mac=$wan_mac label_mac=$wan_mac
;; ;;
huasifei,wh3000-emmc)
label_mac=$(mmc_get_mac_binary factory 0x4)
lan_mac="$(macaddr_add $label_mac 2)"
wan_mac="$(macaddr_add $label_mac 3)"
;;
imou,lc-hx3001) imou,lc-hx3001)
lan_mac=$(mtd_get_mac_ascii u-boot-env mac) lan_mac=$(mtd_get_mac_ascii u-boot-env mac)
wan_mac=$(macaddr_add "$lan_mac" 2) wan_mac=$(macaddr_add "$lan_mac" 2)

View File

@@ -351,7 +351,7 @@ define Device/cmcc_xr30-nand
DEVICE_VARIANT := (U-Boot mod) DEVICE_VARIANT := (U-Boot mod)
DEVICE_DTS := mt7981b-cmcc-xr30-nand DEVICE_DTS := mt7981b-cmcc-xr30-nand
DEVICE_DTS_DIR := ../dts DEVICE_DTS_DIR := ../dts
DEVICE_PACKAGES := kmod-mt7981-firmware mt7981-wo-firmware kmod-usb3 DEVICE_PACKAGES := kmod-mt7981-firmware mt7981-wo-firmware kmod-usb3
UBINIZE_OPTS := -E 5 UBINIZE_OPTS := -E 5
BLOCKSIZE := 128k BLOCKSIZE := 128k
PAGESIZE := 2048 PAGESIZE := 2048
@@ -499,6 +499,7 @@ define Device/huasifei_wh3000-emmc
KERNEL_INITRAMFS := kernel-bin | lzma | \ KERNEL_INITRAMFS := kernel-bin | lzma | \
fit lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb with-initrd | pad-to 64k fit lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb with-initrd | pad-to 64k
IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata
SUPPORTED_DEVICES += huasifei,wh3000
endef endef
TARGET_DEVICES += huasifei_wh3000-emmc TARGET_DEVICES += huasifei_wh3000-emmc
@@ -508,11 +509,11 @@ define Device/hf_m7986r1-emmc
DEVICE_DTS := mt7986a-hf-m7986r1-emmc DEVICE_DTS := mt7986a-hf-m7986r1-emmc
DEVICE_DTS_DIR := ../dts DEVICE_DTS_DIR := ../dts
DEVICE_PACKAGES := kmod-usb3 kmod-mt7921e kmod-usb-net-rndis kmod-usb-serial-option f2fsck mkf2fs DEVICE_PACKAGES := kmod-usb3 kmod-mt7921e kmod-usb-net-rndis kmod-usb-serial-option f2fsck mkf2fs
SUPPORTED_DEVICES += HF-M7986R1
KERNEL := kernel-bin | lzma | fit lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb KERNEL := kernel-bin | lzma | fit lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb
KERNEL_INITRAMFS := kernel-bin | lzma | \ KERNEL_INITRAMFS := kernel-bin | lzma | \
fit lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb with-initrd | pad-to 64k fit lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb with-initrd | pad-to 64k
IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata
SUPPORTED_DEVICES += HF-M7986R1
endef endef
TARGET_DEVICES += hf_m7986r1-emmc TARGET_DEVICES += hf_m7986r1-emmc
@@ -526,8 +527,8 @@ define Device/hf_m7986r1-nand
PAGESIZE := 2048 PAGESIZE := 2048
KERNEL_IN_UBI := 1 KERNEL_IN_UBI := 1
DEVICE_PACKAGES := kmod-usb3 kmod-mt7921e kmod-usb-net-rndis kmod-usb-serial-option DEVICE_PACKAGES := kmod-usb3 kmod-mt7921e kmod-usb-net-rndis kmod-usb-serial-option
SUPPORTED_DEVICES += HF-M7986R1
IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata
SUPPORTED_DEVICES += HF-M7986R1
endef endef
TARGET_DEVICES += hf_m7986r1-nand TARGET_DEVICES += hf_m7986r1-nand

View File

@@ -144,7 +144,7 @@ jobs:
- run: ./get-clang.sh - run: ./get-clang.sh
- run: ccache -z - run: ccache -z
- run: ./build.sh - run: ./build.sh
- run: ccache -s - run: ccache -s && ccache --evict-older-than 1d
- run: ../tests/basic.sh out/Release/naive - run: ../tests/basic.sh out/Release/naive
- name: Pack naiveproxy assets - name: Pack naiveproxy assets
run: | run: |
@@ -228,7 +228,7 @@ jobs:
- run: ./get-clang.sh - run: ./get-clang.sh
- run: ccache -z - run: ccache -z
- run: ./build.sh - run: ./build.sh
- run: ccache -s - run: ccache -s && ccache --evict-older-than 1d
- run: ./get-android-sys.sh - run: ./get-android-sys.sh
- run: ../tests/basic.sh out/Release/naive - run: ../tests/basic.sh out/Release/naive
- name: Gradle cache - name: Gradle cache
@@ -355,7 +355,7 @@ jobs:
- run: ./get-clang.sh - run: ./get-clang.sh
- run: ccache -z - run: ccache -z
- run: ./build.sh - run: ./build.sh
- run: ccache -s - run: ccache -s && ccache --evict-older-than 1d
- run: ../tests/basic.sh out/Release/naive - run: ../tests/basic.sh out/Release/naive
# No real or emulated environment is available to test this. # No real or emulated environment is available to test this.
if: ${{ matrix.arch != 'arm64' }} if: ${{ matrix.arch != 'arm64' }}
@@ -558,7 +558,7 @@ jobs:
- run: ./get-clang.sh - run: ./get-clang.sh
- run: ccache -z - run: ccache -z
- run: ./build.sh - run: ./build.sh
- run: ccache -s - run: ccache -s && ccache --evict-older-than 1d
- run: ../tests/basic.sh out/Release/naive - run: ../tests/basic.sh out/Release/naive
- name: Pack naiveproxy assets - name: Pack naiveproxy assets
run: | run: |

View File

@@ -368,7 +368,7 @@ declare_args() {
stack_scan_supported = stack_scan_supported =
current_cpu == "x64" || current_cpu == "x86" || current_cpu == "arm" || current_cpu == "x64" || current_cpu == "x86" || current_cpu == "arm" ||
current_cpu == "arm64" || current_cpu == "riscv64" || current_cpu == "loong64" current_cpu == "arm64" || current_cpu == "riscv64"
# We want to provide assertions that guard against inconsistent build # We want to provide assertions that guard against inconsistent build
# args, but there is no point in having them fire if we're not building # args, but there is no point in having them fire if we're not building

View File

@@ -521,9 +521,6 @@ if (is_clang_or_gcc) {
} else if (current_cpu == "riscv64") { } else if (current_cpu == "riscv64") {
assert(stack_scan_supported) assert(stack_scan_supported)
sources += [ "stack/asm/riscv64/push_registers_asm.cc" ] sources += [ "stack/asm/riscv64/push_registers_asm.cc" ]
} else if (current_cpu == "loong64") {
assert(stack_scan_supported)
sources += [ "stack/asm/loong64/push_registers_asm.cc" ]
} else { } else {
# To support a trampoline for another arch, please refer to v8/src/heap/base. # To support a trampoline for another arch, please refer to v8/src/heap/base.
assert(!stack_scan_supported) assert(!stack_scan_supported)

View File

@@ -1,49 +0,0 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Push all callee-saved registers to get them on the stack for conservative
// stack scanning.
//
// See asm/x64/push_registers_asm.cc for why the function is not generated
// using clang.
//
// Calling convention source:
// https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html
asm(".global PAPushAllRegistersAndIterateStack \n"
".type PAPushAllRegistersAndIterateStack, %function \n"
".hidden PAPushAllRegistersAndIterateStack \n"
"PAPushAllRegistersAndIterateStack: \n"
// Push all callee-saved registers and save return address.
" addi.d $sp, $sp, -96 \n"
// Save return address.
" st.d $ra, $sp, 88 \n"
// sp is callee-saved.
" st.d $sp, $sp, 80 \n"
// s0-s9(fp) are callee-saved.
" st.d $fp, $sp, 72 \n"
" st.d $s8, $sp, 64 \n"
" st.d $s7, $sp, 56 \n"
" st.d $s6, $sp, 48 \n"
" st.d $s5, $sp, 40 \n"
" st.d $s4, $sp, 32 \n"
" st.d $s3, $sp, 24 \n"
" st.d $s2, $sp, 16 \n"
" st.d $s1, $sp, 8 \n"
" st.d $s0, $sp, 0 \n"
// Maintain frame pointer(fp is s9).
" move $fp, $sp \n"
// Pass 1st parameter (a0) unchanged (Stack*).
// Pass 2nd parameter (a1) unchanged (StackVisitor*).
// Save 3rd parameter (a2; IterateStackCallback) to a3.
" move $a3, $a2 \n"
// Pass 3rd parameter as sp (stack pointer).
" move $a2, $sp \n"
// Call the callback.
" jirl $ra, $a3, 0 \n"
// Load return address.
" ld.d $ra, $sp, 88 \n"
// Restore frame pointer.
" ld.d $fp, $sp, 72 \n"
" addi.d $sp, $sp, 96 \n"
" jr $ra \n");

View File

@@ -55,7 +55,7 @@ namespace base {
// defined as a constant. // defined as a constant.
// These constants are borrowed from glibcs (arch)/bits/pthread_stack_min.h. // These constants are borrowed from glibcs (arch)/bits/pthread_stack_min.h.
#if defined(ARCH_CPU_ARM64) || defined(ARCH_CPU_LOONGARCH64) #if defined(ARCH_CPU_ARM64)
#define PTHREAD_STACK_MIN_CONST \ #define PTHREAD_STACK_MIN_CONST \
(__builtin_constant_p(PTHREAD_STACK_MIN) ? PTHREAD_STACK_MIN : 131072) (__builtin_constant_p(PTHREAD_STACK_MIN) ? PTHREAD_STACK_MIN : 131072)
#else #else

View File

@@ -216,14 +216,11 @@ config("default_libs") {
# linking can have run-time side effects, nothing should be listed here. # linking can have run-time side effects, nothing should be listed here.
libs = [] libs = []
} else if (is_linux || is_chromeos) { } else if (is_linux || is_chromeos) {
# loong64 uses newer libc that subsumes libdl.so etc. libs = [
if (current_cpu != "loong64") { "dl",
libs = [ "pthread",
"dl", "rt",
"pthread", ]
"rt",
]
}
} }
} }

View File

@@ -488,6 +488,11 @@ def removing_unnecessary_files(install_root, arch):
ALLOWLIST = { ALLOWLIST = {
f"usr/lib/gcc/{gcc_triple}/{GCC_VERSION}/libgcc.a", f"usr/lib/gcc/{gcc_triple}/{GCC_VERSION}/libgcc.a",
f"usr/lib/{TRIPLES[arch]}/libc_nonshared.a", f"usr/lib/{TRIPLES[arch]}/libc_nonshared.a",
# https://developers.redhat.com/articles/2021/12/17/why-glibc-234-removed-libpthread
f"usr/lib/{TRIPLES[arch]}/libdl.a",
f"usr/lib/{TRIPLES[arch]}/libpthread.a",
f"usr/lib/{TRIPLES[arch]}/librt.a",
} }
for file in ALLOWLIST: for file in ALLOWLIST:

View File

@@ -195,6 +195,12 @@ def test_naive_once(proxy, *args, **kwargs):
def test_naive(label, proxy, *args, **kwargs): def test_naive(label, proxy, *args, **kwargs):
RETRIES = 5 RETRIES = 5
result = None result = None
if argv.target_cpu == 'arm' and not label.startswith('Default'):
# Arm tests are too slow in qemu-user
# due to https://www.openwall.com/lists/musl/2017/06/15/9
print('** SKIP TEST:', label, end='\n\n')
return
for i in range(RETRIES): for i in range(RETRIES):
result = test_naive_once(proxy, *args, **kwargs) result = test_naive_once(proxy, *args, **kwargs)
if result == 'Failed to listen': if result == 'Failed to listen':

View File

@@ -2019,7 +2019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.52.6", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@@ -3900,9 +3900,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.44.1" version = "1.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",

View File

@@ -654,7 +654,7 @@ if (!isEmpty(main_node)) {
urltest_nodes = [...urltest_nodes, ...filter(main_udp_urltest_nodes, (l) => !~index(urltest_nodes, l))]; urltest_nodes = [...urltest_nodes, ...filter(main_udp_urltest_nodes, (l) => !~index(urltest_nodes, l))];
} else if (dedicated_udp_node) { } else if (dedicated_udp_node) {
const main_udp_node_cfg = uci.get_all(uciconfig, main_udp_node) || {}; const main_udp_node_cfg = uci.get_all(uciconfig, main_udp_node) || {};
if (main_node_cfg.type === 'wireguard') { if (main_udp_node_cfg.type === 'wireguard') {
push(config.endpoints, generate_endpoint(main_udp_node_cfg)); push(config.endpoints, generate_endpoint(main_udp_node_cfg));
config.endpoints[length(config.endpoints)-1].tag = 'main-udp-out'; config.endpoints[length(config.endpoints)-1].tag = 'main-udp-out';
} else { } else {

View File

@@ -30,7 +30,7 @@ local subscribe_url = ucic:get_first(name, 'server_subscribe', 'subscribe_url',
local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words', '过期时间/剩余流量') local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words', '过期时间/剩余流量')
local save_words = ucic:get_first(name, 'server_subscribe', 'save_words', '') local save_words = ucic:get_first(name, 'server_subscribe', 'save_words', '')
-- 读取 ss_type 设置 -- 读取 ss_type 设置
local ss_type = ucic:get_first(name, 'server_subscribe', 'ss_type') local ss_type = ucic:get_first(name, 'server_subscribe', 'ss_type', 'ss-rust')
-- 根据 ss_type 选择对应的程序 -- 根据 ss_type 选择对应的程序
local ss_program = "" local ss_program = ""
if ss_type == "ss-rust" then if ss_type == "ss-rust" then
@@ -180,6 +180,7 @@ local function processData(szType, content)
if not isCompleteJSON(content) then if not isCompleteJSON(content) then
return nil return nil
end end
if szType == "hysteria2" or szType == "hy2" then if szType == "hysteria2" or szType == "hy2" then
local url = URL.parse("http://" .. content) local url = URL.parse("http://" .. content)
local params = url.query local params = url.query
@@ -788,7 +789,7 @@ local execute = function()
if result then if result then
-- 中文做地址的 也没有人拿中文域名搞就算中文域也有Puny Code SB 机场 -- 中文做地址的 也没有人拿中文域名搞就算中文域也有Puny Code SB 机场
if not result.server or not result.server_port or result.alias == "NULL" or check_filer(result) or result.server:match("[^0-9a-zA-Z%-_%.%s]") or cache[groupHash][result.hashkey] then if not result.server or not result.server_port or result.alias == "NULL" or check_filer(result) or result.server:match("[^0-9a-zA-Z%-_%.%s]") or cache[groupHash][result.hashkey] then
log('丢弃无效节点: ' .. result.type .. ' 节点, ' .. result.alias) log('丢弃无效节点: ' .. result.alias)
else else
-- log('成功解析: ' .. result.type ..' 节点, ' .. result.alias) -- log('成功解析: ' .. result.type ..' 节点, ' .. result.alias)
result.grouphashkey = groupHash result.grouphashkey = groupHash

View File

@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=v2ray-core PKG_NAME:=v2ray-core
PKG_VERSION:=5.29.3 PKG_VERSION:=5.30.0
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/v2fly/v2ray-core/tar.gz/v$(PKG_VERSION)? PKG_SOURCE_URL:=https://codeload.github.com/v2fly/v2ray-core/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=f2a2eb4c835a99339746eaadbb1c88b4dcd022aa6d801ca44936be36f3ed027a PKG_HASH:=8b10fc864289cb73e328d07b6d25b5e064d95e13fc5621ad4b5deb10137482b2
PKG_LICENSE:=MIT PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE PKG_LICENSE_FILES:=LICENSE

View File

@@ -21,22 +21,22 @@ define Download/geoip
HASH:=735786c00694313090c5d525516463836167422b132ce293873443613b496e92 HASH:=735786c00694313090c5d525516463836167422b132ce293873443613b496e92
endef endef
GEOSITE_VER:=20250405160157 GEOSITE_VER:=20250407044718
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER) GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
define Download/geosite define Download/geosite
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/ URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
URL_FILE:=dlc.dat URL_FILE:=dlc.dat
FILE:=$(GEOSITE_FILE) FILE:=$(GEOSITE_FILE)
HASH:=bf18a50193c260b5913af089394e49ca92967a1bb416d1e8e651667985e018bc HASH:=a35d248bdf7892fbf747d94e656e45339c1d90c6b656b5c1311d62c1f2cbaadf
endef endef
GEOSITE_IRAN_VER:=202503310039 GEOSITE_IRAN_VER:=202504070038
GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER) GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER)
define Download/geosite-ir define Download/geosite-ir
URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/ URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/
URL_FILE:=iran.dat URL_FILE:=iran.dat
FILE:=$(GEOSITE_IRAN_FILE) FILE:=$(GEOSITE_IRAN_FILE)
HASH:=1eb78f2dee6952b43dde65f72af44c1a064c97572ed0d72ad36fcf47ac4feafd HASH:=ea5ed940fee6d7c872a143d160486e5d576124fc5167dfc6a8d55708281276ec
endef endef
define Package/v2ray-geodata/template define Package/v2ray-geodata/template

View File

@@ -1023,14 +1023,21 @@ public class ConfigHandler
var profileItem = await AppHandler.Instance.GetProfileItem(indexId) ?? new(); var profileItem = await AppHandler.Instance.GetProfileItem(indexId) ?? new();
profileItem.IndexId = indexId; profileItem.IndexId = indexId;
profileItem.Remarks = multipleLoad switch if (coreType == ECoreType.Xray)
{ {
EMultipleLoad.Random => ResUI.menuSetDefaultMultipleServerXrayRandom, profileItem.Remarks = multipleLoad switch
EMultipleLoad.RoundRobin => ResUI.menuSetDefaultMultipleServerXrayRoundRobin, {
EMultipleLoad.LeastPing => ResUI.menuSetDefaultMultipleServerXrayLeastPing, EMultipleLoad.Random => ResUI.menuSetDefaultMultipleServerXrayRandom,
EMultipleLoad.LeastLoad => ResUI.menuSetDefaultMultipleServerXrayLeastLoad, EMultipleLoad.RoundRobin => ResUI.menuSetDefaultMultipleServerXrayRoundRobin,
_ => ResUI.menuSetDefaultMultipleServerXrayRoundRobin, EMultipleLoad.LeastPing => ResUI.menuSetDefaultMultipleServerXrayLeastPing,
}; EMultipleLoad.LeastLoad => ResUI.menuSetDefaultMultipleServerXrayLeastLoad,
_ => ResUI.menuSetDefaultMultipleServerXrayRoundRobin,
};
}
else if (coreType == ECoreType.sing_box)
{
profileItem.Remarks = ResUI.menuSetDefaultMultipleServerSingBoxLeastPing;
}
profileItem.Address = Global.CoreMultipleLoadConfigFileName; profileItem.Address = Global.CoreMultipleLoadConfigFileName;
profileItem.ConfigType = EConfigType.Custom; profileItem.ConfigType = EConfigType.Custom;
profileItem.CoreType = coreType; profileItem.CoreType = coreType;

View File

@@ -172,14 +172,6 @@ object AppConfig {
const val DNS_QUAD9_DOMAIN = "dns.quad9.net" const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net" const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
val DNS_CLOUDFLARE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
const val DEFAULT_PORT = 443 const val DEFAULT_PORT = 443
const val DEFAULT_SECURITY = "auto" const val DEFAULT_SECURITY = "auto"
const val DEFAULT_LEVEL = 8 const val DEFAULT_LEVEL = 8
@@ -188,4 +180,27 @@ object AppConfig {
const val REALITY = "reality" const val REALITY = "reality"
const val HEADER_TYPE_HTTP = "http" const val HEADER_TYPE_HTTP = "http"
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
val DNS_CLOUDFLARE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
val PRIVATE_IP_LIST = arrayListOf(
"0.0.0.0/8",
"10.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.88.99.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"224.0.0.0/4",
"240.0.0.0/4"
)
} }

View File

@@ -417,6 +417,9 @@ object AngConfigManager {
if (!Utils.isValidUrl(url)) { if (!Utils.isValidUrl(url)) {
return 0 return 0
} }
if (!Utils.isValidSubUrl(url)) {
return 0
}
Log.i(AppConfig.TAG, url) Log.i(AppConfig.TAG, url)
var configText = try { var configText = try {
@@ -430,7 +433,7 @@ object AngConfigManager {
configText = try { configText = try {
HttpUtil.getUrlContentWithUserAgent(url) HttpUtil.getUrlContentWithUserAgent(url)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to get URL content with user agent", e) Log.e(AppConfig.TAG, "Update subscription: Failed to get URL content with user agent", e)
"" ""
} }
} }

View File

@@ -166,7 +166,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER) //builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
val bypassLan = SettingsManager.routingRulesetsBypassLan() val bypassLan = SettingsManager.routingRulesetsBypassLan()
if (bypassLan) { if (bypassLan) {
resources.getStringArray(R.array.bypass_private_ip_address).forEach { AppConfig.PRIVATE_IP_LIST.forEach {
val addr = it.split('/') val addr = it.split('/')
builder.addRoute(addr[0], addr[1].toInt()) builder.addRoute(addr[0], addr[1].toInt())
} }

View File

@@ -90,7 +90,7 @@ class SubEditActivity : BaseActivity() {
if (!Utils.isValidSubUrl(subItem.url)) { if (!Utils.isValidSubUrl(subItem.url)) {
toast(R.string.toast_insecure_url_protocol) toast(R.string.toast_insecure_url_protocol)
//return false return false
} }
} }

View File

@@ -7,8 +7,11 @@ import com.v2ray.ang.BuildConfig
import com.v2ray.ang.util.Utils.encode import com.v2ray.ang.util.Utils.encode
import com.v2ray.ang.util.Utils.urlDecode import com.v2ray.ang.util.Utils.urlDecode
import java.io.IOException import java.io.IOException
import java.net.* import java.net.HttpURLConnection
import java.util.* import java.net.IDN
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.URL
object HttpUtil { object HttpUtil {
@@ -20,7 +23,7 @@ object HttpUtil {
* @return The ASCII representation of the URL. * @return The ASCII representation of the URL.
*/ */
fun idnToASCII(str: String): String { fun idnToASCII(str: String): String {
val url = URI(str) val url = URL(str)
val host = url.host val host = url.host
val asciiHost = IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED) val asciiHost = IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED)
if (host != asciiHost) { if (host != asciiHost) {

View File

@@ -20,7 +20,9 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig import com.v2ray.ang.BuildConfig
import java.io.IOException import java.io.IOException
import java.net.InetAddress
import java.net.ServerSocket import java.net.ServerSocket
import java.net.URI
import java.net.URLDecoder import java.net.URLDecoder
import java.net.URLEncoder import java.net.URLEncoder
import java.util.Locale import java.util.Locale
@@ -461,13 +463,23 @@ object Utils {
fun isValidSubUrl(value: String?): Boolean { fun isValidSubUrl(value: String?): Boolean {
if (value.isNullOrEmpty()) return false if (value.isNullOrEmpty()) return false
return try { try {
URLUtil.isHttpsUrl(value) || if (URLUtil.isHttpsUrl(value)) return true
(URLUtil.isHttpUrl(value) && value.contains(LOOPBACK)) if (URLUtil.isHttpUrl(value)) {
if (value.contains(LOOPBACK)) return true
//Check private ip address
val uri = URI(fixIllegalUrl(value))
if (isIpAddress(uri.host)) {
AppConfig.PRIVATE_IP_LIST.forEach {
if (isIpInCidr(uri.host, it)) return true
}
}
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to validate subscription URL", e) Log.e(AppConfig.TAG, "Failed to validate subscription URL", e)
false
} }
return false
} }
/** /**
@@ -495,5 +507,49 @@ object Utils {
*/ */
fun isGoogleFlavor(): Boolean = BuildConfig.FLAVOR == "playstore" fun isGoogleFlavor(): Boolean = BuildConfig.FLAVOR == "playstore"
/**
* Converts an InetAddress to its long representation
*
* @param ip The InetAddress to convert
* @return The long representation of the IP address
*/
private fun inetAddressToLong(ip: InetAddress): Long {
val bytes = ip.address
var result: Long = 0
for (i in bytes.indices) {
result = result shl 8 or (bytes[i].toInt() and 0xff).toLong()
}
return result
}
/**
* Check if an IP address is within a CIDR range
*
* @param ip The IP address to check
* @param cidr The CIDR notation range (e.g., "192.168.1.0/24")
* @return True if the IP is within the CIDR range, false otherwise
*/
fun isIpInCidr(ip: String, cidr: String): Boolean {
try {
if (!isIpAddress(ip)) return false
// Parse CIDR (e.g., "192.168.1.0/24")
val (cidrIp, prefixLen) = cidr.split("/")
val prefixLength = prefixLen.toInt()
// Convert IP and CIDR's IP portion to Long
val ipLong = inetAddressToLong(InetAddress.getByName(ip))
val cidrIpLong = inetAddressToLong(InetAddress.getByName(cidrIp))
// Calculate subnet mask (e.g., /24 → 0xFFFFFF00)
val mask = if (prefixLength == 0) 0L else (-1L shl (32 - prefixLength))
// Check if they're in the same subnet
return (ipLong and mask) == (cidrIpLong and mask)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to check if IP is in CIDR", e)
return false
}
}
} }

View File

@@ -124,43 +124,6 @@
<item>xtls-rprx-vision-udp443</item> <item>xtls-rprx-vision-udp443</item>
</string-array> </string-array>
<!-- minimum list https://serverfault.com/a/304791 -->
<string-array name="bypass_private_ip_address" translatable="false">
<item>0.0.0.0/5</item>
<item>8.0.0.0/7</item>
<item>11.0.0.0/8</item>
<item>12.0.0.0/6</item>
<item>16.0.0.0/4</item>
<item>32.0.0.0/3</item>
<item>64.0.0.0/2</item>
<item>128.0.0.0/3</item>
<item>160.0.0.0/5</item>
<item>168.0.0.0/6</item>
<item>172.0.0.0/12</item>
<item>172.32.0.0/11</item>
<item>172.64.0.0/10</item>
<item>172.128.0.0/9</item>
<item>173.0.0.0/8</item>
<item>174.0.0.0/7</item>
<item>176.0.0.0/4</item>
<item>192.0.0.0/9</item>
<item>192.128.0.0/11</item>
<item>192.160.0.0/13</item>
<item>192.169.0.0/16</item>
<item>192.170.0.0/15</item>
<item>192.172.0.0/14</item>
<item>192.176.0.0/12</item>
<item>192.192.0.0/10</item>
<item>193.0.0.0/8</item>
<item>194.0.0.0/7</item>
<item>196.0.0.0/6</item>
<item>200.0.0.0/5</item>
<item>208.0.0.0/4</item>
<item>240.0.0.0/4</item>
</string-array>
<string-array name="language_select" translatable="false"> <string-array name="language_select" translatable="false">
<item>auto</item> <item>auto</item>
<item>English</item> <item>English</item>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools"> <network-security-config xmlns:tools="http://schemas.android.com/tools">
<base-config> <base-config cleartextTrafficPermitted="true">
<trust-anchors> <trust-anchors>
<certificates src="system" /> <certificates src="system" />
<certificates <certificates

View File

@@ -0,0 +1,41 @@
package com.v2ray.ang
import com.v2ray.ang.util.HttpUtil
import org.junit.Assert.assertEquals
import org.junit.Test
class HttpUtilTest {
@Test
fun testIdnToASCII() {
// Regular URL remains unchanged
val regularUrl = "https://example.com/path"
assertEquals(regularUrl, HttpUtil.idnToASCII(regularUrl))
// Non-ASCII URL converts to ASCII (Punycode)
val nonAsciiUrl = "https://例子.测试/path"
val expectedNonAscii = "https://xn--fsqu00a.xn--0zwm56d/path"
assertEquals(expectedNonAscii, HttpUtil.idnToASCII(nonAsciiUrl))
// Mixed URL only converts the host part
val mixedUrl = "https://例子.com/测试"
val expectedMixed = "https://xn--fsqu00a.com/测试"
assertEquals(expectedMixed, HttpUtil.idnToASCII(mixedUrl))
// URL with Basic Authentication using regular domain
val basicAuthUrl = "https://user:password@example.com/path"
assertEquals(basicAuthUrl, HttpUtil.idnToASCII(basicAuthUrl))
// URL with Basic Authentication using non-ASCII domain
val basicAuthNonAscii = "https://user:password@例子.测试/path"
val expectedBasicAuthNonAscii = "https://user:password@xn--fsqu00a.xn--0zwm56d/path"
assertEquals(expectedBasicAuthNonAscii, HttpUtil.idnToASCII(basicAuthNonAscii))
// URL with non-ASCII username and password
val nonAsciiAuth = "https://用户:密码@example.com/path"
// Basic auth credentials should remain unchanged as they're percent-encoded separately
assertEquals(nonAsciiAuth, HttpUtil.idnToASCII(nonAsciiAuth))
}
}

View File

@@ -11,7 +11,7 @@ import org.junit.Test
* *
* See [testing documentation](http://d.android.com/tools/testing). * See [testing documentation](http://d.android.com/tools/testing).
*/ */
class AngUnitTest { class UtilsTest {
@Test @Test
fun test_parseInt() { fun test_parseInt() {
@@ -45,4 +45,18 @@ class AngUnitTest {
assertTrue(Utils.isIpAddress("240e:1234:abcd:12::/64")) assertTrue(Utils.isIpAddress("240e:1234:abcd:12::/64"))
} }
@Test
fun test_IsIpInCidr() {
assertTrue(Utils.isIpInCidr("192.168.1.1", "192.168.1.0/24"))
assertTrue(Utils.isIpInCidr("192.168.1.254", "192.168.1.0/24"))
assertFalse(Utils.isIpInCidr("192.168.2.1", "192.168.1.0/24"))
assertTrue(Utils.isIpInCidr("10.0.0.0", "10.0.0.0/8"))
assertTrue(Utils.isIpInCidr("10.255.255.255", "10.0.0.0/8"))
assertFalse(Utils.isIpInCidr("11.0.0.0", "10.0.0.0/8"))
assertFalse(Utils.isIpInCidr("invalid-ip", "192.168.1.0/24"))
assertFalse(Utils.isIpInCidr("192.168.1.1", "invalid-cidr"))
}
} }

View File

@@ -9,7 +9,7 @@ require (
github.com/golang/mock v1.7.0-rc.1 github.com/golang/mock v1.7.0-rc.1
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/miekg/dns v1.1.64 github.com/miekg/dns v1.1.65
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
github.com/pires/go-proxyproto v0.8.0 github.com/pires/go-proxyproto v0.8.0
github.com/quic-go/quic-go v0.50.1 github.com/quic-go/quic-go v0.50.1
@@ -22,12 +22,12 @@ require (
github.com/vishvananda/netlink v1.3.0 github.com/vishvananda/netlink v1.3.0
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.37.0
golang.org/x/net v0.38.0 golang.org/x/net v0.38.0
golang.org/x/sync v0.12.0 golang.org/x/sync v0.13.0
golang.org/x/sys v0.31.0 golang.org/x/sys v0.32.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
google.golang.org/grpc v1.71.0 google.golang.org/grpc v1.71.1
google.golang.org/protobuf v1.36.6 google.golang.org/protobuf v1.36.6
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0
h12.io/socks v1.0.3 h12.io/socks v1.0.3
@@ -51,7 +51,7 @@ require (
go.uber.org/mock v0.5.0 // indirect go.uber.org/mock v0.5.0 // indirect
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
golang.org/x/mod v0.23.0 // indirect golang.org/x/mod v0.23.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.30.0 // indirect golang.org/x/tools v0.30.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect

View File

@@ -38,8 +38,8 @@ github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0N
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ= github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
@@ -97,8 +97,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/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-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.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
@@ -111,8 +111,8 @@ golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -121,14 +121,14 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -145,8 +145,8 @@ golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uI
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

View File

@@ -8,11 +8,13 @@ import (
type Duration int64 type Duration int64
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (d *Duration) MarshalJSON() ([]byte, error) { func (d *Duration) MarshalJSON() ([]byte, error) {
dr := time.Duration(*d) dr := time.Duration(*d)
return json.Marshal(dr.String()) return json.Marshal(dr.String())
} }
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (d *Duration) UnmarshalJSON(b []byte) error { func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{} var v interface{}
if err := json.Unmarshal(b, &v); err != nil { if err := json.Unmarshal(b, &v); err != nil {

View File

@@ -23,6 +23,7 @@ func (v StringList) Len() int {
return len(v) return len(v)
} }
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *StringList) UnmarshalJSON(data []byte) error { func (v *StringList) UnmarshalJSON(data []byte) error {
var strarray []string var strarray []string
if err := json.Unmarshal(data, &strarray); err == nil { if err := json.Unmarshal(data, &strarray); err == nil {
@@ -43,10 +44,12 @@ type Address struct {
net.Address net.Address
} }
func (v Address) MarshalJSON() ([]byte, error) { // MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (v *Address) MarshalJSON() ([]byte, error) {
return json.Marshal(v.Address.String()) return json.Marshal(v.Address.String())
} }
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *Address) UnmarshalJSON(data []byte) error { func (v *Address) UnmarshalJSON(data []byte) error {
var rawStr string var rawStr string
if err := json.Unmarshal(data, &rawStr); err != nil { if err := json.Unmarshal(data, &rawStr); err != nil {
@@ -81,6 +84,7 @@ func (v Network) Build() net.Network {
type NetworkList []Network type NetworkList []Network
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *NetworkList) UnmarshalJSON(data []byte) error { func (v *NetworkList) UnmarshalJSON(data []byte) error {
var strarray []Network var strarray []Network
if err := json.Unmarshal(data, &strarray); err == nil { if err := json.Unmarshal(data, &strarray); err == nil {
@@ -169,6 +173,19 @@ func (v *PortRange) Build() *net.PortRange {
} }
} }
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (v *PortRange) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
func (port *PortRange) String() string {
if port.From == port.To {
return strconv.Itoa(int(port.From))
} else {
return fmt.Sprintf("%d-%d", port.From, port.To)
}
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *PortRange) UnmarshalJSON(data []byte) error { func (v *PortRange) UnmarshalJSON(data []byte) error {
port, err := parseIntPort(data) port, err := parseIntPort(data)
@@ -203,20 +220,21 @@ func (list *PortList) Build() *net.PortList {
return portList return portList
} }
func (v PortList) MarshalJSON() ([]byte, error) { // MarshalJSON implements encoding/json.Marshaler.MarshalJSON
return json.Marshal(v.String()) func (v *PortList) MarshalJSON() ([]byte, error) {
portStr := v.String()
port, err := strconv.Atoi(portStr)
if err == nil {
return json.Marshal(port)
} else {
return json.Marshal(portStr)
}
} }
func (v PortList) String() string { func (v PortList) String() string {
ports := []string{} ports := []string{}
for _, port := range v.Range { for _, port := range v.Range {
if port.From == port.To { ports = append(ports, port.String())
p := strconv.Itoa(int(port.From))
ports = append(ports, p)
} else {
p := fmt.Sprintf("%d-%d", port.From, port.To)
ports = append(ports, p)
}
} }
return strings.Join(ports, ",") return strings.Join(ports, ",")
} }
@@ -277,7 +295,8 @@ type Int32Range struct {
To int32 To int32
} }
func (v Int32Range) MarshalJSON() ([]byte, error) { // MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (v *Int32Range) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String()) return json.Marshal(v.String())
} }
@@ -289,6 +308,7 @@ func (v Int32Range) String() string {
} }
} }
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *Int32Range) UnmarshalJSON(data []byte) error { func (v *Int32Range) UnmarshalJSON(data []byte) error {
defer v.ensureOrder() defer v.ensureOrder()
var str string var str string

View File

@@ -25,6 +25,7 @@ type NameServerConfig struct {
TimeoutMs uint64 `json:"timeoutMs"` TimeoutMs uint64 `json:"timeoutMs"`
} }
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (c *NameServerConfig) UnmarshalJSON(data []byte) error { func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
var address Address var address Address
if err := json.Unmarshal(data, &address); err == nil { if err := json.Unmarshal(data, &address); err == nil {
@@ -163,6 +164,18 @@ type HostAddress struct {
addrs []*Address addrs []*Address
} }
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (h *HostAddress) MarshalJSON() ([]byte, error) {
if (h.addr != nil) != (h.addrs != nil) {
if h.addr != nil {
return json.Marshal(h.addr)
} else if h.addrs != nil {
return json.Marshal(h.addrs)
}
}
return nil, errors.New("unexpected config state")
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (h *HostAddress) UnmarshalJSON(data []byte) error { func (h *HostAddress) UnmarshalJSON(data []byte) error {
addr := new(Address) addr := new(Address)
@@ -208,6 +221,11 @@ func getHostMapping(ha *HostAddress) *dns.Config_HostMapping {
} }
} }
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (m *HostsWrapper) MarshalJSON() ([]byte, error) {
return json.Marshal(m.Hosts)
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (m *HostsWrapper) UnmarshalJSON(data []byte) error { func (m *HostsWrapper) UnmarshalJSON(data []byte) error {
hosts := make(map[string]*HostAddress) hosts := make(map[string]*HostAddress)

View File

@@ -20,6 +20,18 @@ type FakeDNSConfig struct {
pools []*FakeDNSPoolElementConfig pools []*FakeDNSPoolElementConfig
} }
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (f *FakeDNSConfig) MarshalJSON() ([]byte, error) {
if (f.pool != nil) != (f.pools != nil) {
if f.pool != nil {
return json.Marshal(f.pool)
} else if f.pools != nil {
return json.Marshal(f.pools)
}
}
return nil, errors.New("unexpected config state")
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (f *FakeDNSConfig) UnmarshalJSON(data []byte) error { func (f *FakeDNSConfig) UnmarshalJSON(data []byte) error {
var pool FakeDNSPoolElementConfig var pool FakeDNSPoolElementConfig

View File

@@ -13,9 +13,6 @@ const (
TCP_FASTOPEN = 15 TCP_FASTOPEN = 15
IP_UNICAST_IF = 31 IP_UNICAST_IF = 31
IPV6_UNICAST_IF = 31 IPV6_UNICAST_IF = 31
IP_MULTICAST_IF = 9
IPV6_MULTICAST_IF = 9
IPV6_V6ONLY = 27
) )
func setTFO(fd syscall.Handle, tfo int) error { func setTFO(fd syscall.Handle, tfo int) error {
@@ -44,14 +41,14 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx)); err != nil { if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx)); err != nil {
return errors.New("failed to set IP_UNICAST_IF").Base(err) return errors.New("failed to set IP_UNICAST_IF").Base(err)
} }
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, IP_MULTICAST_IF, int(idx)); err != nil { if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, int(idx)); err != nil {
return errors.New("failed to set IP_MULTICAST_IF").Base(err) return errors.New("failed to set IP_MULTICAST_IF").Base(err)
} }
} else { } else {
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, inf.Index); err != nil { if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, inf.Index); err != nil {
return errors.New("failed to set IPV6_UNICAST_IF").Base(err) return errors.New("failed to set IPV6_UNICAST_IF").Base(err)
} }
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_MULTICAST_IF, inf.Index); err != nil { if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, syscall.IPV6_MULTICAST_IF, inf.Index); err != nil {
return errors.New("failed to set IPV6_MULTICAST_IF").Base(err) return errors.New("failed to set IPV6_MULTICAST_IF").Base(err)
} }
} }
@@ -92,7 +89,7 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
} }
if config.V6Only { if config.V6Only {
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_V6ONLY, 1); err != nil { if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 1); err != nil {
return errors.New("failed to set IPV6_V6ONLY").Base(err) return errors.New("failed to set IPV6_V6ONLY").Base(err)
} }
} }

View File

@@ -659,6 +659,8 @@ class TestUtil(unittest.TestCase):
self.assertEqual(url_or_none('mms://foo.de'), 'mms://foo.de') self.assertEqual(url_or_none('mms://foo.de'), 'mms://foo.de')
self.assertEqual(url_or_none('rtspu://foo.de'), 'rtspu://foo.de') self.assertEqual(url_or_none('rtspu://foo.de'), 'rtspu://foo.de')
self.assertEqual(url_or_none('ftps://foo.de'), 'ftps://foo.de') self.assertEqual(url_or_none('ftps://foo.de'), 'ftps://foo.de')
self.assertEqual(url_or_none('ws://foo.de'), 'ws://foo.de')
self.assertEqual(url_or_none('wss://foo.de'), 'wss://foo.de')
def test_parse_age_limit(self): def test_parse_age_limit(self):
self.assertEqual(parse_age_limit(None), None) self.assertEqual(parse_age_limit(None), None)

View File

@@ -85,6 +85,7 @@ class NiconicoLiveFD(FileDownloader):
'quality': live_quality, 'quality': live_quality,
'protocol': 'hls+fmp4', 'protocol': 'hls+fmp4',
'latency': live_latency, 'latency': live_latency,
'accessRightMethod': 'single_cookie',
'chasePlay': False, 'chasePlay': False,
}, },
'room': { 'room': {

View File

@@ -903,6 +903,7 @@ from .ivi import (
IviIE, IviIE,
) )
from .ivideon import IvideonIE from .ivideon import IvideonIE
from .ivoox import IvooxIE
from .iwara import ( from .iwara import (
IwaraIE, IwaraIE,
IwaraPlaylistIE, IwaraPlaylistIE,
@@ -960,7 +961,10 @@ from .kick import (
) )
from .kicker import KickerIE from .kicker import KickerIE
from .kickstarter import KickStarterIE from .kickstarter import KickStarterIE
from .kika import KikaIE from .kika import (
KikaIE,
KikaPlaylistIE,
)
from .kinja import KinjaEmbedIE from .kinja import KinjaEmbedIE
from .kinopoisk import KinoPoiskIE from .kinopoisk import KinoPoiskIE
from .kommunetv import KommunetvIE from .kommunetv import KommunetvIE
@@ -1061,6 +1065,7 @@ from .loom import (
from .lovehomeporn import LoveHomePornIE from .lovehomeporn import LoveHomePornIE
from .lrt import ( from .lrt import (
LRTVODIE, LRTVODIE,
LRTRadioIE,
LRTStreamIE, LRTStreamIE,
) )
from .lsm import ( from .lsm import (

View File

@@ -0,0 +1,78 @@
from .common import InfoExtractor
from ..utils import int_or_none, parse_iso8601, url_or_none, urljoin
from ..utils.traversal import traverse_obj
class IvooxIE(InfoExtractor):
_VALID_URL = (
r'https?://(?:www\.)?ivoox\.com/(?:\w{2}/)?[^/?#]+_rf_(?P<id>[0-9]+)_1\.html',
r'https?://go\.ivoox\.com/rf/(?P<id>[0-9]+)',
)
_TESTS = [{
'url': 'https://www.ivoox.com/dex-08x30-rostros-del-mal-los-asesinos-en-audios-mp3_rf_143594959_1.html',
'md5': '993f712de5b7d552459fc66aa3726885',
'info_dict': {
'id': '143594959',
'ext': 'mp3',
'timestamp': 1742731200,
'channel': 'DIAS EXTRAÑOS con Santiago Camacho',
'title': 'DEx 08x30 Rostros del mal: Los asesinos en serie que aterrorizaron España',
'description': 'md5:eae8b4b9740d0216d3871390b056bb08',
'uploader': 'Santiago Camacho',
'thumbnail': 'https://static-1.ivoox.com/audios/c/d/5/2/cd52f46783fe735000c33a803dce2554_XXL.jpg',
'upload_date': '20250323',
'episode': 'DEx 08x30 Rostros del mal: Los asesinos en serie que aterrorizaron España',
'duration': 11837,
'tags': ['españa', 'asesinos en serie', 'arropiero', 'historia criminal', 'mataviejas'],
},
}, {
'url': 'https://go.ivoox.com/rf/143594959',
'only_matching': True,
}, {
'url': 'https://www.ivoox.com/en/campodelgas-28-03-2025-audios-mp3_rf_144036942_1.html',
'only_matching': True,
}]
def _real_extract(self, url):
media_id = self._match_id(url)
webpage = self._download_webpage(url, media_id, fatal=False)
data = self._search_nuxt_data(
webpage, media_id, fatal=False, traverse=('data', 0, 'data', 'audio'))
direct_download = self._download_json(
f'https://vcore-web.ivoox.com/v1/public/audios/{media_id}/download-url', media_id, fatal=False,
note='Fetching direct download link', headers={'Referer': url})
download_paths = {
*traverse_obj(direct_download, ('data', 'downloadUrl', {str}, filter, all)),
*traverse_obj(data, (('downloadUrl', 'mediaUrl'), {str}, filter)),
}
formats = []
for path in download_paths:
formats.append({
'url': urljoin('https://ivoox.com', path),
'http_headers': {'Referer': url},
})
return {
'id': media_id,
'formats': formats,
'uploader': self._html_search_regex(r'data-prm-author="([^"]+)"', webpage, 'author', default=None),
'timestamp': parse_iso8601(
self._html_search_regex(r'data-prm-pubdate="([^"]+)"', webpage, 'timestamp', default=None)),
'channel': self._html_search_regex(r'data-prm-podname="([^"]+)"', webpage, 'channel', default=None),
'title': self._html_search_regex(r'data-prm-title="([^"]+)"', webpage, 'title', default=None),
'thumbnail': self._og_search_thumbnail(webpage, default=None),
'description': self._og_search_description(webpage, default=None),
**self._search_json_ld(webpage, media_id, default={}),
**traverse_obj(data, {
'title': ('title', {str}),
'description': ('description', {str}),
'thumbnail': ('image', {url_or_none}),
'timestamp': ('uploadDate', {parse_iso8601(delimiter=' ')}),
'duration': ('duration', {int_or_none}),
'tags': ('tags', ..., 'name', {str}),
}),
}

View File

@@ -1,3 +1,5 @@
import itertools
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
determine_ext, determine_ext,
@@ -124,3 +126,43 @@ class KikaIE(InfoExtractor):
'vbr': ('bitrateVideo', {int_or_none}, {lambda x: None if x == -1 else x}), 'vbr': ('bitrateVideo', {int_or_none}, {lambda x: None if x == -1 else x}),
}), }),
} }
class KikaPlaylistIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?kika\.de/[\w-]+/(?P<id>[a-z-]+\d+)'
_TESTS = [{
'url': 'https://www.kika.de/logo/logo-die-welt-und-ich-562',
'info_dict': {
'id': 'logo-die-welt-und-ich-562',
'title': 'logo!',
'description': 'md5:7b9d7f65561b82fa512f2cfb553c397d',
},
'playlist_count': 100,
}]
def _entries(self, playlist_url, playlist_id):
for page in itertools.count(1):
data = self._download_json(playlist_url, playlist_id, note=f'Downloading page {page}')
for item in traverse_obj(data, ('content', lambda _, v: url_or_none(v['api']['url']))):
yield self.url_result(
item['api']['url'], ie=KikaIE,
**traverse_obj(item, {
'id': ('id', {str}),
'title': ('title', {str}),
'duration': ('duration', {int_or_none}),
'timestamp': ('date', {parse_iso8601}),
}))
playlist_url = traverse_obj(data, ('links', 'next', {url_or_none}))
if not playlist_url:
break
def _real_extract(self, url):
playlist_id = self._match_id(url)
brand_data = self._download_json(
f'https://www.kika.de/_next-api/proxy/v1/brands/{playlist_id}', playlist_id)
return self.playlist_result(
self._entries(brand_data['videoSubchannel']['videosPageUrl'], playlist_id),
playlist_id, title=brand_data.get('title'), description=brand_data.get('description'))

View File

@@ -2,8 +2,11 @@ from .common import InfoExtractor
from ..utils import ( from ..utils import (
clean_html, clean_html,
merge_dicts, merge_dicts,
str_or_none,
traverse_obj, traverse_obj,
unified_timestamp,
url_or_none, url_or_none,
urljoin,
) )
@@ -80,7 +83,7 @@ class LRTVODIE(LRTBaseIE):
}] }]
def _real_extract(self, url): def _real_extract(self, url):
path, video_id = self._match_valid_url(url).groups() path, video_id = self._match_valid_url(url).group('path', 'id')
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
media_url = self._extract_js_var(webpage, 'main_url', path) media_url = self._extract_js_var(webpage, 'main_url', path)
@@ -106,3 +109,42 @@ class LRTVODIE(LRTBaseIE):
} }
return merge_dicts(clean_info, jw_data, json_ld_data) return merge_dicts(clean_info, jw_data, json_ld_data)
class LRTRadioIE(LRTBaseIE):
_VALID_URL = r'https?://(?:www\.)?lrt\.lt/radioteka/irasas/(?P<id>\d+)/(?P<path>[^?#/]+)'
_TESTS = [{
# m3u8 download
'url': 'https://www.lrt.lt/radioteka/irasas/2000359728/nemarios-eiles-apie-pragarus-ir-skaistyklas-su-aiste-kiltinaviciute',
'info_dict': {
'id': '2000359728',
'ext': 'm4a',
'title': 'Nemarios eilės: apie pragarus ir skaistyklas su Aiste Kiltinavičiūte',
'description': 'md5:5eee9a0e86a55bf547bd67596204625d',
'timestamp': 1726143120,
'upload_date': '20240912',
'tags': 'count:5',
'thumbnail': r're:https?://.+/.+\.jpe?g',
'categories': ['Daiktiniai įrodymai'],
},
}, {
'url': 'https://www.lrt.lt/radioteka/irasas/2000304654/vakaras-su-knyga-svetlana-aleksijevic-cernobylio-malda-v-dalis?season=%2Fmediateka%2Faudio%2Fvakaras-su-knyga%2F2023',
'only_matching': True,
}]
def _real_extract(self, url):
video_id, path = self._match_valid_url(url).group('id', 'path')
media = self._download_json(
'https://www.lrt.lt/radioteka/api/media', video_id,
query={'url': f'/mediateka/irasas/{video_id}/{path}'})
return traverse_obj(media, {
'id': ('id', {int}, {str_or_none}),
'title': ('title', {str}),
'tags': ('tags', ..., 'name', {str}),
'categories': ('playlist_item', 'category', {str}, filter, all, filter),
'description': ('content', {clean_html}, {str}),
'timestamp': ('date', {lambda x: x.replace('.', '/')}, {unified_timestamp}),
'thumbnail': ('playlist_item', 'image', {urljoin('https://www.lrt.lt')}),
'formats': ('playlist_item', 'file', {lambda x: self._extract_m3u8_formats(x, video_id)}),
})

View File

@@ -27,6 +27,7 @@ from ..utils import (
traverse_obj, traverse_obj,
try_get, try_get,
unescapeHTML, unescapeHTML,
unified_timestamp,
update_url_query, update_url_query,
url_basename, url_basename,
url_or_none, url_or_none,
@@ -985,6 +986,7 @@ class NiconicoLiveIE(InfoExtractor):
'quality': 'abr', 'quality': 'abr',
'protocol': 'hls+fmp4', 'protocol': 'hls+fmp4',
'latency': latency, 'latency': latency,
'accessRightMethod': 'single_cookie',
'chasePlay': False, 'chasePlay': False,
}, },
'room': { 'room': {
@@ -1005,6 +1007,7 @@ class NiconicoLiveIE(InfoExtractor):
if data.get('type') == 'stream': if data.get('type') == 'stream':
m3u8_url = data['data']['uri'] m3u8_url = data['data']['uri']
qualities = data['data']['availableQualities'] qualities = data['data']['availableQualities']
cookies = data['data']['cookies']
break break
elif data.get('type') == 'disconnect': elif data.get('type') == 'disconnect':
self.write_debug(recv) self.write_debug(recv)
@@ -1043,6 +1046,11 @@ class NiconicoLiveIE(InfoExtractor):
**res, **res,
}) })
for cookie in cookies:
self._set_cookie(
cookie['domain'], cookie['name'], cookie['value'],
expire_time=unified_timestamp(cookie['expires']), path=cookie['path'], secure=cookie['secure'])
formats = self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4', live=True) formats = self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4', live=True)
for fmt, q in zip(formats, reversed(qualities[1:])): for fmt, q in zip(formats, reversed(qualities[1:])):
fmt.update({ fmt.update({

View File

@@ -2044,7 +2044,7 @@ def url_or_none(url):
if not url or not isinstance(url, str): if not url or not isinstance(url, str):
return None return None
url = url.strip() url = url.strip()
return url if re.match(r'(?:(?:https?|rt(?:m(?:pt?[es]?|fp)|sp[su]?)|mms|ftps?):)?//', url) else None return url if re.match(r'(?:(?:https?|rt(?:m(?:pt?[es]?|fp)|sp[su]?)|mms|ftps?|wss?):)?//', url) else None
def strftime_or_none(timestamp, date_format='%Y%m%d', default=None): def strftime_or_none(timestamp, date_format='%Y%m%d', default=None):