Update On Thu May 30 20:33:30 CEST 2024

This commit is contained in:
github-action[bot]
2024-05-30 20:33:31 +02:00
parent e29dd906df
commit 3bb06d18ab
334 changed files with 56759 additions and 3551 deletions
+1
View File
@@ -662,3 +662,4 @@ Update On Sun May 26 20:29:29 CEST 2024
Update On Mon May 27 20:31:35 CEST 2024
Update On Tue May 28 20:30:03 CEST 2024
Update On Wed May 29 20:30:48 CEST 2024
Update On Thu May 30 20:33:19 CEST 2024
+1 -13
View File
@@ -3,22 +3,10 @@ package inbound
import (
"context"
"net"
"github.com/metacubex/tfo-go"
)
var (
lc = tfo.ListenConfig{
DisableTFO: true,
}
)
func SetTfo(open bool) {
lc.DisableTFO = !open
}
func SetMPTCP(open bool) {
setMultiPathTCP(&lc.ListenConfig, open)
setMultiPathTCP(getListenConfig(), open)
}
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
+23
View File
@@ -0,0 +1,23 @@
//go:build unix
package inbound
import (
"net"
"github.com/metacubex/tfo-go"
)
var (
lc = tfo.ListenConfig{
DisableTFO: true,
}
)
func SetTfo(open bool) {
lc.DisableTFO = !open
}
func getListenConfig() *net.ListenConfig {
return &lc.ListenConfig
}
@@ -0,0 +1,15 @@
package inbound
import (
"net"
)
var (
lc = net.ListenConfig{}
)
func SetTfo(open bool) {}
func getListenConfig() *net.ListenConfig {
return &lc
}
-17
View File
@@ -5,12 +5,8 @@ import (
"io"
"net"
"time"
"github.com/metacubex/tfo-go"
)
var DisableTFO = false
type tfoConn struct {
net.Conn
closed bool
@@ -124,16 +120,3 @@ func (c *tfoConn) ReaderReplaceable() bool {
func (c *tfoConn) WriterReplaceable() bool {
return c.Conn != nil
}
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTCPTimeout)
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
return &tfoConn{
dialed: make(chan bool, 1),
cancel: cancel,
ctx: ctx,
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
return dialer.DialContext(ctx, network, address, earlyData)
},
}, nil
}
+25
View File
@@ -0,0 +1,25 @@
//go:build unix
package dialer
import (
"context"
"net"
"github.com/metacubex/tfo-go"
)
const DisableTFO = false
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTCPTimeout)
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
return &tfoConn{
dialed: make(chan bool, 1),
cancel: cancel,
ctx: ctx,
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
return dialer.DialContext(ctx, network, address, earlyData)
},
}, nil
}
+8 -7
View File
@@ -1,11 +1,12 @@
package dialer
import "github.com/metacubex/mihomo/constant/features"
import (
"context"
"net"
)
func init() {
// According to MSDN, this option is available since Windows 10, 1607
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms738596(v=vs.85).aspx
if features.WindowsMajorVersion < 10 || (features.WindowsMajorVersion == 10 && features.WindowsBuildNumber < 14393) {
DisableTFO = true
}
const DisableTFO = true
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
return netDialer.DialContext(ctx, network, address)
}
@@ -56,7 +56,7 @@
"@typescript-eslint/eslint-plugin": "7.11.0",
"@typescript-eslint/parser": "7.11.0",
"@vitejs/plugin-react": "4.3.0",
"sass": "1.77.2",
"sass": "1.77.3",
"shiki": "1.6.1",
"vite": "5.2.12",
"vite-plugin-monaco-editor": "1.1.3",
+1 -1
View File
@@ -16,7 +16,7 @@
"react-i18next": "14.1.2"
},
"devDependencies": {
"sass": "1.77.2",
"sass": "1.77.3",
"typescript-plugin-css-modules": "5.1.0"
}
}
+1 -1
View File
@@ -74,7 +74,7 @@
"@tauri-apps/cli": "1.5.14",
"@types/fs-extra": "11.0.4",
"@types/lodash-es": "4.17.12",
"@types/node": "20.12.12",
"@types/node": "20.12.13",
"autoprefixer": "10.4.19",
"conventional-changelog-conventionalcommits": "8.0.0",
"cross-env": "7.0.3",
+51 -51
View File
@@ -24,7 +24,7 @@ importers:
devDependencies:
'@commitlint/cli':
specifier: 19.3.0
version: 19.3.0(@types/node@20.12.12)(typescript@5.4.5)
version: 19.3.0(@types/node@20.12.13)(typescript@5.4.5)
'@commitlint/config-conventional':
specifier: 19.2.2
version: 19.2.2
@@ -38,8 +38,8 @@ importers:
specifier: 4.17.12
version: 4.17.12
'@types/node':
specifier: 20.12.12
version: 20.12.12
specifier: 20.12.13
version: 20.12.13
autoprefixer:
specifier: 10.4.19
version: 10.4.19(postcss@8.4.38)
@@ -175,7 +175,7 @@ importers:
version: 11.11.5(@emotion/react@11.11.4(react@19.0.0-rc-935180c7e0-20240524)(types-react@19.0.0-rc.0))(react@19.0.0-rc-935180c7e0-20240524)(types-react@19.0.0-rc.0)
'@generouted/react-router':
specifier: 1.19.5
version: 1.19.5(react-router-dom@6.23.1(react-dom@19.0.0-rc-935180c7e0-20240524(react@19.0.0-rc-935180c7e0-20240524))(react@19.0.0-rc-935180c7e0-20240524))(react@19.0.0-rc-935180c7e0-20240524)(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0))
version: 1.19.5(react-router-dom@6.23.1(react-dom@19.0.0-rc-935180c7e0-20240524(react@19.0.0-rc-935180c7e0-20240524))(react@19.0.0-rc-935180c7e0-20240524))(react@19.0.0-rc-935180c7e0-20240524)(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0))
'@juggle/resize-observer':
specifier: 3.4.0
version: 3.4.0
@@ -296,28 +296,28 @@ importers:
version: 7.11.0(eslint@8.57.0)(typescript@5.4.5)
'@vitejs/plugin-react':
specifier: 4.3.0
version: 4.3.0(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0))
version: 4.3.0(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0))
sass:
specifier: 1.77.2
version: 1.77.2
specifier: 1.77.3
version: 1.77.3
shiki:
specifier: 1.6.1
version: 1.6.1
vite:
specifier: 5.2.12
version: 5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0)
version: 5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0)
vite-plugin-monaco-editor:
specifier: npm:vite-plugin-monaco-editor-new@1.1.3
version: vite-plugin-monaco-editor-new@1.1.3(monaco-editor@0.49.0)
vite-plugin-sass-dts:
specifier: 1.3.22
version: 1.3.22(postcss@8.4.38)(prettier@3.2.5)(sass@1.77.2)(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0))
version: 1.3.22(postcss@8.4.38)(prettier@3.2.5)(sass@1.77.3)(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0))
vite-plugin-svgr:
specifier: 4.2.0
version: 4.2.0(rollup@4.17.2)(typescript@5.4.5)(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0))
version: 4.2.0(rollup@4.17.2)(typescript@5.4.5)(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0))
vite-tsconfig-paths:
specifier: 4.3.2
version: 4.3.2(typescript@5.4.5)(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0))
version: 4.3.2(typescript@5.4.5)(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0))
frontend/ui:
dependencies:
@@ -353,8 +353,8 @@ importers:
version: 14.1.2(i18next@23.11.5)(react-dom@19.0.0-rc-935180c7e0-20240524(react@19.0.0-rc-935180c7e0-20240524))(react@19.0.0-rc-935180c7e0-20240524)
devDependencies:
sass:
specifier: 1.77.2
version: 1.77.2
specifier: 1.77.3
version: 1.77.3
typescript-plugin-css-modules:
specifier: 5.1.0
version: 5.1.0(typescript@5.4.5)
@@ -1581,8 +1581,8 @@ packages:
'@types/node@20.12.10':
resolution: {integrity: sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==}
'@types/node@20.12.12':
resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==}
'@types/node@20.12.13':
resolution: {integrity: sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -4283,8 +4283,8 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
sass@1.77.2:
resolution: {integrity: sha512-eb4GZt1C3avsX3heBNlrc7I09nyT00IUuo4eFhAbeXWU2fvA7oXI53SxODVAA+zgZCk9aunAZgO+losjR3fAwA==}
sass@1.77.3:
resolution: {integrity: sha512-WJHo+jmFp0dwRuymPmIovuxHaBntcCyja5hCB0yYY9wWrViEp4kF5Cdai98P72v6FzroPuABqu+ddLMbQWmwzA==}
engines: {node: '>=14.0.0'}
hasBin: true
@@ -5199,11 +5199,11 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.5
to-fast-properties: 2.0.0
'@commitlint/cli@19.3.0(@types/node@20.12.12)(typescript@5.4.5)':
'@commitlint/cli@19.3.0(@types/node@20.12.13)(typescript@5.4.5)':
dependencies:
'@commitlint/format': 19.3.0
'@commitlint/lint': 19.2.2
'@commitlint/load': 19.2.0(@types/node@20.12.12)(typescript@5.4.5)
'@commitlint/load': 19.2.0(@types/node@20.12.13)(typescript@5.4.5)
'@commitlint/read': 19.2.1
'@commitlint/types': 19.0.3
execa: 8.0.1
@@ -5250,7 +5250,7 @@ snapshots:
'@commitlint/rules': 19.0.3
'@commitlint/types': 19.0.3
'@commitlint/load@19.2.0(@types/node@20.12.12)(typescript@5.4.5)':
'@commitlint/load@19.2.0(@types/node@20.12.13)(typescript@5.4.5)':
dependencies:
'@commitlint/config-validator': 19.0.3
'@commitlint/execute-rule': 19.0.0
@@ -5258,7 +5258,7 @@ snapshots:
'@commitlint/types': 19.0.3
chalk: 5.3.0
cosmiconfig: 9.0.0(typescript@5.4.5)
cosmiconfig-typescript-loader: 5.0.0(@types/node@20.12.12)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5)
cosmiconfig-typescript-loader: 5.0.0(@types/node@20.12.13)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5)
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
@@ -5627,13 +5627,13 @@ snapshots:
'@floating-ui/utils@0.2.2': {}
'@generouted/react-router@1.19.5(react-router-dom@6.23.1(react-dom@19.0.0-rc-935180c7e0-20240524(react@19.0.0-rc-935180c7e0-20240524))(react@19.0.0-rc-935180c7e0-20240524))(react@19.0.0-rc-935180c7e0-20240524)(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0))':
'@generouted/react-router@1.19.5(react-router-dom@6.23.1(react-dom@19.0.0-rc-935180c7e0-20240524(react@19.0.0-rc-935180c7e0-20240524))(react@19.0.0-rc-935180c7e0-20240524))(react@19.0.0-rc-935180c7e0-20240524)(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0))':
dependencies:
fast-glob: 3.3.2
generouted: 1.19.5(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0))
generouted: 1.19.5(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0))
react: 19.0.0-rc-935180c7e0-20240524
react-router-dom: 6.23.1(react-dom@19.0.0-rc-935180c7e0-20240524(react@19.0.0-rc-935180c7e0-20240524))(react@19.0.0-rc-935180c7e0-20240524)
vite: 5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0)
vite: 5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0)
'@humanwhocodes/config-array@0.11.14':
dependencies:
@@ -6098,12 +6098,12 @@ snapshots:
dependencies:
'@types/http-cache-semantics': 4.0.4
'@types/keyv': 3.1.4
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/responselike': 1.0.3
'@types/conventional-commits-parser@5.0.0':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/debug@4.1.12':
dependencies:
@@ -6120,7 +6120,7 @@ snapshots:
'@types/fs-extra@11.0.4':
dependencies:
'@types/jsonfile': 6.1.4
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/hast@3.0.4':
dependencies:
@@ -6134,11 +6134,11 @@ snapshots:
'@types/jsonfile@6.1.4':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/keyv@3.1.4':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/lodash-es@4.17.12':
dependencies:
@@ -6158,7 +6158,7 @@ snapshots:
dependencies:
undici-types: 5.26.5
'@types/node@20.12.12':
'@types/node@20.12.13':
dependencies:
undici-types: 5.26.5
@@ -6180,7 +6180,7 @@ snapshots:
'@types/responselike@1.0.3':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/unist@2.0.10': {}
@@ -6188,7 +6188,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
optional: true
'@typescript-eslint/eslint-plugin@7.11.0(@typescript-eslint/parser@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)':
@@ -6274,14 +6274,14 @@ snapshots:
'@ungap/structured-clone@1.2.0': {}
'@vitejs/plugin-react@4.3.0(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0))':
'@vitejs/plugin-react@4.3.0(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0))':
dependencies:
'@babel/core': 7.24.5
'@babel/plugin-transform-react-jsx-self': 7.24.5(@babel/core@7.24.5)
'@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.5)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
vite: 5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0)
vite: 5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0)
transitivePeerDependencies:
- supports-color
@@ -6613,7 +6613,7 @@ snapshots:
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
braces: 3.0.2
braces: 3.0.3
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
@@ -6724,9 +6724,9 @@ snapshots:
dependencies:
is-what: 3.14.1
cosmiconfig-typescript-loader@5.0.0(@types/node@20.12.12)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5):
cosmiconfig-typescript-loader@5.0.0(@types/node@20.12.13)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5):
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
cosmiconfig: 9.0.0(typescript@5.4.5)
jiti: 1.21.0
typescript: 5.4.5
@@ -7537,9 +7537,9 @@ snapshots:
functions-have-names@1.2.3: {}
generouted@1.19.5(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0)):
generouted@1.19.5(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0)):
dependencies:
vite: 5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0)
vite: 5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0)
gensync@1.0.0-beta.2: {}
@@ -9199,7 +9199,7 @@ snapshots:
safer-buffer@2.1.2:
optional: true
sass@1.77.2:
sass@1.77.3:
dependencies:
chokidar: 3.6.0
immutable: 4.3.5
@@ -9746,7 +9746,7 @@ snapshots:
postcss-modules-local-by-default: 4.0.5(postcss@8.4.38)
postcss-modules-scope: 3.2.0(postcss@8.4.38)
reserved-words: 0.1.2
sass: 1.77.2
sass: 1.77.3
source-map-js: 1.2.0
stylus: 0.62.0
tsconfig-paths: 4.2.0
@@ -9888,46 +9888,46 @@ snapshots:
esbuild: 0.19.12
monaco-editor: 0.49.0
vite-plugin-sass-dts@1.3.22(postcss@8.4.38)(prettier@3.2.5)(sass@1.77.2)(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0)):
vite-plugin-sass-dts@1.3.22(postcss@8.4.38)(prettier@3.2.5)(sass@1.77.3)(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0)):
dependencies:
postcss: 8.4.38
postcss-js: 4.0.1(postcss@8.4.38)
prettier: 3.2.5
sass: 1.77.2
vite: 5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0)
sass: 1.77.3
vite: 5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0)
vite-plugin-svgr@4.2.0(rollup@4.17.2)(typescript@5.4.5)(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0)):
vite-plugin-svgr@4.2.0(rollup@4.17.2)(typescript@5.4.5)(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0)):
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.17.2)
'@svgr/core': 8.1.0(typescript@5.4.5)
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.4.5))
vite: 5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0)
vite: 5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0)
transitivePeerDependencies:
- rollup
- supports-color
- typescript
vite-tsconfig-paths@4.3.2(typescript@5.4.5)(vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0)):
vite-tsconfig-paths@4.3.2(typescript@5.4.5)(vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0)):
dependencies:
debug: 4.3.4
globrex: 0.1.2
tsconfck: 3.0.3(typescript@5.4.5)
optionalDependencies:
vite: 5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0)
vite: 5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0)
transitivePeerDependencies:
- supports-color
- typescript
vite@5.2.12(@types/node@20.12.12)(less@4.2.0)(sass@1.77.2)(stylus@0.62.0):
vite@5.2.12(@types/node@20.12.13)(less@4.2.0)(sass@1.77.3)(stylus@0.62.0):
dependencies:
esbuild: 0.20.2
postcss: 8.4.38
rollup: 4.17.2
optionalDependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
fsevents: 2.3.3
less: 4.2.0
sass: 1.77.2
sass: 1.77.3
stylus: 0.62.0
void-elements@3.1.0: {}
-1
View File
@@ -35,7 +35,6 @@
"dayjs": "1.11.5",
"i18next": "^23.11.3",
"lodash-es": "^4.17.21",
"matchmedia-polyfill": "^0.3.2",
"meta-json-schema": "1.18.5-alpha",
"monaco-editor": "^0.49.0",
"monaco-yaml": "^5.1.1",
-11
View File
@@ -58,9 +58,6 @@ importers:
lodash-es:
specifier: ^4.17.21
version: 4.17.21
matchmedia-polyfill:
specifier: ^0.3.2
version: 0.3.2
meta-json-schema:
specifier: 1.18.5-alpha
version: 1.18.5-alpha
@@ -3224,12 +3221,6 @@ packages:
integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==,
}
matchmedia-polyfill@0.3.2:
resolution:
{
integrity: sha512-B2zRzjqxZFUusBZrZux59XFFLoTN99SbGranxIHfjZVLGZuy8Iaf/s5iNR3qJwRQZBjBKsU6qBSUCltLV82gdw==,
}
mdast-util-from-markdown@2.0.1:
resolution:
{
@@ -6432,8 +6423,6 @@ snapshots:
dependencies:
"@jridgewell/sourcemap-codec": 1.4.15
matchmedia-polyfill@0.3.2: {}
mdast-util-from-markdown@2.0.1:
dependencies:
"@types/mdast": 4.0.4
@@ -1,6 +1,6 @@
import { Box, SvgIcon, TextField, Theme, styled } from "@mui/material";
import { Box, SvgIcon, TextField, styled } from "@mui/material";
import Tooltip from "@mui/material/Tooltip";
import { ChangeEvent, useState } from "react";
import { ChangeEvent, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import matchCaseIcon from "@/assets/image/component/match_case.svg?react";
@@ -22,6 +22,7 @@ type SearchProps = {
export const BaseSearchBox = styled((props: SearchProps) => {
const { t } = useTranslation();
const inputRef = useRef<HTMLInputElement>(null);
const [matchCase, setMatchCase] = useState(true);
const [matchWholeWord, setMatchWholeWord] = useState(false);
const [useRegularExpression, setUseRegularExpression] = useState(false);
@@ -36,6 +37,14 @@ export const BaseSearchBox = styled((props: SearchProps) => {
inheritViewBox: true,
};
useEffect(() => {
if (!inputRef.current) return;
onChange({
target: inputRef.current,
} as ChangeEvent<HTMLInputElement>);
}, [matchCase, matchWholeWord, useRegularExpression]);
const onChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
props.onSearch(
(content) => doSearch([content], e.target?.value ?? "").length > 0,
@@ -80,6 +89,7 @@ export const BaseSearchBox = styled((props: SearchProps) => {
return (
<Tooltip title={errorMessage} placement="bottom-start">
<TextField
inputRef={inputRef}
hiddenLabel
fullWidth
size="small"
+28 -48
View File
@@ -1,50 +1,30 @@
(function (global) {
if (typeof global === "object" && global) {
if (typeof global.RegExp !== "undefined") {
const OriginalRegExp = global.RegExp;
const CustomRegExp = function (pattern, flags) {
if (typeof pattern === "string" && typeof flags === "string") {
flags = flags;
} else if (pattern instanceof OriginalRegExp && flags === undefined) {
flags = pattern.flags;
}
if (flags) {
if (!global.RegExp.prototype.hasOwnProperty("unicodeSets")) {
if (flags.includes("v")) {
flags = flags.replace("v", "u");
}
}
if (!global.RegExp.prototype.hasOwnProperty("hasIndices")) {
if (flags.includes("d")) {
flags = flags.replace("d", "");
}
}
}
return new OriginalRegExp(pattern, flags);
};
CustomRegExp.prototype = OriginalRegExp.prototype;
global.RegExp = CustomRegExp;
}
(function () {
if (typeof window.RegExp === "undefined") {
return;
}
})(
(function () {
switch (true) {
case typeof globalThis === "object" && !!globalThis:
return globalThis;
case typeof self === "object" && !!self:
return self;
case typeof window === "object" && !!window:
return window;
case typeof global === "object" && !!global:
return global;
case typeof Function === "function":
return Function("return this")();
const originalRegExp = window.RegExp;
window.RegExp = function (pattern, flags) {
if (pattern instanceof originalRegExp && flags === undefined) {
flags = pattern.flags;
}
return null;
})()
);
if (flags) {
if (!originalRegExp.prototype.hasOwnProperty("unicodeSets")) {
if (flags.includes("v")) {
flags = flags.replace("v", "u");
}
}
if (!originalRegExp.prototype.hasOwnProperty("hasIndices")) {
if (flags.includes("d")) {
flags = flags.replace("d", "");
}
}
}
return new originalRegExp(pattern, flags);
};
window.RegExp.prototype = originalRegExp.prototype;
})();
+14 -31
View File
@@ -1,33 +1,16 @@
(function (global) {
if (typeof global === "object" && global) {
if (typeof global["WeakRef"] === "undefined") {
global.WeakRef = (function (wm) {
function WeakRef(target) {
wm.set(this, target);
}
WeakRef.prototype.deref = function () {
return wm.get(this);
};
return WeakRef;
})(new WeakMap());
}
(function () {
if (typeof window.WeakRef !== "undefined") {
return;
}
})(
(function () {
switch (true) {
case typeof globalThis === "object" && !!globalThis:
return globalThis;
case typeof self === "object" && !!self:
return self;
case typeof window === "object" && !!window:
return window;
case typeof global === "object" && !!global:
return global;
case typeof Function === "function":
return Function("return this")();
window.WeakRef = (function (weakMap) {
function WeakRef(target) {
weakMap.set(this, target);
}
return null;
})()
);
WeakRef.prototype.deref = function () {
return weakMap.get(this);
};
return WeakRef;
})(new WeakMap());
})();
@@ -0,0 +1,36 @@
(function () {
if (window.matchMedia && window.matchMedia("all").addEventListener) {
return;
}
const originalMatchMedia = window.matchMedia;
window.matchMedia = function (query) {
const mediaQueryList = originalMatchMedia(query);
if (!mediaQueryList.addEventListener) {
mediaQueryList.addEventListener = function (eventType, listener) {
if (eventType !== "change" || typeof listener !== "function") {
console.error("Invalid arguments for addEventListener:", arguments);
return;
}
mediaQueryList.addListener(listener);
};
}
if (!mediaQueryList.removeEventListener) {
mediaQueryList.removeEventListener = function (eventType, listener) {
if (eventType !== "change" || typeof listener !== "function") {
console.error(
"Invalid arguments for removeEventListener:",
arguments
);
return;
}
mediaQueryList.removeListener(listener);
};
}
return mediaQueryList;
};
})();
+1 -2
View File
@@ -16,8 +16,7 @@ export default defineConfig({
modernPolyfills: true,
polyfills: ["web.structured-clone"],
additionalModernPolyfills: [
"matchmedia-polyfill",
"matchmedia-polyfill/matchMedia.addListener",
path.resolve("./src/polyfills/matchMedia.js"),
path.resolve("./src/polyfills/WeakRef.js"),
path.resolve("./src/polyfills/RegExp.js"),
],
+3 -2
View File
@@ -17,8 +17,9 @@ run:
tests: false
# list of build tags, all linters use it. Default is empty list.
# build-tags:
# - mytag
build-tags:
- nofibrechannel
- nomountstats
# which dirs to skip: they won't be analyzed;
# can use regexp here: generated.*, regexp is applied on full path;
+3
View File
@@ -7,6 +7,9 @@ builds:
main: ./cmd/ehco/main.go
flags:
- -trimpath
tags:
- nofibrechannel
- nomountstats
ldflags:
- -w -s
- -X github.com/Ehco1996/ehco/internal/constant.GitBranch={{.Branch}}
+4 -5
View File
@@ -13,12 +13,11 @@ PACKAGE_LIST := go list ./...
FILES := $(shell find . -name "*.go" -type f)
FAIL_ON_STDOUT := awk '{ print } END { if (NR > 0) { exit 1 } }'
BUILD_TAG_FOR_NODE_EXPORTER="nofibrechannel,nomountstats"
# -w -s 参数的解释:You will get the smallest binaries if you compile with -ldflags '-w -s'. The -w turns off DWARF debugging information
# for more information, please refer to https://stackoverflow.com/questions/22267189/what-does-the-w-flag-mean-when-passed-in-via-the-ldflags-option-to-the-go-comman
# we need CGO_ENABLED=1 because we import the node_exporter ,and we need install `glibc-source,libc6` to make it work
# TODO check if node_exporter collector with CGO_ENABLED=0 is enough
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags="-w -s -X ${PACKAGE}.GitBranch=${BRANCH} -X ${PACKAGE}.GitRevision=${REVISION} -X ${PACKAGE}.BuildTime=${BUILDTIME}"
GOBUILD=CGO_ENABLED=0 go build -tags ${BUILD_TAG_FOR_NODE_EXPORTER} -trimpath -ldflags="-w -s -X ${PACKAGE}.GitBranch=${BRANCH} -X ${PACKAGE}.GitRevision=${REVISION} -X ${PACKAGE}.BuildTime=${BUILDTIME}"
tools:
@echo "run setup tools"
@@ -35,7 +34,7 @@ fmt: tools
@tools/bin/gofumpt -l -w $(FILES) 2>&1 | $(FAIL_ON_STDOUT)
test:
go test -v -count=1 -timeout=1m ./...
go test -tags ${BUILD_TAG_FOR_NODE_EXPORTER} -v -count=1 -timeout=1m ./...
build:
${GOBUILD} -o $(BINDIR)/$(NAME) cmd/ehco/main.go
+16 -16
View File
@@ -5,15 +5,15 @@ go 1.22
toolchain go1.22.1
require (
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/getsentry/sentry-go v0.27.0
github.com/go-ping/ping v1.1.0
github.com/gobwas/ws v1.3.2
github.com/labstack/echo/v4 v4.11.4
github.com/prometheus/client_golang v1.19.0
github.com/prometheus/client_model v0.5.0
github.com/prometheus/common v0.48.0
github.com/prometheus/node_exporter v1.7.0
github.com/prometheus/client_golang v1.19.1
github.com/prometheus/client_model v0.6.1
github.com/prometheus/common v0.53.0
github.com/prometheus/node_exporter v1.8.1
github.com/sagernet/sing v0.3.8
github.com/sagernet/sing-box v1.8.14
github.com/stretchr/testify v1.9.0
github.com/urfave/cli/v2 v2.27.1
@@ -27,16 +27,17 @@ require (
)
require (
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/beevik/ntp v1.3.0 // indirect
github.com/beevik/ntp v1.4.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dennwc/btrfs v0.0.0-20230312211831-a1f570bd01a1 // indirect
github.com/dennwc/btrfs v0.0.0-20240418142341-0167142bde7a // indirect
github.com/dennwc/ioctl v1.0.0 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/ema/qdisc v1.0.0 // indirect
@@ -60,7 +61,7 @@ require (
github.com/hodgesds/perf-utils v0.7.0 // indirect
github.com/illumos/go-kstat v0.0.0-20210513183136-173c9b0a9973 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/jsimonetti/rtnetlink v1.3.5 // indirect
github.com/jsimonetti/rtnetlink v1.4.2 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/labstack/gommon v0.4.2 // indirect
@@ -72,8 +73,8 @@ require (
github.com/mdlayher/ethtool v0.1.0 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mdlayher/wifi v0.1.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mdlayher/wifi v0.2.0 // indirect
github.com/miekg/dns v1.1.59 // indirect
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
github.com/opencontainers/selinux v1.11.0 // indirect
@@ -82,7 +83,7 @@ require (
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus-community/go-runit v0.1.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/prometheus/procfs v0.15.0 // indirect
github.com/quic-go/quic-go v0.41.0 // indirect
github.com/refraction-networking/utls v1.6.3 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
@@ -90,7 +91,6 @@ require (
github.com/safchain/ethtool v0.3.0 // indirect
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f // indirect
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect
github.com/sagernet/sing v0.3.8 // indirect
github.com/sagernet/sing-dns v0.1.14 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
github.com/sagernet/sing-tun v0.2.7 // indirect
@@ -107,7 +107,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
@@ -117,7 +117,7 @@ require (
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 // indirect
google.golang.org/protobuf v1.33.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
howett.net/plist v1.0.1 // indirect
+30 -67
View File
@@ -10,22 +10,22 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs=
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beevik/ntp v1.3.0 h1:/w5VhpW5BGKS37vFm1p9oVk/t4HnnkKZAZIubHM6F7Q=
github.com/beevik/ntp v1.3.0/go.mod h1:vD6h1um4kzXpqmLTuu0cCLcC+NfvC0IC+ltmEDA8E78=
github.com/beevik/ntp v1.4.2 h1:cjYhZqczanf6br/ocViahE75ipj7CmKQAh7fSBaCNK4=
github.com/beevik/ntp v1.4.2/go.mod h1:zkATLTt8VUZuOfYX2KgOnir4yvtAxWbnUUA24umXFnc=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
@@ -37,8 +37,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dennwc/btrfs v0.0.0-20230312211831-a1f570bd01a1 h1:ue4Es4Xzz255hWQ7NAWzZxuXG+YOV7URzzusLLSe0zU=
github.com/dennwc/btrfs v0.0.0-20230312211831-a1f570bd01a1/go.mod h1:MYsOV9Dgsec3FFSOjywi0QK5r6TeBbdWxdrMGtiYXHA=
github.com/dennwc/btrfs v0.0.0-20240418142341-0167142bde7a h1:KfFsGLJFVdCXlySUkV2FmxNtmiztpJb6tV+XYBmmv8E=
github.com/dennwc/btrfs v0.0.0-20240418142341-0167142bde7a/go.mod h1:MYsOV9Dgsec3FFSOjywi0QK5r6TeBbdWxdrMGtiYXHA=
github.com/dennwc/ioctl v1.0.0 h1:DsWAAjIxRqNcLn9x6mwfuf2pet3iB7aK90K4tF16rLg=
github.com/dennwc/ioctl v1.0.0/go.mod h1:ellh2YB5ldny99SBU/VX7Nq0xiZbHphf1DrtHxxjMk0=
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
@@ -128,8 +128,8 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsimonetti/rtnetlink v1.3.5 h1:hVlNQNRlLDGZz31gBPicsG7Q53rnlsz1l1Ix/9XlpVA=
github.com/jsimonetti/rtnetlink v1.3.5/go.mod h1:0LFedyiTkebnd43tE4YAkWGIq9jQphow4CcwxaT2Y00=
github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90=
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -169,10 +169,10 @@ github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy5
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/mdlayher/wifi v0.1.0 h1:y8wYRUXwok5CtUZOXT3egghYesX0O79E3ALl+SIDm9Q=
github.com/mdlayher/wifi v0.1.0/go.mod h1:+gBYnZAMcUKHSFzMJXwlz7tLsEHgwDJ9DJCefhJM+gI=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/mdlayher/wifi v0.2.0 h1:vwbVyu5MWTiFNvOmWdvIx9veBlMVnEasZ90PhUi1DYU=
github.com/mdlayher/wifi v0.2.0/go.mod h1:yOfWhVZ4FFJxeHzAxDzt87Om9EkqqcCiY9Gi5gfSXwI=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
@@ -203,19 +203,19 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus-community/go-runit v0.1.0 h1:uTWEj/Fn2RoLdfg/etSqwzgYNOYPrARx1BHUN052tGA=
github.com/prometheus-community/go-runit v0.1.0/go.mod h1:AvJ9Jo3gAFu2lbM4+qfjdpq30FfiLDJZKbQ015u08IQ=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/node_exporter v1.7.0 h1:7MVpSdfWrThNo0SlldhUyAVFZ7LWbC9+QJRzB4QmkE8=
github.com/prometheus/node_exporter v1.7.0/go.mod h1:iPAWQmoxv93c51WymsZMdPOJtL/Q4IkGQrgkUGrrgIc=
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
github.com/prometheus/node_exporter v1.8.1 h1:qYIN+ghn7kEggHe4pcIRp9oXkljU8ARWyEHBr286RPY=
github.com/prometheus/node_exporter v1.8.1/go.mod h1:rJMoAQMglUjAZ7nggHnRuwfJ0hKUVW6+Gv+IaMxh6js=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek=
github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk=
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
@@ -273,15 +273,10 @@ github.com/siebenmann/go-kstat v0.0.0-20210513183136-173c9b0a9973/go.mod h1:G81a
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
@@ -310,7 +305,6 @@ github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.9 h1:wefcON0behu4DoQvCKJYZKsJlSvNhyq2I7vC2fxLFcY=
github.com/xtls/xray-core v1.8.9/go.mod h1:XDE4f422qJKAU3hNDSNZyWrOHvn9kF8UHVdyOzU38rc=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
@@ -331,18 +325,14 @@ golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10 h1:vpzMC/iZhYFAjJzHU0Cfuq+w1vLLsF2vLkDrPjzKYck=
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -353,13 +343,7 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -371,10 +355,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -385,32 +366,18 @@ golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -422,12 +389,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
@@ -452,8 +415,8 @@ google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-154
View File
@@ -1,22 +1,11 @@
package metrics
import (
"fmt"
"math"
"net/url"
"os"
"runtime"
"strings"
"time"
"github.com/Ehco1996/ehco/internal/config"
"github.com/alecthomas/kingpin/v2"
"github.com/go-ping/ping"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/promlog"
"github.com/prometheus/common/version"
"github.com/prometheus/node_exporter/collector"
"go.uber.org/zap"
)
const (
@@ -104,125 +93,6 @@ var (
}, []string{METRIC_LABEL_REMOTE})
)
func (pg *PingGroup) newPinger(addr string) (*ping.Pinger, error) {
pinger := ping.New(addr)
if err := pinger.Resolve(); err != nil {
pg.logger.Error("failed to resolve pinger", zap.String("addr", addr), zap.Error(err))
return nil, err
}
pinger.Interval = pingInterval
pinger.Timeout = time.Duration(math.MaxInt64)
pinger.RecordRtts = false
if runtime.GOOS != "darwin" {
pinger.SetPrivileged(true)
}
return pinger, nil
}
type PingGroup struct {
logger *zap.Logger
// k: addr
Pingers map[string]*ping.Pinger
// k: addr v:relay rule label joined by ","
PingerLabels map[string]string
}
func extractHost(input string) (string, error) {
// Check if the input string has a scheme, if not, add "http://"
if !strings.Contains(input, "://") {
input = "http://" + input
}
// Parse the URL
u, err := url.Parse(input)
if err != nil {
return "", err
}
return u.Hostname(), nil
}
func NewPingGroup(cfg *config.Config) *PingGroup {
logger := zap.L().Named("pinger")
pg := &PingGroup{
logger: logger,
Pingers: make(map[string]*ping.Pinger),
PingerLabels: map[string]string{},
}
// parse addr from rule
for _, relayCfg := range cfg.RelayConfigs {
// NOTE for (https/ws/wss)://xxx.com -> xxx.com
for _, remote := range relayCfg.TCPRemotes {
addr, err := extractHost(remote)
if err != nil {
pg.logger.Error("try parse host error", zap.Error(err))
}
if _, ok := pg.Pingers[addr]; ok {
// append rule label when remote host is same
pg.PingerLabels[addr] += fmt.Sprintf(",%s", relayCfg.Label)
continue
}
if pinger, err := pg.newPinger(addr); err != nil {
pg.logger.Error("new pinger meet error", zap.Error(err))
} else {
pg.Pingers[pinger.Addr()] = pinger
pg.PingerLabels[addr] = relayCfg.Label
}
}
}
// update metrics
for addr, pinger := range pg.Pingers {
pinger.OnRecv = func(pkt *ping.Packet) {
PingResponseDurationSeconds.WithLabelValues(
pkt.IPAddr.String(), pkt.Addr, pg.PingerLabels[addr]).Observe(pkt.Rtt.Seconds())
pg.logger.Sugar().Infof("%d bytes from %s icmp_seq=%d time=%v ttl=%v",
pkt.Nbytes, pkt.Addr, pkt.Seq, pkt.Rtt, pkt.Ttl)
}
pinger.OnDuplicateRecv = func(pkt *ping.Packet) {
pg.logger.Sugar().Infof("%d bytes from %s icmp_seq=%d time=%v ttl=%v (DUP!)",
pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt, pkt.Ttl)
}
}
return pg
}
func (pg *PingGroup) Describe(ch chan<- *prometheus.Desc) {
ch <- PingRequestTotal
}
func (pg *PingGroup) Collect(ch chan<- prometheus.Metric) {
for addr, pinger := range pg.Pingers {
stats := pinger.Statistics()
ch <- prometheus.MustNewConstMetric(
PingRequestTotal,
prometheus.CounterValue,
float64(stats.PacketsSent),
stats.IPAddr.String(),
stats.Addr,
pg.PingerLabels[addr],
)
}
}
func (pg *PingGroup) Run() {
if len(pg.Pingers) <= 0 {
return
}
pg.logger.Sugar().Infof("Start Ping Group now total pinger: %d", len(pg.Pingers))
splay := time.Duration(pingInterval.Nanoseconds() / int64(len(pg.Pingers)))
for addr, pinger := range pg.Pingers {
go func() {
if err := pinger.Run(); err != nil {
pg.logger.Error("Starting pinger meet err", zap.String("addr", addr), zap.Error(err))
}
}()
time.Sleep(splay)
}
}
func RegisterEhcoMetrics(cfg *config.Config) error {
// traffic
prometheus.MustRegister(EhcoAlive)
@@ -241,27 +111,3 @@ func RegisterEhcoMetrics(cfg *config.Config) error {
}
return nil
}
func RegisterNodeExporterMetrics(cfg *config.Config) error {
level := &promlog.AllowedLevel{}
// mute node_exporter logger
if err := level.Set("error"); err != nil {
return err
}
promlogConfig := &promlog.Config{Level: level}
logger := promlog.New(promlogConfig)
// see this https://github.com/prometheus/node_exporter/pull/2463
if _, err := kingpin.CommandLine.Parse([]string{}); err != nil {
return err
}
nc, err := collector.NewNodeCollector(logger)
if err != nil {
return fmt.Errorf("couldn't create collector: %s", err)
}
// nc.Collectors = collectors
prometheus.MustRegister(
nc,
version.NewCollector("node_exporter"),
)
return nil
}
+32
View File
@@ -0,0 +1,32 @@
package metrics
import (
"fmt"
"github.com/Ehco1996/ehco/internal/config"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/promlog"
"github.com/prometheus/node_exporter/collector"
)
func RegisterNodeExporterMetrics(cfg *config.Config) error {
level := &promlog.AllowedLevel{}
// mute node_exporter logger
if err := level.Set("error"); err != nil {
return err
}
logger := promlog.New(&promlog.Config{Level: level})
// node_exporter relay on `kingpin` to enable default node collector
// see https://github.com/prometheus/node_exporter/pull/2463
if _, err := kingpin.CommandLine.Parse([]string{}); err != nil {
return err
}
nc, err := collector.NewNodeCollector(logger)
if err != nil {
return fmt.Errorf("couldn't create collector: %s", err)
}
prometheus.MustRegister(nc)
return nil
}
+134
View File
@@ -0,0 +1,134 @@
package metrics
import (
"fmt"
"math"
"net/url"
"runtime"
"strings"
"time"
"github.com/Ehco1996/ehco/internal/config"
"github.com/go-ping/ping"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
)
func (pg *PingGroup) newPinger(addr string) (*ping.Pinger, error) {
pinger := ping.New(addr)
if err := pinger.Resolve(); err != nil {
pg.logger.Error("failed to resolve pinger", zap.String("addr", addr), zap.Error(err))
return nil, err
}
pinger.Interval = pingInterval
pinger.Timeout = time.Duration(math.MaxInt64)
pinger.RecordRtts = false
if runtime.GOOS != "darwin" {
pinger.SetPrivileged(true)
}
return pinger, nil
}
type PingGroup struct {
logger *zap.Logger
// k: addr
Pingers map[string]*ping.Pinger
// k: addr v:relay rule label joined by ","
PingerLabels map[string]string
}
func extractHost(input string) (string, error) {
// Check if the input string has a scheme, if not, add "http://"
if !strings.Contains(input, "://") {
input = "http://" + input
}
// Parse the URL
u, err := url.Parse(input)
if err != nil {
return "", err
}
return u.Hostname(), nil
}
func NewPingGroup(cfg *config.Config) *PingGroup {
logger := zap.L().Named("pinger")
pg := &PingGroup{
logger: logger,
Pingers: make(map[string]*ping.Pinger),
PingerLabels: map[string]string{},
}
// parse addr from rule
for _, relayCfg := range cfg.RelayConfigs {
// NOTE for (https/ws/wss)://xxx.com -> xxx.com
for _, remote := range relayCfg.TCPRemotes {
addr, err := extractHost(remote)
if err != nil {
pg.logger.Error("try parse host error", zap.Error(err))
}
if _, ok := pg.Pingers[addr]; ok {
// append rule label when remote host is same
pg.PingerLabels[addr] += fmt.Sprintf(",%s", relayCfg.Label)
continue
}
if pinger, err := pg.newPinger(addr); err != nil {
pg.logger.Error("new pinger meet error", zap.Error(err))
} else {
pg.Pingers[pinger.Addr()] = pinger
pg.PingerLabels[addr] = relayCfg.Label
}
}
}
// update metrics
for addr, pinger := range pg.Pingers {
pinger.OnRecv = func(pkt *ping.Packet) {
PingResponseDurationSeconds.WithLabelValues(
pkt.IPAddr.String(), pkt.Addr, pg.PingerLabels[addr]).Observe(pkt.Rtt.Seconds())
pg.logger.Sugar().Infof("%d bytes from %s icmp_seq=%d time=%v ttl=%v",
pkt.Nbytes, pkt.Addr, pkt.Seq, pkt.Rtt, pkt.Ttl)
}
pinger.OnDuplicateRecv = func(pkt *ping.Packet) {
pg.logger.Sugar().Infof("%d bytes from %s icmp_seq=%d time=%v ttl=%v (DUP!)",
pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt, pkt.Ttl)
}
}
return pg
}
func (pg *PingGroup) Describe(ch chan<- *prometheus.Desc) {
ch <- PingRequestTotal
}
func (pg *PingGroup) Collect(ch chan<- prometheus.Metric) {
for addr, pinger := range pg.Pingers {
stats := pinger.Statistics()
ch <- prometheus.MustNewConstMetric(
PingRequestTotal,
prometheus.CounterValue,
float64(stats.PacketsSent),
stats.IPAddr.String(),
stats.Addr,
pg.PingerLabels[addr],
)
}
}
func (pg *PingGroup) Run() {
if len(pg.Pingers) <= 0 {
return
}
pg.logger.Sugar().Infof("Start Ping Group now total pinger: %d", len(pg.Pingers))
splay := time.Duration(pingInterval.Nanoseconds() / int64(len(pg.Pingers)))
for addr, pinger := range pg.Pingers {
go func() {
if err := pinger.Run(); err != nil {
pg.logger.Error("Starting pinger meet err", zap.String("addr", addr), zap.Error(err))
}
}()
time.Sleep(splay)
}
}
+4 -2
View File
@@ -43,7 +43,8 @@ func (s *WsClient) TCPHandShake(remote *lb.Node) (net.Conn, error) {
latency := time.Since(t1)
metrics.HandShakeDuration.WithLabelValues(remote.Label).Observe(float64(latency.Milliseconds()))
remote.HandShakeDuration = latency
return wsc, nil
c := newWsConn(wsc, false)
return c, nil
}
type WsServer struct {
@@ -90,7 +91,8 @@ func (s *WsServer) HandleRequest(w http.ResponseWriter, req *http.Request) {
if err != nil {
return
}
if err := s.RelayTCPConn(wsc, s.relayer.TCPHandShake); err != nil {
c := newWsConn(wsc, true)
if err := s.RelayTCPConn(c, s.relayer.TCPHandShake); err != nil {
s.l.Errorf("RelayTCPConn error: %s", err.Error())
}
}
+82
View File
@@ -0,0 +1,82 @@
package transporter
import (
"fmt"
"io"
"net"
"time"
"github.com/Ehco1996/ehco/pkg/buffer"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
)
type wsConn struct {
conn net.Conn
isServer bool
buf []byte
}
func newWsConn(conn net.Conn, isServer bool) *wsConn {
return &wsConn{conn: conn, isServer: isServer, buf: buffer.BufferPool.Get()}
}
func (c *wsConn) Read(b []byte) (n int, err error) {
header, err := ws.ReadHeader(c.conn)
if err != nil {
return 0, err
}
if header.Length > int64(cap(c.buf)) {
c.buf = make([]byte, header.Length)
}
payload := c.buf[:header.Length]
_, err = io.ReadFull(c.conn, payload)
if err != nil {
return 0, err
}
if header.Masked {
ws.Cipher(payload, header.Mask, 0)
}
if len(payload) > len(b) {
return 0, fmt.Errorf("buffer too small to transport ws msg")
}
copy(b, payload)
return len(payload), nil
}
func (c *wsConn) Write(b []byte) (n int, err error) {
if c.isServer {
err = wsutil.WriteServerBinary(c.conn, b)
} else {
err = wsutil.WriteClientBinary(c.conn, b)
}
if err != nil {
return 0, err
}
return len(b), nil
}
func (c *wsConn) Close() error {
defer buffer.BufferPool.Put(c.buf)
return c.conn.Close()
}
func (c *wsConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *wsConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *wsConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *wsConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *wsConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
+70
View File
@@ -0,0 +1,70 @@
package transporter
import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/gobwas/ws"
"github.com/stretchr/testify/assert"
)
func TestClientConn_ReadWrite(t *testing.T) {
data := []byte("hello")
// Create a WebSocket server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, _, _, err := ws.UpgradeHTTP(r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
go func() {
defer conn.Close()
wsc := newWsConn(conn, true)
buf := make([]byte, 1024)
for {
n, err := wsc.Read(buf)
if err != nil {
return
}
assert.Equal(t, len(data), n)
assert.Equal(t, "hello", string(buf[:n]))
_, err = wsc.Write(buf[:n])
if err != nil {
return
}
}
}()
}))
defer server.Close()
// Create a WebSocket client
addr, err := url.Parse(server.URL)
if err != nil {
t.Fatal(err)
}
conn, _, _, err := ws.DefaultDialer.Dial(context.TODO(), "ws://"+addr.Host)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
wsClientConn := newWsConn(conn, false)
for i := 0; i < 3; i++ {
// test write
n, err := wsClientConn.Write(data)
assert.NoError(t, err, "test cnt %d", i)
assert.Equal(t, len(data), n, "test cnt %d", i)
// test read
buf := make([]byte, 100)
n, err = wsClientConn.Read(buf)
assert.NoError(t, err, "test cnt %d", i)
assert.Equal(t, len(data), n, "test cnt %d", i)
assert.Equal(t, "hello", string(buf[:n]), "test cnt %d", i)
}
}
+16 -7
View File
@@ -1,17 +1,26 @@
package main
import "github.com/Ehco1996/ehco/test/echo"
import (
"time"
"github.com/Ehco1996/ehco/test/echo"
)
func main() {
msg := []byte("hello")
echoServerAddr := "127.0.0.1:2333"
println("real echo server at:", echoServerAddr)
// start ehco real server
// go run cmd/ehco/main.go -l 0.0.0.0:2234 -r 0.0.0.0:2333
relayAddr := "127.0.0.1:2234"
println("real echo server at:", echoServerAddr, "relay addr:", relayAddr)
ret := echo.SendTcpMsg(msg, relayAddr)
println(string(ret))
if string(ret) != "hello" {
panic("relay short failed")
}
println("test short conn success, hello sended and received")
if err := echo.EchoTcpMsgLong(msg, time.Second, relayAddr); err != nil {
panic("relay long failed:" + err.Error())
}
println("test long conn success")
}
+2 -1
View File
@@ -27,6 +27,7 @@ func echo(conn net.Conn) {
logger.Error(err.Error())
return
}
println("echo server receive", string(buf[:i]))
_, err = conn.Write(buf[:i])
if err != nil {
logger.Error(err.Error())
@@ -135,7 +136,7 @@ func EchoTcpMsgLong(msg []byte, sleepTime time.Duration, address string) error {
return err
}
if string(buf[:n]) != string(msg) {
return fmt.Errorf("msg not equal")
return fmt.Errorf("msg not equal at %d send:%s receive:%s n:%d", i, msg, buf[:n], n)
}
// to fake a long connection
time.Sleep(sleepTime)
+16
View File
@@ -0,0 +1,16 @@
{
"relay_configs": [
{
"listen": "127.0.0.1:2234",
"listen_type": "raw",
"transport_type": "ws",
"tcp_remotes": ["ws://0.0.0.0:2443"]
},
{
"listen": "127.0.0.1:2443",
"listen_type": "ws",
"transport_type": "raw",
"tcp_remotes": ["127.0.0.1:2333"]
}
]
}
@@ -67,8 +67,8 @@
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <80000000>;
m25p,fast-read;
spi-max-frequency = <50000000>;
broken-flash-reset;
partitions {
compatible = "fixed-partitions";
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "mt7621.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
/ {
compatible = "jdcloud,re-cp-02", "mediatek,mt7621-soc";
model = "JDCloud RE-CP-02";
aliases {
label-mac-device = &gmac0;
led-boot = &led_status_blue;
led-failsafe = &led_status_red;
led-running = &led_status_green;
led-upgrade = &led_status_blue;
};
chosen {
bootargs = "console=ttyS0,115200n8";
};
leds {
compatible = "gpio-leds";
led_status_red: red {
label = "red:status";
gpios = <&gpio 6 GPIO_ACTIVE_LOW>;
};
led_status_blue: blue {
label = "blue:status";
gpios = <&gpio 7 GPIO_ACTIVE_LOW>;
};
led_status_green: green {
label = "green:status";
gpios = <&gpio 8 GPIO_ACTIVE_LOW>;
};
};
keys {
compatible = "gpio-keys";
wps {
label = "wps";
gpios = <&gpio 15 GPIO_ACTIVE_LOW>;
linux,code = <KEY_WPS_BUTTON>;
};
reset {
label = "reset";
gpios = <&gpio 18 GPIO_ACTIVE_LOW>;
linux,code = <KEY_RESTART>;
};
};
};
&spi0 {
status = "okay";
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <10000000>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "U-Boot";
reg = <0x0 0x40000>;
read-only;
};
partition@40000 {
compatible = "u-boot,env";
label = "Config";
reg = <0x40000 0x10000>;
};
factory: partition@50000 {
label = "Factory";
reg = <0x50000 0x40000>;
read-only;
};
partition@90000 {
compatible = "denx,uimage";
label = "firmware";
reg = <0x90000 0xf70000>;
};
};
};
};
&state_default {
gpio {
groups = "uart3", "jtag", "wdt";
function = "gpio";
};
};
&sdhci {
status = "okay";
};
&pcie {
status = "okay";
};
&pcie1 {
wifi@0,0 {
compatible = "mediatek,mt76";
reg = <0x0000 0 0 0 0>;
mediatek,mtd-eeprom = <&factory 0x0>;
mediatek,disable-radar-background;
};
};
&gmac0 {
mtd-mac-address = <&factory 0x3fff4>;
};
&gmac1 {
mtd-mac-address = <&factory 0x3fffa>;
status = "okay";
};
&switch0 {
ports {
port@1 {
status = "okay";
label = "lan1";
};
port@2 {
status = "okay";
label = "lan2";
};
port@3 {
status = "okay";
label = "lan3";
};
port@4 {
status = "okay";
label = "wan";
mtd-mac-address = <&factory 0x3fffa>;
};
};
};
&xhci {
status = "disabled";
};
+9
View File
@@ -1030,6 +1030,15 @@ define Device/jcg_y2
endef
TARGET_DEVICES += jcg_y2
define Device/jdcloud_re-cp-02
$(Device/dsa-migration)
IMAGE_SIZE := 16000k
DEVICE_VENDOR := JD-Cloud
DEVICE_MODEL := RE-CP-02
DEVICE_PACKAGES := kmod-mt7915-firmware kmod-sdhci-mt7620
endef
TARGET_DEVICES += jdcloud_re-cp-02
define Device/jdcloud_re-sp-01b
$(Device/dsa-migration)
IMAGE_SIZE := 27328k
+1
View File
@@ -26,6 +26,7 @@ ramips_setup_interfaces()
h3c,tx1801-plus|\
h3c,tx1806|\
hiwifi,hc5962|\
jdcloud,re-cp-02|\
xiaomi,mi-router-3-pro)
ucidef_set_interfaces_lan_wan "lan1 lan2 lan3" "wan"
;;
@@ -13,6 +13,9 @@ boot() {
$((0xFF)) ]] || printf '\xff' | dd of=/dev/mtdblock3 count=1 \
bs=1 seek=$((0x20001))
;;
jdcloud,re-cp-02)
echo -e "bootcount 0\nbootlimit 5\nupgrade_available 1" | /usr/sbin/fw_setenv -s -
;;
linksys,e5600|\
linksys,ea7300-v1|\
linksys,ea7300-v2|\
+149 -84
View File
@@ -20,8 +20,10 @@ import (
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os/exec"
"runtime/pprof"
"strconv"
@@ -39,6 +41,7 @@ import (
"github.com/enfein/mieru/pkg/stderror"
"github.com/enfein/mieru/pkg/util"
"github.com/enfein/mieru/pkg/util/sockopts"
"golang.org/x/net/proxy"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
)
@@ -80,6 +83,13 @@ func RegisterClientCommands() {
},
clientStatusFunc,
)
RegisterCallback(
[]string{"", "test"},
func(s []string) error {
return unexpectedArgsError(s, 2)
},
clientTestFunc,
)
RegisterCallback(
[]string{"", "apply", "config"},
func(s []string) error {
@@ -225,6 +235,10 @@ var clientHelpFunc = func(s []string) error {
cmd: "status",
help: "Check mieru client status.",
},
{
cmd: "test",
help: "Test mieru client connection to the Internet via proxy server.",
},
{
cmd: "apply config <FILE>",
help: "Apply client configuration from JSON file.",
@@ -300,7 +314,7 @@ var clientStartFunc = func(s []string) error {
if err == stderror.ErrFileNotExist {
return fmt.Errorf(stderror.ClientConfigNotExist)
} else {
return fmt.Errorf(stderror.LoadClientConfigFailedErr, err)
return fmt.Errorf(stderror.GetClientConfigFailedErr, err)
}
}
if err = appctl.ValidateFullClientConfig(config); err != nil {
@@ -309,9 +323,9 @@ var clientStartFunc = func(s []string) error {
if err = appctl.IsClientDaemonRunning(context.Background()); err == nil {
if config.GetSocks5ListenLAN() {
log.Infof("mieru client is running, listening to 0.0.0.0:%d", config.GetSocks5Port())
log.Infof("mieru client is running, listening to socks5://0.0.0.0:%d", config.GetSocks5Port())
} else {
log.Infof("mieru client is running, listening to 127.0.0.1:%d", config.GetSocks5Port())
log.Infof("mieru client is running, listening to socks5://127.0.0.1:%d", config.GetSocks5Port())
}
return nil
}
@@ -331,9 +345,9 @@ var clientStartFunc = func(s []string) error {
lastErr = appctl.IsClientDaemonRunning(context.Background())
if lastErr == nil {
if config.GetSocks5ListenLAN() {
log.Infof("mieru client is started, listening to 0.0.0.0:%d", config.GetSocks5Port())
log.Infof("mieru client is started, listening to socks5://0.0.0.0:%d", config.GetSocks5Port())
} else {
log.Infof("mieru client is started, listening to 127.0.0.1:%d", config.GetSocks5Port())
log.Infof("mieru client is started, listening to socks5://127.0.0.1:%d", config.GetSocks5Port())
}
return nil
}
@@ -362,7 +376,7 @@ var clientRunFunc = func(s []string) error {
if err == stderror.ErrFileNotExist {
return fmt.Errorf(stderror.ClientConfigNotExist)
} else {
return fmt.Errorf(stderror.LoadClientConfigFailedErr, err)
return fmt.Errorf(stderror.GetClientConfigFailedErr, err)
}
}
if proto.Equal(config, &appctlpb.ClientConfig{}) {
@@ -555,18 +569,18 @@ var clientRunFunc = func(s []string) error {
}
var clientStopFunc = func(s []string) error {
if err := appctl.IsClientDaemonRunning(context.Background()); err != nil {
ctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, running, err := newClientLifecycleRPCClient(ctx)
if !running {
log.Infof(stderror.ClientNotRunning)
return nil
}
timedctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, err := appctl.NewClientLifecycleRPCClient(timedctx)
if err != nil {
return fmt.Errorf(stderror.CreateClientLifecycleRPCClientFailedErr, err)
return err
}
if _, err = client.Exit(timedctx, &appctlpb.Empty{}); err != nil {
if _, err = client.Exit(ctx, &appctlpb.Empty{}); err != nil {
return fmt.Errorf(stderror.ExitFailedErr, err)
}
log.Infof("mieru client is stopped")
@@ -589,6 +603,50 @@ var clientStatusFunc = func(s []string) error {
return nil
}
var clientTestFunc = func(s []string) error {
if err := appctl.IsClientDaemonRunning(context.Background()); err != nil {
return fmt.Errorf(stderror.ClientNotRunning)
}
config, err := appctl.LoadClientConfig()
if err != nil {
return fmt.Errorf(stderror.GetClientConfigFailedErr, err)
}
proxyURL, err := url.Parse(fmt.Sprintf("socks5://127.0.0.1:%d", config.GetSocks5Port()))
if err != nil {
return fmt.Errorf("failed to parse proxy URL: %w", err)
}
dialer, err := proxy.FromURL(proxyURL, proxy.Direct)
if err != nil {
return fmt.Errorf("failed to create proxy dialer: %w", err)
}
httpClient := &http.Client{
Transport: &http.Transport{
Dial: dialer.Dial,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return nil
},
Timeout: appctl.RPCTimeout,
}
beginTime := time.Now()
resp, err := httpClient.Get("https://google.com/generate_204")
if err != nil {
return err
}
endTime := time.Now()
d := endTime.Sub(beginTime).Round(time.Millisecond)
defer resp.Body.Close()
io.ReadAll(resp.Body)
if resp.StatusCode != 204 {
return fmt.Errorf("received unexpected status code %d after %v", resp.StatusCode, d)
}
log.Infof("Connected to https://google.com after %v", d)
return nil
}
var clientApplyConfigFunc = func(s []string) error {
_, err := appctl.LoadClientConfig()
if err == stderror.ErrFileNotExist {
@@ -642,24 +700,23 @@ var clientExportConfigFunc = func(s []string) error {
var clientDeleteProfileFunc = func(s []string) error {
_, err := appctl.LoadClientConfig()
if err != nil {
return fmt.Errorf(stderror.LoadClientConfigFailedErr, err)
return fmt.Errorf(stderror.GetClientConfigFailedErr, err)
}
return appctl.DeleteClientConfigProfile(s[3])
}
var clientGetMetricsFunc = func(s []string) error {
if err := appctl.IsClientDaemonRunning(context.Background()); err != nil {
log.Infof(stderror.ClientNotRunning)
return nil
ctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, running, err := newClientLifecycleRPCClient(ctx)
if !running {
return fmt.Errorf(stderror.ClientNotRunning)
}
if err != nil {
return err
}
timedctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, err := appctl.NewClientLifecycleRPCClient(timedctx)
if err != nil {
return fmt.Errorf(stderror.CreateClientLifecycleRPCClientFailedErr, err)
}
metrics, err := client.GetMetrics(timedctx, &appctlpb.Empty{})
metrics, err := client.GetMetrics(ctx, &appctlpb.Empty{})
if err != nil {
return fmt.Errorf(stderror.GetMetricsFailedErr, err)
}
@@ -668,18 +725,17 @@ var clientGetMetricsFunc = func(s []string) error {
}
var clientGetConnectionsFunc = func(s []string) error {
if err := appctl.IsClientDaemonRunning(context.Background()); err != nil {
log.Infof(stderror.ClientNotRunning)
return nil
ctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, running, err := newClientLifecycleRPCClient(ctx)
if !running {
return fmt.Errorf(stderror.ClientNotRunning)
}
if err != nil {
return err
}
timedctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, err := appctl.NewClientLifecycleRPCClient(timedctx)
if err != nil {
return fmt.Errorf(stderror.CreateClientLifecycleRPCClientFailedErr, err)
}
info, err := client.GetSessionInfo(timedctx, &appctlpb.Empty{})
info, err := client.GetSessionInfo(ctx, &appctlpb.Empty{})
if err != nil {
return fmt.Errorf(stderror.GetConnectionsFailedErr, err)
}
@@ -690,18 +746,17 @@ var clientGetConnectionsFunc = func(s []string) error {
}
var clientGetThreadDumpFunc = func(s []string) error {
if err := appctl.IsClientDaemonRunning(context.Background()); err != nil {
log.Infof(stderror.ClientNotRunning)
return nil
ctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, running, err := newClientLifecycleRPCClient(ctx)
if !running {
return fmt.Errorf(stderror.ClientNotRunning)
}
if err != nil {
return err
}
timedctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, err := appctl.NewClientLifecycleRPCClient(timedctx)
if err != nil {
return fmt.Errorf(stderror.CreateClientLifecycleRPCClientFailedErr, err)
}
dump, err := client.GetThreadDump(timedctx, &appctlpb.Empty{})
dump, err := client.GetThreadDump(ctx, &appctlpb.Empty{})
if err != nil {
return fmt.Errorf(stderror.GetThreadDumpFailedErr, err)
}
@@ -710,18 +765,17 @@ var clientGetThreadDumpFunc = func(s []string) error {
}
var clientGetHeapProfileFunc = func(s []string) error {
if err := appctl.IsClientDaemonRunning(context.Background()); err != nil {
log.Infof(stderror.ClientNotRunning)
return nil
ctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, running, err := newClientLifecycleRPCClient(ctx)
if !running {
return fmt.Errorf(stderror.ClientNotRunning)
}
if err != nil {
return err
}
timedctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, err := appctl.NewClientLifecycleRPCClient(timedctx)
if err != nil {
return fmt.Errorf(stderror.CreateClientLifecycleRPCClientFailedErr, err)
}
if _, err := client.GetHeapProfile(timedctx, &appctlpb.ProfileSavePath{FilePath: proto.String(s[3])}); err != nil {
if _, err := client.GetHeapProfile(ctx, &appctlpb.ProfileSavePath{FilePath: proto.String(s[3])}); err != nil {
return fmt.Errorf(stderror.GetHeapProfileFailedErr, err)
}
log.Infof("heap profile is saved to %q", s[3])
@@ -729,18 +783,17 @@ var clientGetHeapProfileFunc = func(s []string) error {
}
var clientGetMemoryStatisticsFunc = func(s []string) error {
if err := appctl.IsClientDaemonRunning(context.Background()); err != nil {
log.Infof(stderror.ClientNotRunning)
return nil
ctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, running, err := newClientLifecycleRPCClient(ctx)
if !running {
return fmt.Errorf(stderror.ClientNotRunning)
}
if err != nil {
return err
}
timedctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, err := appctl.NewClientLifecycleRPCClient(timedctx)
if err != nil {
return fmt.Errorf(stderror.CreateClientLifecycleRPCClientFailedErr, err)
}
memStats, err := client.GetMemoryStatistics(timedctx, &appctlpb.Empty{})
memStats, err := client.GetMemoryStatistics(ctx, &appctlpb.Empty{})
if err != nil {
return fmt.Errorf(stderror.GetMemoryStatisticsFailedErr, err)
}
@@ -749,18 +802,17 @@ var clientGetMemoryStatisticsFunc = func(s []string) error {
}
var clientStartCPUProfileFunc = func(s []string) error {
if err := appctl.IsClientDaemonRunning(context.Background()); err != nil {
log.Infof(stderror.ClientNotRunning)
return nil
ctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, running, err := newClientLifecycleRPCClient(ctx)
if !running {
return fmt.Errorf(stderror.ClientNotRunning)
}
if err != nil {
return err
}
timedctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, err := appctl.NewClientLifecycleRPCClient(timedctx)
if err != nil {
return fmt.Errorf(stderror.CreateClientLifecycleRPCClientFailedErr, err)
}
if _, err := client.StartCPUProfile(timedctx, &appctlpb.ProfileSavePath{FilePath: proto.String(s[4])}); err != nil {
if _, err := client.StartCPUProfile(ctx, &appctlpb.ProfileSavePath{FilePath: proto.String(s[4])}); err != nil {
return fmt.Errorf(stderror.StartCPUProfileFailedErr, err)
}
log.Infof("CPU profile will be saved to %q", s[4])
@@ -768,17 +820,30 @@ var clientStartCPUProfileFunc = func(s []string) error {
}
var clientStopCPUProfileFunc = func(s []string) error {
if err := appctl.IsClientDaemonRunning(context.Background()); err != nil {
log.Infof(stderror.ClientNotRunning)
return nil
ctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, running, err := newClientLifecycleRPCClient(ctx)
if !running {
return fmt.Errorf(stderror.ClientNotRunning)
}
if err != nil {
return err
}
timedctx, cancelFunc := context.WithTimeout(context.Background(), appctl.RPCTimeout)
defer cancelFunc()
client, err := appctl.NewClientLifecycleRPCClient(timedctx)
if err != nil {
return fmt.Errorf(stderror.CreateClientLifecycleRPCClientFailedErr, err)
}
client.StopCPUProfile(timedctx, &appctlpb.Empty{})
client.StopCPUProfile(ctx, &appctlpb.Empty{})
return nil
}
// newClientLifecycleRPCClient returns a new client lifecycle RPC client.
// No RPC client is returned if mieru is not running.
func newClientLifecycleRPCClient(ctx context.Context) (client appctlpb.ClientLifecycleServiceClient, running bool, err error) {
if err := appctl.IsClientDaemonRunning(ctx); err != nil {
return nil, false, nil
}
running = true
client, err = appctl.NewClientLifecycleRPCClient(ctx)
if err != nil {
return nil, true, fmt.Errorf(stderror.CreateClientLifecycleRPCClientFailedErr, err)
}
return
}
+1 -1
View File
@@ -360,7 +360,7 @@ var serverRunFunc = func(s []string) error {
if config == nil {
config, err = appctl.LoadServerConfig()
if err != nil {
return fmt.Errorf(stderror.LoadServerConfigFailedErr, err)
return fmt.Errorf(stderror.GetServerConfigFailedErr, err)
}
}
-2
View File
@@ -38,8 +38,6 @@ const (
GetThreadDumpFailedErr = "get thread dump failed: %w"
InvalidPortBindingsErr = "invalid port bindings: %w"
InvalidTransportProtocol = "invalid transport protocol"
LoadClientConfigFailedErr = "load mieru client config failed: %w"
LoadServerConfigFailedErr = "load mieru server config failed: %w"
LookupIPFailedErr = "look up IP address failed: %w"
ParseIPFailed = "parse IP address failed"
ReloadServerFailedErr = "reload mieru server failed: %w"
+1 -13
View File
@@ -3,22 +3,10 @@ package inbound
import (
"context"
"net"
"github.com/metacubex/tfo-go"
)
var (
lc = tfo.ListenConfig{
DisableTFO: true,
}
)
func SetTfo(open bool) {
lc.DisableTFO = !open
}
func SetMPTCP(open bool) {
setMultiPathTCP(&lc.ListenConfig, open)
setMultiPathTCP(getListenConfig(), open)
}
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
+23
View File
@@ -0,0 +1,23 @@
//go:build unix
package inbound
import (
"net"
"github.com/metacubex/tfo-go"
)
var (
lc = tfo.ListenConfig{
DisableTFO: true,
}
)
func SetTfo(open bool) {
lc.DisableTFO = !open
}
func getListenConfig() *net.ListenConfig {
return &lc.ListenConfig
}
+15
View File
@@ -0,0 +1,15 @@
package inbound
import (
"net"
)
var (
lc = net.ListenConfig{}
)
func SetTfo(open bool) {}
func getListenConfig() *net.ListenConfig {
return &lc
}
-17
View File
@@ -5,12 +5,8 @@ import (
"io"
"net"
"time"
"github.com/metacubex/tfo-go"
)
var DisableTFO = false
type tfoConn struct {
net.Conn
closed bool
@@ -124,16 +120,3 @@ func (c *tfoConn) ReaderReplaceable() bool {
func (c *tfoConn) WriterReplaceable() bool {
return c.Conn != nil
}
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTCPTimeout)
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
return &tfoConn{
dialed: make(chan bool, 1),
cancel: cancel,
ctx: ctx,
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
return dialer.DialContext(ctx, network, address, earlyData)
},
}, nil
}
+25
View File
@@ -0,0 +1,25 @@
//go:build unix
package dialer
import (
"context"
"net"
"github.com/metacubex/tfo-go"
)
const DisableTFO = false
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTCPTimeout)
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
return &tfoConn{
dialed: make(chan bool, 1),
cancel: cancel,
ctx: ctx,
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
return dialer.DialContext(ctx, network, address, earlyData)
},
}, nil
}
+8 -7
View File
@@ -1,11 +1,12 @@
package dialer
import "github.com/metacubex/mihomo/constant/features"
import (
"context"
"net"
)
func init() {
// According to MSDN, this option is available since Windows 10, 1607
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms738596(v=vs.85).aspx
if features.WindowsMajorVersion < 10 || (features.WindowsMajorVersion == 10 && features.WindowsBuildNumber < 14393) {
DisableTFO = true
}
const DisableTFO = true
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
return netDialer.DialContext(ctx, network, address)
}
@@ -76,7 +76,6 @@ start_service() {
local update_time
config_get update_time "config" "update_time" "3"
sed -i "/$NAME/d" /etc/crontabs/root
echo -e "30 2 * * * /etc/init.d/unblockneteasemusic" >> "/etc/crontabs/root"
! is_enabled "config" "auto_update" || echo -e "0 ${update_time} * * * $UNM_DIR/update.sh update_core" >> "/etc/crontabs/root"
/etc/init.d/cron restart
@@ -3,7 +3,6 @@ local appname = "passwall"
local sys = api.sys
m = Map(appname)
api.set_apply_on_parse(m)
s = m:section(TypedSection, "global", translate("ACLs"), "<font color='red'>" .. translate("ACLs is a tools which used to designate specific IP proxy mode.") .. "</font>")
s.anonymous = true
@@ -13,7 +13,6 @@ local port_validate = function(self, value, t)
end
m = Map(appname)
api.set_apply_on_parse(m)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
@@ -2,7 +2,6 @@ local api = require "luci.passwall.api"
local appname = "passwall"
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ App Settings ]]--
s = m:section(TypedSection, "global_app", translate("App Update"),
@@ -9,7 +9,6 @@ local has_chnlist = api.fs.access("/usr/share/passwall/rules/chnlist")
local has_chnroute = api.fs.access("/usr/share/passwall/rules/chnroute")
m = Map(appname)
api.set_apply_on_parse(m)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
@@ -16,7 +16,6 @@ for k, e in ipairs(api.get_valid_nodes()) do
end
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Haproxy Settings ]]--
s = m:section(TypedSection, "global_haproxy")
@@ -10,7 +10,6 @@ end
m = Map(appname, translate("Node Config"))
m.redirect = api.url()
api.set_apply_on_parse(m)
s = m:section(NamedSection, arg[1], "nodes", "")
s.addremove = false
@@ -4,7 +4,6 @@ local sys = api.sys
local datatypes = api.datatypes
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Other Settings ]]--
s = m:section(TypedSection, "global_other")
@@ -44,7 +44,6 @@ if has_hysteria2 then
end
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Subscribe Settings ]]--
s = m:section(TypedSection, "global_subscribe", "")
@@ -45,7 +45,6 @@ end
m = Map(appname)
m.redirect = api.url("node_subscribe")
api.set_apply_on_parse(m)
s = m:section(NamedSection, arg[1])
s.addremove = false
@@ -11,7 +11,6 @@ local port_validate = function(self, value, t)
end
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Delay Settings ]]--
s = m:section(TypedSection, "global_delay", translate("Delay Settings"))
@@ -4,8 +4,6 @@ local has_xray = api.finded_com("xray")
local has_singbox = api.finded_com("singbox")
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Rule Settings ]]--
s = m:section(TypedSection, "global_rules", translate("Rule status"))
s.anonymous = true
@@ -9,7 +9,6 @@ local chnlist_path = "/usr/share/passwall/rules/chnlist"
local chnroute_path = "/usr/share/passwall/rules/chnroute"
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Rule List Settings ]]--
s = m:section(TypedSection, "global_rules")
@@ -4,7 +4,6 @@ local datatypes = api.datatypes
m = Map(appname, "Sing-Box/Xray " .. translate("Shunt Rule"))
m.redirect = api.url()
api.set_apply_on_parse(m)
s = m:section(NamedSection, arg[1], "shunt_rules", "")
s.addremove = false
@@ -5,7 +5,6 @@ local has_singbox = api.finded_com("singbox")
local has_xray = api.finded_com("xray")
m = Map(appname)
api.set_apply_on_parse(m)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
@@ -1,7 +1,6 @@
local api = require "luci.passwall.api"
m = Map("passwall_server", translate("Server-Side"))
api.set_apply_on_parse(m)
t = m:section(NamedSection, "global", "global")
t.anonymous = true
@@ -4,7 +4,6 @@ local types_dir = "/usr/lib/lua/luci/model/cbi/passwall/server/type/"
m = Map("passwall_server", translate("Server Config"))
m.redirect = api.url("server")
api.set_apply_on_parse(m)
s = m:section(NamedSection, arg[1], "user", "")
s.addremove = false
@@ -999,22 +999,6 @@ function to_check_self()
}
end
function is_js_luci()
return sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0
end
function set_apply_on_parse(map)
if is_js_luci() == true then
map.apply_on_parse = false
map.on_after_apply = function(self)
if self.redirect then
os.execute("sleep 1")
luci.http.redirect(self.redirect)
end
end
end
end
function luci_types(id, m, s, type_name, option_prefix)
local rewrite_option_table = {}
for key, value in pairs(s.fields) do
+8
View File
@@ -2,6 +2,14 @@
icon: material/alert-decagram
---
#### 1.10.0-alpha.3
* Fix auto redirect **1**
**1**:
Tun inbound with `auto_route` and `auto_redirect` now works as expected on routers without intervention.
#### 1.10.0-alpha.2
* Move auto redirect to Tun **1**
+3 -3
View File
@@ -88,8 +88,8 @@ icon: material/new-box
"match_domain": []
}
},
... // Listen Fields
...
// Listen Fields
}
```
@@ -150,7 +150,7 @@ Enforce strict routing rules when `auto_route` is enabled:
*In Linux*:
* Let unsupported network unreachable
* Route all connections to tun
* Make ICMP traffic route to tun instead of upstream interfaces
It prevents address leaks and makes DNS hijacking work on Android.
@@ -150,7 +150,7 @@ tun 接口的 IPv6 前缀。
*在 Linux 中*:
* 让不支持的网络无法到达
* 将所有连接路由到 tun
* 使 ICMP 流量路由到 tun 而不是上游接口
它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作。
+2 -2
View File
@@ -33,7 +33,7 @@ require (
github.com/sagernet/sing-shadowsocks v0.2.6
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.3.0-beta.6
github.com/sagernet/sing-tun v0.4.0-beta.2
github.com/sagernet/sing-vmess v0.1.8
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6
@@ -45,6 +45,7 @@ require (
go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.23.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.25.0
golang.org/x/sys v0.20.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
@@ -84,7 +85,6 @@ require (
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.15.0 // indirect
+2 -2
View File
@@ -120,8 +120,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-tun v0.3.0-beta.6 h1:L11kMrM7UfUW0pzQiU66Fffh4o86KZc1SFGbkYi8Ma8=
github.com/sagernet/sing-tun v0.3.0-beta.6/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=
github.com/sagernet/sing-tun v0.4.0-beta.2 h1:Czf5w73shqnbbLFDUyrFkGnm5B2EuYZB5D8bMP2d0lk=
github.com/sagernet/sing-tun v0.4.0-beta.2/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
+134 -52
View File
@@ -13,10 +13,15 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
"golang.org/x/exp/slices"
)
const (
@@ -27,12 +32,18 @@ const (
type tunAutoRedirect struct {
myInboundAdapter
tunOptions *tun.Options
iptablesPath string
androidSu bool
suPath string
enableIPv6 bool
ip6tablesPath string
tunOptions *tun.Options
interfaceFinder control.InterfaceFinder
networkMonitor tun.NetworkUpdateMonitor
networkCallback *list.Element[tun.NetworkUpdateCallback]
enableIPv4 bool
enableIPv6 bool
localAddresses4 []netip.Prefix
localAddresses6 []netip.Prefix
iptablesPath string
ip6tablesPath string
androidSu bool
suPath string
}
func newAutoRedirect(t *Tun) (*tunAutoRedirect, error) {
@@ -47,39 +58,41 @@ func newAutoRedirect(t *Tun) (*tunAutoRedirect, error) {
router: t.router,
logger: t.logger,
tag: t.tag,
listenOptions: option.ListenOptions{
Listen: option.NewListenAddress(netip.AddrFrom4([4]byte{127, 0, 0, 1})),
},
},
tunOptions: &t.tunOptions,
tunOptions: &t.tunOptions,
interfaceFinder: t.router.InterfaceFinder(),
networkMonitor: t.router.NetworkMonitor(),
}
server.connHandler = server
if C.IsAndroid {
server.iptablesPath = "/system/bin/iptables"
userId := os.Getuid()
if userId != 0 {
var (
suPath string
err error
)
if t.platformInterface != nil {
suPath, err = exec.LookPath("/bin/su")
} else {
suPath, err = exec.LookPath("su")
if len(t.tunOptions.Inet4Address) > 0 {
server.enableIPv4 = true
if C.IsAndroid {
server.iptablesPath = "/system/bin/iptables"
userId := os.Getuid()
if userId != 0 {
var (
suPath string
err error
)
if t.platformInterface != nil {
suPath, err = exec.LookPath("/bin/su")
} else {
suPath, err = exec.LookPath("su")
}
if err == nil {
server.androidSu = true
server.suPath = suPath
} else {
return nil, E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH"))
}
}
if err == nil {
server.androidSu = true
server.suPath = suPath
} else {
return nil, E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH"))
} else {
iptablesPath, err := exec.LookPath("iptables")
if err != nil {
return nil, E.Cause(err, "iptables is required")
}
server.iptablesPath = iptablesPath
}
} else {
iptablesPath, err := exec.LookPath("iptables")
if err != nil {
return nil, E.Cause(err, "iptables is required")
}
server.iptablesPath = iptablesPath
}
if !C.IsAndroid && len(t.tunOptions.Inet6Address) > 0 {
err := server.initializeIP6Tables()
@@ -87,6 +100,15 @@ func newAutoRedirect(t *Tun) (*tunAutoRedirect, error) {
t.logger.Debug("device has no ip6tables nat support: ", err)
}
}
var listenAddr netip.Addr
if C.IsAndroid {
listenAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
} else if server.enableIPv6 {
listenAddr = netip.IPv6Unspecified()
} else {
listenAddr = netip.IPv4Unspecified()
}
server.listenOptions.Listen = option.NewListenAddress(listenAddr)
return server, nil
}
@@ -95,7 +117,7 @@ func (t *tunAutoRedirect) initializeIP6Tables() error {
if err != nil {
return err
}
output, err := exec.Command(ip6tablesPath, "-t nat -L", tableNameOutput).CombinedOutput()
/*output, err := exec.Command(ip6tablesPath, "-t nat -L", tableNameOutput).CombinedOutput()
switch exitErr := err.(type) {
case nil:
case *exec.ExitError:
@@ -104,7 +126,7 @@ func (t *tunAutoRedirect) initializeIP6Tables() error {
}
default:
return err
}
}*/
t.ip6tablesPath = ip6tablesPath
t.enableIPv6 = true
return nil
@@ -115,25 +137,79 @@ func (t *tunAutoRedirect) Start(tunName string) error {
if err != nil {
return E.Cause(err, "start redirect server")
}
t.cleanupIPTables(t.iptablesPath)
if t.enableIPv4 {
t.cleanupIPTables(t.iptablesPath)
}
if t.enableIPv6 {
t.cleanupIPTables(t.ip6tablesPath)
}
err = t.setupIPTables(t.iptablesPath, tunName)
err = t.updateInterfaces(false)
if err != nil {
return err
}
if t.enableIPv4 {
err = t.setupIPTables(t.iptablesPath, tunName)
if err != nil {
return err
}
}
if t.enableIPv6 {
err = t.setupIPTables(t.ip6tablesPath, tunName)
if err != nil {
return err
}
}
t.networkCallback = t.networkMonitor.RegisterCallback(func() {
rErr := t.updateInterfaces(true)
if rErr != nil {
t.logger.Error("recreate prerouting rules: ", rErr)
}
})
return nil
}
func (t *tunAutoRedirect) updateInterfaces(recreate bool) error {
addresses := common.Filter(common.FlatMap(common.Filter(t.interfaceFinder.Interfaces(), func(it control.Interface) bool {
return it.Name != t.tunOptions.Name
}), func(it control.Interface) []netip.Prefix {
return it.Addresses
}), func(it netip.Prefix) bool {
address := it.Addr()
return !(address.IsLoopback() || address.IsLinkLocalUnicast())
})
oldLocalAddresses4 := t.localAddresses4
oldLocalAddresses6 := t.localAddresses6
localAddresses4 := common.Filter(addresses, func(it netip.Prefix) bool { return it.Addr().Is4() })
localAddresses6 := common.Filter(addresses, func(it netip.Prefix) bool { return it.Addr().Is6() })
t.localAddresses4 = localAddresses4
t.localAddresses6 = localAddresses6
if !recreate || t.androidSu {
return nil
}
if t.enableIPv4 {
if !slices.Equal(localAddresses4, oldLocalAddresses4) {
err := t.setupIPTablesPreRouting(t.iptablesPath, true)
if err != nil {
return err
}
}
}
if t.enableIPv6 {
if !slices.Equal(localAddresses6, oldLocalAddresses6) {
err := t.setupIPTablesPreRouting(t.ip6tablesPath, true)
if err != nil {
return err
}
}
}
return nil
}
func (t *tunAutoRedirect) Close() error {
t.cleanupIPTables(t.iptablesPath)
t.networkMonitor.UnregisterCallback(t.networkCallback)
if t.enableIPv4 {
t.cleanupIPTables(t.iptablesPath)
}
if t.enableIPv6 {
t.cleanupIPTables(t.ip6tablesPath)
}
@@ -186,7 +262,7 @@ func (t *tunAutoRedirect) setupIPTables(iptablesPath string, tunName string) err
return err
}
// PREROUTING
err = t.setupIPTablesPreRouting(iptablesPath)
err = t.setupIPTablesPreRouting(iptablesPath, false)
if err != nil {
return err
}
@@ -194,8 +270,13 @@ func (t *tunAutoRedirect) setupIPTables(iptablesPath string, tunName string) err
return nil
}
func (t *tunAutoRedirect) setupIPTablesPreRouting(iptablesPath string) error {
err := t.runShell(iptablesPath, "-t nat -N", tableNamePreRouteing)
func (t *tunAutoRedirect) setupIPTablesPreRouting(iptablesPath string, recreate bool) error {
var err error
if !recreate {
err = t.runShell(iptablesPath, "-t nat -N", tableNamePreRouteing)
} else {
err = t.runShell(iptablesPath, "-t nat -F", tableNamePreRouteing)
}
if err != nil {
return err
}
@@ -225,7 +306,7 @@ func (t *tunAutoRedirect) setupIPTablesPreRouting(iptablesPath string) error {
if len(t.tunOptions.ExcludeInterface) > 0 {
for _, name := range t.tunOptions.ExcludeInterface {
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
"-o", name, "-j RETURN")
"-i", name, "-j RETURN")
if err != nil {
return err
}
@@ -240,15 +321,16 @@ func (t *tunAutoRedirect) setupIPTablesPreRouting(iptablesPath string) error {
}
}
}
for _, netIf := range t.router.(adapter.Router).InterfaceFinder().Interfaces() {
for _, addr := range netIf.Addresses {
if (t.iptablesPath == iptablesPath) != addr.Addr().Is4() {
continue
}
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing, "-d", addr.String(), "-j RETURN")
if err != nil {
return err
}
var addresses []netip.Prefix
if t.iptablesPath == iptablesPath {
addresses = t.localAddresses4
} else {
addresses = t.localAddresses6
}
for _, address := range addresses {
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing, "-d", address.String(), "-j RETURN")
if err != nil {
return err
}
}
if len(routeAddress) > 0 {
@@ -262,7 +344,7 @@ func (t *tunAutoRedirect) setupIPTablesPreRouting(iptablesPath string) error {
} else if len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0 {
for _, name := range t.tunOptions.IncludeInterface {
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
"-o", name, "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
"-i", name, "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
if err != nil {
return err
}
@@ -3,7 +3,6 @@ local appname = "passwall"
local sys = api.sys
m = Map(appname)
api.set_apply_on_parse(m)
s = m:section(TypedSection, "global", translate("ACLs"), "<font color='red'>" .. translate("ACLs is a tools which used to designate specific IP proxy mode.") .. "</font>")
s.anonymous = true
@@ -13,7 +13,6 @@ local port_validate = function(self, value, t)
end
m = Map(appname)
api.set_apply_on_parse(m)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
@@ -2,7 +2,6 @@ local api = require "luci.passwall.api"
local appname = "passwall"
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ App Settings ]]--
s = m:section(TypedSection, "global_app", translate("App Update"),
@@ -9,7 +9,6 @@ local has_chnlist = api.fs.access("/usr/share/passwall/rules/chnlist")
local has_chnroute = api.fs.access("/usr/share/passwall/rules/chnroute")
m = Map(appname)
api.set_apply_on_parse(m)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
@@ -16,7 +16,6 @@ for k, e in ipairs(api.get_valid_nodes()) do
end
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Haproxy Settings ]]--
s = m:section(TypedSection, "global_haproxy")
@@ -10,7 +10,6 @@ end
m = Map(appname, translate("Node Config"))
m.redirect = api.url()
api.set_apply_on_parse(m)
s = m:section(NamedSection, arg[1], "nodes", "")
s.addremove = false
@@ -4,7 +4,6 @@ local sys = api.sys
local datatypes = api.datatypes
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Other Settings ]]--
s = m:section(TypedSection, "global_other")
@@ -44,7 +44,6 @@ if has_hysteria2 then
end
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Subscribe Settings ]]--
s = m:section(TypedSection, "global_subscribe", "")
@@ -45,7 +45,6 @@ end
m = Map(appname)
m.redirect = api.url("node_subscribe")
api.set_apply_on_parse(m)
s = m:section(NamedSection, arg[1])
s.addremove = false
@@ -11,7 +11,6 @@ local port_validate = function(self, value, t)
end
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Delay Settings ]]--
s = m:section(TypedSection, "global_delay", translate("Delay Settings"))
@@ -4,8 +4,6 @@ local has_xray = api.finded_com("xray")
local has_singbox = api.finded_com("singbox")
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Rule Settings ]]--
s = m:section(TypedSection, "global_rules", translate("Rule status"))
s.anonymous = true
@@ -9,7 +9,6 @@ local chnlist_path = "/usr/share/passwall/rules/chnlist"
local chnroute_path = "/usr/share/passwall/rules/chnroute"
m = Map(appname)
api.set_apply_on_parse(m)
-- [[ Rule List Settings ]]--
s = m:section(TypedSection, "global_rules")
@@ -4,7 +4,6 @@ local datatypes = api.datatypes
m = Map(appname, "Sing-Box/Xray " .. translate("Shunt Rule"))
m.redirect = api.url()
api.set_apply_on_parse(m)
s = m:section(NamedSection, arg[1], "shunt_rules", "")
s.addremove = false
@@ -5,7 +5,6 @@ local has_singbox = api.finded_com("singbox")
local has_xray = api.finded_com("xray")
m = Map(appname)
api.set_apply_on_parse(m)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
@@ -1,7 +1,6 @@
local api = require "luci.passwall.api"
m = Map("passwall_server", translate("Server-Side"))
api.set_apply_on_parse(m)
t = m:section(NamedSection, "global", "global")
t.anonymous = true
@@ -4,7 +4,6 @@ local types_dir = "/usr/lib/lua/luci/model/cbi/passwall/server/type/"
m = Map("passwall_server", translate("Server Config"))
m.redirect = api.url("server")
api.set_apply_on_parse(m)
s = m:section(NamedSection, arg[1], "user", "")
s.addremove = false
@@ -999,22 +999,6 @@ function to_check_self()
}
end
function is_js_luci()
return sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0
end
function set_apply_on_parse(map)
if is_js_luci() == true then
map.apply_on_parse = false
map.on_after_apply = function(self)
if self.redirect then
os.execute("sleep 1")
luci.http.redirect(self.redirect)
end
end
end
end
function luci_types(id, m, s, type_name, option_prefix)
local rewrite_option_table = {}
for key, value in pairs(s.fields) do
+3 -3
View File
@@ -10,9 +10,9 @@ PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/semigodking/redsocks.git
PKG_SOURCE_DATE:=2024-01-27
PKG_SOURCE_VERSION:=92dbff008a54540159bbb4c0ff19ccf224155d76
PKG_MIRROR_HASH:=6c45324e824fd261eb919592207b368c8a2668c01ef882bd348868362ea80f44
PKG_SOURCE_DATE:=2024-05-28
PKG_SOURCE_VERSION:=c8e1e6c4c1d623b2e540528ac9efd06dde952006
PKG_MIRROR_HASH:=b1e64e3af162ed91976eec9fa07ccd569ee8369f896bb2a19b8507eb01f4f769
PKG_MAINTAINER:=semigodking <semigodking@gmail.com>
PKG_LICENSE:=Apache-2.0
+2 -2
View File
@@ -12,13 +12,13 @@ PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
include $(INCLUDE_DIR)/package.mk
GEOIP_VER:=202405230041
GEOIP_VER:=202405300042
GEOIP_FILE:=geoip.dat.$(GEOIP_VER)
define Download/geoip
URL:=https://github.com/v2fly/geoip/releases/download/$(GEOIP_VER)/
URL_FILE:=geoip.dat
FILE:=$(GEOIP_FILE)
HASH:=0401b0a1b82ad0d01c119f311d7ae0e0bae4d928f287251df2a98281d173f3d7
HASH:=ee22e254e7cb9a2e45d8851a70022662c15c739604c379029ae8f6a19a3ccc4f
endef
GEOSITE_VER:=20240508170917
@@ -67,7 +67,7 @@ object SubscriptionUpdater {
}
fun importBatchConfig(server: String?, subid: String = "") {
val append = subid.isEmpty()
val append = false
val count = AngConfigManager.importBatchConfig(server, subid, append)
if (count <= 0) {
@@ -31,6 +31,7 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityMainBinding
import com.v2ray.ang.databinding.LayoutProgressBinding
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
@@ -39,7 +40,9 @@ import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.MainViewModel
import com.v2ray.ang.viewmodel.SubViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import me.drakeet.support.toast.ToastCompat
import rx.Observable
@@ -61,6 +64,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
private var mItemTouchHelper: ItemTouchHelper? = null
val mainViewModel: MainViewModel by viewModels()
val subViewModel: SubViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -281,11 +285,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true
}
// R.id.sub_setting -> {
// startActivity<SubSettingActivity>()
// true
// }
R.id.sub_update -> {
importConfigViaSub()
true
@@ -423,26 +422,24 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
return true
}
fun importBatchConfig(server: String?, subid: String = "") {
val subid2 = if(subid.isNullOrEmpty()){
mainViewModel.subscriptionId
}else{
subid
}
val append = subid.isNullOrEmpty()
fun importBatchConfig(server: String?) {
val dialog = AlertDialog.Builder(this)
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
.setCancelable(false)
.show()
var count = AngConfigManager.importBatchConfig(server, subid2, append)
if (count <= 0) {
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid2, append)
}
if (count <= 0) {
count = AngConfigManager.appendCustomConfigServer(server, subid2)
}
if (count > 0) {
toast(R.string.toast_success)
mainViewModel.reloadServerList()
} else {
toast(R.string.toast_failure)
lifecycleScope.launch(Dispatchers.IO) {
val count = subViewModel.importBatchConfig(server, mainViewModel.subscriptionId, true)
delay(500L)
launch(Dispatchers.Main) {
if (count > 0) {
toast(R.string.toast_success)
mainViewModel.reloadServerList()
} else {
toast(R.string.toast_failure)
}
dialog.dismiss()
}
}
}
@@ -520,55 +517,24 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/**
* import config from sub
*/
fun importConfigViaSub()
: Boolean {
try {
toast(R.string.title_sub_update)
MmkvManager.decodeSubscriptions().forEach {
if (TextUtils.isEmpty(it.first)
|| TextUtils.isEmpty(it.second.remarks)
|| TextUtils.isEmpty(it.second.url)
) {
return@forEach
}
if (!it.second.enabled) {
return@forEach
}
val url = Utils.idnToASCII(it.second.url)
if (!Utils.isValidUrl(url)) {
return@forEach
}
Log.d(ANG_PACKAGE, url)
lifecycleScope.launch(Dispatchers.IO) {
var configText = try {
Utils.getUrlContentWithCustomUserAgent(url)
} catch (e: Exception) {
e.printStackTrace()
""
}
if(configText.isEmpty()) {
configText = try {
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
Utils.getUrlContentWithCustomUserAgent(url, httpPort)
} catch (e: Exception) {
e.printStackTrace()
""
}
}
if(configText.isEmpty()) {
launch(Dispatchers.Main) {
toast("\"" + it.second.remarks + "\" " + getString(R.string.toast_failure))
}
return@launch
}
launch(Dispatchers.Main) {
importBatchConfig(configText, it.first)
}
fun importConfigViaSub() : Boolean {
val dialog = AlertDialog.Builder(this)
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
.setCancelable(false)
.show()
lifecycleScope.launch(Dispatchers.IO) {
val count = subViewModel.updateConfigViaSubAll()
delay(500L)
launch(Dispatchers.Main) {
if (count > 0) {
toast(R.string.toast_success)
mainViewModel.reloadServerList()
} else {
toast(R.string.toast_failure)
}
dialog.dismiss()
}
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
@@ -1,20 +1,30 @@
package com.v2ray.ang.ui
import android.content.Intent
import androidx.recyclerview.widget.LinearLayoutManager
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.v2ray.ang.R
import android.os.Bundle
import com.v2ray.ang.databinding.ActivitySubSettingBinding
import com.v2ray.ang.databinding.LayoutProgressBinding
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.viewmodel.SubViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class SubSettingActivity : BaseActivity() {
private lateinit var binding: ActivitySubSettingBinding
var subscriptions:List<Pair<String, SubscriptionItem>> = listOf()
var subscriptions: List<Pair<String, SubscriptionItem>> = listOf()
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
val subViewModel: SubViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -37,9 +47,6 @@ class SubSettingActivity : BaseActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.action_sub_setting, menu)
menu.findItem(R.id.del_config)?.isVisible = false
menu.findItem(R.id.save_config)?.isVisible = false
return super.onCreateOptionsMenu(menu)
}
@@ -48,6 +55,30 @@ class SubSettingActivity : BaseActivity() {
startActivity(Intent(this, SubEditActivity::class.java))
true
}
R.id.sub_update -> {
val dialog = AlertDialog.Builder(this)
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
.setCancelable(false)
.show()
lifecycleScope.launch(Dispatchers.IO) {
val count = subViewModel.updateConfigViaSubAll()
delay(500L)
launch(Dispatchers.Main) {
if (count > 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
dialog.dismiss()
}
}
true
}
else -> super.onOptionsItemSelected(item)
}
}
@@ -3,7 +3,6 @@ package com.v2ray.ang.util
import android.content.Context
import android.graphics.Bitmap
import android.text.TextUtils
import android.util.Log
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonPrimitive
@@ -11,20 +10,18 @@ import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.reflect.TypeToken
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.PROTOCOL_HTTP
import com.v2ray.ang.AppConfig.PROTOCOL_HTTPS
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_MTU
import com.v2ray.ang.R
import com.v2ray.ang.dto.*
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_SECURITY
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.removeWhiteSpace
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
import com.v2ray.ang.util.fmt.ShadowsocksFmt
import com.v2ray.ang.util.fmt.SocksFmt
import com.v2ray.ang.util.fmt.TrojanFmt
import com.v2ray.ang.util.fmt.VlessFmt
import com.v2ray.ang.util.fmt.VmessFmt
import com.v2ray.ang.util.fmt.WireguardFmt
import java.lang.reflect.Type
import java.net.URI
import java.util.*
object AngConfigManager {
@@ -219,250 +216,28 @@ object AngConfigManager {
//maybe Subscription
if (TextUtils.isEmpty(subid)
&& (str.startsWith(PROTOCOL_HTTP) || str.startsWith(PROTOCOL_HTTPS))) {
&& (str.startsWith(PROTOCOL_HTTP) || str.startsWith(PROTOCOL_HTTPS))
) {
MmkvManager.importUrlAsSubscription(str)
return 0
}
var config: ServerConfig? = null
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
config = ServerConfig.create(EConfigType.VMESS)
val streamSetting = config.outboundBean?.streamSettings ?: return -1
if (!tryParseNewVmess(str, config, allowInsecure)) {
if (str.indexOf("?") > 0) {
if (!tryResolveVmess4Kitsunebi(str, config)) {
return R.string.toast_incorrect_protocol
}
} else {
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
result = Utils.decode(result)
if (TextUtils.isEmpty(result)) {
return R.string.toast_decoding_failed
}
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
if (TextUtils.isEmpty(vmessQRCode.add)
|| TextUtils.isEmpty(vmessQRCode.port)
|| TextUtils.isEmpty(vmessQRCode.id)
|| TextUtils.isEmpty(vmessQRCode.net)
) {
return R.string.toast_incorrect_protocol
}
config.remarks = vmessQRCode.ps
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = vmessQRCode.add
vnext.port = Utils.parseInt(vmessQRCode.port)
vnext.users[0].id = vmessQRCode.id
vnext.users[0].security =
if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
}
val sni = streamSetting.populateTransportSettings(
vmessQRCode.net,
vmessQRCode.type,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.path,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.type,
vmessQRCode.path,
vmessQRCode.host
)
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
streamSetting.populateTlsSettings(
vmessQRCode.tls, allowInsecure,
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
fingerprint, vmessQRCode.alpn, null, null, null
)
}
}
val config = if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
VmessFmt.parseVmess(str)
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
config = ServerConfig.create(EConfigType.SHADOWSOCKS)
if (!tryResolveResolveSip002(str, config)) {
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#")
if (indexSplit > 0) {
try {
config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
result = result.substring(0, indexSplit)
}
//part decode
val indexS = result.indexOf("@")
result = if (indexS > 0) {
Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else {
Utils.decode(result)
}
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
val match = legacyPattern.matchEntire(result)
?: return R.string.toast_incorrect_protocol
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt()
server.password = match.groupValues[2]
server.method = match.groupValues[1].lowercase()
}
}
ShadowsocksFmt.parseShadowsocks(str)
} else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) {
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#")
config = ServerConfig.create(EConfigType.SOCKS)
if (indexSplit > 0) {
try {
config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
result = result.substring(0, indexSplit)
}
//part decode
val indexS = result.indexOf("@")
if (indexS > 0) {
result = Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else {
result = Utils.decode(result)
}
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
val match =
legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt()
val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = match.groupValues[1]
socksUsersBean.pass = match.groupValues[2]
server.users = listOf(socksUsersBean)
}
SocksFmt.parseSocks(str)
} else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) {
val uri = URI(Utils.fixIllegalUrl(str))
config = ServerConfig.create(EConfigType.TROJAN)
config.remarks = Utils.urlDecode(uri.fragment ?: "")
var flow = ""
var fingerprint = config.outboundBean?.streamSettings?.tlsSettings?.fingerprint
if (uri.rawQuery != null) {
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
fingerprint = queryParam["fp"] ?: ""
config.outboundBean?.streamSettings?.populateTlsSettings(
queryParam["security"] ?: TLS,
allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"],
null, null, null
)
flow = queryParam["flow"] ?: ""
} else {
config.outboundBean?.streamSettings?.populateTlsSettings(
TLS, allowInsecure, "",
fingerprint, null, null, null, null
)
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port
server.password = uri.userInfo
server.flow = flow
}
TrojanFmt.parseTrojan(str)
} else if (str.startsWith(EConfigType.VLESS.protocolScheme)) {
val uri = URI(Utils.fixIllegalUrl(str))
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
config = ServerConfig.create(EConfigType.VLESS)
val streamSetting = config.outboundBean?.streamSettings ?: return -1
config.remarks = Utils.urlDecode(uri.fragment ?: "")
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = uri.idnHost
vnext.port = uri.port
vnext.users[0].id = uri.userInfo
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
vnext.users[0].flow = queryParam["flow"] ?: ""
}
val sni = streamSetting.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
streamSetting.populateTlsSettings(
queryParam["security"] ?: "",
allowInsecure,
queryParam["sni"] ?: sni,
queryParam["fp"] ?: "",
queryParam["alpn"],
queryParam["pbk"] ?: "",
queryParam["sid"] ?: "",
queryParam["spx"] ?: ""
)
VlessFmt.parseVless(str)
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
val uri = URI(Utils.fixIllegalUrl(str))
config = ServerConfig.create(EConfigType.WIREGUARD)
config.remarks = Utils.urlDecode(uri.fragment ?: "")
if (uri.rawQuery != null) {
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
config.outboundBean?.settings?.let { wireguard ->
wireguard.secretKey = uri.userInfo
wireguard.address =
(queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace()
.split(",")
wireguard.peers?.get(0)?.publicKey = queryParam["publickey"] ?: ""
wireguard.peers?.get(0)?.endpoint = "${uri.idnHost}:${uri.port}"
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: WIREGUARD_LOCAL_MTU)
wireguard.reserved =
(queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",")
.map { it.toInt() }
}
}
WireguardFmt.parseWireguard(str)
} else {
null
}
if (config == null) {
return R.string.toast_incorrect_protocol
}
@@ -485,171 +260,6 @@ object AngConfigManager {
return 0
}
private fun tryParseNewVmess(
uriString: String,
config: ServerConfig,
allowInsecure: Boolean
): Boolean {
return runCatching {
val uri = URI(Utils.fixIllegalUrl(uriString))
check(uri.scheme == "vmess")
val (_, protocol, tlsStr, uuid, alterId) =
Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})")
.matchEntire(uri.userInfo)?.groupValues
?: error("parse user info fail.")
val tls = tlsStr.isNotBlank()
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val streamSetting = config.outboundBean?.streamSettings ?: return false
config.remarks = Utils.urlDecode(uri.fragment ?: "")
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = uri.idnHost
vnext.port = uri.port
vnext.users[0].id = uuid
vnext.users[0].security = DEFAULT_SECURITY
vnext.users[0].alterId = alterId.toInt()
}
var fingerprint = streamSetting.tlsSettings?.fingerprint
val sni = streamSetting.populateTransportSettings(protocol,
queryParam["type"],
queryParam["host"]?.split("|")?.get(0) ?: "",
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
queryParam["seed"],
queryParam["security"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"])
streamSetting.populateTlsSettings(
if (tls) TLS else "", allowInsecure, sni, fingerprint, null,
null, null, null
)
true
}.getOrElse { false }
}
private fun tryResolveVmess4Kitsunebi(server: String, config: ServerConfig): Boolean {
var result = server.replace(EConfigType.VMESS.protocolScheme, "")
val indexSplit = result.indexOf("?")
if (indexSplit > 0) {
result = result.substring(0, indexSplit)
}
result = Utils.decode(result)
val arr1 = result.split('@')
if (arr1.count() != 2) {
return false
}
val arr21 = arr1[0].split(':')
val arr22 = arr1[1].split(':')
if (arr21.count() != 2) {
return false
}
config.remarks = "Alien"
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = arr22[0]
vnext.port = Utils.parseInt(arr22[1])
vnext.users[0].id = arr21[1]
vnext.users[0].security = arr21[0]
vnext.users[0].alterId = 0
}
return true
}
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {
try {
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment ?: "")
val method: String
val password: String
if (uri.userInfo.contains(":")) {
val arrUserInfo = uri.userInfo.split(":").map { it.trim() }
if (arrUserInfo.count() != 2) {
return false
}
method = arrUserInfo[0]
password = Utils.urlDecode(arrUserInfo[1])
} else {
val base64Decode = Utils.decode(uri.userInfo)
val arrUserInfo = base64Decode.split(":").map { it.trim() }
if (arrUserInfo.count() < 2) {
return false
}
method = arrUserInfo[0]
password = base64Decode.substringAfter(":")
}
val query = Utils.urlDecode(uri.query ?: "")
if (query != "") {
val queryPairs = HashMap<String, String>()
val pairs = query.split(";")
Log.d(AppConfig.ANG_PACKAGE, pairs.toString())
for (pair in pairs) {
val idx = pair.indexOf("=")
if (idx == -1) {
queryPairs[Utils.urlDecode(pair)] = "";
} else {
queryPairs[Utils.urlDecode(pair.substring(0, idx))] =
Utils.urlDecode(pair.substring(idx + 1))
}
}
Log.d(AppConfig.ANG_PACKAGE, queryPairs.toString())
var sni: String? = ""
if (queryPairs["plugin"] == "obfs-local" && queryPairs["obfs"] == "http") {
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
"tcp",
"http",
queryPairs["obfs-host"],
queryPairs["path"],
null,
null,
null,
null,
null,
null
)
} else if (queryPairs["plugin"] == "v2ray-plugin") {
var network = "ws";
if (queryPairs["mode"] == "quic") {
network = "quic";
}
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
network,
null,
queryPairs["host"],
queryPairs["path"],
null,
null,
null,
null,
null,
null
)
}
if ("tls" in queryPairs) {
config.outboundBean?.streamSettings?.populateTlsSettings(
"tls", false, sni ?: "", null, null, null, null, null
)
}
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port
server.password = password
server.method = method
}
return true
} catch (e: Exception) {
Log.d(AppConfig.ANG_PACKAGE, e.toString())
return false
}
}
/**
* share config
@@ -657,211 +267,15 @@ object AngConfigManager {
private fun shareConfig(guid: String): String {
try {
val config = MmkvManager.decodeServerConfig(guid) ?: return ""
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting =
outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
if (config.configType != EConfigType.WIREGUARD) {
if (outbound.streamSettings == null) return ""
}
return config.configType.protocolScheme + when (config.configType) {
EConfigType.VMESS -> {
val vmessQRCode = VmessQRCode()
vmessQRCode.v = "2"
vmessQRCode.ps = config.remarks
vmessQRCode.add = outbound.getServerAddress().orEmpty()
vmessQRCode.port = outbound.getServerPort().toString()
vmessQRCode.id = outbound.getPassword().orEmpty()
vmessQRCode.aid =
outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
vmessQRCode.scy =
outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
vmessQRCode.net = streamSetting.network
vmessQRCode.tls = streamSetting.security
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
vmessQRCode.alpn =
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString())
.orEmpty()
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
outbound.getTransportSettingDetails()?.let { transportDetails ->
vmessQRCode.type = transportDetails[0]
vmessQRCode.host = transportDetails[1]
vmessQRCode.path = transportDetails[2]
}
val json = Gson().toJson(vmessQRCode)
Utils.encode(json)
}
EConfigType.VMESS -> VmessFmt.toUri(config)
EConfigType.CUSTOM -> ""
EConfigType.SHADOWSOCKS -> {
val remark = "#" + Utils.urlEncode(config.remarks)
val pw =
Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
val url = String.format(
"%s@%s:%s",
pw,
Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
url + remark
}
EConfigType.SOCKS -> {
val remark = "#" + Utils.urlEncode(config.remarks)
val pw =
if (outbound.settings?.servers?.get(0)?.users?.get(0)?.user != null)
"${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}"
else
":"
val url = String.format(
"%s@%s:%s",
Utils.encode(pw),
Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
url + remark
}
EConfigType.VLESS,
EConfigType.TROJAN -> {
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
if (config.configType == EConfigType.VLESS) {
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it
}
}
dicQuery["encryption"] =
if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
else outbound.getSecurityEncryption().orEmpty()
} else if (config.configType == EConfigType.TROJAN) {
config.outboundBean?.settings?.servers?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it
}
}
}
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint!!
}
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
dicQuery["pbk"] = tlsSetting.publicKey!!
}
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
dicQuery["sid"] = tlsSetting.shortId!!
}
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX!!)
}
}
dicQuery["type"] =
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
}
}
"ws", "httpupgrade" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
}
}
}
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format(
"%s@%s:%s",
outbound.getPassword(),
Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
url + query + remark
}
EConfigType.WIREGUARD -> {
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
dicQuery["publickey"] =
Utils.urlEncode(outbound.settings?.peers?.get(0)?.publicKey.toString())
if (outbound.settings?.reserved != null) {
dicQuery["reserved"] = Utils.urlEncode(
Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString())
.toString()
)
}
dicQuery["address"] = Utils.urlEncode(
Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString())
.toString()
)
if (outbound.settings?.mtu != null) {
dicQuery["mtu"] = outbound.settings?.mtu.toString()
}
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format(
"%s@%s:%s",
Utils.urlEncode(outbound.getPassword().toString()),
Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
url + query + remark
}
EConfigType.SHADOWSOCKS -> ShadowsocksFmt.toUri(config)
EConfigType.SOCKS -> SocksFmt.toUri(config)
EConfigType.VLESS-> VlessFmt.toUri(config)
EConfigType.TROJAN-> TrojanFmt.toUri(config)
EConfigType.WIREGUARD -> WireguardFmt.toUri(config)
}
} catch (e: Exception) {
e.printStackTrace()
@@ -1023,7 +437,7 @@ object AngConfigManager {
return 0
}
fun appendCustomConfigServer(server: String?, subid: String): Int {
fun appendCustomConfigServer(server: String?, subid: String): Int {
if (server == null) {
return 0
}
@@ -1052,7 +466,8 @@ object AngConfigManager {
var count = 0
for (srv in serverList) {
val config = ServerConfig.create(EConfigType.CUSTOM)
config.fullConfig = Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java)
config.fullConfig =
Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java)
config.remarks = config.fullConfig?.remarks
?: ("%04d-".format(count + 1) + System.currentTimeMillis()
.toString())
@@ -0,0 +1,159 @@
package com.v2ray.ang.util.fmt
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.Utils
import java.net.URI
object ShadowsocksFmt {
fun parseShadowsocks(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.SHADOWSOCKS)
if (!tryResolveResolveSip002(str, config)) {
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#")
if (indexSplit > 0) {
try {
config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
result = result.substring(0, indexSplit)
}
//part decode
val indexS = result.indexOf("@")
result = if (indexS > 0) {
Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else {
Utils.decode(result)
}
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
val match = legacyPattern.matchEntire(result)
?: return null
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt()
server.password = match.groupValues[2]
server.method = match.groupValues[1].lowercase()
}
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val pw =
Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
val url = String.format(
"%s@%s:%s",
pw,
Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
return url + remark
}
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {
try {
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment ?: "")
val method: String
val password: String
if (uri.userInfo.contains(":")) {
val arrUserInfo = uri.userInfo.split(":").map { it.trim() }
if (arrUserInfo.count() != 2) {
return false
}
method = arrUserInfo[0]
password = Utils.urlDecode(arrUserInfo[1])
} else {
val base64Decode = Utils.decode(uri.userInfo)
val arrUserInfo = base64Decode.split(":").map { it.trim() }
if (arrUserInfo.count() < 2) {
return false
}
method = arrUserInfo[0]
password = base64Decode.substringAfter(":")
}
val query = Utils.urlDecode(uri.query ?: "")
if (query != "") {
val queryPairs = HashMap<String, String>()
val pairs = query.split(";")
Log.d(AppConfig.ANG_PACKAGE, pairs.toString())
for (pair in pairs) {
val idx = pair.indexOf("=")
if (idx == -1) {
queryPairs[Utils.urlDecode(pair)] = "";
} else {
queryPairs[Utils.urlDecode(pair.substring(0, idx))] =
Utils.urlDecode(pair.substring(idx + 1))
}
}
Log.d(AppConfig.ANG_PACKAGE, queryPairs.toString())
var sni: String? = ""
if (queryPairs["plugin"] == "obfs-local" && queryPairs["obfs"] == "http") {
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
"tcp",
"http",
queryPairs["obfs-host"],
queryPairs["path"],
null,
null,
null,
null,
null,
null
)
} else if (queryPairs["plugin"] == "v2ray-plugin") {
var network = "ws";
if (queryPairs["mode"] == "quic") {
network = "quic";
}
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
network,
null,
queryPairs["host"],
queryPairs["path"],
null,
null,
null,
null,
null,
null
)
}
if ("tls" in queryPairs) {
config.outboundBean?.streamSettings?.populateTlsSettings(
"tls", false, sni ?: "", null, null, null, null, null
)
}
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port
server.password = password
server.method = method
}
return true
} catch (e: Exception) {
Log.d(AppConfig.ANG_PACKAGE, e.toString())
return false
}
}
}
@@ -0,0 +1,69 @@
package com.v2ray.ang.util.fmt
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.util.Utils
object SocksFmt {
fun parseSocks(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.SOCKS)
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#")
if (indexSplit > 0) {
try {
config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
result = result.substring(0, indexSplit)
}
//part decode
val indexS = result.indexOf("@")
if (indexS > 0) {
result = Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else {
result = Utils.decode(result)
}
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
val match =
legacyPattern.matchEntire(result) ?: return null
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt()
val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = match.groupValues[1]
socksUsersBean.pass = match.groupValues[2]
server.users = listOf(socksUsersBean)
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val pw =
if (outbound.settings?.servers?.get(0)?.users?.get(0)?.user != null)
"${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}"
else
":"
val url = String.format(
"%s@%s:%s",
Utils.encode(pw),
Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
return url + remark
}
}
@@ -0,0 +1,170 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import java.net.URI
object TrojanFmt {
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING,
MMKV.MULTI_PROCESS_MODE
)
}
fun parseTrojan(str: String): ServerConfig? {
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.TROJAN)
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment ?: "")
var flow = ""
var fingerprint = config.outboundBean?.streamSettings?.tlsSettings?.fingerprint
if (uri.rawQuery.isNullOrEmpty()) {
config.outboundBean?.streamSettings?.populateTlsSettings(
V2rayConfig.TLS, allowInsecure, "",
fingerprint, null, null, null, null
)
} else {
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
fingerprint = queryParam["fp"] ?: ""
config.outboundBean?.streamSettings?.populateTlsSettings(
queryParam["security"] ?: V2rayConfig.TLS,
allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"],
null, null, null
)
flow = queryParam["flow"] ?: ""
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port
server.password = uri.userInfo
server.flow = flow
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
config.outboundBean?.settings?.servers?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it
}
}
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint!!
}
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
dicQuery["pbk"] = tlsSetting.publicKey!!
}
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
dicQuery["sid"] = tlsSetting.shortId!!
}
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX!!)
}
}
dicQuery["type"] =
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
}
}
"ws", "httpupgrade" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
}
}
}
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format(
"%s@%s:%s",
outbound.getPassword(),
Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
return url + query + remark
}
}
@@ -0,0 +1,170 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import java.net.URI
object VlessFmt {
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING,
MMKV.MULTI_PROCESS_MODE
)
}
fun parseVless(str: String): ServerConfig? {
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.VLESS)
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val streamSetting = config.outboundBean?.streamSettings ?: return null
config.remarks = Utils.urlDecode(uri.fragment ?: "")
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = uri.idnHost
vnext.port = uri.port
vnext.users[0].id = uri.userInfo
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
vnext.users[0].flow = queryParam["flow"] ?: ""
}
val sni = streamSetting.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
streamSetting.populateTlsSettings(
queryParam["security"] ?: "",
allowInsecure,
queryParam["sni"] ?: sni,
queryParam["fp"] ?: "",
queryParam["alpn"],
queryParam["pbk"] ?: "",
queryParam["sid"] ?: "",
queryParam["spx"] ?: ""
)
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it
}
}
dicQuery["encryption"] =
if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
else outbound.getSecurityEncryption().orEmpty()
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint!!
}
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
dicQuery["pbk"] = tlsSetting.publicKey!!
}
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
dicQuery["sid"] = tlsSetting.shortId!!
}
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX!!)
}
}
dicQuery["type"] =
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
}
}
"ws", "httpupgrade" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
}
}
}
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format(
"%s@%s:%s",
outbound.getPassword(),
Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
return url + query + remark
}
}
@@ -0,0 +1,193 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import android.util.Log
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.VmessQRCode
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import java.net.URI
object VmessFmt {
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE
)
}
fun parseVmess(str: String): ServerConfig? {
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.VMESS)
val streamSetting = config.outboundBean?.streamSettings ?: return null
if (!tryParseNewVmess(str, config, allowInsecure)) {
if (str.indexOf("?") > 0) {
if (!tryResolveVmess4Kitsunebi(str, config)) {
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_incorrect_protocol")
return null
}
} else {
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
result = Utils.decode(result)
if (TextUtils.isEmpty(result)) {
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed")
return null
}
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
if (TextUtils.isEmpty(vmessQRCode.add) || TextUtils.isEmpty(vmessQRCode.port) || TextUtils.isEmpty(
vmessQRCode.id
) || TextUtils.isEmpty(vmessQRCode.net)
) {
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_incorrect_protocol")
return null
}
config.remarks = vmessQRCode.ps
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = vmessQRCode.add
vnext.port = Utils.parseInt(vmessQRCode.port)
vnext.users[0].id = vmessQRCode.id
vnext.users[0].security =
if (TextUtils.isEmpty(vmessQRCode.scy)) V2rayConfig.DEFAULT_SECURITY else vmessQRCode.scy
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
}
val sni = streamSetting.populateTransportSettings(
vmessQRCode.net,
vmessQRCode.type,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.path,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.type,
vmessQRCode.path,
vmessQRCode.host
)
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
streamSetting.populateTlsSettings(
vmessQRCode.tls,
allowInsecure,
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
fingerprint,
vmessQRCode.alpn,
null,
null,
null
)
}
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val vmessQRCode = VmessQRCode()
vmessQRCode.v = "2"
vmessQRCode.ps = config.remarks
vmessQRCode.add = outbound.getServerAddress().orEmpty()
vmessQRCode.port = outbound.getServerPort().toString()
vmessQRCode.id = outbound.getPassword().orEmpty()
vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
vmessQRCode.scy = outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
vmessQRCode.net = streamSetting.network
vmessQRCode.tls = streamSetting.security
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
vmessQRCode.alpn =
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString()).orEmpty()
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
outbound.getTransportSettingDetails()?.let { transportDetails ->
vmessQRCode.type = transportDetails[0]
vmessQRCode.host = transportDetails[1]
vmessQRCode.path = transportDetails[2]
}
val json = Gson().toJson(vmessQRCode)
return Utils.encode(json)
}
private fun tryParseNewVmess(
uriString: String, config: ServerConfig, allowInsecure: Boolean
): Boolean {
return runCatching {
val uri = URI(Utils.fixIllegalUrl(uriString))
check(uri.scheme == "vmess")
val (_, protocol, tlsStr, uuid, alterId) = Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})").matchEntire(
uri.userInfo
)?.groupValues ?: error("parse user info fail.")
val tls = tlsStr.isNotBlank()
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val streamSetting = config.outboundBean?.streamSettings ?: return false
config.remarks = Utils.urlDecode(uri.fragment ?: "")
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = uri.idnHost
vnext.port = uri.port
vnext.users[0].id = uuid
vnext.users[0].security = V2rayConfig.DEFAULT_SECURITY
vnext.users[0].alterId = alterId.toInt()
}
var fingerprint = streamSetting.tlsSettings?.fingerprint
val sni = streamSetting.populateTransportSettings(protocol,
queryParam["type"],
queryParam["host"]?.split("|")?.get(0) ?: "",
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
queryParam["seed"],
queryParam["security"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"])
streamSetting.populateTlsSettings(
if (tls) V2rayConfig.TLS else "",
allowInsecure,
sni,
fingerprint,
null,
null,
null,
null
)
true
}.getOrElse { false }
}
private fun tryResolveVmess4Kitsunebi(server: String, config: ServerConfig): Boolean {
var result = server.replace(EConfigType.VMESS.protocolScheme, "")
val indexSplit = result.indexOf("?")
if (indexSplit > 0) {
result = result.substring(0, indexSplit)
}
result = Utils.decode(result)
val arr1 = result.split('@')
if (arr1.count() != 2) {
return false
}
val arr21 = arr1[0].split(':')
val arr22 = arr1[1].split(':')
if (arr21.count() != 2) {
return false
}
config.remarks = "Alien"
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = arr22[0]
vnext.port = Utils.parseInt(arr22[1])
vnext.users[0].id = arr21[1]
vnext.users[0].security = arr21[0]
vnext.users[0].alterId = 0
}
return true
}
}
@@ -0,0 +1,71 @@
package com.v2ray.ang.util.fmt
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.removeWhiteSpace
import com.v2ray.ang.util.Utils
import java.net.URI
object WireguardFmt {
fun parseWireguard(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.WIREGUARD)
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment ?: "")
if (uri.rawQuery != null) {
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
config.outboundBean?.settings?.let { wireguard ->
wireguard.secretKey = uri.userInfo
wireguard.address =
(queryParam["address"]
?: AppConfig.WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace()
.split(",")
wireguard.peers?.get(0)?.publicKey = queryParam["publickey"] ?: ""
wireguard.peers?.get(0)?.endpoint = "${uri.idnHost}:${uri.port}"
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
wireguard.reserved =
(queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",")
.map { it.toInt() }
}
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
dicQuery["publickey"] =
Utils.urlEncode(outbound.settings?.peers?.get(0)?.publicKey.toString())
if (outbound.settings?.reserved != null) {
dicQuery["reserved"] = Utils.urlEncode(
Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString())
.toString()
)
}
dicQuery["address"] = Utils.urlEncode(
Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString())
.toString()
)
if (outbound.settings?.mtu != null) {
dicQuery["mtu"] = outbound.settings?.mtu.toString()
}
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format(
"%s@%s:%s",
Utils.urlEncode(outbound.getPassword().toString()),
Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
return url + query + remark
}
}
@@ -0,0 +1,93 @@
package com.v2ray.ang.viewmodel
import android.app.Application
import android.text.TextUtils
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
class SubViewModel(application: Application) : AndroidViewModel(application) {
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING,
MMKV.MULTI_PROCESS_MODE
)
}
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
fun updateConfigViaSubAll(): Int {
var count = 0
try {
MmkvManager.decodeSubscriptions().forEach {
count += updateConfigViaSub(it)
}
} catch (e: Exception) {
e.printStackTrace()
return 0
}
return count
}
fun updateConfigViaSub(it: Pair<String, SubscriptionItem>): Int {
try {
if (TextUtils.isEmpty(it.first)
|| TextUtils.isEmpty(it.second.remarks)
|| TextUtils.isEmpty(it.second.url)
) {
return 0
}
if (!it.second.enabled) {
return 0
}
val url = Utils.idnToASCII(it.second.url)
if (!Utils.isValidUrl(url)) {
return 0
}
Log.d(AppConfig.ANG_PACKAGE, url)
var configText = try {
Utils.getUrlContentWithCustomUserAgent(url)
} catch (e: Exception) {
e.printStackTrace()
""
}
if (configText.isEmpty()) {
configText = try {
val httpPort = Utils.parseInt(
settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT),
AppConfig.PORT_HTTP.toInt()
)
Utils.getUrlContentWithCustomUserAgent(url, httpPort)
} catch (e: Exception) {
e.printStackTrace()
""
}
}
if (configText.isEmpty()) {
return 0
}
return importBatchConfig(configText, it.first, false)
} catch (e: Exception) {
e.printStackTrace()
return 0
}
}
fun importBatchConfig(server: String?, subid: String = "", append: Boolean): Int {
var count = AngConfigManager.importBatchConfig(server, subid, append)
if (count <= 0) {
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid, append)
}
if (count <= 0) {
count = AngConfigManager.appendCustomConfigServer(server, subid)
}
return count
}
}

Some files were not shown because too many files have changed in this diff Show More