From 328a1aa9d6afdd0c9b7560fc47745b4e25212bd3 Mon Sep 17 00:00:00 2001 From: "github-action[bot]" Date: Mon, 6 Oct 2025 20:37:54 +0200 Subject: [PATCH] Update On Mon Oct 6 20:37:53 CEST 2025 --- .github/update.log | 1 + clash-meta/.github/workflows/build.yml | 10 +- clash-meta/.github/workflows/test.yml | 6 +- clash-nyanpasu/frontend/nyanpasu/package.json | 2 +- clash-nyanpasu/pnpm-lock.yaml | 43 ++- lede/target/linux/airoha/dts/an7581.dtsi | 58 +++- mihomo/.github/workflows/build.yml | 10 +- mihomo/.github/workflows/test.yml | 6 +- nodepass/docs/en/api.md | 111 +------ nodepass/docs/en/configuration.md | 47 ++- nodepass/docs/zh/api.md | 107 +------ nodepass/docs/zh/configuration.md | 47 ++- nodepass/go.mod | 4 +- nodepass/go.sum | 8 +- nodepass/internal/client.go | 6 +- nodepass/internal/common.go | 298 +++++++++++------- nodepass/internal/master.go | 94 +----- nodepass/internal/server.go | 6 +- openwrt-packages/luci-app-amlogic/Makefile | 2 +- .../root/usr/sbin/openwrt-install-amlogic | 6 +- small/v2ray-geodata/Makefile | 2 +- xray-core/common/protocol/headers.go | 2 +- xray-core/common/singbridge/pipe.go | 22 +- xray-core/go.mod | 3 +- xray-core/go.sum | 8 +- 25 files changed, 417 insertions(+), 492 deletions(-) diff --git a/.github/update.log b/.github/update.log index 940dbe0b0f..3d0a008aa1 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1142,3 +1142,4 @@ Update On Thu Oct 2 20:40:58 CEST 2025 Update On Fri Oct 3 20:39:08 CEST 2025 Update On Sat Oct 4 20:34:01 CEST 2025 Update On Sun Oct 5 20:34:00 CEST 2025 +Update On Mon Oct 6 20:37:45 CEST 2025 diff --git a/clash-meta/.github/workflows/build.yml b/clash-meta/.github/workflows/build.yml index 48858ef500..a9f99dc85a 100644 --- a/clash-meta/.github/workflows/build.yml +++ b/clash-meta/.github/workflows/build.yml @@ -146,17 +146,17 @@ jobs: - { goos: linux, goarch: amd64, goamd64: v3, output: amd64-v3-go120, goversion: '1.20' } steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Go if: ${{ matrix.jobs.goversion == '' && matrix.jobs.abi != '1' }} - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: '1.25' - name: Set up Go if: ${{ matrix.jobs.goversion != '' && matrix.jobs.abi != '1' }} - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.jobs.goversion }} @@ -438,7 +438,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: Meta fetch-depth: '0' @@ -497,7 +497,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/clash-meta/.github/workflows/test.yml b/clash-meta/.github/workflows/test.yml index dd76733df2..8d7a35c2a1 100644 --- a/clash-meta/.github/workflows/test.yml +++ b/clash-meta/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - 'windows-latest' # amd64 windows - 'macos-latest' # arm64 macos - 'ubuntu-24.04-arm' # arm64 linux - - 'macos-13' # amd64 macos + - 'macos-15-intel' # amd64 macos go-version: - '1.25' - '1.24' @@ -41,10 +41,10 @@ jobs: # Fix mingw trying to be smart and converting paths https://github.com/moby/moby/issues/24029#issuecomment-250412919 MSYS_NO_PATHCONV: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index b4a09f970e..5745a46702 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -36,7 +36,7 @@ "jotai": "2.15.0", "json-schema": "0.4.0", "material-react-table": "3.2.1", - "monaco-editor": "0.52.2", + "monaco-editor": "0.54.0", "mui-color-input": "7.0.0", "react": "19.1.1", "react-dom": "19.1.1", diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 007d5ed794..a5a0505e61 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -291,8 +291,8 @@ importers: specifier: npm:@greenhat616/material-react-table@4.0.0 version: '@greenhat616/material-react-table@4.0.0(1271a7024e323b47c86660a86391d8f0)' monaco-editor: - specifier: 0.52.2 - version: 0.52.2 + specifier: 0.54.0 + version: 0.54.0 mui-color-input: specifier: 7.0.0 version: 7.0.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -350,7 +350,7 @@ importers: version: 2.2.388 '@monaco-editor/react': specifier: 4.7.0 - version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 4.7.0(monaco-editor@0.54.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/react-query': specifier: 5.90.2 version: 5.90.2(react@19.1.1) @@ -422,7 +422,7 @@ importers: version: 1.19.13 monaco-yaml: specifier: 5.4.0 - version: 5.4.0(monaco-editor@0.52.2) + version: 5.4.0(monaco-editor@0.54.0) nanoid: specifier: 5.1.6 version: 5.1.6 @@ -4719,6 +4719,9 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dompurify@3.1.7: + resolution: {integrity: sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==} + domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -6317,6 +6320,11 @@ packages: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + matcher@3.0.0: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} engines: {node: '>=10'} @@ -6508,8 +6516,8 @@ packages: mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} - monaco-editor@0.52.2: - resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} + monaco-editor@0.54.0: + resolution: {integrity: sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw==} monaco-languageserver-types@0.4.0: resolution: {integrity: sha512-QQ3BZiU5LYkJElGncSNb5AKoJ/LCs6YBMCJMAz9EA7v+JaOdn3kx2cXpPTcZfKA5AEsR0vc97sAw+5mdNhVBmw==} @@ -10296,10 +10304,10 @@ snapshots: dependencies: state-local: 1.0.7 - '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@monaco-editor/react@4.7.0(monaco-editor@0.54.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@monaco-editor/loader': 1.5.0 - monaco-editor: 0.52.2 + monaco-editor: 0.54.0 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -13163,6 +13171,8 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.1.7: {} + domutils@2.8.0: dependencies: dom-serializer: 1.4.1 @@ -15030,6 +15040,8 @@ snapshots: semver: 5.7.2 optional: true + marked@14.0.0: {} + matcher@3.0.0: dependencies: escape-string-regexp: 4.0.0 @@ -15335,7 +15347,10 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.1 - monaco-editor@0.52.2: {} + monaco-editor@0.54.0: + dependencies: + dompurify: 3.1.7 + marked: 14.0.0 monaco-languageserver-types@0.4.0: dependencies: @@ -15349,18 +15364,18 @@ snapshots: monaco-types@0.1.0: {} - monaco-worker-manager@2.0.1(monaco-editor@0.52.2): + monaco-worker-manager@2.0.1(monaco-editor@0.54.0): dependencies: - monaco-editor: 0.52.2 + monaco-editor: 0.54.0 - monaco-yaml@5.4.0(monaco-editor@0.52.2): + monaco-yaml@5.4.0(monaco-editor@0.54.0): dependencies: jsonc-parser: 3.3.1 - monaco-editor: 0.52.2 + monaco-editor: 0.54.0 monaco-languageserver-types: 0.4.0 monaco-marker-data-provider: 1.2.3 monaco-types: 0.1.0 - monaco-worker-manager: 2.0.1(monaco-editor@0.52.2) + monaco-worker-manager: 2.0.1(monaco-editor@0.54.0) path-browserify: 1.0.1 prettier: 3.6.2 vscode-languageserver-textdocument: 1.0.12 diff --git a/lede/target/linux/airoha/dts/an7581.dtsi b/lede/target/linux/airoha/dts/an7581.dtsi index 11622dccf2..55f4cf180a 100644 --- a/lede/target/linux/airoha/dts/an7581.dtsi +++ b/lede/target/linux/airoha/dts/an7581.dtsi @@ -17,29 +17,39 @@ #size-cells = <2>; ranges; - npu-binary@84000000 { + atf@80000000 { + no-map; + reg = <0x0 0x80000000 0x0 0x200000>; + }; + + npu_binary: npu-binary@84000000 { no-map; reg = <0x0 0x84000000 0x0 0xa00000>; }; - npu-flag@84b0000 { + qdma0_buf: qdma0-buf@87000000 { no-map; - reg = <0x0 0x84b00000 0x0 0x100000>; + reg = <0x0 0x87000000 0x0 0x2000000>; }; - npu-pkt@85000000 { + qdma1_buf: qdma1-buf@89000000 { no-map; - reg = <0x0 0x85000000 0x0 0x1a00000>; + reg = <0x0 0x89000000 0x0 0x1000000>; }; - npu-phyaddr@86b00000 { + npu_pkt: npu-pkt@8a000000 { no-map; - reg = <0x0 0x86b00000 0x0 0x100000>; + reg = <0x0 0x8a000000 0x0 0x2c00000>; }; - npu-rxdesc@86d00000 { + npu_txpkt: npu-txpkt@8cc00000 { no-map; - reg = <0x0 0x86d00000 0x0 0x100000>; + reg = <0x0 0x8cc00000 0x0 0x4000000>; + }; + + npu_txbufid: npu-txbufid@90c00000 { + no-map; + reg = <0x0 0x90c00000 0x0 0x6800>; }; }; @@ -662,6 +672,31 @@ }; }; + npu: npu@1e900000 { + compatible = "airoha,en7581-npu"; + reg = <0x0 0x1e900000 0x0 0x313000>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + memory-region = <&npu_binary>, <&npu_pkt>, <&npu_txpkt>, + <&npu_txbufid>; + memory-region-names = "binary", "pkt", "tx-pkt", + "tx-bufid"; + status = "disabled"; + }; + eth: ethernet@1fb50000 { compatible = "airoha,en7581-eth"; reg = <0 0x1fb50000 0 0x2600>, @@ -692,6 +727,11 @@ , ; + memory-region = <&qdma0_buf>, <&qdma1_buf>; + memory-region-names = "qdma0-buf", "qdma1-buf"; + + airoha,npu = <&npu>; + status = "disabled"; #address-cells = <1>; diff --git a/mihomo/.github/workflows/build.yml b/mihomo/.github/workflows/build.yml index 48858ef500..a9f99dc85a 100644 --- a/mihomo/.github/workflows/build.yml +++ b/mihomo/.github/workflows/build.yml @@ -146,17 +146,17 @@ jobs: - { goos: linux, goarch: amd64, goamd64: v3, output: amd64-v3-go120, goversion: '1.20' } steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Go if: ${{ matrix.jobs.goversion == '' && matrix.jobs.abi != '1' }} - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: '1.25' - name: Set up Go if: ${{ matrix.jobs.goversion != '' && matrix.jobs.abi != '1' }} - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.jobs.goversion }} @@ -438,7 +438,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: Meta fetch-depth: '0' @@ -497,7 +497,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/mihomo/.github/workflows/test.yml b/mihomo/.github/workflows/test.yml index dd76733df2..8d7a35c2a1 100644 --- a/mihomo/.github/workflows/test.yml +++ b/mihomo/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - 'windows-latest' # amd64 windows - 'macos-latest' # arm64 macos - 'ubuntu-24.04-arm' # arm64 linux - - 'macos-13' # amd64 macos + - 'macos-15-intel' # amd64 macos go-version: - '1.25' - '1.24' @@ -41,10 +41,10 @@ jobs: # Fix mingw trying to be smart and converting paths https://github.com/moby/moby/issues/24029#issuecomment-250412919 MSYS_NO_PATHCONV: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} diff --git a/nodepass/docs/en/api.md b/nodepass/docs/en/api.md index 37c4ca04cf..56e197bb9c 100644 --- a/nodepass/docs/en/api.md +++ b/nodepass/docs/en/api.md @@ -67,11 +67,6 @@ API Key authentication is enabled by default, automatically generated and saved "url": "...", "config": "server://0.0.0.0:8080/localhost:3000?log=info&tls=1&max=1024&mode=0&read=1h&rate=0&slot=65536&proxy=0", "restart": true, - "tags": [ - {"key": "environment", "value": "production"}, - {"key": "region", "value": "us-west-2"}, - {"key": "project", "value": "web-service"} - ], "mode": 0, "ping": 0, "pool": 0, @@ -90,7 +85,6 @@ API Key authentication is enabled by default, automatically generated and saved - `tcprx`/`tcptx`/`udprx`/`udptx`: Cumulative traffic statistics - `config`: Instance configuration URL with complete startup configuration - `restart`: Auto-restart policy -- `tags`: Optional key-value pairs for labeling and organizing instances ### Instance URL Format @@ -514,7 +508,7 @@ To properly manage lifecycles: method: 'PATCH', headers: { 'Content-Type': 'application/json', - 'X-API-Key': apiKey // If API Key is enabled + 'X-API-Key': apiKey // If API Key is enabled }, body: JSON.stringify({ alias }) }); @@ -523,31 +517,6 @@ To properly manage lifecycles: return data.success; } - // Update instance tags - async function updateInstanceTags(instanceId, tags) { - const response = await fetch(`${API_URL}/instances/${instanceId}`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': apiKey // If API Key is enabled - }, - body: JSON.stringify({ tags }) - }); - - const data = await response.json(); - return data.success; - } - - // Delete specific tags by setting their values to empty string - async function deleteInstanceTags(instanceId, tagKeys) { - const tagsToDelete = {}; - tagKeys.forEach(key => { - tagsToDelete[key] = ""; // Empty string removes the tag - }); - - return await updateInstanceTags(instanceId, tagsToDelete); - } - // Update instance URL configuration async function updateInstanceURL(instanceId, newURL) { const response = await fetch(`${API_URL}/instances/${instanceId}`, { @@ -564,44 +533,7 @@ To properly manage lifecycles: } ``` -5. **Tag Management**: Label and organize instances using key-value pairs - ```javascript - // Update instance tags via PATCH method - async function updateInstanceTags(instanceId, tags) { - const response = await fetch(`${API_URL}/instances/${instanceId}`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': apiKey - }, - body: JSON.stringify({ tags }) - }); - - return response.json(); - } - - // Add or update tags - existing tags are preserved unless specified - await updateInstanceTags('abc123', [ - {"key": "environment", "value": "production"}, // Add/update - {"key": "region", "value": "us-west-2"}, // Add/update - {"key": "team", "value": "backend"} // Add/update - ]); - - // Delete specific tags by setting empty values - await updateInstanceTags('abc123', [ - {"key": "old-tag", "value": ""}, // Delete this tag - {"key": "temp-env", "value": ""} // Delete this tag - ]); - - // Mix operations: add/update some tags, delete others - await updateInstanceTags('abc123', [ - {"key": "environment", "value": "staging"}, // Update existing - {"key": "version", "value": "2.0"}, // Add new - {"key": "deprecated", "value": ""} // Delete existing - ]); - ``` - -6. **Auto-restart Policy Management**: Configure automatic startup behavior +5. **Auto-restart Policy Management**: Configure automatic startup behavior ```javascript async function setAutoStartPolicy(instanceId, enableAutoStart) { const response = await fetch(`${API_URL}/instances/${instanceId}`, { @@ -846,24 +778,13 @@ async function configureAutoStartPolicies(instances) { for (const instance of instances) { // Enable auto-start for servers and critical clients const shouldAutoStart = instance.type === 'server' || - instance.tags?.includes('critical'); + instance.critical === true; await setAutoStartPolicy(instance.id, shouldAutoStart); } } ``` -### Tag Management Rules - -1. **Merge-based Updates**: Tags are processed with merge logic - existing tags are preserved unless explicitly updated or deleted -2. **Array Format**: Tags use array format `[{"key": "key1", "value": "value1"}]` -3. **Key Filtering**: Empty keys are automatically filtered out and rejected by validation -4. **Delete by Empty Value**: Set `value: ""` (empty string) to delete an existing tag key -5. **Add/Update Logic**: Non-empty values will add new tags or update existing ones -6. **Uniqueness Check**: Duplicate keys are not allowed within the same tag operation -7. **Limits**: Maximum 50 tags, key names ≤100 characters, values ≤500 characters -8. **Persistence**: All tag operations are automatically saved to disk and restored after restart - ## Instance Data Structure The instance object in API responses contains the following fields: @@ -877,11 +798,6 @@ The instance object in API responses contains the following fields: "url": "server://...", // Instance configuration URL "config": "server://0.0.0.0:8080/localhost:3000?log=info&tls=1&max=1024&mode=0&read=1h&rate=0&slot=65536&proxy=0", // Complete configuration URL "restart": true, // Auto-restart policy - "tags": [ // Tag array - {"key": "environment", "value": "production"}, - {"key": "project", "value": "web-service"}, - {"key": "region", "value": "us-west-2"} - ], "mode": 0, // Instance mode "tcprx": 1024, // TCP received bytes "tcptx": 2048, // TCP transmitted bytes @@ -895,7 +811,6 @@ The instance object in API responses contains the following fields: - `config` field contains the instance's complete configuration URL, auto-generated by the system - `mode` field indicates the current runtime mode of the instance - `restart` field controls the auto-restart behavior of the instance -- `tags` field is optional, only included when tags are present ### Instance Configuration Field @@ -1103,12 +1018,12 @@ const instance = await fetch(`${API_URL}/instances/abc123`, { ``` #### PATCH /instances/{id} -- **Description**: Update instance state, alias, tags, or perform control operations +- **Description**: Update instance state, alias, or perform control operations - **Authentication**: Requires API Key -- **Request body**: `{ "alias": "new alias", "action": "start|stop|restart|reset", "restart": true|false, "tags": [{"key": "key1", "value": "value1"}] }` +- **Request body**: `{ "alias": "new alias", "action": "start|stop|restart|reset", "restart": true|false }` - **Example**: ```javascript -// Update tags +// Update alias and restart policy await fetch(`${API_URL}/instances/abc123`, { method: 'PATCH', headers: { @@ -1116,14 +1031,12 @@ await fetch(`${API_URL}/instances/abc123`, { 'X-API-Key': apiKey }, body: JSON.stringify({ - tags: [ - {"key": "environment", "value": "production"}, - {"key": "region", "value": "us-west-2"} - ] + alias: "production-server", + restart: true }) }); -// Update and delete tags in one operation +// Perform control action await fetch(`${API_URL}/instances/abc123`, { method: 'PATCH', headers: { @@ -1131,11 +1044,7 @@ await fetch(`${API_URL}/instances/abc123`, { 'X-API-Key': apiKey }, body: JSON.stringify({ - tags: [ - {"key": "environment", "value": "staging"}, // Update existing - {"key": "version", "value": "2.0"}, // Add new - {"key": "old-tag", "value": ""} // Delete existing - ] + action: "restart" }) }); ``` diff --git a/nodepass/docs/en/configuration.md b/nodepass/docs/en/configuration.md index d8a779ba64..b2a51ebc1d 100644 --- a/nodepass/docs/en/configuration.md +++ b/nodepass/docs/en/configuration.md @@ -125,26 +125,40 @@ Example: nodepass "client://server.example.com:10101/127.0.0.1:8080?min=32" ``` -## Data Read Timeout -Data read timeout can be set using the URL query parameter `read`, with units in seconds or minutes: -- `read`: Data read timeout (default: 1 hour) - - Value format: integer followed by optional unit (`s` for seconds, `m` for minutes) - - Examples: `30s` (30 seconds), `5m` (5 minutes), `1h` (1 hour) +## Data Read Timeout and Connection Reuse + +The `read` parameter controls both data read timeout and connection pool reuse behavior: + +- `read`: Data read timeout and connection reuse (default: 0, meaning no timeout and no connection recycling) + - Value 0 or omitted: No data read timeout, connections are not recycled to the pool after data transfer completes + - Positive integer with time unit: Sets read timeout and enables connection reuse + - Value format: integer followed by unit (`s` for seconds, `m` for minutes, `h` for hours) + - Examples: `30s` (30 seconds), `5m` (5 minutes), `1h` (1 hour) + - If no data is received within the timeout period, the connection is closed + - After data transfer completes, the connection is recycled to the pool for reuse - Applies to both client and server modes - - If no data is received within the timeout period, the connection is closed Example: ```bash -# Set data read timeout to 5 minutes +# Set data read timeout to 5 minutes, enable connection reuse nodepass "client://server.example.com:10101/127.0.0.1:8080?read=5m" -# Set data read timeout to 30 seconds for fast-response applications +# Set data read timeout to 30 seconds for fast-response applications, enable connection reuse nodepass "client://server.example.com:10101/127.0.0.1:8080?read=30s" -# Set data read timeout to 30 minutes for long-running transfers -nodepass "client://server.example.com:10101/127.0.0.1:8080?read=30m" +# Set data read timeout to 1 hour for long-running transfers, enable connection reuse +nodepass "client://server.example.com:10101/127.0.0.1:8080?read=1h" + +# Default behavior: no timeout and no connection recycling (omit read parameter or set to 0) +nodepass "client://server.example.com:10101/127.0.0.1:8080" ``` +**Connection Reuse Use Cases:** +- **HTTP Short Connections**: Set reasonable read timeout and enable connection reuse for frequent short connections to improve pool utilization +- **Long Connection Optimization**: Set larger timeout values for long-lived connections while avoiding resource waste +- **Resource Control**: Enable connection recycling by setting timeout in scenarios requiring strict connection lifecycle control +- **Default Mode**: Use the default no-timeout no-recycling mode for scenarios requiring maximum flexibility + ## Rate Limiting NodePass supports bandwidth rate limiting for traffic control through the `rate` parameter. This feature helps prevent network congestion and ensures fair resource allocation across multiple connections. @@ -263,7 +277,7 @@ NodePass allows flexible configuration via URL query parameters. The following t | `min` | Minimum pool capacity | `64` | X | O | X | | `max` | Maximum pool capacity | `1024` | O | X | X | | `mode` | Run mode control | `0` | O | O | X | -| `read` | Data read timeout | `1h` | O | O | X | +| `read` | Data read timeout and connection reuse | `0` | O | O | X | | `rate` | Bandwidth rate limit | `0` | O | O | X | | `slot` | Maximum connection limit | `65536` | O | O | X | | `proxy` | PROXY protocol support| `0` | O | O | X | @@ -285,12 +299,13 @@ NodePass behavior can be fine-tuned using environment variables. Below is the co | Variable | Description | Default | Example | |----------|-------------|---------|---------| | `NP_SEMAPHORE_LIMIT` | Signal channel buffer size | 65536 | `export NP_SEMAPHORE_LIMIT=2048` | -| `NP_TCP_DATA_BUF_SIZE` | Buffer size for TCP data transfer | 32768 | `export NP_TCP_DATA_BUF_SIZE=65536` | +| `NP_TCP_DATA_BUF_SIZE` | Buffer size for TCP data transfer | 16384 | `export NP_TCP_DATA_BUF_SIZE=65536` | | `NP_UDP_DATA_BUF_SIZE` | Buffer size for UDP packets | 2048 | `export NP_UDP_DATA_BUF_SIZE=16384` | | `NP_HANDSHAKE_TIMEOUT` | Timeout for handshake operations | 10s | `export NP_HANDSHAKE_TIMEOUT=30s` | +| `NP_UDP_READ_TIMEOUT` | Timeout for UDP read operations | 30s | `export NP_UDP_READ_TIMEOUT=60s` | | `NP_TCP_DIAL_TIMEOUT` | Timeout for establishing TCP connections | 30s | `export NP_TCP_DIAL_TIMEOUT=60s` | | `NP_UDP_DIAL_TIMEOUT` | Timeout for establishing UDP connections | 10s | `export NP_UDP_DIAL_TIMEOUT=30s` | -| `NP_POOL_GET_TIMEOUT` | Timeout for getting connections from pool | 30s | `export NP_POOL_GET_TIMEOUT=60s` | +| `NP_POOL_GET_TIMEOUT` | Timeout for getting connections from pool | 5s | `export NP_POOL_GET_TIMEOUT=60s` | | `NP_MIN_POOL_INTERVAL` | Minimum interval between connection creations | 100ms | `export NP_MIN_POOL_INTERVAL=200ms` | | `NP_MAX_POOL_INTERVAL` | Maximum interval between connection creations | 1s | `export NP_MAX_POOL_INTERVAL=3s` | | `NP_REPORT_INTERVAL` | Interval for health check reports | 5s | `export NP_REPORT_INTERVAL=10s` | @@ -340,6 +355,12 @@ For applications relying heavily on UDP traffic: - Default (8192) works well for most cases - Consider increasing to 16384 or higher for media streaming or game servers +- `NP_UDP_READ_TIMEOUT`: Timeout for UDP read operations + - Default (30s) is suitable for most UDP application scenarios + - Controls the maximum wait time for UDP connections when no data is being transferred + - For real-time applications (e.g., gaming, VoIP), consider reducing this value to quickly detect disconnections + - For applications allowing intermittent transmission, increase this value to avoid false timeout detection + - `NP_UDP_DIAL_TIMEOUT`: Timeout for establishing UDP connections - Default (10s) provides good balance for most applications - Increase for high-latency networks or applications with slow response times diff --git a/nodepass/docs/zh/api.md b/nodepass/docs/zh/api.md index 00723d4df9..30b134f9f7 100644 --- a/nodepass/docs/zh/api.md +++ b/nodepass/docs/zh/api.md @@ -67,11 +67,6 @@ API Key 认证默认启用,首次启动自动生成并保存在 `nodepass.gob` "url": "...", "config": "server://0.0.0.0:8080/localhost:3000?log=info&tls=1&max=1024&mode=0&read=1h&rate=0&slot=65536&proxy=0", "restart": true, - "tags": [ - {"key": "environment", "value": "production"}, - {"key": "region", "value": "us-west-2"}, - {"key": "project", "value": "web-service"} - ], "mode": 0, "ping": 0, "pool": 0, @@ -90,7 +85,6 @@ API Key 认证默认启用,首次启动自动生成并保存在 `nodepass.gob` - `tcprx`/`tcptx`/`udprx`/`udptx`:累计流量统计 - `config`:实例配置URL,包含完整的启动配置 - `restart`:自启动策略 -- `tags`:可选的键值对,用于标记和组织实例 ### 实例 URL 格式 @@ -523,31 +517,6 @@ NodePass主控模式提供自动备份功能,定期备份状态文件以防止 return data.success; } - // 更新实例标签 - async function updateInstanceTags(instanceId, tags) { - const response = await fetch(`${API_URL}/instances/${instanceId}`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': apiKey // 如果启用了API Key - }, - body: JSON.stringify({ tags }) - }); - - const data = await response.json(); - return data.success; - } - - // 通过设置空字符串值删除特定标签 - async function deleteInstanceTags(instanceId, tagKeys) { - const tagsToDelete = {}; - tagKeys.forEach(key => { - tagsToDelete[key] = ""; // 空字符串会删除标签 - }); - - return await updateInstanceTags(instanceId, tagsToDelete); - } - // 更新实例URL配置 async function updateInstanceURL(instanceId, newURL) { const response = await fetch(`${API_URL}/instances/${instanceId}`, { @@ -564,43 +533,6 @@ NodePass主控模式提供自动备份功能,定期备份状态文件以防止 } ``` -5. **标签管理**:使用键值对标记和组织实例 - ```javascript - // 通过PATCH方法更新实例标签 - async function updateInstanceTags(instanceId, tags) { - const response = await fetch(`${API_URL}/instances/${instanceId}`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': apiKey - }, - body: JSON.stringify({ tags }) - }); - - return response.json(); - } - - // 添加或更新标签 - 现有标签会保留,除非被显式指定 - await updateInstanceTags('abc123', [ - {"key": "environment", "value": "production"}, // 添加/更新 - {"key": "region", "value": "us-west-2"}, // 添加/更新 - {"key": "team", "value": "backend"} // 添加/更新 - ]); - - // 通过设置空值删除特定标签 - await updateInstanceTags('abc123', [ - {"key": "old-tag", "value": ""}, // 删除此标签 - {"key": "temp-env", "value": ""} // 删除此标签 - ]); - - // 混合操作:添加/更新某些标签,删除其他标签 - await updateInstanceTags('abc123', [ - {"key": "environment", "value": "staging"}, // 更新现有 - {"key": "version", "value": "2.0"}, // 添加新的 - {"key": "deprecated", "value": ""} // 删除现有 - ]); - ``` - 6. **自启动策略管理**:配置自动启动行为 ```javascript async function setAutoStartPolicy(instanceId, enableAutoStart) { @@ -846,24 +778,13 @@ async function configureAutoStartPolicies(instances) { for (const instance of instances) { // 为服务器和关键客户端启用自启动 const shouldAutoStart = instance.type === 'server' || - instance.tags?.includes('critical'); + instance.critical === true; await setAutoStartPolicy(instance.id, shouldAutoStart); } } ``` -### 标签管理规则 - -1. **合并式更新**:标签采用合并逻辑处理 - 现有标签会保留,除非被显式更新或删除 -2. **数组格式**:标签使用数组格式 `[{"key": "key1", "value": "value1"}]` -3. **键过滤**:空键会被自动过滤并被验证拒绝 -4. **空值删除**:设置 `value: ""` (空字符串)可删除现有标签键 -5. **添加/更新逻辑**:非空值会添加新标签或更新现有标签 -6. **唯一性检查**:同一标签操作中不允许重复的键名 -7. **限制**:最多50个标签,键名长度≤100字符,值长度≤500字符 -8. **持久化**:所有标签操作自动保存到磁盘,重启后恢复 - ## 实例数据结构 API响应中的实例对象包含以下字段: @@ -877,11 +798,6 @@ API响应中的实例对象包含以下字段: "url": "server://...", // 实例配置URL "config": "server://0.0.0.0:8080/localhost:3000?log=info&tls=1&max=1024&mode=0&read=1h&rate=0&slot=65536&proxy=0", // 完整配置URL "restart": true, // 自启动策略 - "tags": [ // 标签数组 - {"key": "environment", "value": "production"}, - {"key": "project", "value": "web-service"}, - {"key": "region", "value": "us-west-2"} - ], "mode": 0, // 运行模式 "tcprx": 1024, // TCP接收字节数 "tcptx": 2048, // TCP发送字节数 @@ -895,7 +811,6 @@ API响应中的实例对象包含以下字段: - `config` 字段包含实例的完整配置URL,由系统自动生成 - `mode` 字段表示实例当前的运行模式 - `restart` 字段控制实例的自启动行为 -- `tags` 字段为可选,仅在设置标签时存在 ### 实例配置字段 @@ -1103,12 +1018,12 @@ const instance = await fetch(`${API_URL}/instances/abc123`, { ``` #### PATCH /instances/{id} -- **描述**:更新实例状态、别名、标签或执行控制操作 +- **描述**:更新实例状态、别名或执行控制操作 - **认证**:需要API Key -- **请求体**:`{ "alias": "新别名", "action": "start|stop|restart|reset", "restart": true|false, "tags": [{"key": "键", "value": "值"}] }` +- **请求体**:`{ "alias": "新别名", "action": "start|stop|restart|reset", "restart": true|false }` - **示例**: ```javascript -// 更新标签 +// 更新别名和自启动策略 await fetch(`${API_URL}/instances/abc123`, { method: 'PATCH', headers: { @@ -1116,14 +1031,12 @@ await fetch(`${API_URL}/instances/abc123`, { 'X-API-Key': apiKey }, body: JSON.stringify({ - tags: [ - {"key": "environment", "value": "production"}, - {"key": "region", "value": "us-west-2"} - ] + alias: "生产服务器", + restart: true }) }); -// 一次操作中更新和删除标签 +// 执行控制操作 await fetch(`${API_URL}/instances/abc123`, { method: 'PATCH', headers: { @@ -1131,11 +1044,7 @@ await fetch(`${API_URL}/instances/abc123`, { 'X-API-Key': apiKey }, body: JSON.stringify({ - tags: [ - {"key": "environment", "value": "staging"}, // 更新现有 - {"key": "version", "value": "2.0"}, // 添加新的 - {"key": "old-tag", "value": ""} // 删除现有 - ] + action: "restart" }) }); ``` diff --git a/nodepass/docs/zh/configuration.md b/nodepass/docs/zh/configuration.md index 40c34f3fa5..fe56cdf6a3 100644 --- a/nodepass/docs/zh/configuration.md +++ b/nodepass/docs/zh/configuration.md @@ -125,26 +125,40 @@ nodepass "server://0.0.0.0:10101/remote.example.com:8080?mode=2" nodepass "client://server.example.com:10101/127.0.0.1:8080?min=32" ``` -## 数据读取超时 -数据读取超时可以通过URL查询参数`read`设置,单位为秒或分钟: -- `read`: 数据读取超时时间(默认: 1小时) - - 值格式:整数后跟可选单位(`s`表示秒,`m`表示分钟) - - 示例:`30s`(30秒),`5m`(5分钟),`1h`(1小时) +## 数据读取超时与连接重用 + +`read`参数用于控制数据读取超时时间和连接池连接的重用行为: + +- `read`: 数据读取超时时间(默认: 0,表示无超时且不回收连接) + - 值为0或省略:无数据读取超时,连接在数据传输完成后不回收到连接池 + - 正整数加时间单位:设置读取超时时间,并启用连接重用 + - 值格式:整数后跟单位(`s`表示秒,`m`表示分钟,`h`表示小时) + - 示例:`30s`(30秒),`5m`(5分钟),`1h`(1小时) + - 如果在超时时间内未接收到数据,连接将被关闭 + - 数据传输完成后,连接将被回收到连接池供重用 - 适用于客户端和服务端模式 - - 如果在超时时间内未接收到数据,连接将被关闭 示例: ```bash -# 设置数据读取超时为5分钟 +# 设置数据读取超时为5分钟,启用连接重用 nodepass "client://server.example.com:10101/127.0.0.1:8080?read=5m" -# 设置数据读取超时为30秒,适用于快速响应应用 +# 设置数据读取超时为30秒,适用于快速响应应用,启用连接重用 nodepass "client://server.example.com:10101/127.0.0.1:8080?read=30s" -# 设置数据读取超时为30分钟,适用于长时间传输 -nodepass "client://server.example.com:10101/127.0.0.1:8080?read=30m" +# 设置数据读取超时为1小时,适用于长时间传输,启用连接重用 +nodepass "client://server.example.com:10101/127.0.0.1:8080?read=1h" + +# 默认行为:无超时且不回收连接(省略read参数或设置为0) +nodepass "client://server.example.com:10101/127.0.0.1:8080" ``` +**连接重用使用场景:** +- **HTTP短连接**:为频繁的短连接设置合理的读取超时并启用连接重用,提高连接池利用率 +- **长连接优化**:为长时间保持的连接设置较大的超时值,同时避免资源浪费 +- **资源控制**:在需要严格控制连接生命周期的场景下,通过设置超时启用连接回收 +- **默认模式**:对于需要最大灵活性的场景,使用默认的无超时不回收模式 + ## 速率限制 NodePass支持通过`rate`参数进行带宽速率限制,用于流量控制。此功能有助于防止网络拥塞,确保多个连接间的公平资源分配。 @@ -263,7 +277,7 @@ NodePass支持通过URL查询参数进行灵活配置,不同参数在 server | `min` | 最小连接池容量 | `64` | X | O | X | | `max` | 最大连接池容量 | `1024` | O | X | X | | `mode` | 运行模式控制 | `0` | O | O | X | -| `read` | 读取超时时间 | `1h` | O | O | X | +| `read` | 数据读取超时与连接重用 | `0` | O | O | X | | `rate` | 带宽速率限制 | `0` | O | O | X | | `slot` | 最大连接数限制 | `65536` | O | O | X | | `proxy` | PROXY协议支持 | `0` | O | O | X | @@ -286,12 +300,13 @@ NodePass支持通过URL查询参数进行灵活配置,不同参数在 server | 变量 | 描述 | 默认值 | 示例 | |----------|-------------|---------|---------| | `NP_SEMAPHORE_LIMIT` | 信号缓冲区大小 | 65536 | `export NP_SEMAPHORE_LIMIT=2048` | -| `NP_TCP_DATA_BUF_SIZE` | TCP数据传输缓冲区大小 | 32768 | `export NP_TCP_DATA_BUF_SIZE=65536` | +| `NP_TCP_DATA_BUF_SIZE` | TCP数据传输缓冲区大小 | 16384 | `export NP_TCP_DATA_BUF_SIZE=65536` | | `NP_UDP_DATA_BUF_SIZE` | UDP数据包缓冲区大小 | 2048 | `export NP_UDP_DATA_BUF_SIZE=16384` | | `NP_HANDSHAKE_TIMEOUT` | 握手操作超时 | 10s | `export NP_HANDSHAKE_TIMEOUT=30s` | +| `NP_UDP_READ_TIMEOUT` | UDP读取操作超时 | 30s | `export NP_UDP_READ_TIMEOUT=60s` | | `NP_TCP_DIAL_TIMEOUT` | TCP连接建立超时 | 30s | `export NP_TCP_DIAL_TIMEOUT=60s` | | `NP_UDP_DIAL_TIMEOUT` | UDP连接建立超时 | 10s | `export NP_UDP_DIAL_TIMEOUT=30s` | -| `NP_POOL_GET_TIMEOUT` | 从连接池获取连接的超时时间 | 30s | `export NP_POOL_GET_TIMEOUT=60s` | +| `NP_POOL_GET_TIMEOUT` | 从连接池获取连接的超时时间 | 5s | `export NP_POOL_GET_TIMEOUT=60s` | | `NP_MIN_POOL_INTERVAL` | 连接创建之间的最小间隔 | 100ms | `export NP_MIN_POOL_INTERVAL=200ms` | | `NP_MAX_POOL_INTERVAL` | 连接创建之间的最大间隔 | 1s | `export NP_MAX_POOL_INTERVAL=3s` | | `NP_REPORT_INTERVAL` | 健康检查报告间隔 | 5s | `export NP_REPORT_INTERVAL=10s` | @@ -341,6 +356,12 @@ NodePass支持通过URL查询参数进行灵活配置,不同参数在 server - 默认值(8192)适用于大多数情况 - 考虑为媒体流或游戏服务器增加到16384或更高 +- `NP_UDP_READ_TIMEOUT`:UDP读取操作超时 + - 默认值(30s)适用于大多数UDP应用场景 + - 控制UDP连接在无数据传输时的最大等待时间 + - 对于实时性要求高的应用(如游戏、VoIP)可以适当减小此值以快速检测断线 + - 对于允许间歇性传输的应用可以增加此值以避免误判超时 + - `NP_UDP_DIAL_TIMEOUT`:UDP连接建立超时 - 默认值(10s)为大多数应用提供良好平衡 - 对于高延迟网络或响应缓慢的应用增加此值 diff --git a/nodepass/go.mod b/nodepass/go.mod index 8b8584acfe..bcab69c021 100644 --- a/nodepass/go.mod +++ b/nodepass/go.mod @@ -4,7 +4,7 @@ go 1.25.0 require ( github.com/NodePassProject/cert v1.0.1 - github.com/NodePassProject/conn v1.0.15 + github.com/NodePassProject/conn v1.0.16 github.com/NodePassProject/logs v1.0.3 - github.com/NodePassProject/pool v1.0.30 + github.com/NodePassProject/pool v1.0.40 ) diff --git a/nodepass/go.sum b/nodepass/go.sum index 9da59339ed..2edae160dd 100644 --- a/nodepass/go.sum +++ b/nodepass/go.sum @@ -1,8 +1,8 @@ github.com/NodePassProject/cert v1.0.1 h1:BDy2tTOudy6yk7hvcmScAJMw4NrpCdSCsbuu7hHsIuw= github.com/NodePassProject/cert v1.0.1/go.mod h1:wP7joOJeQAIlIuOUmhHPwMExjuwGa4XApMWQYChGSrk= -github.com/NodePassProject/conn v1.0.15 h1:YJaWphxGO4EZGdel/Lw4taLcb2hmHefjJipkilHn0B4= -github.com/NodePassProject/conn v1.0.15/go.mod h1:xfQ7ZLUxrtdLsljGHYYCToW+Hdg6DAbmL1Cs94n5h6E= +github.com/NodePassProject/conn v1.0.16 h1:ojHfyBveZMcyOikdUs1SOW4yKp92NOBnNhfNenLYljc= +github.com/NodePassProject/conn v1.0.16/go.mod h1:xfQ7ZLUxrtdLsljGHYYCToW+Hdg6DAbmL1Cs94n5h6E= github.com/NodePassProject/logs v1.0.3 h1:CDUZVQ477vmmFQHazrQCWM0gJPNINm0C2N3FzC4jVyw= github.com/NodePassProject/logs v1.0.3/go.mod h1:TwtPXOzLtb8iH+fdduQjEEywICXivsM39cy9AinMSks= -github.com/NodePassProject/pool v1.0.30 h1:EupeGn6nTOCzybQMHWmEphPjR7CTEptgOFvkD0UMw6Q= -github.com/NodePassProject/pool v1.0.30/go.mod h1:joQFk1oocg56QpJ1QK/2g5Jv/AyqYUQgPXMG1gWe8iA= +github.com/NodePassProject/pool v1.0.40 h1:sGvhtTYpR2svltSa+7yMR1thlQZrNIR/LMyF8NudO58= +github.com/NodePassProject/pool v1.0.40/go.mod h1:joQFk1oocg56QpJ1QK/2g5Jv/AyqYUQgPXMG1gWe8iA= diff --git a/nodepass/internal/client.go b/nodepass/internal/client.go index 2c37719163..d5637fa121 100644 --- a/nodepass/internal/client.go +++ b/nodepass/internal/client.go @@ -45,6 +45,10 @@ func NewClient(parsedURL *url.URL, logger *logs.Logger) (*Client, error) { return &buf }, }, + cleanURL: &url.URL{Scheme: "np", Fragment: "c"}, + flushURL: &url.URL{Scheme: "np", Fragment: "f"}, + pingURL: &url.URL{Scheme: "np", Fragment: "i"}, + pongURL: &url.URL{Scheme: "np", Fragment: "o"}, }, tunnelName: parsedURL.Hostname(), } @@ -177,7 +181,7 @@ func (c *Client) tunnelHandshake() error { } c.tunnelTCPConn = tunnelTCPConn.(*net.TCPConn) - c.bufReader = bufio.NewReader(&conn.TimeoutReader{Conn: c.tunnelTCPConn, Timeout: 2 * reportInterval}) + c.bufReader = bufio.NewReader(&conn.TimeoutReader{Conn: c.tunnelTCPConn, Timeout: 3 * reportInterval}) c.tunnelTCPConn.SetKeepAlive(true) c.tunnelTCPConn.SetKeepAlivePeriod(reportInterval) diff --git a/nodepass/internal/common.go b/nodepass/internal/common.go index ae5f45fef0..32a4acaf65 100644 --- a/nodepass/internal/common.go +++ b/nodepass/internal/common.go @@ -48,11 +48,17 @@ type Common struct { rateLimit int // 速率限制 rateLimiter *conn.RateLimiter // 全局限速器 readTimeout time.Duration // 读取超时 + poolReuse bool // 池重用标志 bufReader *bufio.Reader // 缓冲读取器 tcpBufferPool *sync.Pool // TCP缓冲区池 udpBufferPool *sync.Pool // UDP缓冲区池 signalChan chan string // 信号通道 checkPoint time.Time // 检查点时间 + lastClean time.Time // 上次清理时间 + cleanURL *url.URL // 清理信号 + flushURL *url.URL // 重置信号 + pingURL *url.URL // PING信号 + pongURL *url.URL // PONG信号 slotLimit int32 // 槽位限制 tcpSlot int32 // TCP连接数 udpSlot int32 // UDP连接数 @@ -67,12 +73,13 @@ type Common struct { // 配置变量,可通过环境变量调整 var ( semaphoreLimit = getEnvAsInt("NP_SEMAPHORE_LIMIT", 65536) // 信号量限制 - tcpDataBufSize = getEnvAsInt("NP_TCP_DATA_BUF_SIZE", 32768) // TCP缓冲区大小 + tcpDataBufSize = getEnvAsInt("NP_TCP_DATA_BUF_SIZE", 16384) // TCP缓冲区大小 udpDataBufSize = getEnvAsInt("NP_UDP_DATA_BUF_SIZE", 2048) // UDP缓冲区大小 handshakeTimeout = getEnvAsDuration("NP_HANDSHAKE_TIMEOUT", 10*time.Second) // 握手超时 tcpDialTimeout = getEnvAsDuration("NP_TCP_DIAL_TIMEOUT", 30*time.Second) // TCP拨号超时 udpDialTimeout = getEnvAsDuration("NP_UDP_DIAL_TIMEOUT", 10*time.Second) // UDP拨号超时 - poolGetTimeout = getEnvAsDuration("NP_POOL_GET_TIMEOUT", 30*time.Second) // 池连接获取超时 + udpReadTimeout = getEnvAsDuration("NP_UDP_READ_TIMEOUT", 30*time.Second) // UDP读取超时 + poolGetTimeout = getEnvAsDuration("NP_POOL_GET_TIMEOUT", 5*time.Second) // 池连接获取超时 minPoolInterval = getEnvAsDuration("NP_MIN_POOL_INTERVAL", 100*time.Millisecond) // 最小池间隔 maxPoolInterval = getEnvAsDuration("NP_MAX_POOL_INTERVAL", 1*time.Second) // 最大池间隔 reportInterval = getEnvAsDuration("NP_REPORT_INTERVAL", 5*time.Second) // 报告间隔 @@ -83,13 +90,13 @@ var ( // 默认配置 const ( - defaultMinPool = 64 // 默认最小池容量 - defaultMaxPool = 1024 // 默认最大池容量 - defaultRunMode = "0" // 默认运行模式 - defaultReadTimeout = 1 * time.Hour // 默认读取超时 - defaultRateLimit = 0 // 默认速率限制 - defaultSlotLimit = 65536 // 默认槽位限制 - defaultProxyProtocol = "0" // 默认代理协议 + defaultMinPool = 64 // 默认最小池容量 + defaultMaxPool = 1024 // 默认最大池容量 + defaultRunMode = "0" // 默认运行模式 + defaultReadTimeout = 0 * time.Second // 默认读取超时 + defaultRateLimit = 0 // 默认速率限制 + defaultSlotLimit = 65536 // 默认槽位限制 + defaultProxyProtocol = "0" // 默认代理协议 ) // getTCPBuffer 获取TCP缓冲区 @@ -274,12 +281,13 @@ func (c *Common) getRunMode(parsedURL *url.URL) { } } -// getReadTimeout 获取读取超时设置 +// getReadTimeout 获取读取超时设置并配置池重用 func (c *Common) getReadTimeout(parsedURL *url.URL) { if timeout := parsedURL.Query().Get("read"); timeout != "" { if value, err := time.ParseDuration(timeout); err == nil && value > 0 { c.readTimeout = value } + c.poolReuse = true } else { c.readTimeout = defaultReadTimeout } @@ -584,8 +592,6 @@ func (c *Common) commonQueue() error { // healthCheck 共用健康度检查 func (c *Common) healthCheck() error { - flushURL := &url.URL{Fragment: "0"} // 连接池刷新信号 - pingURL := &url.URL{Fragment: "i"} // PING信号 for { if c.ctx.Err() != nil { return fmt.Errorf("healthCheck: context error: %w", c.ctx.Err()) @@ -596,13 +602,30 @@ func (c *Common) healthCheck() error { continue } + // 连接池定期清理 + if time.Since(c.lastClean) >= ReloadInterval { + // 发送清理信号到对端 + if c.ctx.Err() == nil && c.tunnelTCPConn != nil { + _, err := c.tunnelTCPConn.Write(c.encode([]byte(c.cleanURL.String()))) + if err != nil { + c.mu.Unlock() + return fmt.Errorf("healthCheck: write clean signal failed: %w", err) + } + } + c.tunnelPool.Clean() + c.lastClean = time.Now() + c.logger.Debug("Tunnel pool cleaned: %v active connections", c.tunnelPool.Active()) + } + // 连接池健康度检查 if c.tunnelPool.ErrorCount() > c.tunnelPool.Active()/2 { // 发送刷新信号到对端 - _, err := c.tunnelTCPConn.Write(c.encode([]byte(flushURL.String()))) - if err != nil { - c.mu.Unlock() - return fmt.Errorf("healthCheck: write flush signal failed: %w", err) + if c.ctx.Err() == nil && c.tunnelTCPConn != nil { + _, err := c.tunnelTCPConn.Write(c.encode([]byte(c.flushURL.String()))) + if err != nil { + c.mu.Unlock() + return fmt.Errorf("healthCheck: write flush signal failed: %w", err) + } } c.tunnelPool.Flush() c.tunnelPool.ResetError() @@ -613,15 +636,17 @@ func (c *Common) healthCheck() error { case <-time.After(reportInterval): } - c.logger.Debug("Tunnel pool reset: %v active connections", c.tunnelPool.Active()) + c.logger.Debug("Tunnel pool flushed: %v active connections", c.tunnelPool.Active()) } // 发送PING信号 c.checkPoint = time.Now() - _, err := c.tunnelTCPConn.Write(c.encode([]byte(pingURL.String()))) - if err != nil { - c.mu.Unlock() - return fmt.Errorf("healthCheck: write ping signal failed: %w", err) + if c.ctx.Err() == nil && c.tunnelTCPConn != nil { + _, err := c.tunnelTCPConn.Write(c.encode([]byte(c.pingURL.String()))) + if err != nil { + c.mu.Unlock() + return fmt.Errorf("healthCheck: write ping signal failed: %w", err) + } } c.mu.Unlock() @@ -682,22 +707,20 @@ func (c *Common) commonTCPLoop() { c.logger.Debug("Target connection: %v <-> %v", targetConn.LocalAddr(), targetConn.RemoteAddr()) go func(targetConn net.Conn) { - // 尝试获取TCP连接槽位 - if !c.tryAcquireSlot(false) { - c.logger.Error("commonTCPLoop: TCP slot limit reached: %v/%v", c.tcpSlot, c.slotLimit) - if targetConn != nil { - targetConn.Close() - } - return - } - defer func() { if targetConn != nil { targetConn.Close() } - c.releaseSlot(false) }() + // 尝试获取TCP连接槽位 + if !c.tryAcquireSlot(false) { + c.logger.Error("commonTCPLoop: TCP slot limit reached: %v/%v", c.tcpSlot, c.slotLimit) + return + } + + defer c.releaseSlot(false) + // 从连接池获取连接 id, remoteConn, err := c.tunnelPool.ServerGet(poolGetTimeout) if err != nil { @@ -708,13 +731,20 @@ func (c *Common) commonTCPLoop() { c.logger.Debug("Tunnel connection: get %v <- pool active %v", id, c.tunnelPool.Active()) defer func() { + // 池连接关闭或复用 + if !c.poolReuse && remoteConn != nil { + remoteConn.Close() + c.logger.Debug("Tunnel connection: closed %v", id) + return + } + remoteConn.SetReadDeadline(time.Time{}) c.tunnelPool.Put(id, remoteConn) c.logger.Debug("Tunnel connection: put %v -> pool active %v", id, c.tunnelPool.Active()) }() c.logger.Debug("Tunnel connection: %v <-> %v", remoteConn.LocalAddr(), remoteConn.RemoteAddr()) - // 构建并发送启动URL到客户端 + // 构建并发送启动信号 launchURL := &url.URL{ Scheme: "np", Host: targetConn.RemoteAddr().String(), @@ -722,13 +752,15 @@ func (c *Common) commonTCPLoop() { Fragment: "1", // TCP模式 } - c.mu.Lock() - _, err = c.tunnelTCPConn.Write(c.encode([]byte(launchURL.String()))) - c.mu.Unlock() + if c.ctx.Err() == nil && c.tunnelTCPConn != nil { + c.mu.Lock() + _, err = c.tunnelTCPConn.Write(c.encode([]byte(launchURL.String()))) + c.mu.Unlock() - if err != nil { - c.logger.Error("commonTCPLoop: write launch signal failed: %v", err) - return + if err != nil { + c.logger.Error("commonTCPLoop: write launch signal failed: %v", err) + return + } } c.logger.Debug("TCP launch signal: cid %v -> %v", id, c.tunnelTCPConn.RemoteAddr()) @@ -806,25 +838,26 @@ func (c *Common) commonUDPLoop() { go func(remoteConn net.Conn, clientAddr *net.UDPAddr, sessionKey, id string) { defer func() { - // 重置池连接的读取超时 + // 清理UDP会话和释放槽位 + c.targetUDPSession.Delete(sessionKey) + c.releaseSlot(true) + + // 池连接关闭或复用 + if !c.poolReuse && remoteConn != nil { + remoteConn.Close() + c.logger.Debug("Tunnel connection: closed %v", id) + return + } remoteConn.SetReadDeadline(time.Time{}) c.tunnelPool.Put(id, remoteConn) c.logger.Debug("Tunnel connection: put %v -> pool active %v", id, c.tunnelPool.Active()) - - // 清理UDP会话 - c.targetUDPSession.Delete(sessionKey) - c.releaseSlot(true) }() buffer := c.getUDPBuffer() defer c.putUDPBuffer(buffer) - reader := &conn.TimeoutReader{Conn: remoteConn, Timeout: c.readTimeout} + reader := &conn.TimeoutReader{Conn: remoteConn, Timeout: udpReadTimeout} for { - if c.ctx.Err() != nil { - return - } - // 从池连接读取数据 x, err := reader.Read(buffer) if err != nil { @@ -847,7 +880,7 @@ func (c *Common) commonUDPLoop() { } }(remoteConn, clientAddr, sessionKey, id) - // 构建并发送启动URL到客户端 + // 构建并发送启动信号 launchURL := &url.URL{ Scheme: "np", Host: clientAddr.String(), @@ -855,12 +888,14 @@ func (c *Common) commonUDPLoop() { Fragment: "2", // UDP模式 } - c.mu.Lock() - _, err = c.tunnelTCPConn.Write(c.encode([]byte(launchURL.String()))) - c.mu.Unlock() - if err != nil { - c.logger.Error("commonUDPLoop: write launch signal failed: %v", err) - continue + if c.ctx.Err() == nil && c.tunnelTCPConn != nil { + c.mu.Lock() + _, err = c.tunnelTCPConn.Write(c.encode([]byte(launchURL.String()))) + c.mu.Unlock() + if err != nil { + c.logger.Error("commonUDPLoop: write launch signal failed: %v", err) + continue + } } c.logger.Debug("UDP launch signal: cid %v -> %v", id, c.tunnelTCPConn.RemoteAddr()) @@ -885,7 +920,6 @@ func (c *Common) commonUDPLoop() { // commonOnce 共用处理单个请求 func (c *Common) commonOnce() error { - pongURL := &url.URL{Fragment: "o"} // PONG信号 for { // 等待连接池准备就绪 if !c.tunnelPool.Ready() { @@ -915,7 +949,23 @@ func (c *Common) commonOnce() error { // 处理信号 switch signalURL.Fragment { - case "0": // 连接池刷新 + case "1": // TCP + go c.commonTCPOnce(signalURL) + case "2": // UDP + go c.commonUDPOnce(signalURL) + case "c": // 连接池清理 + go func() { + c.tunnelPool.Clean() + + select { + case <-c.ctx.Done(): + return + case <-time.After(reportInterval): + } + + c.logger.Debug("Tunnel pool cleaned: %v active connections", c.tunnelPool.Active()) + }() + case "f": // 连接池刷新 go func() { c.tunnelPool.Flush() c.tunnelPool.ResetError() @@ -926,18 +976,16 @@ func (c *Common) commonOnce() error { case <-time.After(reportInterval): } - c.logger.Debug("Tunnel pool reset: %v active connections", c.tunnelPool.Active()) + c.logger.Debug("Tunnel pool flushed: %v active connections", c.tunnelPool.Active()) }() - case "1": // TCP - go c.commonTCPOnce(signalURL) - case "2": // UDP - go c.commonUDPOnce(signalURL) case "i": // PING - c.mu.Lock() - _, err := c.tunnelTCPConn.Write(c.encode([]byte(pongURL.String()))) - c.mu.Unlock() - if err != nil { - return fmt.Errorf("commonOnce: write pong signal failed: %w", err) + if c.ctx.Err() == nil && c.tunnelTCPConn != nil { + c.mu.Lock() + _, err := c.tunnelTCPConn.Write(c.encode([]byte(c.pongURL.String()))) + c.mu.Unlock() + if err != nil { + return fmt.Errorf("commonOnce: write pong signal failed: %w", err) + } } case "o": // PONG // 发送检查点事件 @@ -964,17 +1012,10 @@ func (c *Common) commonTCPOnce(signalURL *url.URL) { } c.logger.Debug("TCP launch signal: cid %v <- %v", id, c.tunnelTCPConn.RemoteAddr()) - // 尝试获取TCP连接槽位 - if !c.tryAcquireSlot(false) { - c.logger.Error("commonTCPOnce: TCP slot limit reached: %v/%v", c.tcpSlot, c.slotLimit) - return - } - defer c.releaseSlot(false) - // 从连接池获取连接 - remoteConn, err := c.tunnelPool.ClientGet(id) + remoteConn, err := c.tunnelPool.ClientGet(id, poolGetTimeout) if err != nil { - c.logger.Error("commonTCPOnce: clientGet failed: %v", err) + c.logger.Error("commonTCPOnce: request timeout: %v", err) c.tunnelPool.AddError() return } @@ -982,12 +1023,27 @@ func (c *Common) commonTCPOnce(signalURL *url.URL) { c.logger.Debug("Tunnel connection: get %v <- pool active %v", id, c.tunnelPool.Active()) defer func() { + // 池连接关闭或复用 + if !c.poolReuse && remoteConn != nil { + remoteConn.Close() + c.logger.Debug("Tunnel connection: closed %v", id) + return + } + remoteConn.SetReadDeadline(time.Time{}) c.tunnelPool.Put(id, remoteConn) c.logger.Debug("Tunnel connection: put %v -> pool active %v", id, c.tunnelPool.Active()) }() c.logger.Debug("Tunnel connection: %v <-> %v", remoteConn.LocalAddr(), remoteConn.RemoteAddr()) + // 尝试获取TCP连接槽位 + if !c.tryAcquireSlot(false) { + c.logger.Error("commonTCPOnce: TCP slot limit reached: %v/%v", c.tcpSlot, c.slotLimit) + return + } + + defer c.releaseSlot(false) + // 连接到目标TCP地址 targetConn, err := net.DialTimeout("tcp", c.targetTCPAddr.String(), tcpDialTimeout) if err != nil { @@ -1033,17 +1089,10 @@ func (c *Common) commonUDPOnce(signalURL *url.URL) { } c.logger.Debug("UDP launch signal: cid %v <- %v", id, c.tunnelTCPConn.RemoteAddr()) - // 尝试获取UDP连接槽位 - if !c.tryAcquireSlot(true) { - c.logger.Error("commonUDPOnce: UDP slot limit reached: %v/%v", c.udpSlot, c.slotLimit) - return - } - defer c.releaseSlot(true) - // 获取池连接 - remoteConn, err := c.tunnelPool.ClientGet(id) + remoteConn, err := c.tunnelPool.ClientGet(id, poolGetTimeout) if err != nil { - c.logger.Error("commonUDPOnce: clientGet failed: %v", err) + c.logger.Error("commonUDPOnce: request timeout: %v", err) c.tunnelPool.AddError() return } @@ -1051,8 +1100,21 @@ func (c *Common) commonUDPOnce(signalURL *url.URL) { c.logger.Debug("Tunnel connection: get %v <- pool active %v", id, c.tunnelPool.Active()) c.logger.Debug("Tunnel connection: %v <-> %v", remoteConn.LocalAddr(), remoteConn.RemoteAddr()) + defer func() { + // 池连接关闭或复用 + if !c.poolReuse && remoteConn != nil { + remoteConn.Close() + c.logger.Debug("Tunnel connection: closed %v", id) + return + } + remoteConn.SetReadDeadline(time.Time{}) + c.tunnelPool.Put(id, remoteConn) + c.logger.Debug("Tunnel connection: put %v -> pool active %v", id, c.tunnelPool.Active()) + }() + var targetConn net.Conn sessionKey := signalURL.Host + isNewSession := false // 获取或创建目标UDP会话 if session, ok := c.targetUDPSession.Load(sessionKey); ok { @@ -1060,15 +1122,36 @@ func (c *Common) commonUDPOnce(signalURL *url.URL) { c.logger.Debug("Using UDP session: %v <-> %v", targetConn.LocalAddr(), targetConn.RemoteAddr()) } else { // 创建新的会话 + isNewSession = true + + // 尝试获取UDP连接槽位 + if !c.tryAcquireSlot(true) { + c.logger.Error("commonUDPOnce: UDP slot limit reached: %v/%v", c.udpSlot, c.slotLimit) + return + } + newSession, err := net.DialTimeout("udp", c.targetUDPAddr.String(), udpDialTimeout) if err != nil { c.logger.Error("commonUDPOnce: dialTimeout failed: %v", err) + c.releaseSlot(true) return } targetConn = &conn.StatConn{Conn: newSession, RX: &c.udpRX, TX: &c.udpTX, Rate: c.rateLimiter} c.targetUDPSession.Store(sessionKey, targetConn) c.logger.Debug("Target connection: %v <-> %v", targetConn.LocalAddr(), targetConn.RemoteAddr()) } + + if isNewSession { + defer func() { + // 清理UDP会话和释放槽位 + c.targetUDPSession.Delete(sessionKey) + if targetConn != nil { + targetConn.Close() + } + c.releaseSlot(true) + }() + } + c.logger.Debug("Starting transfer: %v <-> %v", remoteConn.LocalAddr(), targetConn.LocalAddr()) done := make(chan struct{}, 2) @@ -1078,12 +1161,9 @@ func (c *Common) commonUDPOnce(signalURL *url.URL) { buffer := c.getUDPBuffer() defer c.putUDPBuffer(buffer) - reader := &conn.TimeoutReader{Conn: remoteConn, Timeout: c.readTimeout} - for { - if c.ctx.Err() != nil { - return - } + reader := &conn.TimeoutReader{Conn: remoteConn, Timeout: udpReadTimeout} + for { // 从隧道连接读取数据 x, err := reader.Read(buffer) if err != nil { @@ -1112,12 +1192,9 @@ func (c *Common) commonUDPOnce(signalURL *url.URL) { buffer := c.getUDPBuffer() defer c.putUDPBuffer(buffer) - reader := &conn.TimeoutReader{Conn: targetConn, Timeout: c.readTimeout} - for { - if c.ctx.Err() != nil { - return - } + reader := &conn.TimeoutReader{Conn: targetConn, Timeout: udpReadTimeout} + for { // 从目标UDP连接读取数据 x, err := reader.Read(buffer) if err != nil { @@ -1143,17 +1220,6 @@ func (c *Common) commonUDPOnce(signalURL *url.URL) { // 等待任一协程完成 <-done - - // 清理连接和会话 - c.targetUDPSession.Delete(sessionKey) - if targetConn != nil { - targetConn.Close() - } - - // 重置池连接的读取超时 - remoteConn.SetReadDeadline(time.Time{}) - c.tunnelPool.Put(id, remoteConn) - c.logger.Debug("Tunnel connection: put %v -> pool active %v", id, c.tunnelPool.Active()) } // singleControl 单端控制处理循环 @@ -1231,22 +1297,20 @@ func (c *Common) singleTCPLoop() error { c.logger.Debug("Tunnel connection: %v <-> %v", tunnelConn.LocalAddr(), tunnelConn.RemoteAddr()) go func(tunnelConn net.Conn) { - // 尝试获取TCP连接槽位 - if !c.tryAcquireSlot(false) { - c.logger.Error("singleTCPLoop: TCP slot limit reached: %v/%v", c.tcpSlot, c.slotLimit) - if tunnelConn != nil { - tunnelConn.Close() - } - return - } - defer func() { - c.releaseSlot(false) if tunnelConn != nil { tunnelConn.Close() } }() + // 尝试获取TCP连接槽位 + if !c.tryAcquireSlot(false) { + c.logger.Error("singleTCPLoop: TCP slot limit reached: %v/%v", c.tcpSlot, c.slotLimit) + return + } + + defer c.releaseSlot(false) + // 尝试建立目标连接 targetConn, err := net.DialTimeout("tcp", c.targetTCPAddr.String(), tcpDialTimeout) if err != nil { @@ -1348,7 +1412,7 @@ func (c *Common) singleUDPLoop() error { buffer := c.getUDPBuffer() defer c.putUDPBuffer(buffer) - reader := &conn.TimeoutReader{Conn: targetConn, Timeout: c.readTimeout} + reader := &conn.TimeoutReader{Conn: targetConn, Timeout: udpReadTimeout} for { if c.ctx.Err() != nil { diff --git a/nodepass/internal/master.go b/nodepass/internal/master.go index 4dda79b377..dc5f1d569e 100644 --- a/nodepass/internal/master.go +++ b/nodepass/internal/master.go @@ -38,9 +38,7 @@ const ( apiKeyID = "********" // API Key的特殊ID tcpingSemLimit = 10 // TCPing最大并发数 baseDuration = 100 * time.Millisecond // 基准持续时间 - maxTagsCount = 50 // 最大标签数量 - maxTagKeyLen = 100 // 标签键最大长度 - maxTagValueLen = 500 // 标签值最大长度 + maxValueLen = 256 // 字符长度限制 ) // Swagger UI HTML模板 @@ -204,34 +202,6 @@ func (m *Master) performTCPing(target string) *TCPingResult { return result } -// validateTags 验证标签的有效性 -func validateTags(tags []Tag) error { - if len(tags) > maxTagsCount { - return fmt.Errorf("too many tags: maximum %d allowed", maxTagsCount) - } - - keySet := make(map[string]bool) - for _, tag := range tags { - if len(tag.Key) == 0 { - return fmt.Errorf("tag key cannot be empty") - } - if len(tag.Key) > maxTagKeyLen { - return fmt.Errorf("tag key exceeds maximum length %d", maxTagKeyLen) - } - if len(tag.Value) > maxTagValueLen { - return fmt.Errorf("tag value for key exceeds maximum length %d", maxTagValueLen) - } - - // 检查重复的键 - if keySet[tag.Key] { - return fmt.Errorf("duplicate tag key: '%s'", tag.Key) - } - keySet[tag.Key] = true - } - - return nil -} - // InstanceLogWriter 实例日志写入器 type InstanceLogWriter struct { instanceID string // 实例ID @@ -764,6 +734,8 @@ func (m *Master) loadState() { instance.Config = m.generateConfigURL(instance) } + instance.Tags = nil + m.instances.Store(id, instance) // 处理自启动 @@ -806,8 +778,8 @@ func (m *Master) handleInfo(w http.ResponseWriter, r *http.Request) { } // 更新主控别名 - if len(reqData.Alias) > maxTagKeyLen { - httpError(w, fmt.Sprintf("Master alias exceeds maximum length %d", maxTagKeyLen), http.StatusBadRequest) + if len(reqData.Alias) > maxValueLen { + httpError(w, fmt.Sprintf("Master alias exceeds maximum length %d", maxValueLen), http.StatusBadRequest) return } m.alias = reqData.Alias @@ -1041,7 +1013,6 @@ func (m *Master) handleInstances(w http.ResponseWriter, r *http.Request) { URL: m.enhanceURL(reqData.URL, instanceType), Status: "stopped", Restart: true, - Tags: []Tag{}, stopped: make(chan struct{}), } @@ -1107,7 +1078,6 @@ func (m *Master) handlePatchInstance(w http.ResponseWriter, r *http.Request, id Alias string `json:"alias,omitempty"` Action string `json:"action,omitempty"` Restart *bool `json:"restart,omitempty"` - Tags []Tag `json:"tags,omitempty"` } if err := json.NewDecoder(r.Body).Decode(&reqData); err == nil { if id == apiKeyID { @@ -1118,44 +1088,6 @@ func (m *Master) handlePatchInstance(w http.ResponseWriter, r *http.Request, id m.sendSSEEvent("update", instance) } } else { - // 处理标签更新 - if reqData.Tags != nil { - if err := validateTags(reqData.Tags); err != nil { - httpError(w, err.Error(), http.StatusBadRequest) - return - } - - // 创建现有标签的映射表 - existingTags := make(map[string]Tag) - for _, tag := range instance.Tags { - existingTags[tag.Key] = tag - } - - for _, tag := range reqData.Tags { - if tag.Value == "" { - // value为空,删除key - delete(existingTags, tag.Key) - } else { - // value非空,更新或添加key - existingTags[tag.Key] = tag - } - } - - // 将映射表转换回标签数组 - newTags := make([]Tag, 0, len(existingTags)) - for _, tag := range existingTags { - newTags = append(newTags, tag) - } - - instance.Tags = newTags - m.instances.Store(id, instance) - go m.saveState() - m.logger.Info("Tags updated: [%v]", instance.ID) - - // 发送标签变更事件 - m.sendSSEEvent("update", instance) - } - // 重置流量统计 if reqData.Action == "reset" { instance.TCPRX = 0 @@ -1183,8 +1115,8 @@ func (m *Master) handlePatchInstance(w http.ResponseWriter, r *http.Request, id // 更新实例别名 if reqData.Alias != "" && instance.Alias != reqData.Alias { - if len(reqData.Alias) > maxTagKeyLen { - httpError(w, fmt.Sprintf("Instance alias exceeds maximum length %d", maxTagKeyLen), http.StatusBadRequest) + if len(reqData.Alias) > maxValueLen { + httpError(w, fmt.Sprintf("Instance alias exceeds maximum length %d", maxValueLen), http.StatusBadRequest) return } instance.Alias = reqData.Alias @@ -1957,7 +1889,6 @@ func (m *Master) generateOpenAPISpec() string { "url": {"type": "string", "description": "Command string or API Key"}, "config": {"type": "string", "description": "Instance configuration URL"}, "restart": {"type": "boolean", "description": "Restart policy"}, - "tags": {"type": "array", "items": {"$ref": "#/components/schemas/Tag"}, "description": "Tag array"}, "mode": {"type": "integer", "description": "Instance mode"}, "ping": {"type": "integer", "description": "TCPing latency"}, "pool": {"type": "integer", "description": "Pool active count"}, @@ -1979,8 +1910,7 @@ func (m *Master) generateOpenAPISpec() string { "properties": { "alias": {"type": "string", "description": "Instance alias"}, "action": {"type": "string", "enum": ["start", "stop", "restart", "reset"], "description": "Action for the instance"}, - "restart": {"type": "boolean", "description": "Instance restart policy"}, - "tags": {"type": "array", "items": {"$ref": "#/components/schemas/Tag"}, "description": "Tag array"} + "restart": {"type": "boolean", "description": "Instance restart policy"} } }, "PutInstanceRequest": { @@ -2026,14 +1956,6 @@ func (m *Master) generateOpenAPISpec() string { "latency": {"type": "integer", "format": "int64", "description": "Latency in milliseconds"}, "error": {"type": "string", "nullable": true, "description": "Error message"} } - }, - "Tag": { - "type": "object", - "required": ["key", "value"], - "properties": { - "key": {"type": "string", "description": "Tag key"}, - "value": {"type": "string", "description": "Tag value"} - } } } } diff --git a/nodepass/internal/server.go b/nodepass/internal/server.go index da00f86200..37100e83d1 100644 --- a/nodepass/internal/server.go +++ b/nodepass/internal/server.go @@ -47,6 +47,10 @@ func NewServer(parsedURL *url.URL, tlsCode string, tlsConfig *tls.Config, logger return &buf }, }, + cleanURL: &url.URL{Scheme: "np", Fragment: "c"}, + flushURL: &url.URL{Scheme: "np", Fragment: "f"}, + pingURL: &url.URL{Scheme: "np", Fragment: "i"}, + pongURL: &url.URL{Scheme: "np", Fragment: "o"}, }, tlsConfig: tlsConfig, } @@ -222,7 +226,7 @@ func (s *Server) tunnelHandshake() error { } s.tunnelTCPConn = tunnelTCPConn.(*net.TCPConn) - s.bufReader = bufio.NewReader(&conn.TimeoutReader{Conn: s.tunnelTCPConn, Timeout: 2 * reportInterval}) + s.bufReader = bufio.NewReader(&conn.TimeoutReader{Conn: s.tunnelTCPConn, Timeout: 3 * reportInterval}) s.tunnelTCPConn.SetKeepAlive(true) s.tunnelTCPConn.SetKeepAlivePeriod(reportInterval) diff --git a/openwrt-packages/luci-app-amlogic/Makefile b/openwrt-packages/luci-app-amlogic/Makefile index ae6bc7ac4d..b1de6b37f5 100644 --- a/openwrt-packages/luci-app-amlogic/Makefile +++ b/openwrt-packages/luci-app-amlogic/Makefile @@ -16,7 +16,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-amlogic -PKG_VERSION:=3.1.268 +PKG_VERSION:=3.1.269 PKG_RELEASE:=1 PKG_LICENSE:=GPL-2.0 License diff --git a/openwrt-packages/luci-app-amlogic/root/usr/sbin/openwrt-install-amlogic b/openwrt-packages/luci-app-amlogic/root/usr/sbin/openwrt-install-amlogic index c807f96040..a3cd3fc9c7 100755 --- a/openwrt-packages/luci-app-amlogic/root/usr/sbin/openwrt-install-amlogic +++ b/openwrt-packages/luci-app-amlogic/root/usr/sbin/openwrt-install-amlogic @@ -195,11 +195,7 @@ else # 6. MAINLINE_UBOOT # 7. ANDROID_UBOOT AMLOGIC_SOC="$(echo "${ret}" | awk -F ':' '{print $3}')" - FDTFILE="$( - grep -iE '^[[:space:]]*fdt[[:space:]]+/.*\.dtb' /boot/extlinux/extlinux.conf 2>/dev/null | grep -v '^[[:space:]]*#' | awk '{print $2}' | xargs basename 2>/dev/null || - grep -oE '^FDT=.*meson[^[:space:]]*\.dtb' /boot/uEnv.txt 2>/dev/null | cut -d= -f2 | xargs basename 2>/dev/null || - echo "${ret}" | awk -F':' '{print $4}' - )" + FDTFILE="$(echo "${ret}" | awk -F ':' '{print $4}')" UBOOT_OVERLOAD="$(echo "${ret}" | awk -F ':' '{print $5}')" MAINLINE_UBOOT="$(echo "${ret}" | awk -F ':' '{print $6}')" ANDROID_UBOOT="$(echo "${ret}" | awk -F ':' '{print $7}')" diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index d5bf4eae85..7691c64077 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -30,7 +30,7 @@ define Download/geosite HASH:=1a7dad0ceaaf1f6d12fef585576789699bd1c6ea014c887c04b94cb9609350e9 endef -GEOSITE_IRAN_VER:=202509290038 +GEOSITE_IRAN_VER:=202510060038 GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER) define Download/geosite-ir URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/ diff --git a/xray-core/common/protocol/headers.go b/xray-core/common/protocol/headers.go index bc90ca6c7b..9c6573cb33 100644 --- a/xray-core/common/protocol/headers.go +++ b/xray-core/common/protocol/headers.go @@ -73,7 +73,7 @@ type ResponseHeader struct { var ( // Keep in sync with crypto/tls/cipher_suites.go. hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3 - hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL + hasGCMAsmARM64 = (cpu.ARM64.HasAES && cpu.ARM64.HasPMULL) || (runtime.GOOS == "darwin" && runtime.GOARCH == "arm64") hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" diff --git a/xray-core/common/singbridge/pipe.go b/xray-core/common/singbridge/pipe.go index d04ebda44e..d6c0f3d885 100644 --- a/xray-core/common/singbridge/pipe.go +++ b/xray-core/common/singbridge/pipe.go @@ -4,8 +4,10 @@ import ( "context" "io" "net" + "time" "github.com/sagernet/sing/common/bufio" + "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/transport" ) @@ -33,8 +35,26 @@ func (w *PipeConnWrapper) Close() error { return nil } +// This Read implemented a timeout to avoid goroutine leak. +// as a temporarily solution func (w *PipeConnWrapper) Read(b []byte) (n int, err error) { - return w.R.Read(b) + type readResult struct { + n int + err error + } + c := make(chan readResult, 1) + go func() { + n, err := w.R.Read(b) + c <- readResult{n: n, err: err} + }() + select { + case result := <-c: + return result.n, result.err + case <-time.After(300 * time.Second): + common.Close(w.R) + common.Interrupt(w.R) + return 0, buf.ErrReadTimeout + } } func (w *PipeConnWrapper) Write(p []byte) (n int, err error) { diff --git a/xray-core/go.mod b/xray-core/go.mod index da380f068b..9763bbea45 100644 --- a/xray-core/go.mod +++ b/xray-core/go.mod @@ -11,7 +11,7 @@ require ( github.com/miekg/dns v1.1.68 github.com/pelletier/go-toml v1.9.5 github.com/pires/go-proxyproto v0.8.1 - github.com/quic-go/quic-go v0.54.1 + github.com/quic-go/quic-go v0.55.0 github.com/refraction-networking/utls v1.8.0 github.com/sagernet/sing v0.5.1 github.com/sagernet/sing-shadowsocks v0.2.7 @@ -46,7 +46,6 @@ require ( github.com/quic-go/qpack v0.5.1 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/vishvananda/netns v0.0.5 // indirect - go.uber.org/mock v0.5.0 // indirect golang.org/x/mod v0.27.0 // indirect golang.org/x/text v0.29.0 // indirect golang.org/x/time v0.7.0 // indirect diff --git a/xray-core/go.sum b/xray-core/go.sum index 2a661ea1ad..53ae6aaf95 100644 --- a/xray-core/go.sum +++ b/xray-core/go.sum @@ -51,8 +51,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg= -github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= +github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE= github.com/refraction-networking/utls v1.8.0/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= @@ -90,8 +90,8 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=