mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-23 00:17:16 +08:00
Update On Thu May 30 20:33:30 CEST 2024
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Generated
+51
-51
@@ -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: {}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Generated
-11
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
})();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
})();
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
@@ -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
|
||||
|
||||
Executable → Regular
+1
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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", "")
|
||||
|
||||
-1
@@ -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
|
||||
|
||||
@@ -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**
|
||||
|
||||
@@ -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
@@ -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
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user