mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
Update On Mon Aug 26 20:34:39 CEST 2024
This commit is contained in:
@@ -745,3 +745,4 @@ Update On Thu Aug 22 20:34:16 CEST 2024
|
||||
Update On Fri Aug 23 20:31:39 CEST 2024
|
||||
Update On Sat Aug 24 20:31:41 CEST 2024
|
||||
Update On Sun Aug 25 20:30:10 CEST 2024
|
||||
Update On Mon Aug 26 20:34:29 CEST 2024
|
||||
|
||||
+2
@@ -207,6 +207,8 @@ jobs:
|
||||
if: ${{ matrix.jobs.test == 'test' }}
|
||||
run: |
|
||||
go test ./...
|
||||
echo "---test with_gvisor---"
|
||||
go test ./... -tags "with_gvisor" -count=1
|
||||
|
||||
- name: Update CA
|
||||
run: |
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/metacubex/quic-go/congestion"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@@ -43,6 +45,8 @@ type Hysteria struct {
|
||||
|
||||
option *HysteriaOption
|
||||
client *core.Client
|
||||
|
||||
closeCh chan struct{} // for test
|
||||
}
|
||||
|
||||
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
@@ -51,7 +55,7 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts .
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(tcpConn, h), nil
|
||||
return NewConn(CN.NewRefConn(tcpConn, h), h), nil
|
||||
}
|
||||
|
||||
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
@@ -59,7 +63,7 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newPacketConn(&hyPacketConn{udpConn}, h), nil
|
||||
return newPacketConn(CN.NewRefPacketConn(&hyPacketConn{udpConn}, h), h), nil
|
||||
}
|
||||
|
||||
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
|
||||
@@ -218,7 +222,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
|
||||
}
|
||||
return &Hysteria{
|
||||
outbound := &Hysteria{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
@@ -231,7 +235,19 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
},
|
||||
option: &option,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeHysteria)
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func closeHysteria(h *Hysteria) {
|
||||
if h.client != nil {
|
||||
_ = h.client.Close()
|
||||
}
|
||||
if h.closeCh != nil {
|
||||
close(h.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
type hyPacketConn struct {
|
||||
|
||||
@@ -38,6 +38,8 @@ type Hysteria2 struct {
|
||||
option *Hysteria2Option
|
||||
client *hysteria2.Client
|
||||
dialer proxydialer.SingDialer
|
||||
|
||||
closeCh chan struct{} // for test
|
||||
}
|
||||
|
||||
type Hysteria2Option struct {
|
||||
@@ -89,6 +91,9 @@ func closeHysteria2(h *Hysteria2) {
|
||||
if h.client != nil {
|
||||
_ = h.client.CloseWithError(errors.New("proxy removed"))
|
||||
}
|
||||
if h.closeCh != nil {
|
||||
close(h.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHysteria2GC(t *testing.T) {
|
||||
option := Hysteria2Option{}
|
||||
option.Server = "127.0.0.1"
|
||||
option.Ports = "200,204,401-429,501-503"
|
||||
option.HopInterval = 30
|
||||
option.Password = "password"
|
||||
option.Obfs = "salamander"
|
||||
option.ObfsPassword = "password"
|
||||
option.SNI = "example.com"
|
||||
option.ALPN = []string{"h3"}
|
||||
hy, err := NewHysteria2(option)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
closeCh := make(chan struct{})
|
||||
hy.closeCh = closeCh
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
hy = nil
|
||||
runtime.GC()
|
||||
select {
|
||||
case <-closeCh:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
t.Error("timeout not GC")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHysteriaGC(t *testing.T) {
|
||||
option := HysteriaOption{}
|
||||
option.Server = "127.0.0.1"
|
||||
option.Ports = "200,204,401-429,501-503"
|
||||
option.Protocol = "udp"
|
||||
option.Up = "1Mbps"
|
||||
option.Down = "1Mbps"
|
||||
option.HopInterval = 30
|
||||
option.Obfs = "salamander"
|
||||
option.SNI = "example.com"
|
||||
option.ALPN = []string{"h3"}
|
||||
hy, err := NewHysteria(option)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
closeCh := make(chan struct{})
|
||||
hy.closeCh = closeCh
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
hy = nil
|
||||
runtime.GC()
|
||||
select {
|
||||
case <-closeCh:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
t.Error("timeout not GC")
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
|
||||
wireguard "github.com/metacubex/sing-wireguard"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -456,7 +455,6 @@ func closeWireGuard(w *WireGuard) {
|
||||
if w.device != nil {
|
||||
w.device.Close()
|
||||
}
|
||||
_ = common.Close(w.tunDevice)
|
||||
if w.closeCh != nil {
|
||||
close(w.closeCh)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ func TestWireGuardGC(t *testing.T) {
|
||||
err = wg.init(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
// must do a small sleep before test GC
|
||||
// because it maybe deadlocks if w.device.Close call too fast after w.device.Start
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ require (
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd
|
||||
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d
|
||||
github.com/metacubex/utls v1.6.6
|
||||
github.com/miekg/dns v1.1.62
|
||||
|
||||
+2
-2
@@ -120,8 +120,8 @@ github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 h1:ypfofGDZbP
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a h1:NpSGclHJUYndUwBmyIpFBSoBVg8PoVX7QQKhYg0DjM0=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd h1:r7alry8u4qlUFLNMwGvG1A8ZcfPM6AMSmrm6E2yKdB4=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNvXW824QQeN7Y26uPL5249kzWKbzO9U=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
|
||||
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
|
||||
|
||||
@@ -101,6 +101,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
updateNTP(cfg.NTP)
|
||||
updateDNS(cfg.DNS, cfg.General.IPv6)
|
||||
updateListeners(cfg.General, cfg.Listeners, force)
|
||||
updateTun(cfg.General) // tun should not care "force"
|
||||
updateIPTables(cfg)
|
||||
updateTunnels(cfg.Tunnels)
|
||||
|
||||
@@ -198,6 +199,9 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
|
||||
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
|
||||
listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel)
|
||||
listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
|
||||
}
|
||||
|
||||
func updateTun(general *config.General) {
|
||||
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
|
||||
}
|
||||
|
||||
|
||||
@@ -289,7 +289,10 @@ func (c *Client) DialUDP(dialer utils.PacketDialer) (UDPConn, error) {
|
||||
func (c *Client) Close() error {
|
||||
c.reconnectMutex.Lock()
|
||||
defer c.reconnectMutex.Unlock()
|
||||
err := c.quicSession.CloseWithError(closeErrorCodeGeneric, "")
|
||||
var err error
|
||||
if c.quicSession != nil {
|
||||
err = c.quicSession.CloseWithError(closeErrorCodeGeneric, "")
|
||||
}
|
||||
c.closed = true
|
||||
return err
|
||||
}
|
||||
|
||||
Generated
+8
-8
@@ -620,9 +620,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "backon"
|
||||
version = "0.5.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33e5b65cc81d81fbb8488f36458ab4771be35a722967bbc959df28b47397e3ff"
|
||||
checksum = "274cb2897ebb0ed1d2f70adb956fb0a0384aa208eea7f2845fb7aa6cd7f39277"
|
||||
dependencies = [
|
||||
"fastrand 2.1.0",
|
||||
"tokio",
|
||||
@@ -685,7 +685,7 @@ dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static 1.5.0",
|
||||
"lazycell",
|
||||
"log 0.4.22",
|
||||
@@ -3229,7 +3229,7 @@ dependencies = [
|
||||
"httpdate",
|
||||
"itoa 1.0.11",
|
||||
"pin-project-lite",
|
||||
"socket2 0.4.10",
|
||||
"socket2 0.5.7",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -4054,7 +4054,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6033,9 +6033,9 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
|
||||
|
||||
[[package]]
|
||||
name = "redb"
|
||||
version = "2.1.1"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6dd20d3cdeb9c7d2366a0b16b93b35b75aec15309fbeb7ce477138c9f68c8c0"
|
||||
checksum = "58323dc32ea52a8ae105ff94bc0460c5d906307533ba3401aa63db3cbe491fe5"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -8886,7 +8886,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -18,76 +18,79 @@ pub fn register<F: FnMut(String) + Send + 'static>(schemes: &[&str], handler: F)
|
||||
|
||||
create_dir_all(&target)?;
|
||||
|
||||
for (i, scheme) in schemes.iter().enumerate() {
|
||||
let exe = tauri_utils::platform::current_exe()?;
|
||||
let exe = tauri_utils::platform::current_exe()?;
|
||||
|
||||
let file_name = format!(
|
||||
"{}-handler-{}.desktop",
|
||||
exe.file_name()
|
||||
.ok_or_else(|| Error::new(
|
||||
ErrorKind::NotFound,
|
||||
"Couldn't get file name of curent executable.",
|
||||
))?
|
||||
.to_string_lossy(),
|
||||
i
|
||||
);
|
||||
let file_name = format!(
|
||||
"{}-handler.desktop",
|
||||
exe.file_name()
|
||||
.ok_or_else(|| Error::new(
|
||||
ErrorKind::NotFound,
|
||||
"Couldn't get file name of curent executable.",
|
||||
))?
|
||||
.to_string_lossy()
|
||||
);
|
||||
|
||||
target.push(&file_name);
|
||||
target.push(&file_name);
|
||||
|
||||
let mime_types = format!("x-scheme-handler/{};", scheme);
|
||||
let mime_types = format!(
|
||||
"{};",
|
||||
schemes
|
||||
.iter()
|
||||
.map(|s| format!("x-scheme-handler/{}", s))
|
||||
.collect::<Vec<String>>()
|
||||
.join(";")
|
||||
);
|
||||
|
||||
let mut file = File::create(&target)?;
|
||||
file.write_all(
|
||||
format!(
|
||||
include_str!("template.desktop"),
|
||||
name = ID
|
||||
.get()
|
||||
.expect("Called register() before prepare()")
|
||||
.split('.')
|
||||
.last()
|
||||
.unwrap(),
|
||||
exec = std::env::var("APPIMAGE").unwrap_or_else(|_| exe.display().to_string()),
|
||||
mime_types = mime_types
|
||||
)
|
||||
.as_bytes(),
|
||||
)?;
|
||||
let mut file = File::create(&target)?;
|
||||
file.write_all(
|
||||
format!(
|
||||
include_str!("template.desktop"),
|
||||
name = ID
|
||||
.get()
|
||||
.expect("Called register() before prepare()")
|
||||
.split('.')
|
||||
.last()
|
||||
.unwrap(),
|
||||
exec = std::env::var("APPIMAGE").unwrap_or_else(|_| exe.display().to_string()),
|
||||
mime_types = mime_types
|
||||
)
|
||||
.as_bytes(),
|
||||
)?;
|
||||
|
||||
Command::new("update-desktop-database")
|
||||
.arg(&target)
|
||||
.status()?;
|
||||
Command::new("update-desktop-database")
|
||||
.arg(&target)
|
||||
.status()?;
|
||||
|
||||
for scheme in schemes {
|
||||
Command::new("xdg-mime")
|
||||
.args(["default", &file_name, scheme])
|
||||
.status()?;
|
||||
|
||||
target.pop();
|
||||
}
|
||||
|
||||
target.pop();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unregister(schemes: &[&str]) -> Result<()> {
|
||||
pub fn unregister(_schemes: &[&str]) -> Result<()> {
|
||||
let mut target =
|
||||
data_dir().ok_or_else(|| Error::new(ErrorKind::NotFound, "data directory not found."))?;
|
||||
|
||||
target.push("applications");
|
||||
|
||||
for (i, _) in schemes.iter().enumerate() {
|
||||
target.push(format!(
|
||||
"{}-handler-{}.desktop",
|
||||
tauri_utils::platform::current_exe()?
|
||||
.file_name()
|
||||
.ok_or_else(|| Error::new(
|
||||
ErrorKind::NotFound,
|
||||
"Couldn't get file name of curent executable.",
|
||||
))?
|
||||
.to_string_lossy(),
|
||||
i
|
||||
));
|
||||
target.push(format!(
|
||||
"{}-handler.desktop",
|
||||
tauri_utils::platform::current_exe()?
|
||||
.file_name()
|
||||
.ok_or_else(|| Error::new(
|
||||
ErrorKind::NotFound,
|
||||
"Couldn't get file name of current executable.",
|
||||
))?
|
||||
.to_string_lossy()
|
||||
));
|
||||
|
||||
remove_file(&target)?;
|
||||
target.pop();
|
||||
}
|
||||
remove_file(&target)?;
|
||||
target.pop();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::{
|
||||
os::windows::thread,
|
||||
path::Path,
|
||||
sync::atomic::{AtomicU16, Ordering},
|
||||
};
|
||||
|
||||
@@ -76,7 +76,7 @@ rs-snowflake = "0.6"
|
||||
thiserror = { workspace = true }
|
||||
simd-json = "0.13.8"
|
||||
runas = "1.2.0"
|
||||
backon = { version = "0.5", features = ["tokio-sleep"] }
|
||||
backon = { version = "1.0.1", features = ["tokio-sleep"] }
|
||||
rust-i18n = "3"
|
||||
adler = "1.0.2"
|
||||
rfd = "0.10" # should bump to v0.14 when clarify why the rfd v0.10 from tauri breaks build
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 5.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -73,7 +73,7 @@ impl Proxies {
|
||||
#[instrument]
|
||||
pub async fn fetch() -> Result<Self> {
|
||||
let (inner_proxies, providers_proxies) = fetch_proxies
|
||||
.retry(&*CLASH_API_DEFAULT_BACKOFF_STRATEGY)
|
||||
.retry(*CLASH_API_DEFAULT_BACKOFF_STRATEGY)
|
||||
.await?;
|
||||
let inner_proxies = inner_proxies.proxies;
|
||||
// 1. filter out the Http or File type provider proxies
|
||||
|
||||
@@ -48,6 +48,8 @@ ${StrLoc}
|
||||
!define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}"
|
||||
!define ESTIMATEDSIZE "{{estimated_size}}"
|
||||
|
||||
Var ProgramDataPathVar
|
||||
|
||||
Name "${PRODUCTNAME}"
|
||||
BrandingText "${COPYRIGHT}"
|
||||
OutFile "${OUTFILE}"
|
||||
@@ -381,6 +383,23 @@ FunctionEnd
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
!define FOLDERID_ProgramData "{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}"
|
||||
!macro GetProgramDataPath
|
||||
; 调用SHGetKnownFolderIDList获取PIDL
|
||||
System::Call 'shell32::SHGetKnownFolderIDList(g"${FOLDERID_ProgramData}", i0x1000, i0, *i.r1)i.r0'
|
||||
${If} $0 = 0
|
||||
; 调用SHGetPathFromIDList将PIDL转换为路径
|
||||
System::Call 'shell32::SHGetPathFromIDList(ir1,t.r0)'
|
||||
StrCpy $ProgramDataPathVar $0 ; 将结果保存到变量
|
||||
; DetailPrint "ProgramData Path: $ProgramDataPathVar"
|
||||
|
||||
; 释放PIDL内存
|
||||
System::Call 'ole32::CoTaskMemFree(ir1)'
|
||||
${Else}
|
||||
DetailPrint "Failed to get ProgramData path, error code: $0"
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
Var PassiveMode
|
||||
Function .onInit
|
||||
${GetOptions} $CMDLINE "/P" $PassiveMode
|
||||
@@ -428,6 +447,7 @@ FunctionEnd
|
||||
!endif
|
||||
Pop $R0
|
||||
${If} $R0 = 0
|
||||
DetailPrint "${Process} is running"
|
||||
IfSilent kill${ID} 0
|
||||
${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL "${Process} is running, ok to kill?" IDOK kill${ID} IDCANCEL cancel${ID} ${|}
|
||||
kill${ID}:
|
||||
@@ -468,9 +488,9 @@ FunctionEnd
|
||||
!insertmacro CheckNyanpasuProcess "mihomo-alpha.exe" "6"
|
||||
!macroend
|
||||
|
||||
Section CheckProcesses
|
||||
!insertmacro CheckAllNyanpasuProcesses
|
||||
SectionEnd
|
||||
; Section CheckProcesses
|
||||
; !insertmacro CheckAllNyanpasuProcesses
|
||||
; SectionEnd
|
||||
|
||||
Section EarlyChecks
|
||||
; Abort silent installer if downgrades is disabled
|
||||
@@ -589,9 +609,30 @@ SectionEnd
|
||||
; app_check_done:
|
||||
; !macroend
|
||||
|
||||
Section Install
|
||||
SetOutPath $INSTDIR
|
||||
!macro StopCoreByService
|
||||
; 构建服务可执行文件的完整路径
|
||||
StrCpy $1 "$ProgramDataPathVar\nyanpasu-service\data\nyanpasu-service.exe"
|
||||
|
||||
; 检查文件是否存在
|
||||
IfFileExists "$1" 0 SkipStopCore
|
||||
|
||||
; 文件存在,执行停止核心服务
|
||||
nsExec::ExecToLog '"$1" rpc stop-core'
|
||||
Pop $0 ; 弹出命令执行的返回值
|
||||
${If} $0 == "0"
|
||||
DetailPrint "Core service stopped successfully."
|
||||
${Else}
|
||||
DetailPrint "Core stop failed with exit code $0"
|
||||
${EndIf}
|
||||
SkipStopCore:
|
||||
; 如果文件不存在,打印错误
|
||||
DetailPrint "Nyanpasu Service is not installed, skipping stop-core"
|
||||
!macroend
|
||||
|
||||
Section Install
|
||||
!insertmacro GetProgramDataPath
|
||||
!insertmacro StopCoreByService
|
||||
SetOutPath $INSTDIR
|
||||
!insertmacro CheckAllNyanpasuProcesses
|
||||
; !insertmacro CheckIfAppIsRunning
|
||||
|
||||
@@ -708,7 +749,36 @@ FunctionEnd
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
!macro StopAndRemoveServiceDirectory
|
||||
; 构建服务路径
|
||||
StrCpy $1 "$ProgramDataPathVar\nyanpasu-service\data\nyanpasu-service.exe"
|
||||
|
||||
; 检查服务可执行文件是否存在
|
||||
IfFileExists "$1" 0 Skip
|
||||
nsExec::ExecToLog '"$1" stop'
|
||||
Pop $0
|
||||
DetailPrint "Stopping service with exit code $0"
|
||||
|
||||
; 检查停止服务是否成功(假设0, 100, 102为成功)
|
||||
IntCmp $0 0 0 StopFailed StopFailed
|
||||
IntCmp $0 100 0 StopFailed StopFailed
|
||||
IntCmp $0 102 0 StopFailed StopFailed
|
||||
StopFailed:
|
||||
Abort "Failed to stop the service. Aborting installation."
|
||||
|
||||
; 如果服务成功停止,继续检查目录是否存在并删除
|
||||
StrCpy $2 "$ProgramDataPathVar\nyanpasu-service"
|
||||
IfFileExists "$2\*" 0 Skip
|
||||
RMDir /r "$2"
|
||||
DetailPrint "Removed service directory successfully"
|
||||
|
||||
Skip:
|
||||
DetailPrint "Service directory does not exist, skipping stop and remove service directory"
|
||||
!macroend
|
||||
|
||||
Section Uninstall
|
||||
!insertmacro GetProgramDataPath
|
||||
!insertmacro StopAndRemoveServiceDirectory
|
||||
!insertmacro CheckAllNyanpasuProcesses
|
||||
; !insertmacro CheckIfAppIsRunning
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<!--
|
||||
Copyright (C) 2022 The Project Nyanpasu
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
thanks: @ReallySnow
|
||||
-->
|
||||
|
||||
<svg width="420px" height="490px" viewBox="0 0 420 490" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style><![CDATA[
|
||||
#element {
|
||||
fill: #000000;
|
||||
}
|
||||
]]></style>
|
||||
</defs>
|
||||
|
||||
<g id="nyanpasu_logo" fill-rule="nonzero">
|
||||
<path
|
||||
d="M349.980734,470.820835 C362.906597,470.820835 375.380055,471.040616 387.808272,470.736801 C394.678368,470.562269 398.898662,465.526692 400.333433,457.394784 C402.20122,446.832354 400.417451,436.780592 395.809381,427.284747 C390.832924,417.03906 385.255414,407.090725 380.233716,396.903216 C360.528238,356.825451 356.463054,315.532425 371.896535,273.134028 C376.646789,260.076434 384.111475,248.479739 394.781775,239.255389 C400.869857,233.993566 405.684741,233.870747 409.911498,238.718864 C414.138255,243.56698 413.285148,248.415097 407.500824,253.663991 C390.535629,269.081003 384.285974,289.34613 382.230762,311.27901 C379.516331,340.367711 385.669042,367.633519 399.499715,393.373787 C407.100123,407.523823 416.070671,421.169656 418.88851,437.433471 C420.614112,447.317165 420.484854,457.129754 417.402035,466.787202 C412.613003,481.764651 401.244707,490.012913 385.513931,490 C273.567341,489.896558 161.622905,489.814679 49.6806226,489.754347 C42.5713979,489.754347 35.4621733,489.993521 28.605003,487.73753 C11.4136053,482.081394 -0.0322464095,467.259085 0,448.855634 C0.103475152,390.930336 0.374918274,333.011502 3.10227536,275.124988 C4.89250738,237.057576 7.30964376,199.054805 11.2067914,161.148996 C16.3771366,110.612228 23.6608604,60.405131 34.5315112,10.7604159 C35.0024707,8.67513302 35.7547542,6.66358964 36.7676855,4.78107191 C38.5191399,1.50374502 41.1689418,-0.513071536 45.1694964,0.113951558 C49.170051,0.740974653 49.3768648,4.11526388 49.9326769,6.9530282 C53.9913979,27.6706469 60.3056819,47.7612426 66.8590945,67.7742684 C68.9853989,74.238424 70.1875042,82.4543658 74.7891114,86.326395 C79.26146,90.0949977 87.6051045,87.3541957 94.2425352,87.3735882 C154.302557,87.5028713 214.369043,86.8176708 274.422602,88.3755323 C279.256875,88.4983513 281.615845,87.0827012 283.509484,82.6353621 C293.203881,59.8750702 301.967616,36.8044988 308.495177,12.925908 C309.309506,9.94593225 309.942874,6.778496 313.710763,6.46175237 C317.129653,6.17732953 319.346439,8.49796139 320.858765,11.1547293 C321.981523,13.2263513 322.777248,15.4591645 323.217735,17.7740247 C332.832735,64.3247661 340.022746,111.343788 344.758685,158.640904 C354.220417,251.724745 355.745669,345.112401 356.152834,438.538842 C356.152834,444.123872 356.217463,449.695975 355.370819,455.274541 C354.569415,460.646254 352.048872,465.38448 349.980734,470.820835 Z"
|
||||
id="bg" fill="#ffffff" fill-rule="evenodd"></path>
|
||||
<path
|
||||
d="M314.373914,51.8580359 C319.16418,79.1645649 322.474053,106.509891 325.189183,133.926344 C335.474355,237.585109 337,341.580114 337,445.633314 C337,462.193111 328.052999,470.974135 311.283837,470.980602 C222.356854,471.006466 133.429871,471.006466 44.5028879,470.980602 C27.8242301,470.980602 18.9741981,462.050856 19,445.491059 C19.0776317,397.460537 19.1422776,349.423548 20.816608,301.425356 C22.7559868,246.3726 25.665055,191.384506 31.4637977,136.538667 C34.2500385,110.221458 37.8831415,84.0335719 41.044329,57.7745583 C41.348165,55.2398296 42.0398768,52.7503639 42.8026991,49 C47.5541772,66.3034032 53.702008,81.848015 58.6926762,97.7676631 C60.6902363,104.123883 64.0001095,106.632747 70.7620769,106.632747 C141.816607,106.58964 212.868982,106.72974 283.919202,107.053047 C290.11875,107.053047 293.415694,104.679972 295.555475,99.1643504 C301.63866,83.3804912 308.652746,67.9328716 314.373914,51.8580359 Z"
|
||||
id="bg" fill="#ffffff" fill-rule="evenodd"></path>
|
||||
<path
|
||||
d="M119.999315,204.118735 C120.083729,213.985193 112.350174,222.057169 102.850433,222 C94.0584657,221.961379 86.9547636,214.451375 87,205.249067 C87.0456702,196.148935 94.5909186,188.268541 103.493273,188.006712 C112.395627,187.744884 119.921396,195.178255 119.999315,204.118735 Z"
|
||||
id="element" fill-rule="evenodd"></path>
|
||||
<path
|
||||
d="M236,204.861743 C236,195.671258 243.035135,188.142475 251.816026,188.001869 C261.124552,187.861264 268.993481,195.671258 269,205.104607 C269.006509,214.537955 262.179823,221.772746 253.255623,221.996436 C244.16206,222.194562 236.026056,214.103357 236,204.861743 Z"
|
||||
id="element" fill-rule="evenodd"></path>
|
||||
<path
|
||||
d="M190.493614,267.920884 C185.555364,268.142338 181.468093,262.430145 177.097162,262.137045 C172.945422,261.850458 168.716321,267.914371 162.875519,267.999044 C153.295574,268.096744 144.921179,260.691085 144.089542,251.064378 C143.851011,248.296212 143.844564,245.541072 147.312943,245.059085 C150.362279,244.622692 151.0263,246.902358 151.638746,249.481638 C153.327808,256.646305 157.286143,260.411011 162.701456,259.935538 C168.890385,259.394931 172.371657,255.649765 173.087252,249.468612 C173.383805,247.045652 174.234782,245.104678 176.819949,245.026518 C179.869286,244.935332 180.481732,247.208485 180.855647,249.813818 C181.751752,255.988458 185.426428,259.870405 191.570229,259.889945 C197.71403,259.909485 201.433834,255.981945 202.342833,249.872438 C202.774769,246.954465 203.516151,244.433805 206.907168,245.111192 C210.130569,245.762525 210.195037,248.634905 209.872697,251.383532 C208.750953,260.769245 200.621538,268.044638 190.493614,267.920884 Z"
|
||||
id="element" fill-rule="evenodd"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
@@ -1,7 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { useEffect } from "react";
|
||||
import { atomLogData } from "@/store";
|
||||
import { atomEnableLog, atomLogData } from "@/store";
|
||||
import { LogMessage, useClashWS } from "@nyanpasu/interface";
|
||||
|
||||
const MAX_LOG_NUM = 1000;
|
||||
@@ -15,8 +15,10 @@ export const LogProvider = () => {
|
||||
|
||||
const setLogData = useSetAtom(atomLogData);
|
||||
|
||||
const enableLog = useAtomValue(atomEnableLog);
|
||||
|
||||
useEffect(() => {
|
||||
if (!latestMessage?.data) {
|
||||
if (!latestMessage?.data || !enableLog) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -29,7 +31,7 @@ export const LogProvider = () => {
|
||||
|
||||
return [...prev, { ...data, time }];
|
||||
});
|
||||
}, [latestMessage?.data, setLogData]);
|
||||
}, [enableLog, latestMessage?.data, setLogData]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ import { QuickImport } from "@/components/profiles/quick-import";
|
||||
import RuntimeConfigDiffDialog from "@/components/profiles/runtime-config-diff-dialog";
|
||||
import { filterProfiles } from "@/components/profiles/utils";
|
||||
import { Public } from "@mui/icons-material";
|
||||
import { Badge, Button, IconButton } from "@mui/material";
|
||||
import { Badge, Button, IconButton, useTheme } from "@mui/material";
|
||||
import Grid from "@mui/material/Unstable_Grid2";
|
||||
import { Profile, useClash } from "@nyanpasu/interface";
|
||||
import { SidePage } from "@nyanpasu/ui";
|
||||
@@ -27,6 +27,7 @@ import { SidePage } from "@nyanpasu/ui";
|
||||
export const ProfilePage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { getProfiles, getRuntimeLogs } = useClash();
|
||||
const theme = useTheme();
|
||||
const maxLogLevelTriggered = useMemo(() => {
|
||||
const currentProfileChains =
|
||||
getProfiles.data?.items?.find(
|
||||
@@ -110,17 +111,24 @@ export const ProfilePage = () => {
|
||||
title={t("Profiles")}
|
||||
flexReverse
|
||||
header={
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RuntimeConfigDiffDialog
|
||||
open={runtimeConfigViewerOpen}
|
||||
onClose={() => setRuntimeConfigViewerOpen(false)}
|
||||
/>
|
||||
<IconButton
|
||||
className="h-10 w-10"
|
||||
color="inherit"
|
||||
title="Runtime Config"
|
||||
onClick={() => {
|
||||
setRuntimeConfigViewerOpen(true);
|
||||
}}
|
||||
>
|
||||
<IconMdiTextBoxCheckOutline />
|
||||
<IconMdiTextBoxCheckOutline
|
||||
// style={{
|
||||
// color: theme.palette.text.primary,
|
||||
// }}
|
||||
/>
|
||||
</IconButton>
|
||||
<Badge
|
||||
variant="dot"
|
||||
|
||||
@@ -12,6 +12,7 @@ import ProxyGroupName from "@/components/proxies/proxy-group-name";
|
||||
import ScrollCurrentNode from "@/components/proxies/scroll-current-node";
|
||||
import SortSelector from "@/components/proxies/sort-selector";
|
||||
import { proxyGroupAtom } from "@/store";
|
||||
import { Check } from "@mui/icons-material";
|
||||
import {
|
||||
alpha,
|
||||
Box,
|
||||
@@ -71,13 +72,14 @@ export default function ProxyPage() {
|
||||
return (
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<ButtonGroup size="small">
|
||||
{Object.entries(getCurrentMode).map(([key, value], index) => (
|
||||
{Object.entries(getCurrentMode).map(([key, enabled]) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant={value ? "contained" : "outlined"}
|
||||
key={key}
|
||||
variant={enabled ? "contained" : "outlined"}
|
||||
onClick={() => handleSwitch(key)}
|
||||
sx={{ textTransform: "capitalize" }}
|
||||
>
|
||||
{enabled && <Check className="-ml-2 mr-[0.1rem] scale-75" />}
|
||||
{t(key)}
|
||||
</Button>
|
||||
))}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MUI_BREAKPOINTS } from "@nyanpasu/ui";
|
||||
import { MUI_BREAKPOINTS } from "@nyanpasu/ui/src/materialYou/themeConsts.mjs";
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./dist/index.js"
|
||||
".": "./dist/index.js",
|
||||
"./src/materialYou/themeConsts.mjs": "./src/materialYou/themeConsts.mjs"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "vite build"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useAsyncEffect } from "ahooks";
|
||||
import { useEffect, useState } from "react";
|
||||
import { createBreakpoint } from "react-use";
|
||||
import { MUI_BREAKPOINTS } from "../materialYou";
|
||||
import { MUI_BREAKPOINTS } from "../materialYou/themeConsts.mjs";
|
||||
|
||||
export type Breakpoint = "xs" | "sm" | "md" | "lg" | "xl";
|
||||
|
||||
|
||||
@@ -68,18 +68,16 @@ export const BaseDialog = ({
|
||||
|
||||
const clickPosition = useClickPosition();
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (open) {
|
||||
setOffset({
|
||||
x: clickPosition?.x ?? 0,
|
||||
y: clickPosition?.y ?? 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
// not need clickPosition
|
||||
[clickPosition?.x, clickPosition?.y, open],
|
||||
);
|
||||
const getClickPosition = () => clickPosition;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (open) {
|
||||
setOffset({
|
||||
x: getClickPosition()?.x ?? 0,
|
||||
y: getClickPosition()?.y ?? 0,
|
||||
});
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
if (onClose) {
|
||||
|
||||
@@ -5,7 +5,11 @@ export interface LazyImageProps
|
||||
extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||
loadingClassName?: string;
|
||||
}
|
||||
export function LazyImage(props: LazyImageProps) {
|
||||
export function LazyImage({
|
||||
className,
|
||||
loadingClassName,
|
||||
...others
|
||||
}: LazyImageProps) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
return (
|
||||
@@ -13,15 +17,15 @@ export function LazyImage(props: LazyImageProps) {
|
||||
<div
|
||||
className={cn(
|
||||
"inline-block animate-pulse bg-slate-200 ring-1 ring-slate-200 dark:bg-slate-700 dark:ring-slate-700",
|
||||
props.className,
|
||||
props.loadingClassName,
|
||||
className,
|
||||
loadingClassName,
|
||||
loading ? "inline-block" : "hidden",
|
||||
)}
|
||||
/>
|
||||
<img
|
||||
{...props}
|
||||
{...others}
|
||||
onLoad={() => setLoading(false)}
|
||||
className={cn(props.className, loading ? "hidden" : "inline-block")}
|
||||
className={cn(className, loading ? "hidden" : "inline-block")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
hexFromArgb,
|
||||
themeFromSourceColor,
|
||||
} from "@material/material-color-utilities";
|
||||
import type { BreakpointsOptions } from "@mui/material/styles";
|
||||
import createPalette from "@mui/material/styles/createPalette";
|
||||
import extendTheme from "@mui/material/styles/experimental_extendTheme";
|
||||
import {
|
||||
@@ -20,6 +19,7 @@ import {
|
||||
MuiPaper,
|
||||
MuiSwitch,
|
||||
} from "./themeComponents";
|
||||
import { MUI_BREAKPOINTS } from "./themeConsts.mjs";
|
||||
|
||||
interface ThemeSchema {
|
||||
primary_color: string;
|
||||
@@ -33,16 +33,6 @@ interface ThemeSchema {
|
||||
font_family?: string;
|
||||
}
|
||||
|
||||
export const MUI_BREAKPOINTS: BreakpointsOptions = {
|
||||
values: {
|
||||
xs: 0,
|
||||
sm: 400,
|
||||
md: 800,
|
||||
lg: 1200,
|
||||
xl: 1600,
|
||||
},
|
||||
};
|
||||
|
||||
export const createMDYTheme = (themeSchema: ThemeSchema) => {
|
||||
const materialColor = themeFromSourceColor(
|
||||
argbFromHex(themeSchema.primary_color),
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
import { Theme } from "@mui/material";
|
||||
import { alpha, darken, Theme } from "@mui/material";
|
||||
import { Components } from "@mui/material/styles/components";
|
||||
|
||||
export const MuiButtonGroup: Components<Theme>["MuiButtonGroup"] = {
|
||||
styleOverrides: {
|
||||
grouped: {
|
||||
grouped: ({ theme }) => ({
|
||||
fontWeight: 700,
|
||||
},
|
||||
height: "2.5em",
|
||||
padding: "0 1.25em",
|
||||
border: `1px solid ${darken(theme.palette.primary.main, 0.09)}`,
|
||||
color: darken(theme.palette.primary.main, 0.2),
|
||||
|
||||
"&.MuiButton-containedPrimary": {
|
||||
boxShadow: "none",
|
||||
border: `1px solid ${theme.palette.primary.mainChannel}`,
|
||||
backgroundColor: alpha(theme.palette.primary.main, 0.2),
|
||||
color: theme.palette.primary.main,
|
||||
"&::before": {
|
||||
content: "none",
|
||||
},
|
||||
"&:hover": {
|
||||
backgroundColor: alpha(theme.palette.primary.main, 0.3),
|
||||
},
|
||||
},
|
||||
}),
|
||||
firstButton: {
|
||||
borderTopLeftRadius: 48,
|
||||
borderBottomLeftRadius: 48,
|
||||
|
||||
"&.MuiButton-sizeSmall": {
|
||||
paddingLeft: "14px",
|
||||
paddingLeft: "1.5em",
|
||||
},
|
||||
|
||||
"&.MuiButton-sizeMedium": {
|
||||
@@ -27,7 +44,7 @@ export const MuiButtonGroup: Components<Theme>["MuiButtonGroup"] = {
|
||||
borderBottomRightRadius: 48,
|
||||
|
||||
"&.MuiButton-sizeSmall": {
|
||||
paddingRight: "14px",
|
||||
paddingRight: "1.5em",
|
||||
},
|
||||
|
||||
"&.MuiButton-sizeMedium": {
|
||||
@@ -39,4 +56,4 @@ export const MuiButtonGroup: Components<Theme>["MuiButtonGroup"] = {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
} satisfies Components<Theme>["MuiButtonGroup"];
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/** @type {import("@mui/material/styles").BreakpointsOptions} */
|
||||
export const MUI_BREAKPOINTS = {
|
||||
values: {
|
||||
xs: 0,
|
||||
sm: 400,
|
||||
md: 800,
|
||||
lg: 1200,
|
||||
xl: 1600,
|
||||
},
|
||||
};
|
||||
@@ -4,7 +4,7 @@
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 1,
|
||||
"latest": {
|
||||
"mihomo": "v1.18.7",
|
||||
"mihomo_alpha": "alpha-27bcb26",
|
||||
"mihomo_alpha": "alpha-518e9bd",
|
||||
"clash_rs": "v0.2.0",
|
||||
"clash_premium": "2023-09-05-gdcc8d87"
|
||||
},
|
||||
@@ -36,5 +36,5 @@
|
||||
"darwin-x64": "clash-darwin-amd64-n{}.gz"
|
||||
}
|
||||
},
|
||||
"updated_at": "2024-08-24T22:19:55.791Z"
|
||||
"updated_at": "2024-08-25T22:20:29.500Z"
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
"eslint-plugin-react": "7.35.0",
|
||||
"eslint-plugin-react-compiler": "0.0.0-experimental-72f06b2-20240822",
|
||||
"eslint-plugin-react-hooks": "4.6.2",
|
||||
"knip": "5.27.3",
|
||||
"knip": "5.27.4",
|
||||
"lint-staged": "15.2.9",
|
||||
"npm-run-all2": "6.2.2",
|
||||
"postcss": "8.4.41",
|
||||
|
||||
Generated
+5
-5
@@ -98,8 +98,8 @@ importers:
|
||||
specifier: 4.6.2
|
||||
version: 4.6.2(eslint@8.57.0)
|
||||
knip:
|
||||
specifier: 5.27.3
|
||||
version: 5.27.3(@types/node@22.5.0)(typescript@5.5.4)
|
||||
specifier: 5.27.4
|
||||
version: 5.27.4(@types/node@22.5.0)(typescript@5.5.4)
|
||||
lint-staged:
|
||||
specifier: 15.2.9
|
||||
version: 15.2.9
|
||||
@@ -4614,8 +4614,8 @@ packages:
|
||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
knip@5.27.3:
|
||||
resolution: {integrity: sha512-X0zYs0viwENUtp+FZE2Ig6vQZYvKOz8TvuQkWSWMOXiEDoiMAF+NuDczVD9Dhupicfew0YKpYamHhKtNP+f8+g==}
|
||||
knip@5.27.4:
|
||||
resolution: {integrity: sha512-7t1yqIKxaVGYD1cLI4raVLWi9cNqv+JNbngc8mgvTVJbomnxOg1pjxgCGEztB7eVgD+6VEwf7Jg5WHXzk+Kbpw==}
|
||||
engines: {node: '>=18.6.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -11150,7 +11150,7 @@ snapshots:
|
||||
|
||||
kind-of@6.0.3: {}
|
||||
|
||||
knip@5.27.3(@types/node@22.5.0)(typescript@5.5.4):
|
||||
knip@5.27.4(@types/node@22.5.0)(typescript@5.5.4):
|
||||
dependencies:
|
||||
'@nodelib/fs.walk': 1.2.8
|
||||
'@snyk/github-codeowners': 1.1.0
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
"printWidth": 140,
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"useTabs": true
|
||||
}
|
||||
"useTabs": false
|
||||
}
|
||||
@@ -16,6 +16,11 @@ const (
|
||||
ConnectionTypeClosed = "closed"
|
||||
)
|
||||
|
||||
type QueryNodeMetricsReq struct {
|
||||
TimeRange string `json:"time_range"` // 15min/30min/1h/6h/12h/24h
|
||||
Num int `json:"num"` // number of nodes to query
|
||||
}
|
||||
|
||||
// connection manager interface/
|
||||
// TODO support closed connection
|
||||
type Cmgr interface {
|
||||
@@ -34,17 +39,21 @@ type Cmgr interface {
|
||||
|
||||
// Start starts the connection manager.
|
||||
Start(ctx context.Context, errCH chan error)
|
||||
|
||||
QueryNodeMetrics(ctx context.Context, req *QueryNodeMetricsReq) ([]metric_reader.NodeMetrics, error)
|
||||
}
|
||||
|
||||
type cmgrImpl struct {
|
||||
lock sync.RWMutex
|
||||
cfg *Config
|
||||
l *zap.SugaredLogger
|
||||
mr metric_reader.Reader
|
||||
|
||||
// k: relay label, v: connection list
|
||||
activeConnectionsMap map[string][]conn.RelayConn
|
||||
closedConnectionsMap map[string][]conn.RelayConn
|
||||
|
||||
mr metric_reader.Reader
|
||||
ms []*metric_reader.NodeMetrics // TODO gc this
|
||||
}
|
||||
|
||||
func NewCmgr(cfg *Config) Cmgr {
|
||||
@@ -171,6 +180,12 @@ func (cm *cmgrImpl) Start(ctx context.Context, errCH chan error) {
|
||||
cm.l.Infof("Start Cmgr sync interval=%d", cm.cfg.SyncInterval)
|
||||
ticker := time.NewTicker(time.Second * time.Duration(cm.cfg.SyncInterval))
|
||||
defer ticker.Stop()
|
||||
// sync once at the beginning
|
||||
if err := cm.syncOnce(ctx); err != nil {
|
||||
cm.l.Errorf("meet non retry error: %s ,exit now", err)
|
||||
errCH <- err
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -185,3 +200,38 @@ func (cm *cmgrImpl) Start(ctx context.Context, errCH chan error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *cmgrImpl) QueryNodeMetrics(ctx context.Context, req *QueryNodeMetricsReq) ([]metric_reader.NodeMetrics, error) {
|
||||
cm.lock.RLock()
|
||||
defer cm.lock.RUnlock()
|
||||
|
||||
var startTime time.Time
|
||||
switch req.TimeRange {
|
||||
case "15min":
|
||||
startTime = time.Now().Add(-15 * time.Minute)
|
||||
case "30min":
|
||||
startTime = time.Now().Add(-30 * time.Minute)
|
||||
case "1h":
|
||||
startTime = time.Now().Add(-1 * time.Hour)
|
||||
case "6h":
|
||||
startTime = time.Now().Add(-6 * time.Hour)
|
||||
case "12h":
|
||||
startTime = time.Now().Add(-12 * time.Hour)
|
||||
case "24h":
|
||||
startTime = time.Now().Add(-24 * time.Hour)
|
||||
default:
|
||||
// default to 15min
|
||||
startTime = time.Now().Add(-15 * time.Minute)
|
||||
}
|
||||
|
||||
res := []metric_reader.NodeMetrics{}
|
||||
for _, metrics := range cm.ms {
|
||||
if metrics.SyncTime.After(startTime) {
|
||||
res = append(res, *metrics)
|
||||
}
|
||||
if req.Num > 0 && len(res) >= req.Num {
|
||||
break
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -35,13 +35,13 @@ func (cm *cmgrImpl) syncOnce(ctx context.Context) error {
|
||||
// todo: opt lock
|
||||
cm.lock.Lock()
|
||||
|
||||
shorCommit := constant.GitRevision
|
||||
shortCommit := constant.GitRevision
|
||||
if len(constant.GitRevision) > 7 {
|
||||
shorCommit = constant.GitRevision[:7]
|
||||
shortCommit = constant.GitRevision[:7]
|
||||
}
|
||||
req := syncReq{
|
||||
Stats: []StatsPerRule{},
|
||||
Version: VersionInfo{Version: constant.Version, ShortCommit: shorCommit},
|
||||
Version: VersionInfo{Version: constant.Version, ShortCommit: shortCommit},
|
||||
}
|
||||
|
||||
if cm.cfg.NeedMetrics() {
|
||||
@@ -50,6 +50,7 @@ func (cm *cmgrImpl) syncOnce(ctx context.Context) error {
|
||||
cm.l.Errorf("read metrics failed: %v", err)
|
||||
} else {
|
||||
req.Node = *metrics
|
||||
cm.ms = append(cm.ms, metrics)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/Ehco1996/ehco/internal/relay/conf"
|
||||
"github.com/Ehco1996/ehco/internal/tls"
|
||||
myhttp "github.com/Ehco1996/ehco/pkg/http"
|
||||
"github.com/Ehco1996/ehco/pkg/sub"
|
||||
xConf "github.com/xtls/xray-core/infra/conf"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -33,18 +32,15 @@ type Config struct {
|
||||
RelaySyncURL string `json:"relay_sync_url,omitempty"`
|
||||
RelaySyncInterval int `json:"relay_sync_interval,omitempty"`
|
||||
|
||||
SubConfigs []*SubConfig `json:"sub_configs,omitempty"`
|
||||
XRayConfig *xConf.Config `json:"xray_config,omitempty"`
|
||||
SyncTrafficEndPoint string `json:"sync_traffic_endpoint,omitempty"`
|
||||
|
||||
lastLoadTime time.Time
|
||||
l *zap.SugaredLogger
|
||||
|
||||
cachedClashSubMap map[string]*sub.ClashSub // key: clash sub name
|
||||
}
|
||||
|
||||
func NewConfig(path string) *Config {
|
||||
return &Config{PATH: path, l: zap.S().Named("cfg"), cachedClashSubMap: make(map[string]*sub.ClashSub)}
|
||||
return &Config{PATH: path, l: zap.S().Named("cfg")}
|
||||
}
|
||||
|
||||
func (c *Config) NeedSyncFromServer() bool {
|
||||
@@ -93,21 +89,6 @@ func (c *Config) Adjust() error {
|
||||
c.WebHost = "0.0.0.0"
|
||||
}
|
||||
|
||||
clashSubList, err := c.GetClashSubList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, clashSub := range clashSubList {
|
||||
if err := clashSub.Refresh(); err != nil {
|
||||
return err
|
||||
}
|
||||
relayConfigs, err := clashSub.ToRelayConfigs(c.WebHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.RelayConfigs = append(c.RelayConfigs, relayConfigs...)
|
||||
}
|
||||
|
||||
for _, r := range c.RelayConfigs {
|
||||
if err := r.Validate(); err != nil {
|
||||
return err
|
||||
@@ -160,32 +141,3 @@ func (c *Config) GetMetricURL() string {
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
func (c *Config) GetClashSubList() ([]*sub.ClashSub, error) {
|
||||
clashSubList := make([]*sub.ClashSub, 0, len(c.SubConfigs))
|
||||
for _, subCfg := range c.SubConfigs {
|
||||
clashSub, err := c.getOrCreateClashSub(subCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clashSubList = append(clashSubList, clashSub)
|
||||
}
|
||||
return clashSubList, nil
|
||||
}
|
||||
|
||||
func (c *Config) getOrCreateClashSub(subCfg *SubConfig) (*sub.ClashSub, error) {
|
||||
if clashSub, ok := c.cachedClashSubMap[subCfg.Name]; ok {
|
||||
return clashSub, nil
|
||||
}
|
||||
clashSub, err := sub.NewClashSubByURL(subCfg.URL, subCfg.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.cachedClashSubMap[subCfg.Name] = clashSub
|
||||
return clashSub, nil
|
||||
}
|
||||
|
||||
type SubConfig struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
@@ -21,9 +21,7 @@ const (
|
||||
shortHashLength = 7
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIdleTimeout = errors.New("connection closed due to idle timeout")
|
||||
)
|
||||
var ErrIdleTimeout = errors.New("connection closed due to idle timeout")
|
||||
|
||||
// RelayConn is the interface that represents a relay connection.
|
||||
// it contains two connections: clientConn and remoteConn
|
||||
|
||||
@@ -107,7 +107,7 @@ func (r *Config) Adjust() error {
|
||||
zap.S().Debugf("label is empty, set default label:%s", r.Label)
|
||||
}
|
||||
if len(r.Remotes) == 0 && len(r.TCPRemotes) != 0 {
|
||||
zap.S().Warnf("tcp remotes is deprecated, use remotes instead")
|
||||
zap.S().Warnf("tcp remotes is deprecated, please use remotes instead")
|
||||
r.Remotes = r.TCPRemotes
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ func (s *Server) Start(ctx context.Context) error {
|
||||
go s.startOneRelay(ctx, r)
|
||||
}
|
||||
|
||||
if s.cfg.PATH != "" && (s.cfg.ReloadInterval > 0 || len(s.cfg.SubConfigs) > 0) {
|
||||
if s.cfg.PATH != "" && (s.cfg.ReloadInterval > 0) {
|
||||
s.l.Infof("Start to watch relay config %s ", s.cfg.PATH)
|
||||
go s.WatchAndReload(ctx)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Ehco1996/ehco/internal/config"
|
||||
"github.com/Ehco1996/ehco/internal/cmgr"
|
||||
"github.com/Ehco1996/ehco/internal/constant"
|
||||
"github.com/labstack/echo/v4"
|
||||
"go.uber.org/zap"
|
||||
@@ -28,60 +28,16 @@ func (s *Server) index(c echo.Context) error {
|
||||
GitRevision string
|
||||
BuildTime string
|
||||
StartTime string
|
||||
SubConfigs []*config.SubConfig
|
||||
}{
|
||||
Version: constant.Version,
|
||||
GitBranch: constant.GitBranch,
|
||||
GitRevision: constant.GitRevision,
|
||||
BuildTime: constant.BuildTime,
|
||||
StartTime: constant.StartTime.Format("2006-01-02 15:04:05"),
|
||||
SubConfigs: s.cfg.SubConfigs,
|
||||
}
|
||||
return c.Render(http.StatusOK, "index.html", data)
|
||||
}
|
||||
|
||||
func (s *Server) HandleClashProxyProvider(c echo.Context) error {
|
||||
subName := c.QueryParam("sub_name")
|
||||
if subName == "" {
|
||||
return c.String(http.StatusBadRequest, "sub_name is empty")
|
||||
}
|
||||
grouped, _ := strconv.ParseBool(c.QueryParam("grouped")) // defaults to false if parameter is missing or invalid
|
||||
|
||||
return s.handleClashProxyProvider(c, subName, grouped)
|
||||
}
|
||||
|
||||
func (s *Server) handleClashProxyProvider(c echo.Context, subName string, grouped bool) error {
|
||||
if s.Reloader != nil {
|
||||
if err := s.Reloader.Reload(true); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
} else {
|
||||
s.l.Debugf("Reloader is nil this should not happen")
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "should not happen error happen :)")
|
||||
}
|
||||
|
||||
clashSubList, err := s.cfg.GetClashSubList()
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
for _, clashSub := range clashSubList {
|
||||
if clashSub.Name == subName {
|
||||
var clashCfgBuf []byte
|
||||
if grouped {
|
||||
clashCfgBuf, err = clashSub.ToGroupedClashConfigYaml()
|
||||
} else {
|
||||
clashCfgBuf, err = clashSub.ToClashConfigYaml()
|
||||
}
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"message": err.Error()})
|
||||
}
|
||||
return c.String(http.StatusOK, string(clashCfgBuf))
|
||||
}
|
||||
}
|
||||
msg := fmt.Sprintf("sub_name=%s not found", subName)
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"message": msg})
|
||||
}
|
||||
|
||||
func (s *Server) HandleReload(c echo.Context) error {
|
||||
if s.Reloader == nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "reload not support")
|
||||
@@ -165,3 +121,20 @@ func (s *Server) ListRules(c echo.Context) error {
|
||||
"Configs": s.cfg.RelayConfigs,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) GetNodeMetrics(c echo.Context) error {
|
||||
req := &cmgr.QueryNodeMetricsReq{TimeRange: c.QueryParam("time_range")}
|
||||
num := c.QueryParam("num")
|
||||
if num != "" {
|
||||
n, err := strconv.Atoi(num)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
req.Num = n
|
||||
}
|
||||
metrics, err := s.connMgr.QueryNodeMetrics(c.Request().Context(), req)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
return c.JSON(http.StatusOK, metrics)
|
||||
}
|
||||
|
||||
@@ -103,13 +103,13 @@ func NewServer(
|
||||
e.GET("/", s.index)
|
||||
e.GET("/connections/", s.ListConnections)
|
||||
e.GET("/rules/", s.ListRules)
|
||||
e.GET("/clash_proxy_provider/", s.HandleClashProxyProvider)
|
||||
|
||||
// api group
|
||||
api := e.Group("/api/v1")
|
||||
api.GET("/config/", s.CurrentConfig)
|
||||
api.POST("/config/reload/", s.HandleReload)
|
||||
api.GET("/health_check/", s.HandleHealthCheck)
|
||||
api.GET("/node_metrics/", s.GetNodeMetrics)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,167 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="ehco web" />
|
||||
<meta name="keywords" content="ehco-relay" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/1.0.0/css/bulma.min.css"
|
||||
/>
|
||||
<title>Ehco</title>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<section class="hero is-fullheight is-light">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<!-- Title -->
|
||||
<h1 class="title has-text-centered">
|
||||
ehco is a network relay tool and a typo :)
|
||||
</h1>
|
||||
<!-- Build Info Card -->
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title has-text-centered">
|
||||
Build Info
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li>Version: {{.Version}}</li>
|
||||
<li>GitBranch: {{.GitBranch}}</li>
|
||||
<li>GitRevision: {{.GitRevision}}</li>
|
||||
<li>BuildTime: {{.BuildTime}}</li>
|
||||
<li>StartTime: {{.StartTime}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stylish Links Card -->
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title has-text-centered">
|
||||
Quick Links
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="/metrics/"
|
||||
class="button is-info is-light"
|
||||
>Metrics</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/debug/pprof/"
|
||||
class="button is-info is-light"
|
||||
>Debug</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/connections/?conn_type=active"
|
||||
class="button is-info is-light"
|
||||
>Connections</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/rules/"
|
||||
class="button is-info is-light"
|
||||
>Rule List</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/api/v1/config/"
|
||||
class="button is-info is-light"
|
||||
>Config</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Clash Providers card -->
|
||||
{{ if .SubConfigs }}
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title has-text-centered">
|
||||
Clash Providers
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<ul>
|
||||
{{ range .SubConfigs }}
|
||||
<li>
|
||||
<a
|
||||
class="button is-info is-light"
|
||||
href="/clash_proxy_provider/?sub_name={{.Name}}"
|
||||
>{{.Name}}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button is-info is-light"
|
||||
href="/clash_proxy_provider/?sub_name={{.Name}}&grouped=true"
|
||||
>{{.Name}}-lb</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<!-- Reload Config Button -->
|
||||
<div class="has-text-centered">
|
||||
<button
|
||||
class="button is-danger is-outlined"
|
||||
id="reloadButton"
|
||||
>
|
||||
Reload Config
|
||||
</button>
|
||||
</div>
|
||||
<head>
|
||||
<title>Ehco Web</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="ehco web" />
|
||||
<meta name="keywords" content="ehco-relay" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/1.0.1/css/bulma.min.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section class="hero is-fullheight is-light">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<h1 class="title has-text-centered">Ehco Relay</h1>
|
||||
<div class="columns is-variable is-8 is-flex-grow">
|
||||
<div class="column is-flex">
|
||||
<!-- Build Info Card -->
|
||||
<div class="card is-flex is-flex-direction-column is-flex-grow-1">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title has-text-centered">Build Info</p>
|
||||
</header>
|
||||
<div class="card-content is-flex-grow-1">
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li>Version: {{.Version}}</li>
|
||||
<li>GitBranch: {{.GitBranch}}</li>
|
||||
<li>GitRevision: {{.GitRevision}}</li>
|
||||
<li>BuildTime: {{.BuildTime}}</li>
|
||||
<li>StartTime: {{.StartTime}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
<div class="hero-foot">
|
||||
<footer class="footer">
|
||||
<div class="content has-text-centered">
|
||||
<a href="https://github.com/Ehco1996/ehco"
|
||||
>Source code</a
|
||||
>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<button class="button is-danger is-outlined card-footer-item" id="reloadButton">
|
||||
<span class="icon"><i class="fas fa-sync-alt"></i></span>
|
||||
<span>Reload Config</span>
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$("#reloadButton").click(function () {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/v1/config/reload/",
|
||||
success: function (response) {
|
||||
alert(
|
||||
"Reload config success. Response: " + response
|
||||
);
|
||||
},
|
||||
error: function (response) {
|
||||
alert(
|
||||
"Failed to reload config. Response: " +
|
||||
response.responseText
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
<div class="column is-flex">
|
||||
<!-- Stylish Links Card -->
|
||||
<div class="card is-flex is-flex-direction-column is-flex-grow-1">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title has-text-centered">Quick Links</p>
|
||||
</header>
|
||||
<div class="card-content is-flex-grow-1">
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/metrics/" class="button is-info is-light">
|
||||
<span class="icon"><i class="fas fa-chart-bar"></i></span>
|
||||
<span>Metrics</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/debug/pprof/" class="button is-info is-light">
|
||||
<span class="icon"><i class="fas fa-bug"></i></span>
|
||||
<span>Debug</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/connections/?conn_type=active" class="button is-info is-light">
|
||||
<span class="icon"><i class="fas fa-link"></i></span>
|
||||
<span>Connections</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/rules/" class="button is-info is-light">
|
||||
<span class="icon"><i class="fas fa-list"></i></span>
|
||||
<span>Rule List</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/api/v1/config/" class="button is-info is-light">
|
||||
<span class="icon"><i class="fas fa-cog"></i></span>
|
||||
<span>Config</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- metrics -->
|
||||
{{template "metrics.html"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="hero-foot">
|
||||
<footer class="footer">
|
||||
<div class="content has-text-centered">
|
||||
<a href="https://github.com/Ehco1996/ehco"><i class="fab fa-github"></i> Source code</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
// Reload config button click event
|
||||
$('#reloadButton').click(function () {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/v1/config/reload/',
|
||||
success: function (response) {
|
||||
alert('Reload config success. Response: ' + response);
|
||||
},
|
||||
error: function (response) {
|
||||
alert('Failed to reload config. Response: ' + response.responseText);
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
<div class="card" id="metrics-card">
|
||||
<header class="card-header is-flex is-flex-wrap-wrap">
|
||||
<p class="card-header-title has-text-centered">Node Metrics</p>
|
||||
<div class="card-header-icon is-flex-grow-1 is-flex is-justify-content-flex-end">
|
||||
<div class="dropdown is-hoverable">
|
||||
<div class="dropdown-trigger">
|
||||
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
|
||||
<span class="icon">
|
||||
<i class="fas fa-clock"></i>
|
||||
</span>
|
||||
<span>Time</span>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-angle-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" id="dropdown-menu" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<a href="#" class="dropdown-item" data-time="15min">Last 15 Min</a>
|
||||
<a href="#" class="dropdown-item" data-time="30min">Last 30 Min</a>
|
||||
<a href="#" class="dropdown-item" data-time="1h">Last 1 hour</a>
|
||||
<a href="#" class="dropdown-item" data-time="6h">Last 6 hours</a>
|
||||
<a href="#" class="dropdown-item" data-time="12h">Last 12 hours</a>
|
||||
<a href="#" class="dropdown-item" data-time="24h">Last 24 hours</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-4">
|
||||
<h2 class="subtitle is-5">CPU</h2>
|
||||
<canvas id="cpuChart"></canvas>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<h2 class="subtitle is-5">Memory</h2>
|
||||
<canvas id="memoryChart"></canvas>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<h2 class="subtitle is-5">Disk</h2>
|
||||
<canvas id="diskChart"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="column is-6">
|
||||
<h2 class="subtitle is-5">Network</h2>
|
||||
<canvas id="networkChart"></canvas>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<h2 class="subtitle is-5">Ping</h2>
|
||||
<canvas id="pingChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Constants
|
||||
const API_BASE_URL = '/api/v1/node_metrics/';
|
||||
const BYTE_TO_MB = 1024 * 1024;
|
||||
const BYTE_TO_GB = BYTE_TO_MB * 1024;
|
||||
|
||||
// Utility functions
|
||||
const handleError = (error) => {
|
||||
console.error('Error:', error);
|
||||
// You can add user notifications here
|
||||
};
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
};
|
||||
|
||||
const formatBytes = (bytes, decimals = 2) => {
|
||||
return (bytes / BYTE_TO_GB).toFixed(decimals);
|
||||
};
|
||||
|
||||
// Chart functions
|
||||
const initChart = (canvasId, type, datasets, legendPosition = '', yDisplayText = '', additionalInfo = '', unit = '') => {
|
||||
const ctx = document.getElementById(canvasId).getContext('2d');
|
||||
const data = {
|
||||
labels: [],
|
||||
datasets: Array.isArray(datasets) ? datasets.map((dataset) => ({ ...dataset, data: [] })) : [{ ...datasets, data: [] }],
|
||||
};
|
||||
return new Chart(ctx, {
|
||||
type,
|
||||
data,
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { position: legendPosition },
|
||||
title: {
|
||||
display: !!additionalInfo,
|
||||
text: additionalInfo,
|
||||
position: 'bottom',
|
||||
font: { size: 12 },
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
let label = context.dataset.label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
if (context.parsed.y !== null) {
|
||||
label += context.parsed.y.toFixed(2) + ' ' + unit;
|
||||
}
|
||||
return label;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: { display: true, text: yDisplayText },
|
||||
},
|
||||
},
|
||||
elements: { line: { tension: 0.2 } },
|
||||
downsample: {
|
||||
enabled: true,
|
||||
samples: 100,
|
||||
threshold: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const updateChart = (chart, newData, labels) => {
|
||||
if (!newData || !labels) {
|
||||
console.error('Invalid data or labels provided');
|
||||
return;
|
||||
}
|
||||
|
||||
const formattedLabels = labels.map(formatDate);
|
||||
|
||||
if (Array.isArray(newData) && Array.isArray(newData[0])) {
|
||||
chart.data.datasets.forEach((dataset, index) => {
|
||||
if (newData[index]) {
|
||||
dataset.data = newData[index];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
chart.data.datasets[0].data = newData;
|
||||
}
|
||||
|
||||
chart.data.labels = formattedLabels;
|
||||
chart.update();
|
||||
};
|
||||
|
||||
// Data fetching functions
|
||||
const fetchLatestMetric = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}?time_range=15min&num=1`);
|
||||
if (!response.ok) throw new Error('Network response was not ok');
|
||||
const data = await response.json();
|
||||
return data[0];
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchMetrics = async (timeRange) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}?time_range=${timeRange}`);
|
||||
if (!response.ok) throw new Error('Network response was not ok');
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Chart initialization
|
||||
const initializeCharts = async () => {
|
||||
const metric = await fetchLatestMetric();
|
||||
if (!metric) return null;
|
||||
|
||||
const pingTargets = metric.ping_metrics.map((ping) => ping.target);
|
||||
return {
|
||||
cpu: initChart(
|
||||
'cpuChart',
|
||||
'line',
|
||||
{ label: 'CPU' },
|
||||
'top',
|
||||
'Usage (%)',
|
||||
`Load: ${metric.cpu_load_info} | Cores: ${metric.cpu_core_count}`,
|
||||
'%',
|
||||
),
|
||||
memory: initChart(
|
||||
'memoryChart',
|
||||
'line',
|
||||
{ label: 'Memory' },
|
||||
'top',
|
||||
'Usage (%)',
|
||||
`Total: ${formatBytes(metric.memory_total_bytes)} GB`,
|
||||
'%',
|
||||
),
|
||||
disk: initChart(
|
||||
'diskChart',
|
||||
'line',
|
||||
{ label: 'Disk' },
|
||||
'top',
|
||||
'Usage (%)',
|
||||
`Total: ${formatBytes(metric.disk_total_bytes)} GB`,
|
||||
'%',
|
||||
),
|
||||
network: initChart('networkChart', 'line', [{ label: 'Receive' }, { label: 'Transmit' }], 'top', 'Rate (MB/s)', '', 'MB/s'),
|
||||
ping: initChart(
|
||||
'pingChart',
|
||||
'line',
|
||||
pingTargets.map((target) => ({ label: target })),
|
||||
'right',
|
||||
'Latency (ms)',
|
||||
'',
|
||||
'ms',
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
// Update functions
|
||||
const updateCharts = (charts, metrics) => {
|
||||
const timestamps = metrics.map((data) => data.SyncTime);
|
||||
|
||||
updateChart(
|
||||
charts.cpu,
|
||||
metrics.map((data) => data.cpu_usage_percent),
|
||||
timestamps,
|
||||
);
|
||||
updateChart(
|
||||
charts.memory,
|
||||
metrics.map((data) => data.memory_usage_percent),
|
||||
timestamps,
|
||||
);
|
||||
updateChart(
|
||||
charts.disk,
|
||||
metrics.map((data) => data.disk_usage_percent),
|
||||
timestamps,
|
||||
);
|
||||
updateChart(
|
||||
charts.network,
|
||||
[
|
||||
metrics.map((data) => data.network_receive_bytes_rate / BYTE_TO_MB),
|
||||
metrics.map((data) => data.network_transmit_bytes_rate / BYTE_TO_MB),
|
||||
],
|
||||
timestamps,
|
||||
);
|
||||
|
||||
const pingTargets = [...new Set(metrics.flatMap((data) => data.ping_metrics.map((ping) => ping.target)))];
|
||||
const pingData = pingTargets.map((target) =>
|
||||
metrics.map((data) => {
|
||||
const pingMetric = data.ping_metrics.find((ping) => ping.target === target);
|
||||
return pingMetric ? pingMetric.latency : null;
|
||||
}),
|
||||
);
|
||||
updateChart(charts.ping, pingData, timestamps);
|
||||
|
||||
const latestMetric = metrics[metrics.length - 1];
|
||||
updateAdditionalInfo(charts, latestMetric);
|
||||
};
|
||||
|
||||
const updateAdditionalInfo = (charts, metric) => {
|
||||
charts.cpu.options.plugins.title.text = `Load: ${metric.cpu_load_info} | Cores: ${metric.cpu_core_count}`;
|
||||
charts.memory.options.plugins.title.text = `Total: ${formatBytes(metric.memory_total_bytes)} GB`;
|
||||
charts.disk.options.plugins.title.text = `Total: ${formatBytes(metric.disk_total_bytes)} GB`;
|
||||
|
||||
charts.cpu.update();
|
||||
charts.memory.update();
|
||||
charts.disk.update();
|
||||
};
|
||||
|
||||
// Main execution
|
||||
$(document).ready(async function () {
|
||||
let charts = await initializeCharts();
|
||||
if (!charts) return;
|
||||
|
||||
$('.dropdown-item').click(async function () {
|
||||
const timeRange = $(this).data('time');
|
||||
const metrics = await fetchMetrics(timeRange);
|
||||
if (metrics) updateCharts(charts, metrics);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
@@ -1,86 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="ehco web" />
|
||||
<meta name="keywords" content="ehco-relay" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/1.0.1/css/bulma.min.css"
|
||||
/>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<title>Rules</title>
|
||||
</head>
|
||||
<body>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title">Rules</h1>
|
||||
<table class="table is-striped is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Listen</th>
|
||||
<th>Listen Type</th>
|
||||
<th>Transport Type</th>
|
||||
<th>Remote</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Configs}}
|
||||
<tr>
|
||||
<td>{{.Label}}</td>
|
||||
<td>{{.Listen}}</td>
|
||||
<td>{{.ListenType}}</td>
|
||||
<td>{{.TransportType}}</td>
|
||||
<td>{{.GetTCPRemotes}}</td>
|
||||
<td>
|
||||
<button
|
||||
class="button is-small is-primary health-check"
|
||||
data-label="{{.Label}}"
|
||||
onclick="checkHealth('{{.Label}}')"
|
||||
>
|
||||
Check Health
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="ehco web" />
|
||||
<meta name="keywords" content="ehco-relay" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/1.0.1/css/bulma.min.css" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<title>Rules</title>
|
||||
</head>
|
||||
<body>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title">Rules</h1>
|
||||
<table class="table is-striped is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Listen</th>
|
||||
<th>Listen Type</th>
|
||||
<th>Transport Type</th>
|
||||
<th>Remote</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Configs}}
|
||||
<tr>
|
||||
<td>{{.Label}}</td>
|
||||
<td>{{.Listen}}</td>
|
||||
<td>{{.ListenType}}</td>
|
||||
<td>{{.TransportType}}</td>
|
||||
<td>{{.Remotes}}</td>
|
||||
<td>
|
||||
<button class="button is-small is-primary health-check" data-label="{{.Label}}" onclick="checkHealth('{{.Label}}')">
|
||||
Check Health
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
function checkHealth(label) {
|
||||
$.ajax({
|
||||
url: "/api/v1/health_check/?relay_label=" + label,
|
||||
method: "GET",
|
||||
success: function (response) {
|
||||
// Check if the response includes an error code
|
||||
if (response.error_code === 0) {
|
||||
// If no error, show success message with latency
|
||||
alert(
|
||||
"Health Check for " +
|
||||
label +
|
||||
": " +
|
||||
response.msg + // Use 'msg' as per Go struct
|
||||
" (Latency: " +
|
||||
response.latency + // Ensure this matches the Go struct field name
|
||||
"ms)"
|
||||
);
|
||||
} else {
|
||||
// If error code is not 0, show error message
|
||||
alert("Error for " + label + ": " + response.msg);
|
||||
}
|
||||
},
|
||||
error: function (xhr) {
|
||||
// Parse the response JSON in case of HTTP error
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
alert("Error: " + response.msg); // Use 'msg' as per Go struct
|
||||
},
|
||||
});
|
||||
<script>
|
||||
function checkHealth(label) {
|
||||
$.ajax({
|
||||
url: '/api/v1/health_check/?relay_label=' + label,
|
||||
method: 'GET',
|
||||
success: function (response) {
|
||||
// Check if the response includes an error code
|
||||
if (response.error_code === 0) {
|
||||
// If no error, show success message with latency
|
||||
alert(
|
||||
'Health Check for ' +
|
||||
label +
|
||||
': ' +
|
||||
response.msg + // Use 'msg' as per Go struct
|
||||
' (Latency: ' +
|
||||
response.latency + // Ensure this matches the Go struct field name
|
||||
'ms)'
|
||||
);
|
||||
} else {
|
||||
// If error code is not 0, show error message
|
||||
alert('Error for ' + label + ': ' + response.msg);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
},
|
||||
error: function (xhr) {
|
||||
// Parse the response JSON in case of HTTP error
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
alert('Error: ' + response.msg); // Use 'msg' as per Go struct
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -249,7 +249,7 @@ func (b *readerImpl) parseNetworkInfo(metricMap map[string]*dto.MetricFamily, nm
|
||||
}
|
||||
|
||||
if b.lastMetrics != nil {
|
||||
passedTime := now.Sub(b.lastMetrics.syncTime).Seconds()
|
||||
passedTime := now.Sub(b.lastMetrics.SyncTime).Seconds()
|
||||
nm.NetworkReceiveBytesRate = (nm.NetworkReceiveBytesTotal - b.lastMetrics.NetworkReceiveBytesTotal) / passedTime
|
||||
nm.NetworkTransmitBytesRate = (nm.NetworkTransmitBytesTotal - b.lastMetrics.NetworkTransmitBytesTotal) / passedTime
|
||||
}
|
||||
@@ -272,7 +272,7 @@ func (b *readerImpl) ReadOnce(ctx context.Context) (*NodeMetrics, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nm := &NodeMetrics{syncTime: time.Now(), PingMetrics: []PingMetric{}}
|
||||
nm := &NodeMetrics{SyncTime: time.Now(), PingMetrics: []PingMetric{}}
|
||||
if err := b.parseCpuInfo(parsed, nm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ type NodeMetrics struct {
|
||||
// ping
|
||||
PingMetrics []PingMetric `json:"ping_metrics"`
|
||||
|
||||
syncTime time.Time
|
||||
SyncTime time.Time
|
||||
}
|
||||
|
||||
type PingMetric struct {
|
||||
|
||||
+1
-30
@@ -38,36 +38,7 @@ For an explanation of the mieru protocol, see [mieru Proxy Protocol](./docs/prot
|
||||
3. [Client Installation & Configuration - OpenWrt](./docs/client-install-openwrt.md)
|
||||
4. [Maintenance & Troubleshooting](./docs/operation.md)
|
||||
5. [Security Guide](./docs/security.md)
|
||||
|
||||
## Compile
|
||||
|
||||
Compiling should be done in Linux. The compilation process requires downloading dependent packages, which may be blocked by the firewall.
|
||||
|
||||
The following softwares are required for compilation.
|
||||
|
||||
- curl
|
||||
- env
|
||||
- git
|
||||
- go (version >= 1.20)
|
||||
- make
|
||||
- sha256sum
|
||||
- tar
|
||||
- zip
|
||||
|
||||
To build Android executable files:
|
||||
|
||||
- gcc
|
||||
|
||||
To build debian packages:
|
||||
|
||||
- dpkg-deb
|
||||
- fakeroot
|
||||
|
||||
To build RPM packages:
|
||||
|
||||
- rpmbuild
|
||||
|
||||
To compile, go to the root directory of the project and invoke `make`. The compilation result will be stored in the `release` directory.
|
||||
6. [Compilation](./docs/compile.md)
|
||||
|
||||
## Share
|
||||
|
||||
|
||||
+1
-30
@@ -36,36 +36,7 @@ mieru 的翻墙原理与 shadowsocks / v2ray 等软件类似,在客户端和
|
||||
3. [客户端安装与配置 - OpenWrt](./docs/client-install-openwrt.zh_CN.md)
|
||||
4. [运营维护与故障排查](./docs/operation.zh_CN.md)
|
||||
5. [翻墙安全指南](./docs/security.zh_CN.md)
|
||||
|
||||
## 编译
|
||||
|
||||
编译 mieru 的客户端和服务器软件,建议在 Linux 系统中进行。编译过程可能需要翻墙下载依赖的软件包。
|
||||
|
||||
编译所需的软件包括:
|
||||
|
||||
- curl
|
||||
- env
|
||||
- git
|
||||
- go (version >= 1.20)
|
||||
- make
|
||||
- sha256sum
|
||||
- tar
|
||||
- zip
|
||||
|
||||
编译 Android 可执行文件需要:
|
||||
|
||||
- gcc
|
||||
|
||||
编译 debian 安装包需要:
|
||||
|
||||
- dpkg-deb
|
||||
- fakeroot
|
||||
|
||||
编译 RPM 安装包需要:
|
||||
|
||||
- rpmbuild
|
||||
|
||||
编译时,进入项目根目录,调用指令 `make` 即可。编译结果会存放在项目根目录下的 `release` 文件夹。
|
||||
6. [编译](./docs/compile.zh_CN.md)
|
||||
|
||||
## 分享
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# Compilation
|
||||
|
||||
It is recommended to compile the `mieru` client and `mita` server software on Linux. The compilation process might require a proxy to download dependency packages.
|
||||
|
||||
The following softwares are required for compilation:
|
||||
|
||||
- curl
|
||||
- env
|
||||
- git
|
||||
- go (version >= 1.20)
|
||||
- make
|
||||
- sha256sum
|
||||
- tar
|
||||
- zip
|
||||
|
||||
To compile Android executables, you need:
|
||||
|
||||
- gcc
|
||||
|
||||
To compile Debian packages, you need:
|
||||
|
||||
- dpkg-deb
|
||||
- fakeroot
|
||||
|
||||
To compile RPM packages, you need:
|
||||
|
||||
- rpmbuild
|
||||
|
||||
To compile, navigate to the project's root directory and run the command `make`. The compilation results will be stored in the `release` folder under the project's root directory.
|
||||
|
||||
The `make` command will only generate the officially supported executables. If you want to compile executables for a specific CPU instruction set architecture or operating system, you can refer to the following commands:
|
||||
|
||||
```sh
|
||||
# Compile the mita server software, which runs on a Linux system with Loongson processor
|
||||
env GOOS=linux GOARCH=loong64 CGO_ENABLED=0 go build -ldflags="-s -w" -o mita cmd/mita/mita.go
|
||||
|
||||
# Compile the mieru client software, which runs on a FreeBSD system with x86_64 processor
|
||||
env GOOS=freebsd GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o mieru cmd/mieru/mieru.go
|
||||
|
||||
# Compile the mieru client software, which runs on an OpenWRT system with MIPS processor
|
||||
env GOOS=linux GOARCH=mips CGO_ENABLED=0 go build -ldflags="-s -w" -o mieru cmd/mieru/mieru.go
|
||||
```
|
||||
|
||||
**Note: The `mita` server software may not run on operating systems other than Linux.**
|
||||
@@ -0,0 +1,44 @@
|
||||
# 编译
|
||||
|
||||
编译 mieru 客户端和 mita 服务器软件,建议在 Linux 系统中进行。编译过程可能需要翻墙下载依赖的软件包。
|
||||
|
||||
编译所需的软件包括:
|
||||
|
||||
- curl
|
||||
- env
|
||||
- git
|
||||
- go (version >= 1.20)
|
||||
- make
|
||||
- sha256sum
|
||||
- tar
|
||||
- zip
|
||||
|
||||
编译 Android 可执行文件需要:
|
||||
|
||||
- gcc
|
||||
|
||||
编译 Debian 安装包需要:
|
||||
|
||||
- dpkg-deb
|
||||
- fakeroot
|
||||
|
||||
编译 RPM 安装包需要:
|
||||
|
||||
- rpmbuild
|
||||
|
||||
编译时,进入项目根目录,运行指令 `make` 即可。编译结果会存放在项目根目录下的 `release` 文件夹。
|
||||
|
||||
`make` 指令只会生成官方支持的可执行文件。如果你想要编译特定 CPU 指令集架构或操作系统的可执行文件,可以参考下面的几个指令:
|
||||
|
||||
```sh
|
||||
# 编译可以在龙芯处理器 Linux 系统上运行的 mita 服务器软件
|
||||
env GOOS=linux GOARCH=loong64 CGO_ENABLED=0 go build -ldflags="-s -w" -o mita cmd/mita/mita.go
|
||||
|
||||
# 编译可以在 x86_64 处理器 FreeBSD 系统上运行的 mieru 客户端软件
|
||||
env GOOS=freebsd GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o mieru cmd/mieru/mieru.go
|
||||
|
||||
# 编译可以在 MIPS 处理器 OpenWRT 系统上运行的 mieru 客户端软件
|
||||
env GOOS=linux GOARCH=mips CGO_ENABLED=0 go build -ldflags="-s -w" -o mieru cmd/mieru/mieru.go
|
||||
```
|
||||
|
||||
**注意,`mita` 服务器软件可能无法在 Linux 之外的操作系统中运行。**
|
||||
@@ -92,7 +92,7 @@ const (
|
||||
// The size of the bandwidth filter window, in round-trips.
|
||||
bandwidthFilterWindowSize = gainCycleLength + 2
|
||||
|
||||
// The time after which the current min_rtt value expires.
|
||||
// The time after which the current min RTT value expires.
|
||||
minRTTExpiry = 10 * time.Second
|
||||
|
||||
// The minimum time the connection can spend in PROBE RTT mode.
|
||||
@@ -104,13 +104,6 @@ const (
|
||||
startUpGrowthTarget = 1.25
|
||||
|
||||
roundTripsWithoutGrowthBeforeExitingStartup = 3
|
||||
|
||||
// Coefficient of target congestion window to use when basing PROBE RTT on BDP.
|
||||
moderateProbeRttMultiplier = 0.75
|
||||
|
||||
// Coefficient to determine if a new RTT is sufficiently similar to min RTT that
|
||||
// we don't need to enter PROBE RTT.
|
||||
similarMinRttThreshold = 1.125
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -282,23 +275,6 @@ type BBRSender struct {
|
||||
// Used as the initial packet conservation mode when first entering recovery.
|
||||
initialConservationInStartup bbrRecoveryState
|
||||
|
||||
// If true, will not exit low gain mode until bytesInFlight drops below BDP
|
||||
// or it's time for high gain mode.
|
||||
fullyDrainQueue bool
|
||||
|
||||
// If true, use a CWND of 0.75*BDP during PROBE RTT instead of minCongestionWindow.
|
||||
probeRTTBasedOnBDP bool
|
||||
|
||||
// If true, skip PROBE RTT and update the timestamp of the existing minRTT to
|
||||
// now if minRTT over the last cycle is within 12.5% of the current minRTT.
|
||||
// Even if the minRTT is 12.5% too low, the 25% gain cycling and 2x CWND gain
|
||||
// should overcome an overly small minRTT.
|
||||
probeRTTSkippedIfSimilarRTT bool
|
||||
|
||||
// If true, disable PROBE RTT entirely as long as the connection was recently
|
||||
// app limited.
|
||||
probeRTTDisabledIfAppLimited bool
|
||||
|
||||
appLimitedSinceLastProbeRTT bool
|
||||
minRTTSinceLastProbeRTT time.Duration
|
||||
}
|
||||
@@ -492,9 +468,6 @@ func (b *BBRSender) GetTargetCongestionWindow(gain float64) int64 {
|
||||
}
|
||||
|
||||
func (b *BBRSender) ProbeRTTCongestionWindow() int64 {
|
||||
if b.probeRTTBasedOnBDP {
|
||||
return b.GetTargetCongestionWindow(moderateProbeRttMultiplier)
|
||||
}
|
||||
return b.minCongestionWindow
|
||||
}
|
||||
|
||||
@@ -564,11 +537,7 @@ func (b *BBRSender) UpdateBandwidthAndMinRTT(now time.Time, ackedPackets []Acked
|
||||
b.minRTTSinceLastProbeRTT = mathext.Min(b.minRTTSinceLastProbeRTT, sampleMinRTT)
|
||||
minRTTExpired := b.minRTT > 0 && now.After(b.minRTTTimestamp.Add(minRTTExpiry))
|
||||
if b.minRTT <= 0 || minRTTExpired || sampleMinRTT < b.minRTT {
|
||||
if b.ShouldExtendMinRTTExpiry() {
|
||||
minRTTExpired = false
|
||||
} else {
|
||||
b.minRTT = sampleMinRTT
|
||||
}
|
||||
b.minRTT = sampleMinRTT
|
||||
b.minRTTTimestamp = now
|
||||
b.minRTTSinceLastProbeRTT = infDuration
|
||||
b.appLimitedSinceLastProbeRTT = false
|
||||
@@ -576,21 +545,6 @@ func (b *BBRSender) UpdateBandwidthAndMinRTT(now time.Time, ackedPackets []Acked
|
||||
return minRTTExpired
|
||||
}
|
||||
|
||||
func (b *BBRSender) ShouldExtendMinRTTExpiry() bool {
|
||||
if b.probeRTTDisabledIfAppLimited && b.appLimitedSinceLastProbeRTT {
|
||||
// Extend the current min RTT if we've been app limited recently.
|
||||
return true
|
||||
}
|
||||
minRTTIncreasedSinceLastProbe := b.minRTTSinceLastProbeRTT > time.Duration(float64(b.minRTT)*similarMinRttThreshold)
|
||||
if b.probeRTTSkippedIfSimilarRTT && b.appLimitedSinceLastProbeRTT && !minRTTIncreasedSinceLastProbe {
|
||||
// Extend the current min RTT if we've been app limited recently and an RTT
|
||||
// has been measured in that time that's less than 12.5% more than the
|
||||
// current min RTT.
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *BBRSender) UpdateGainCyclePhase(now time.Time, priorInFlight int64, hasLosses bool) {
|
||||
// In most cases, the cycle is advanced after an RTT passes.
|
||||
shouldAdvanceGainCycling := now.Sub(b.lastCycleStart) > b.GetMinRTT()
|
||||
@@ -615,13 +569,6 @@ func (b *BBRSender) UpdateGainCyclePhase(now time.Time, priorInFlight int64, has
|
||||
if shouldAdvanceGainCycling {
|
||||
b.cycleCurrentOffset = (b.cycleCurrentOffset + 1) % gainCycleLength
|
||||
b.lastCycleStart = now
|
||||
|
||||
// Stay in low gain mode until the target BDP is hit.
|
||||
// Low gain mode will be exited immediately when the target BDP is achieved.
|
||||
if b.pacingGain < 1.0 && pacingGainList[b.cycleCurrentOffset] == 1.0 && b.fullyDrainQueue && priorInFlight > b.GetTargetCongestionWindow(1.0) {
|
||||
return
|
||||
}
|
||||
|
||||
b.pacingGain = pacingGainList[b.cycleCurrentOffset]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ import (
|
||||
"github.com/enfein/mieru/pkg/mathext"
|
||||
)
|
||||
|
||||
// Pacer limits the speed of sending packets.
|
||||
// Pacer is not thread safe. The caller should provide synchronization
|
||||
// to avoid race condition.
|
||||
type Pacer struct {
|
||||
budgetAtLastSent int64
|
||||
maxBudget int64 // determine the max burst
|
||||
@@ -28,6 +31,7 @@ type Pacer struct {
|
||||
lastSentTime time.Time
|
||||
}
|
||||
|
||||
// NewPacer returns a new Pacer object.
|
||||
func NewPacer(initialBudget, maxBudget, minPacingRate int64) *Pacer {
|
||||
if initialBudget <= 0 {
|
||||
panic("initial budget must be a positive number")
|
||||
@@ -48,16 +52,19 @@ func NewPacer(initialBudget, maxBudget, minPacingRate int64) *Pacer {
|
||||
}
|
||||
}
|
||||
|
||||
// OnPacketSent updates the budget and time when a packet is sent.
|
||||
func (p *Pacer) OnPacketSent(sentTime time.Time, bytes, pacingRate int64) {
|
||||
budget := p.Budget(sentTime, pacingRate)
|
||||
p.budgetAtLastSent = mathext.Max(budget-bytes, 0)
|
||||
p.lastSentTime = sentTime
|
||||
}
|
||||
|
||||
// CanSend returns true if a packet can be sent based on the given pacing rate.
|
||||
func (p *Pacer) CanSend(now time.Time, bytes, pacingRate int64) bool {
|
||||
return p.Budget(now, pacingRate) >= bytes
|
||||
}
|
||||
|
||||
// Budget returns the maximum number of bytes can be sent right now.
|
||||
func (p *Pacer) Budget(now time.Time, pacingRate int64) int64 {
|
||||
pacingRate = mathext.Max(pacingRate, p.minPacingRate)
|
||||
if p.lastSentTime.IsZero() {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2024 mieru authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package congestion
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPacer(t *testing.T) {
|
||||
pacer := NewPacer(1000, 10000, 100)
|
||||
now := time.Now().Truncate(time.Second)
|
||||
pacer.OnPacketSent(now, 1000, 1000)
|
||||
|
||||
// Budget at now is 0.
|
||||
if can := pacer.CanSend(now, 1, 1000); can {
|
||||
t.Errorf("CanSend() = %v, want %v", can, false)
|
||||
}
|
||||
|
||||
if can := pacer.CanSend(now.Add(time.Second), 999, 1000); !can {
|
||||
t.Errorf("CanSend() = %v, want %v", can, true)
|
||||
}
|
||||
|
||||
// Minimum pacing rate is 100.
|
||||
if can := pacer.CanSend(now.Add(time.Second), 99, 10); !can {
|
||||
t.Errorf("CanSend() = %v, want %v", can, true)
|
||||
}
|
||||
|
||||
// Maximum budget is 10000.
|
||||
if can := pacer.CanSend(now.Add(time.Second), 10001, 100000); can {
|
||||
t.Errorf("CanSend() = %v, want %v", can, false)
|
||||
}
|
||||
}
|
||||
@@ -681,6 +681,8 @@ func (s *Session) runOutputLoop(ctx context.Context) error {
|
||||
bytesInFlight += newBytesInFlight
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.sendAlgorithm.OnApplicationLimited(bytesInFlight)
|
||||
}
|
||||
|
||||
// Send ACK or heartbeat if needed.
|
||||
|
||||
Vendored
+2
@@ -207,6 +207,8 @@ jobs:
|
||||
if: ${{ matrix.jobs.test == 'test' }}
|
||||
run: |
|
||||
go test ./...
|
||||
echo "---test with_gvisor---"
|
||||
go test ./... -tags "with_gvisor" -count=1
|
||||
|
||||
- name: Update CA
|
||||
run: |
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/metacubex/quic-go/congestion"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@@ -43,6 +45,8 @@ type Hysteria struct {
|
||||
|
||||
option *HysteriaOption
|
||||
client *core.Client
|
||||
|
||||
closeCh chan struct{} // for test
|
||||
}
|
||||
|
||||
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
@@ -51,7 +55,7 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts .
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(tcpConn, h), nil
|
||||
return NewConn(CN.NewRefConn(tcpConn, h), h), nil
|
||||
}
|
||||
|
||||
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
@@ -59,7 +63,7 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newPacketConn(&hyPacketConn{udpConn}, h), nil
|
||||
return newPacketConn(CN.NewRefPacketConn(&hyPacketConn{udpConn}, h), h), nil
|
||||
}
|
||||
|
||||
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
|
||||
@@ -218,7 +222,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
|
||||
}
|
||||
return &Hysteria{
|
||||
outbound := &Hysteria{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
@@ -231,7 +235,19 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
},
|
||||
option: &option,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeHysteria)
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func closeHysteria(h *Hysteria) {
|
||||
if h.client != nil {
|
||||
_ = h.client.Close()
|
||||
}
|
||||
if h.closeCh != nil {
|
||||
close(h.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
type hyPacketConn struct {
|
||||
|
||||
@@ -38,6 +38,8 @@ type Hysteria2 struct {
|
||||
option *Hysteria2Option
|
||||
client *hysteria2.Client
|
||||
dialer proxydialer.SingDialer
|
||||
|
||||
closeCh chan struct{} // for test
|
||||
}
|
||||
|
||||
type Hysteria2Option struct {
|
||||
@@ -89,6 +91,9 @@ func closeHysteria2(h *Hysteria2) {
|
||||
if h.client != nil {
|
||||
_ = h.client.CloseWithError(errors.New("proxy removed"))
|
||||
}
|
||||
if h.closeCh != nil {
|
||||
close(h.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHysteria2GC(t *testing.T) {
|
||||
option := Hysteria2Option{}
|
||||
option.Server = "127.0.0.1"
|
||||
option.Ports = "200,204,401-429,501-503"
|
||||
option.HopInterval = 30
|
||||
option.Password = "password"
|
||||
option.Obfs = "salamander"
|
||||
option.ObfsPassword = "password"
|
||||
option.SNI = "example.com"
|
||||
option.ALPN = []string{"h3"}
|
||||
hy, err := NewHysteria2(option)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
closeCh := make(chan struct{})
|
||||
hy.closeCh = closeCh
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
hy = nil
|
||||
runtime.GC()
|
||||
select {
|
||||
case <-closeCh:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
t.Error("timeout not GC")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHysteriaGC(t *testing.T) {
|
||||
option := HysteriaOption{}
|
||||
option.Server = "127.0.0.1"
|
||||
option.Ports = "200,204,401-429,501-503"
|
||||
option.Protocol = "udp"
|
||||
option.Up = "1Mbps"
|
||||
option.Down = "1Mbps"
|
||||
option.HopInterval = 30
|
||||
option.Obfs = "salamander"
|
||||
option.SNI = "example.com"
|
||||
option.ALPN = []string{"h3"}
|
||||
hy, err := NewHysteria(option)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
closeCh := make(chan struct{})
|
||||
hy.closeCh = closeCh
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
hy = nil
|
||||
runtime.GC()
|
||||
select {
|
||||
case <-closeCh:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
t.Error("timeout not GC")
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
|
||||
wireguard "github.com/metacubex/sing-wireguard"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -456,7 +455,6 @@ func closeWireGuard(w *WireGuard) {
|
||||
if w.device != nil {
|
||||
w.device.Close()
|
||||
}
|
||||
_ = common.Close(w.tunDevice)
|
||||
if w.closeCh != nil {
|
||||
close(w.closeCh)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ func TestWireGuardGC(t *testing.T) {
|
||||
err = wg.init(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
// must do a small sleep before test GC
|
||||
// because it maybe deadlocks if w.device.Close call too fast after w.device.Start
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ require (
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd
|
||||
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d
|
||||
github.com/metacubex/utls v1.6.6
|
||||
github.com/miekg/dns v1.1.62
|
||||
|
||||
+2
-2
@@ -120,8 +120,8 @@ github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 h1:ypfofGDZbP
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a h1:NpSGclHJUYndUwBmyIpFBSoBVg8PoVX7QQKhYg0DjM0=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd h1:r7alry8u4qlUFLNMwGvG1A8ZcfPM6AMSmrm6E2yKdB4=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNvXW824QQeN7Y26uPL5249kzWKbzO9U=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
|
||||
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
|
||||
|
||||
@@ -101,6 +101,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
updateNTP(cfg.NTP)
|
||||
updateDNS(cfg.DNS, cfg.General.IPv6)
|
||||
updateListeners(cfg.General, cfg.Listeners, force)
|
||||
updateTun(cfg.General) // tun should not care "force"
|
||||
updateIPTables(cfg)
|
||||
updateTunnels(cfg.Tunnels)
|
||||
|
||||
@@ -198,6 +199,9 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
|
||||
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
|
||||
listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel)
|
||||
listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
|
||||
}
|
||||
|
||||
func updateTun(general *config.General) {
|
||||
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
|
||||
}
|
||||
|
||||
|
||||
@@ -289,7 +289,10 @@ func (c *Client) DialUDP(dialer utils.PacketDialer) (UDPConn, error) {
|
||||
func (c *Client) Close() error {
|
||||
c.reconnectMutex.Lock()
|
||||
defer c.reconnectMutex.Unlock()
|
||||
err := c.quicSession.CloseWithError(closeErrorCodeGeneric, "")
|
||||
var err error
|
||||
if c.quicSession != nil {
|
||||
err = c.quicSession.CloseWithError(closeErrorCodeGeneric, "")
|
||||
}
|
||||
c.closed = true
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ end
|
||||
m = Map(appname)
|
||||
|
||||
-- [[ Haproxy Settings ]]--
|
||||
s = m:section(TypedSection, "global_haproxy")
|
||||
s = m:section(TypedSection, "global_haproxy", translate("Basic Settings"))
|
||||
s.anonymous = true
|
||||
|
||||
s:append(Template(appname .. "/haproxy/status"))
|
||||
@@ -56,6 +56,18 @@ o:value("tcp", "TCP")
|
||||
o:value("passwall_logic", translate("URL Test") .. string.format("(passwall %s)", translate("Inner implement")))
|
||||
o:depends("balancing_enable", true)
|
||||
|
||||
---- Passwall Inner implement Probe URL
|
||||
o = s:option(Value, "health_probe_url", translate("Probe URL"))
|
||||
o.default = "https://www.google.com/generate_204"
|
||||
o:value("https://cp.cloudflare.com/", "Cloudflare")
|
||||
o:value("https://www.gstatic.com/generate_204", "Gstatic")
|
||||
o:value("https://www.google.com/generate_204", "Google")
|
||||
o:value("https://www.youtube.com/generate_204", "YouTube")
|
||||
o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
|
||||
o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
|
||||
o.description = translate("The URL used to detect the connection status.")
|
||||
o:depends("health_check_type", "passwall_logic")
|
||||
|
||||
---- Health Check Inter
|
||||
o = s:option(Value, "health_check_inter", translate("Health Check Inter"), translate("Units:seconds"))
|
||||
o.default = "60"
|
||||
@@ -69,7 +81,7 @@ end
|
||||
o:depends("health_check_type", "passwall_logic")
|
||||
|
||||
-- [[ Balancing Settings ]]--
|
||||
s = m:section(TypedSection, "haproxy_config", "",
|
||||
s = m:section(TypedSection, "haproxy_config", translate("Node List"),
|
||||
"<font color='red'>" ..
|
||||
translate("Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline! Multiple groups can be set, Haproxy port same one for each group.") ..
|
||||
"\n" .. translate("Note that the node configuration parameters for load balancing must be consistent when use TCP health check type, otherwise it cannot be used normally!") ..
|
||||
|
||||
@@ -220,3 +220,11 @@ listen console
|
||||
f_out:write("\n" .. string.format(str, console_port, (console_user and console_user ~= "" and console_password and console_password ~= "") and "stats auth " .. console_user .. ":" .. console_password or ""))
|
||||
|
||||
f_out:close()
|
||||
|
||||
--passwall内置健康检查URL
|
||||
if health_check_type == "passwall_logic" then
|
||||
local probeUrl = uci:get(appname, "@global_haproxy[0]", "health_probe_url") or "https://www.google.com/generate_204"
|
||||
local f_url = io.open(haproxy_path .. "/Probe_URL", "w")
|
||||
f_url:write(probeUrl)
|
||||
f_url:close()
|
||||
end
|
||||
|
||||
@@ -4,7 +4,17 @@ listen_address=$1
|
||||
listen_port=$2
|
||||
server_address=$3
|
||||
server_port=$4
|
||||
status=$(/usr/bin/curl -I -o /dev/null -skL -x socks5h://${server_address}:${server_port} --connect-timeout 3 --retry 3 -w %{http_code} "https://www.google.com/generate_204")
|
||||
|
||||
probe_file="/tmp/etc/passwall/haproxy/Probe_URL"
|
||||
probeUrl="https://www.google.com/generate_204"
|
||||
if [ -f "$probe_file" ]; then
|
||||
firstLine=$(head -n 1 "$probe_file" | tr -d ' \t')
|
||||
if [ -n "$firstLine" ]; then
|
||||
probeUrl="$firstLine"
|
||||
fi
|
||||
fi
|
||||
|
||||
status=$(/usr/bin/curl -I -o /dev/null -skL -x socks5h://${server_address}:${server_port} --connect-timeout 3 --retry 3 -w %{http_code} "${probeUrl}")
|
||||
case "$status" in
|
||||
204|\
|
||||
200)
|
||||
|
||||
Generated
+81
-36
@@ -17,6 +17,12 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
@@ -241,7 +247,7 @@ dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.7.4",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
@@ -308,9 +314,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.5.3"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9ec96fe9a81b5e365f9db71fe00edc4fe4ca2cc7dcb7861f0603012a7caa210"
|
||||
checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
@@ -450,12 +456,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.6"
|
||||
version = "1.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
|
||||
checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -947,12 +954,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.31"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920"
|
||||
checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1731,7 +1738,7 @@ dependencies = [
|
||||
"socket2",
|
||||
"widestring",
|
||||
"windows-sys 0.48.0",
|
||||
"winreg 0.50.0",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2019,6 +2026,15 @@ dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
@@ -2700,9 +2716,9 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.5"
|
||||
version = "0.12.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37"
|
||||
checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"base64 0.22.1",
|
||||
@@ -2747,7 +2763,7 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots 0.26.3",
|
||||
"winreg 0.52.0",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3106,9 +3122,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.125"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
|
||||
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
@@ -3332,6 +3348,12 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
@@ -3509,6 +3531,9 @@ name = "sync_wrapper"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
@@ -3529,20 +3554,20 @@ checksum = "c4008983d29e823b1415f5f12732d5c9a44059795fb6218262cc0185668851e2"
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.6.0",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@@ -3877,9 +3902,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tun2"
|
||||
version = "2.0.6"
|
||||
version = "2.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5254ad58e460ff4d17b593d32f1be9ab28c80e96eec143e6e8237018b13bcae1"
|
||||
checksum = "c1576993bcdccd110d21278396df090cb29219d296a8b8daa697442efdaab0c6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg-if",
|
||||
@@ -3894,7 +3919,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"windows-sys 0.59.0",
|
||||
"wintun",
|
||||
"wintun-bindings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4196,6 +4221,26 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-service"
|
||||
version = "0.7.0"
|
||||
@@ -4207,6 +4252,16 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
@@ -4366,26 +4421,16 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.52.0"
|
||||
name = "wintun-bindings"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wintun"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b196f9328341b035820c54beebca487823e2e20a5977f284f2af2a0ee8f04400"
|
||||
checksum = "79260cdfee91a3de3a0fe0f04b81b695e69c68b170cd6a643746904a8c14da63"
|
||||
dependencies = [
|
||||
"c2rust-bitfields",
|
||||
"libloading",
|
||||
"log",
|
||||
"thiserror",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -186,7 +186,7 @@ flate2 = { version = "1.0", optional = true }
|
||||
brotli = { version = "6.0", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
|
||||
tun2 = { version = "2.0.6", optional = true, default-features = false, features = [
|
||||
tun2 = { version = "2.0.8", optional = true, default-features = false, features = [
|
||||
"async",
|
||||
] }
|
||||
etherparse = { version = "0.15", optional = true }
|
||||
|
||||
@@ -180,7 +180,6 @@ func (w *WireGuard) Close() error {
|
||||
if w.pauseCallback != nil {
|
||||
w.pauseManager.UnregisterCallback(w.pauseCallback)
|
||||
}
|
||||
w.tunDevice.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -230,17 +230,13 @@ func (w *StackDevice) Events() <-chan wgTun.Event {
|
||||
}
|
||||
|
||||
func (w *StackDevice) Close() error {
|
||||
select {
|
||||
case <-w.done:
|
||||
return os.ErrClosed
|
||||
default:
|
||||
}
|
||||
close(w.done)
|
||||
close(w.events)
|
||||
w.stack.Close()
|
||||
for _, endpoint := range w.stack.CleanupEndpoints() {
|
||||
endpoint.Abort()
|
||||
}
|
||||
w.stack.Wait()
|
||||
close(w.done)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
@@ -21,14 +22,16 @@ import (
|
||||
var _ Device = (*SystemDevice)(nil)
|
||||
|
||||
type SystemDevice struct {
|
||||
dialer N.Dialer
|
||||
device tun.Tun
|
||||
batchDevice tun.LinuxTUN
|
||||
name string
|
||||
mtu int
|
||||
events chan wgTun.Event
|
||||
addr4 netip.Addr
|
||||
addr6 netip.Addr
|
||||
dialer N.Dialer
|
||||
device tun.Tun
|
||||
batchDevice tun.LinuxTUN
|
||||
name string
|
||||
mtu uint32
|
||||
inet4Addresses []netip.Prefix
|
||||
inet6Addresses []netip.Prefix
|
||||
gso bool
|
||||
events chan wgTun.Event
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes []netip.Prefix, mtu uint32, gso bool) (*SystemDevice, error) {
|
||||
@@ -44,43 +47,17 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes
|
||||
if interfaceName == "" {
|
||||
interfaceName = tun.CalculateInterfaceName("wg")
|
||||
}
|
||||
tunInterface, err := tun.New(tun.Options{
|
||||
Name: interfaceName,
|
||||
Inet4Address: inet4Addresses,
|
||||
Inet6Address: inet6Addresses,
|
||||
MTU: mtu,
|
||||
GSO: gso,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var inet4Address netip.Addr
|
||||
var inet6Address netip.Addr
|
||||
if len(inet4Addresses) > 0 {
|
||||
inet4Address = inet4Addresses[0].Addr()
|
||||
}
|
||||
if len(inet6Addresses) > 0 {
|
||||
inet6Address = inet6Addresses[0].Addr()
|
||||
}
|
||||
var batchDevice tun.LinuxTUN
|
||||
if gso {
|
||||
batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN)
|
||||
if !isBatchTUN {
|
||||
return nil, E.New("GSO is not supported on current platform")
|
||||
}
|
||||
batchDevice = batchTUN
|
||||
}
|
||||
|
||||
return &SystemDevice{
|
||||
dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{
|
||||
BindInterface: interfaceName,
|
||||
})),
|
||||
device: tunInterface,
|
||||
batchDevice: batchDevice,
|
||||
name: interfaceName,
|
||||
mtu: int(mtu),
|
||||
events: make(chan wgTun.Event),
|
||||
addr4: inet4Address,
|
||||
addr6: inet6Address,
|
||||
name: interfaceName,
|
||||
mtu: mtu,
|
||||
inet4Addresses: inet4Addresses,
|
||||
inet6Addresses: inet6Addresses,
|
||||
gso: gso,
|
||||
events: make(chan wgTun.Event),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -93,14 +70,39 @@ func (w *SystemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr
|
||||
}
|
||||
|
||||
func (w *SystemDevice) Inet4Address() netip.Addr {
|
||||
return w.addr4
|
||||
if len(w.inet4Addresses) == 0 {
|
||||
return netip.Addr{}
|
||||
}
|
||||
return w.inet4Addresses[0].Addr()
|
||||
}
|
||||
|
||||
func (w *SystemDevice) Inet6Address() netip.Addr {
|
||||
return w.addr6
|
||||
if len(w.inet6Addresses) == 0 {
|
||||
return netip.Addr{}
|
||||
}
|
||||
return w.inet6Addresses[0].Addr()
|
||||
}
|
||||
|
||||
func (w *SystemDevice) Start() error {
|
||||
tunInterface, err := tun.New(tun.Options{
|
||||
Name: w.name,
|
||||
Inet4Address: w.inet4Addresses,
|
||||
Inet6Address: w.inet6Addresses,
|
||||
MTU: w.mtu,
|
||||
GSO: w.gso,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.device = tunInterface
|
||||
if w.gso {
|
||||
batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN)
|
||||
if !isBatchTUN {
|
||||
tunInterface.Close()
|
||||
return E.New("GSO is not supported on current platform")
|
||||
}
|
||||
w.batchDevice = batchTUN
|
||||
}
|
||||
w.events <- wgTun.EventUp
|
||||
return nil
|
||||
}
|
||||
@@ -143,7 +145,7 @@ func (w *SystemDevice) Flush() error {
|
||||
}
|
||||
|
||||
func (w *SystemDevice) MTU() (int, error) {
|
||||
return w.mtu, nil
|
||||
return int(w.mtu), nil
|
||||
}
|
||||
|
||||
func (w *SystemDevice) Name() (string, error) {
|
||||
@@ -155,6 +157,7 @@ func (w *SystemDevice) Events() <-chan wgTun.Event {
|
||||
}
|
||||
|
||||
func (w *SystemDevice) Close() error {
|
||||
close(w.events)
|
||||
return w.device.Close()
|
||||
}
|
||||
|
||||
|
||||
@@ -164,14 +164,15 @@ function check_site(host, port)
|
||||
end
|
||||
|
||||
function get_ip_geo_info()
|
||||
local result = luci.sys.exec('curl --retry 3 -m 10 -LfsA "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36" https://ip-api.com/json/')
|
||||
local result = luci.sys.exec('curl --retry 3 -m 10 -LfsA "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36" http://ip-api.com/json/')
|
||||
local json = require "luci.jsonc"
|
||||
local info = json.parse(result)
|
||||
|
||||
return {
|
||||
flag = string.lower(info.countryCode) or "un",
|
||||
country = get_country_name(info.countryCode) or "Unknown",
|
||||
ip = info.query
|
||||
ip = info.query,
|
||||
isp = info.isp
|
||||
}
|
||||
end
|
||||
|
||||
@@ -198,6 +199,7 @@ function check_ip()
|
||||
e.ip = geo_info.ip
|
||||
e.flag = geo_info.flag
|
||||
e.country = geo_info.country
|
||||
e.isp = geo_info.isp
|
||||
e.baidu = check_site('www.baidu.com', port)
|
||||
e.taobao = check_site('www.taobao.com', port)
|
||||
e.google = check_site('www.google.com', port)
|
||||
|
||||
@@ -94,7 +94,7 @@ function resize() {
|
||||
|
||||
function write_status(data) {
|
||||
document.querySelector(".flag img").src = _ASSETS + "flags/" + data.flag + ".svg";
|
||||
document.querySelector(".status-info").innerHTML = data.ip + "<br>" + data.country;
|
||||
document.querySelector(".status-info").innerHTML = data.ip + "<br>" + data.country + " " + data.isp;
|
||||
document.querySelector(".i1").src = data.baidu ? _ASSETS + "img/site_icon_01.png" : _ASSETS + "img/site_icon1_01.png";
|
||||
document.querySelector(".i2").src = data.taobao ? _ASSETS + "img/site_icon_02.png" : _ASSETS + "img/site_icon1_02.png";
|
||||
document.querySelector(".i3").src = data.google ? _ASSETS + "img/site_icon_03.png" : _ASSETS + "img/site_icon1_03.png";
|
||||
@@ -112,7 +112,7 @@ XHR.poll(5, CHECK_IP_URL, null,
|
||||
);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
setTimeout("resize()",10)
|
||||
setTimeout("resize()",100)
|
||||
fetch(CHECK_IP_URL)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
|
||||
@@ -18,7 +18,7 @@ end
|
||||
m = Map(appname)
|
||||
|
||||
-- [[ Haproxy Settings ]]--
|
||||
s = m:section(TypedSection, "global_haproxy")
|
||||
s = m:section(TypedSection, "global_haproxy", translate("Basic Settings"))
|
||||
s.anonymous = true
|
||||
|
||||
s:append(Template(appname .. "/haproxy/status"))
|
||||
@@ -56,6 +56,18 @@ o:value("tcp", "TCP")
|
||||
o:value("passwall_logic", translate("URL Test") .. string.format("(passwall %s)", translate("Inner implement")))
|
||||
o:depends("balancing_enable", true)
|
||||
|
||||
---- Passwall Inner implement Probe URL
|
||||
o = s:option(Value, "health_probe_url", translate("Probe URL"))
|
||||
o.default = "https://www.google.com/generate_204"
|
||||
o:value("https://cp.cloudflare.com/", "Cloudflare")
|
||||
o:value("https://www.gstatic.com/generate_204", "Gstatic")
|
||||
o:value("https://www.google.com/generate_204", "Google")
|
||||
o:value("https://www.youtube.com/generate_204", "YouTube")
|
||||
o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
|
||||
o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
|
||||
o.description = translate("The URL used to detect the connection status.")
|
||||
o:depends("health_check_type", "passwall_logic")
|
||||
|
||||
---- Health Check Inter
|
||||
o = s:option(Value, "health_check_inter", translate("Health Check Inter"), translate("Units:seconds"))
|
||||
o.default = "60"
|
||||
@@ -69,7 +81,7 @@ end
|
||||
o:depends("health_check_type", "passwall_logic")
|
||||
|
||||
-- [[ Balancing Settings ]]--
|
||||
s = m:section(TypedSection, "haproxy_config", "",
|
||||
s = m:section(TypedSection, "haproxy_config", translate("Node List"),
|
||||
"<font color='red'>" ..
|
||||
translate("Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline! Multiple groups can be set, Haproxy port same one for each group.") ..
|
||||
"\n" .. translate("Note that the node configuration parameters for load balancing must be consistent when use TCP health check type, otherwise it cannot be used normally!") ..
|
||||
|
||||
@@ -220,3 +220,11 @@ listen console
|
||||
f_out:write("\n" .. string.format(str, console_port, (console_user and console_user ~= "" and console_password and console_password ~= "") and "stats auth " .. console_user .. ":" .. console_password or ""))
|
||||
|
||||
f_out:close()
|
||||
|
||||
--passwall内置健康检查URL
|
||||
if health_check_type == "passwall_logic" then
|
||||
local probeUrl = uci:get(appname, "@global_haproxy[0]", "health_probe_url") or "https://www.google.com/generate_204"
|
||||
local f_url = io.open(haproxy_path .. "/Probe_URL", "w")
|
||||
f_url:write(probeUrl)
|
||||
f_url:close()
|
||||
end
|
||||
|
||||
@@ -4,7 +4,17 @@ listen_address=$1
|
||||
listen_port=$2
|
||||
server_address=$3
|
||||
server_port=$4
|
||||
status=$(/usr/bin/curl -I -o /dev/null -skL -x socks5h://${server_address}:${server_port} --connect-timeout 3 --retry 3 -w %{http_code} "https://www.google.com/generate_204")
|
||||
|
||||
probe_file="/tmp/etc/passwall/haproxy/Probe_URL"
|
||||
probeUrl="https://www.google.com/generate_204"
|
||||
if [ -f "$probe_file" ]; then
|
||||
firstLine=$(head -n 1 "$probe_file" | tr -d ' \t')
|
||||
if [ -n "$firstLine" ]; then
|
||||
probeUrl="$firstLine"
|
||||
fi
|
||||
fi
|
||||
|
||||
status=$(/usr/bin/curl -I -o /dev/null -skL -x socks5h://${server_address}:${server_port} --connect-timeout 3 --retry 3 -w %{http_code} "${probeUrl}")
|
||||
case "$status" in
|
||||
204|\
|
||||
200)
|
||||
|
||||
@@ -21,22 +21,22 @@ define Download/geoip
|
||||
HASH:=2c6b6ee15f4593a7b54853c8db5a2b881986ae405122d9fa1427f33c26316ff3
|
||||
endef
|
||||
|
||||
GEOSITE_VER:=20240823035651
|
||||
GEOSITE_VER:=20240826041130
|
||||
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
|
||||
define Download/geosite
|
||||
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
|
||||
URL_FILE:=dlc.dat
|
||||
FILE:=$(GEOSITE_FILE)
|
||||
HASH:=e9197238b1b4e6e6ae9f35dfc40484936093562bef354501981bf8d5e29950da
|
||||
HASH:=f154374d3eb0cafcb1d37d15d97a8db6d57ebf3436e519a6747279a39cf4f7ab
|
||||
endef
|
||||
|
||||
GEOSITE_IRAN_VER:=202408190030
|
||||
GEOSITE_IRAN_VER:=202408260030
|
||||
GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER)
|
||||
define Download/geosite-ir
|
||||
URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/
|
||||
URL_FILE:=iran.dat
|
||||
FILE:=$(GEOSITE_IRAN_FILE)
|
||||
HASH:=43f48f650b5259f82024a995990d2c5b55e79123773ce5c4ca332d8cb24707e4
|
||||
HASH:=d95bd88c33b41514400ced2ec117834dd325c24a46c04e82f8c04ef040648f14
|
||||
endef
|
||||
|
||||
define Package/v2ray-geodata/template
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -703,6 +704,18 @@ namespace ServiceLib.Common
|
||||
return systemHosts;
|
||||
}
|
||||
|
||||
public static string GetExeName(string name)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return $"{name}.exe";
|
||||
}
|
||||
else
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 杂项
|
||||
|
||||
#region TempPath
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace ServiceLib.Handler
|
||||
foreach (Process p in existing)
|
||||
{
|
||||
string? path = p.MainModule?.FileName;
|
||||
if (path == $"{Utils.GetBinPath(vName, it.coreType.ToString())}.exe")
|
||||
if (path == Utils.GetExeName(Utils.GetBinPath(vName, it.coreType.ToString())))
|
||||
{
|
||||
KillProcess(p);
|
||||
}
|
||||
@@ -151,7 +151,7 @@ namespace ServiceLib.Handler
|
||||
string fileName = string.Empty;
|
||||
foreach (string name in coreInfo.coreExes)
|
||||
{
|
||||
string vName = $"{name}.exe";
|
||||
string vName = Utils.GetExeName(name);
|
||||
vName = Utils.GetBinPath(vName, coreInfo.coreType.ToString());
|
||||
if (File.Exists(vName))
|
||||
{
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.gotoolchain || '1.22' }}
|
||||
go-version: ${{ matrix.gotoolchain || '1.23' }}
|
||||
check-latest: true
|
||||
|
||||
- name: Get project dependencies
|
||||
|
||||
Vendored
+1
-1
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '1.23'
|
||||
check-latest: true
|
||||
- name: Restore Cache
|
||||
uses: actions/cache/restore@v4
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
[Project X Channel](https://t.me/projectXtls)
|
||||
|
||||
[Project VLESS](https://t.me/projectVless) (non-Chinese)
|
||||
|
||||
## Installation
|
||||
|
||||
- Linux Script
|
||||
|
||||
@@ -118,6 +118,9 @@ func (w *ServerWorker) handleStatusKeepAlive(meta *FrameMetadata, reader *buf.Bu
|
||||
}
|
||||
|
||||
func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error {
|
||||
// deep-clone outbounds because it is going to be mutated concurrently
|
||||
// (Target and OriginalTarget)
|
||||
ctx = session.ContextCloneOutbounds(ctx)
|
||||
errors.LogInfo(ctx, "received request for ", meta.Target)
|
||||
{
|
||||
msg := &log.AccessMessage{
|
||||
@@ -170,7 +173,7 @@ func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata,
|
||||
b.Release()
|
||||
mb = nil
|
||||
}
|
||||
errors.LogInfoInner(ctx, err,"XUDP hit ", meta.GlobalID)
|
||||
errors.LogInfoInner(ctx, err, "XUDP hit ", meta.GlobalID)
|
||||
}
|
||||
if mb != nil {
|
||||
ctx = session.ContextWithTimeoutOnly(ctx, true)
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
package mux_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/mux"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/pipe"
|
||||
)
|
||||
|
||||
func newLinkPair() (*transport.Link, *transport.Link) {
|
||||
opt := pipe.WithoutSizeLimit()
|
||||
uplinkReader, uplinkWriter := pipe.New(opt)
|
||||
downlinkReader, downlinkWriter := pipe.New(opt)
|
||||
|
||||
uplink := &transport.Link{
|
||||
Reader: uplinkReader,
|
||||
Writer: downlinkWriter,
|
||||
}
|
||||
|
||||
downlink := &transport.Link{
|
||||
Reader: downlinkReader,
|
||||
Writer: uplinkWriter,
|
||||
}
|
||||
|
||||
return uplink, downlink
|
||||
}
|
||||
|
||||
type TestDispatcher struct {
|
||||
OnDispatch func(ctx context.Context, dest net.Destination) (*transport.Link, error)
|
||||
}
|
||||
|
||||
func (d *TestDispatcher) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) {
|
||||
return d.OnDispatch(ctx, dest)
|
||||
}
|
||||
|
||||
func (d *TestDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *TestDispatcher) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *TestDispatcher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*TestDispatcher) Type() interface{} {
|
||||
return routing.DispatcherType()
|
||||
}
|
||||
|
||||
func TestRegressionOutboundLeak(t *testing.T) {
|
||||
originalOutbounds := []*session.Outbound{{}}
|
||||
serverCtx := session.ContextWithOutbounds(context.Background(), originalOutbounds)
|
||||
|
||||
websiteUplink, websiteDownlink := newLinkPair()
|
||||
|
||||
dispatcher := TestDispatcher{
|
||||
OnDispatch: func(ctx context.Context, dest net.Destination) (*transport.Link, error) {
|
||||
// emulate what DefaultRouter.Dispatch does, and mutate something on the context
|
||||
ob := session.OutboundsFromContext(ctx)[0]
|
||||
ob.Target = dest
|
||||
return websiteDownlink, nil
|
||||
},
|
||||
}
|
||||
|
||||
muxServerUplink, muxServerDownlink := newLinkPair()
|
||||
_, err := mux.NewServerWorker(serverCtx, &dispatcher, muxServerUplink)
|
||||
common.Must(err)
|
||||
|
||||
client, err := mux.NewClientWorker(*muxServerDownlink, mux.ClientStrategy{})
|
||||
common.Must(err)
|
||||
|
||||
clientCtx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{
|
||||
Target: net.TCPDestination(net.DomainAddress("www.example.com"), 80),
|
||||
}})
|
||||
|
||||
muxClientUplink, muxClientDownlink := newLinkPair()
|
||||
|
||||
ok := client.Dispatch(clientCtx, muxClientUplink)
|
||||
if !ok {
|
||||
t.Error("failed to dispatch")
|
||||
}
|
||||
|
||||
{
|
||||
b := buf.FromBytes([]byte("hello"))
|
||||
common.Must(muxClientDownlink.Writer.WriteMultiBuffer(buf.MultiBuffer{b}))
|
||||
}
|
||||
|
||||
resMb, err := websiteUplink.Reader.ReadMultiBuffer()
|
||||
common.Must(err)
|
||||
res := resMb.String()
|
||||
if res != "hello" {
|
||||
t.Error("upload: ", res)
|
||||
}
|
||||
|
||||
{
|
||||
b := buf.FromBytes([]byte("world"))
|
||||
common.Must(websiteUplink.Writer.WriteMultiBuffer(buf.MultiBuffer{b}))
|
||||
}
|
||||
|
||||
resMb, err = muxClientDownlink.Reader.ReadMultiBuffer()
|
||||
common.Must(err)
|
||||
res = resMb.String()
|
||||
if res != "world" {
|
||||
t.Error("download: ", res)
|
||||
}
|
||||
|
||||
outbounds := session.OutboundsFromContext(serverCtx)
|
||||
if outbounds[0] != originalOutbounds[0] {
|
||||
t.Error("outbound got reassigned: ", outbounds[0])
|
||||
}
|
||||
|
||||
if outbounds[0].Target.Address != nil {
|
||||
t.Error("outbound target got leaked: ", outbounds[0].Target.String())
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,22 @@ func ContextWithOutbounds(ctx context.Context, outbounds []*Outbound) context.Co
|
||||
return context.WithValue(ctx, outboundSessionKey, outbounds)
|
||||
}
|
||||
|
||||
func ContextCloneOutbounds(ctx context.Context) context.Context {
|
||||
outbounds := OutboundsFromContext(ctx)
|
||||
newOutbounds := make([]*Outbound, len(outbounds))
|
||||
for i, ob := range outbounds {
|
||||
if ob == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// copy outbound by value
|
||||
v := *ob
|
||||
newOutbounds[i] = &v
|
||||
}
|
||||
|
||||
return ContextWithOutbounds(ctx, newOutbounds)
|
||||
}
|
||||
|
||||
func OutboundsFromContext(ctx context.Context) []*Outbound {
|
||||
if outbounds, ok := ctx.Value(outboundSessionKey).([]*Outbound); ok {
|
||||
return outbounds
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ require (
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
|
||||
github.com/vishvananda/netlink v1.2.1
|
||||
github.com/vishvananda/netlink v1.3.0
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.26.0
|
||||
|
||||
+2
-2
@@ -160,8 +160,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/vishvananda/netlink v1.2.1 h1:pfLv/qlJUwOTPvtWREA7c3PI4u81YkqZw1DYhI2HmLA=
|
||||
github.com/vishvananda/netlink v1.2.1/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg=
|
||||
|
||||
@@ -49,9 +49,6 @@ bool ReadConfig() {
|
||||
config_impl->Read("fast_open", &FLAGS_tcp_fastopen);
|
||||
config_impl->Read("fast_open_connect", &FLAGS_tcp_fastopen_connect);
|
||||
|
||||
#ifdef __linux__
|
||||
config_impl->Read("congestion_algorithm", &FLAGS_congestion_algorithm);
|
||||
#endif
|
||||
config_impl->Read("doh_url", &FLAGS_doh_url);
|
||||
config_impl->Read("dot_host", &FLAGS_dot_host);
|
||||
config_impl->Read("connect_timeout", &FLAGS_connect_timeout);
|
||||
@@ -62,6 +59,7 @@ bool ReadConfig() {
|
||||
config_impl->Read("tcp_keep_alive_cnt", &FLAGS_tcp_keep_alive_cnt);
|
||||
config_impl->Read("tcp_keep_alive_idle_timeout", &FLAGS_tcp_keep_alive_idle_timeout);
|
||||
config_impl->Read("tcp_keep_alive_interval", &FLAGS_tcp_keep_alive_interval);
|
||||
config_impl->Read("tcp_congestion_algorithm", &FLAGS_tcp_congestion_algorithm);
|
||||
|
||||
/* optional tls fields */
|
||||
config_impl->Read("cacert", &FLAGS_cacert);
|
||||
@@ -121,9 +119,6 @@ bool SaveConfig() {
|
||||
all_fields_written &= config_impl->Write("fast_open", FLAGS_tcp_fastopen);
|
||||
all_fields_written &= config_impl->Write("fast_open_connect", FLAGS_tcp_fastopen_connect);
|
||||
static_cast<void>(config_impl->Delete("threads"));
|
||||
#ifdef __linux__
|
||||
all_fields_written &= config_impl->Write("congestion_algorithm", FLAGS_congestion_algorithm);
|
||||
#endif
|
||||
all_fields_written &= config_impl->Write("doh_url", FLAGS_doh_url);
|
||||
all_fields_written &= config_impl->Write("dot_host", FLAGS_dot_host);
|
||||
all_fields_written &= config_impl->Write("timeout", FLAGS_connect_timeout);
|
||||
@@ -135,6 +130,7 @@ bool SaveConfig() {
|
||||
all_fields_written &= config_impl->Write("tcp_keep_alive_cnt", FLAGS_tcp_keep_alive_cnt);
|
||||
all_fields_written &= config_impl->Write("tcp_keep_alive_idle_timeout", FLAGS_tcp_keep_alive_idle_timeout);
|
||||
all_fields_written &= config_impl->Write("tcp_keep_alive_interval", FLAGS_tcp_keep_alive_interval);
|
||||
all_fields_written &= config_impl->Write("tcp_congestion_algorithm", FLAGS_tcp_congestion_algorithm);
|
||||
|
||||
all_fields_written &= config_impl->Write("cacert", FLAGS_cacert);
|
||||
all_fields_written &= config_impl->Write("capath", FLAGS_capath);
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
ABSL_FLAG(bool, ipv6_mode, true, "Resolve names to IPv6 addresses");
|
||||
|
||||
ABSL_FLAG(bool, reuse_port, true, "Reuse the listening port");
|
||||
#ifdef __linux__
|
||||
ABSL_FLAG(std::string, congestion_algorithm, "", "TCP Congestion Algorithm");
|
||||
#endif
|
||||
ABSL_FLAG(bool, tcp_fastopen, false, "TCP fastopen");
|
||||
ABSL_FLAG(bool, tcp_fastopen_connect, false, "TCP fastopen connect");
|
||||
ABSL_FLAG(int32_t, connect_timeout, 0, "Connect timeout (in seconds)");
|
||||
@@ -24,6 +21,7 @@ ABSL_FLAG(int32_t,
|
||||
7200,
|
||||
"The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes.");
|
||||
ABSL_FLAG(int32_t, tcp_keep_alive_interval, 75, "The number of seconds between TCP keep-alive probes.");
|
||||
ABSL_FLAG(std::string, tcp_congestion_algorithm, "", "TCP Congestion Algorithm (Linux Only)");
|
||||
ABSL_FLAG(bool, redir_mode, false, "Enable TCP Redir mode support (linux only)");
|
||||
|
||||
ABSL_FLAG(std::string, doh_url, "", "Resolve host names over DoH");
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
ABSL_DECLARE_FLAG(bool, ipv6_mode);
|
||||
|
||||
ABSL_DECLARE_FLAG(bool, reuse_port);
|
||||
#ifdef __linux__
|
||||
ABSL_DECLARE_FLAG(std::string, congestion_algorithm);
|
||||
#endif
|
||||
ABSL_DECLARE_FLAG(bool, tcp_fastopen);
|
||||
ABSL_DECLARE_FLAG(bool, tcp_fastopen_connect);
|
||||
// same with proxy_connect_timeout no need for proxy_read_timeout
|
||||
@@ -25,6 +22,7 @@ ABSL_DECLARE_FLAG(bool, tcp_keep_alive);
|
||||
ABSL_DECLARE_FLAG(int32_t, tcp_keep_alive_cnt);
|
||||
ABSL_DECLARE_FLAG(int32_t, tcp_keep_alive_idle_timeout);
|
||||
ABSL_DECLARE_FLAG(int32_t, tcp_keep_alive_interval);
|
||||
ABSL_DECLARE_FLAG(std::string, tcp_congestion_algorithm);
|
||||
ABSL_DECLARE_FLAG(bool, redir_mode);
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, doh_url);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "core/logging.hpp"
|
||||
#include "core/utils.hpp"
|
||||
#include "gtk/utils.hpp"
|
||||
#include "net/network.hpp"
|
||||
|
||||
OptionDialog::OptionDialog(const std::string& title, GtkWindow* parent, bool modal)
|
||||
: impl_(GTK_DIALOG(gtk_dialog_new_with_buttons(title.c_str(),
|
||||
@@ -27,12 +28,14 @@ OptionDialog::OptionDialog(const std::string& title, GtkWindow* parent, bool mod
|
||||
auto tcp_keep_alive_interval_label = gtk_label_new(_("TCP keep alive interval"));
|
||||
|
||||
auto enable_post_quantum_kyber_label = gtk_label_new(_("Kyber post-quantum key agreement for TLS"));
|
||||
auto tcp_congestion_algorithm = gtk_label_new(_("TCP Congestion Algorithm"));
|
||||
|
||||
gtk_grid_attach(grid, GTK_WIDGET(tcp_keep_alive_label), 0, 0, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(tcp_keep_alive_cnt_label), 0, 1, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(tcp_keep_alive_idle_timeout_label), 0, 2, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(tcp_keep_alive_interval_label), 0, 3, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(enable_post_quantum_kyber_label), 0, 4, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(tcp_congestion_algorithm), 0, 5, 1, 1);
|
||||
|
||||
tcp_keep_alive_ = GTK_CHECK_BUTTON(gtk_check_button_new());
|
||||
tcp_keep_alive_cnt_ = GTK_ENTRY(gtk_entry_new());
|
||||
@@ -40,11 +43,19 @@ OptionDialog::OptionDialog(const std::string& title, GtkWindow* parent, bool mod
|
||||
tcp_keep_alive_interval_ = GTK_ENTRY(gtk_entry_new());
|
||||
enable_post_quantum_kyber_ = GTK_CHECK_BUTTON(gtk_check_button_new());
|
||||
|
||||
algorithms_ = net::GetTCPAvailableCongestionAlgorithms();
|
||||
|
||||
tcp_congestion_algorithm_ = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
|
||||
for (const auto& algorithm : algorithms_) {
|
||||
gtk_combo_box_text_append_text(tcp_congestion_algorithm_, algorithm.c_str());
|
||||
}
|
||||
|
||||
gtk_grid_attach(grid, GTK_WIDGET(tcp_keep_alive_), 1, 0, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(tcp_keep_alive_cnt_), 1, 1, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(tcp_keep_alive_idle_timeout_), 1, 2, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(tcp_keep_alive_interval_), 1, 3, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(enable_post_quantum_kyber_), 1, 4, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(tcp_congestion_algorithm_), 1, 5, 1, 1);
|
||||
|
||||
gtk_widget_set_margin_top(GTK_WIDGET(grid), 12);
|
||||
gtk_widget_set_margin_bottom(GTK_WIDGET(grid), 12);
|
||||
@@ -79,8 +90,8 @@ OptionDialog::OptionDialog(const std::string& title, GtkWindow* parent, bool mod
|
||||
|
||||
g_signal_connect(G_OBJECT(cancel_button_), "clicked", G_CALLBACK(*cancel_callback), this);
|
||||
|
||||
gtk_grid_attach(grid, GTK_WIDGET(okay_button_), 0, 5, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(cancel_button_), 1, 5, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(okay_button_), 0, 6, 1, 1);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(cancel_button_), 1, 6, 1, 1);
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(impl_)), GTK_WIDGET(grid));
|
||||
|
||||
@@ -120,6 +131,19 @@ void OptionDialog::LoadChanges() {
|
||||
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enable_post_quantum_kyber_),
|
||||
absl::GetFlag(FLAGS_enable_post_quantum_kyber));
|
||||
|
||||
auto algorithm = absl::GetFlag(FLAGS_tcp_congestion_algorithm);
|
||||
unsigned int i;
|
||||
for (i = 0; i < std::size(algorithms_); ++i) {
|
||||
if (algorithm == algorithms_[i])
|
||||
break;
|
||||
}
|
||||
|
||||
// first is unset
|
||||
if (i == std::size(algorithms_)) {
|
||||
i = 0;
|
||||
}
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(tcp_congestion_algorithm_), i);
|
||||
}
|
||||
|
||||
bool OptionDialog::OnSave() {
|
||||
@@ -143,5 +167,12 @@ bool OptionDialog::OnSave() {
|
||||
|
||||
absl::SetFlag(&FLAGS_enable_post_quantum_kyber, enable_post_quantum_kyber);
|
||||
|
||||
gchar* algorithm_cstr = gtk_combo_box_text_get_active_text(tcp_congestion_algorithm_);
|
||||
if (algorithm_cstr == nullptr || std::string_view(algorithm_cstr).empty()) {
|
||||
absl::SetFlag(&FLAGS_tcp_congestion_algorithm, std::string());
|
||||
} else {
|
||||
absl::SetFlag(&FLAGS_tcp_congestion_algorithm, algorithm_cstr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class OptionDialog {
|
||||
public:
|
||||
@@ -26,6 +27,8 @@ class OptionDialog {
|
||||
GtkEntry* tcp_keep_alive_idle_timeout_;
|
||||
GtkEntry* tcp_keep_alive_interval_;
|
||||
GtkCheckButton* enable_post_quantum_kyber_;
|
||||
GtkComboBoxText* tcp_congestion_algorithm_;
|
||||
std::vector<std::string> algorithms_;
|
||||
|
||||
GtkButton* okay_button_;
|
||||
GtkButton* cancel_button_;
|
||||
|
||||
+47
-43
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 1.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-07-09 09:55+0800\n"
|
||||
"POT-Creation-Date: 2024-08-26 14:19+0800\n"
|
||||
"PO-Revision-Date: 2023-09-15 11:18+0800\n"
|
||||
"Last-Translator: Chilledheart <keeyou-cn@outlook.com>\n"
|
||||
"Language-Team: Chilledheart <keeyou-cn@outlook.com>\n"
|
||||
@@ -17,170 +17,174 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: option_dialog.cpp:27
|
||||
#: option_dialog.cpp:25
|
||||
msgid "TCP keep alive"
|
||||
msgstr "TCP keep alive"
|
||||
|
||||
#: option_dialog.cpp:28
|
||||
#: option_dialog.cpp:26
|
||||
msgid "The number of TCP keep-alive probes"
|
||||
msgstr "The number of TCP keep-alive probes"
|
||||
|
||||
#: option_dialog.cpp:29
|
||||
#: option_dialog.cpp:27
|
||||
msgid "TCP keep alive after idle"
|
||||
msgstr "TCP keep alive after idle"
|
||||
|
||||
#: option_dialog.cpp:30
|
||||
#: option_dialog.cpp:28
|
||||
msgid "TCP keep alive interval"
|
||||
msgstr "TCP keep alive interval"
|
||||
|
||||
#: option_dialog.cpp:32
|
||||
#: option_dialog.cpp:30
|
||||
msgid "Kyber post-quantum key agreement for TLS"
|
||||
msgstr "Kyber post-quantum key agreement for TLS"
|
||||
|
||||
#: option_dialog.cpp:53
|
||||
#: option_dialog.cpp:32
|
||||
msgid "TCP Congestion Algorithm"
|
||||
msgstr "TCP Congestion Algorithm"
|
||||
|
||||
#: option_dialog.cpp:76
|
||||
msgid "Okay"
|
||||
msgstr "Okay"
|
||||
|
||||
#: option_dialog.cpp:56
|
||||
#: option_dialog.cpp:79
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel"
|
||||
|
||||
#: yass.cpp:234
|
||||
#: yass.cpp:232
|
||||
msgid "Connected with conns: "
|
||||
msgstr "Connected with conns: "
|
||||
|
||||
#: yass.cpp:236
|
||||
#: yass.cpp:234
|
||||
msgid "Connecting"
|
||||
msgstr "Connecting"
|
||||
|
||||
#: yass.cpp:238
|
||||
#: yass.cpp:236
|
||||
msgid "Failed to connect due to "
|
||||
msgstr "Failed to connect due to "
|
||||
|
||||
#: yass.cpp:240
|
||||
#: yass.cpp:238
|
||||
msgid "Disconnecting"
|
||||
msgstr "Disconnecting"
|
||||
|
||||
#: yass.cpp:242
|
||||
#: yass.cpp:240
|
||||
msgid "Disconnected with "
|
||||
msgstr "Disconnected with "
|
||||
|
||||
#: yass_window.cpp:67
|
||||
#: yass_window.cpp:64
|
||||
msgid "File"
|
||||
msgstr "File"
|
||||
|
||||
#: yass_window.cpp:68 yass_window.cpp:311 yass_window.cpp:370
|
||||
#: yass_window.cpp:65 yass_window.cpp:323 yass_window.cpp:383
|
||||
msgid "Option..."
|
||||
msgstr "Option..."
|
||||
|
||||
#: yass_window.cpp:69 yass_window.cpp:312 yass_window.cpp:371
|
||||
#: yass_window.cpp:66 yass_window.cpp:324 yass_window.cpp:384
|
||||
msgid "Exit"
|
||||
msgstr "Exit"
|
||||
|
||||
#: yass_window.cpp:92
|
||||
#: yass_window.cpp:89
|
||||
msgid "Help"
|
||||
msgstr "Help"
|
||||
|
||||
#: yass_window.cpp:93
|
||||
#: yass_window.cpp:90
|
||||
msgid "About..."
|
||||
msgstr "About..."
|
||||
|
||||
#: yass_window.cpp:108
|
||||
#: yass_window.cpp:105
|
||||
msgid "Start"
|
||||
msgstr "Start"
|
||||
|
||||
#: yass_window.cpp:114
|
||||
#: yass_window.cpp:109
|
||||
msgid "Stop"
|
||||
msgstr "Stop"
|
||||
|
||||
#: yass_window.cpp:147
|
||||
#: yass_window.cpp:131
|
||||
msgid "Server Host"
|
||||
msgstr "Server Host"
|
||||
|
||||
#: yass_window.cpp:148
|
||||
#: yass_window.cpp:132
|
||||
msgid "Server SNI"
|
||||
msgstr "Server SNI"
|
||||
|
||||
#: yass_window.cpp:149
|
||||
#: yass_window.cpp:133
|
||||
msgid "Server Port"
|
||||
msgstr "Server Port"
|
||||
|
||||
#: yass_window.cpp:150
|
||||
#: yass_window.cpp:134
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
|
||||
#: yass_window.cpp:151
|
||||
#: yass_window.cpp:135
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
#: yass_window.cpp:152
|
||||
#: yass_window.cpp:136
|
||||
msgid "Cipher/Method"
|
||||
msgstr "Cipher/Method"
|
||||
|
||||
#: yass_window.cpp:153
|
||||
#: yass_window.cpp:137
|
||||
msgid "Local Host"
|
||||
msgstr "Local Host"
|
||||
|
||||
#: yass_window.cpp:154
|
||||
#: yass_window.cpp:138
|
||||
msgid "Local Port"
|
||||
msgstr "Local Port"
|
||||
|
||||
#: yass_window.cpp:155
|
||||
#: yass_window.cpp:139
|
||||
msgid "DNS over HTTPS URL"
|
||||
msgstr "DNS over HTTPS URL"
|
||||
|
||||
#: yass_window.cpp:156
|
||||
#: yass_window.cpp:140
|
||||
msgid "DNS over TLS Host"
|
||||
msgstr "DNS over TLS Host"
|
||||
|
||||
#: yass_window.cpp:157
|
||||
#: yass_window.cpp:141
|
||||
msgid "Limit Rate"
|
||||
msgstr "Limit Rate"
|
||||
|
||||
#: yass_window.cpp:158
|
||||
#: yass_window.cpp:142
|
||||
msgid "Timeout"
|
||||
msgstr "Timeout"
|
||||
|
||||
#: yass_window.cpp:159
|
||||
#: yass_window.cpp:143
|
||||
msgid "Auto Start"
|
||||
msgstr "Auto Start"
|
||||
|
||||
#: yass_window.cpp:160
|
||||
#: yass_window.cpp:144
|
||||
msgid "System Proxy"
|
||||
msgstr "System Proxy"
|
||||
|
||||
#: yass_window.cpp:263
|
||||
#: yass_window.cpp:275
|
||||
msgid "READY"
|
||||
msgstr "READY"
|
||||
|
||||
#: yass_window.cpp:336 yass_window.cpp:369
|
||||
#: yass_window.cpp:348 yass_window.cpp:382
|
||||
msgid "Show"
|
||||
msgstr "Show"
|
||||
|
||||
#: yass_window.cpp:531
|
||||
#: yass_window.cpp:544
|
||||
msgid " tx rate: "
|
||||
msgstr " tx rate: "
|
||||
|
||||
#: yass_window.cpp:534
|
||||
#: yass_window.cpp:547
|
||||
msgid " rx rate: "
|
||||
msgstr " rx rate: "
|
||||
|
||||
#: yass_window.cpp:645
|
||||
#: yass_window.cpp:658
|
||||
msgid "YASS Option"
|
||||
msgstr "YASS Option"
|
||||
|
||||
#: yass_window.cpp:656
|
||||
#: yass_window.cpp:669
|
||||
msgid "Last Change: "
|
||||
msgstr "Last Change: "
|
||||
|
||||
#: yass_window.cpp:659
|
||||
#: yass_window.cpp:672
|
||||
msgid "Enabled Feature: "
|
||||
msgstr "Enabled Feature: "
|
||||
|
||||
#: yass_window.cpp:662
|
||||
#: yass_window.cpp:675
|
||||
msgid "GUI Variant: "
|
||||
msgstr "GUI Variant: "
|
||||
|
||||
#: yass_window.cpp:671
|
||||
#: yass_window.cpp:684
|
||||
msgid "official-site"
|
||||
msgstr "official-site"
|
||||
|
||||
+47
-43
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 1.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-07-09 09:55+0800\n"
|
||||
"POT-Creation-Date: 2024-08-26 14:19+0800\n"
|
||||
"PO-Revision-Date: 2023-09-15 11:26+0800\n"
|
||||
"Last-Translator: Chilledheart <keeyou-cn@outlook.com>\n"
|
||||
"Language-Team: Chilledheart <keeyou-cn@outlook.com>\n"
|
||||
@@ -18,170 +18,174 @@ msgstr ""
|
||||
"X-Generator: Poedit 2.2.4\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: option_dialog.cpp:27
|
||||
#: option_dialog.cpp:25
|
||||
msgid "TCP keep alive"
|
||||
msgstr "TCP 保活"
|
||||
|
||||
#: option_dialog.cpp:28
|
||||
#: option_dialog.cpp:26
|
||||
msgid "The number of TCP keep-alive probes"
|
||||
msgstr "TCP 保活测量数"
|
||||
|
||||
#: option_dialog.cpp:29
|
||||
#: option_dialog.cpp:27
|
||||
msgid "TCP keep alive after idle"
|
||||
msgstr "TCP 在闲置以后的保活次数"
|
||||
|
||||
#: option_dialog.cpp:30
|
||||
#: option_dialog.cpp:28
|
||||
msgid "TCP keep alive interval"
|
||||
msgstr "TCP 保活间隔"
|
||||
|
||||
#: option_dialog.cpp:32
|
||||
#: option_dialog.cpp:30
|
||||
msgid "Kyber post-quantum key agreement for TLS"
|
||||
msgstr "TLS的Kyber后量子密钥协商"
|
||||
|
||||
#: option_dialog.cpp:53
|
||||
#: option_dialog.cpp:32
|
||||
msgid "TCP Congestion Algorithm"
|
||||
msgstr "TCP 拥塞算法"
|
||||
|
||||
#: option_dialog.cpp:76
|
||||
msgid "Okay"
|
||||
msgstr "确认"
|
||||
|
||||
#: option_dialog.cpp:56
|
||||
#: option_dialog.cpp:79
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
#: yass.cpp:234
|
||||
#: yass.cpp:232
|
||||
msgid "Connected with conns: "
|
||||
msgstr "已产生连接: "
|
||||
|
||||
#: yass.cpp:236
|
||||
#: yass.cpp:234
|
||||
msgid "Connecting"
|
||||
msgstr "连接中"
|
||||
|
||||
#: yass.cpp:238
|
||||
#: yass.cpp:236
|
||||
msgid "Failed to connect due to "
|
||||
msgstr "无法连接因为 "
|
||||
|
||||
#: yass.cpp:240
|
||||
#: yass.cpp:238
|
||||
msgid "Disconnecting"
|
||||
msgstr "断开连接中"
|
||||
|
||||
#: yass.cpp:242
|
||||
#: yass.cpp:240
|
||||
msgid "Disconnected with "
|
||||
msgstr "断开连接于服务器 "
|
||||
|
||||
#: yass_window.cpp:67
|
||||
#: yass_window.cpp:64
|
||||
msgid "File"
|
||||
msgstr "文件"
|
||||
|
||||
#: yass_window.cpp:68 yass_window.cpp:311 yass_window.cpp:370
|
||||
#: yass_window.cpp:65 yass_window.cpp:323 yass_window.cpp:383
|
||||
msgid "Option..."
|
||||
msgstr "选项..."
|
||||
|
||||
#: yass_window.cpp:69 yass_window.cpp:312 yass_window.cpp:371
|
||||
#: yass_window.cpp:66 yass_window.cpp:324 yass_window.cpp:384
|
||||
msgid "Exit"
|
||||
msgstr "退出"
|
||||
|
||||
#: yass_window.cpp:92
|
||||
#: yass_window.cpp:89
|
||||
msgid "Help"
|
||||
msgstr "帮助"
|
||||
|
||||
#: yass_window.cpp:93
|
||||
#: yass_window.cpp:90
|
||||
msgid "About..."
|
||||
msgstr "关于..."
|
||||
|
||||
#: yass_window.cpp:108
|
||||
#: yass_window.cpp:105
|
||||
msgid "Start"
|
||||
msgstr "启动"
|
||||
|
||||
#: yass_window.cpp:114
|
||||
#: yass_window.cpp:109
|
||||
msgid "Stop"
|
||||
msgstr "停止"
|
||||
|
||||
#: yass_window.cpp:147
|
||||
#: yass_window.cpp:131
|
||||
msgid "Server Host"
|
||||
msgstr "服务器域名"
|
||||
|
||||
#: yass_window.cpp:148
|
||||
#: yass_window.cpp:132
|
||||
msgid "Server SNI"
|
||||
msgstr "服务器名称指示"
|
||||
|
||||
#: yass_window.cpp:149
|
||||
#: yass_window.cpp:133
|
||||
msgid "Server Port"
|
||||
msgstr "服务器端口号"
|
||||
|
||||
#: yass_window.cpp:150
|
||||
#: yass_window.cpp:134
|
||||
msgid "Username"
|
||||
msgstr "用户名"
|
||||
|
||||
#: yass_window.cpp:151
|
||||
#: yass_window.cpp:135
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
#: yass_window.cpp:152
|
||||
#: yass_window.cpp:136
|
||||
msgid "Cipher/Method"
|
||||
msgstr "加密方式"
|
||||
|
||||
#: yass_window.cpp:153
|
||||
#: yass_window.cpp:137
|
||||
msgid "Local Host"
|
||||
msgstr "本地域名"
|
||||
|
||||
#: yass_window.cpp:154
|
||||
#: yass_window.cpp:138
|
||||
msgid "Local Port"
|
||||
msgstr "本地端口号"
|
||||
|
||||
#: yass_window.cpp:155
|
||||
#: yass_window.cpp:139
|
||||
msgid "DNS over HTTPS URL"
|
||||
msgstr "基于 HTTPS 的 DNS (DoH) URL"
|
||||
|
||||
#: yass_window.cpp:156
|
||||
#: yass_window.cpp:140
|
||||
msgid "DNS over TLS Host"
|
||||
msgstr "基于 TLS 的 DNS (DoT) 域名"
|
||||
|
||||
#: yass_window.cpp:157
|
||||
#: yass_window.cpp:141
|
||||
msgid "Limit Rate"
|
||||
msgstr "限制速率"
|
||||
|
||||
#: yass_window.cpp:158
|
||||
#: yass_window.cpp:142
|
||||
msgid "Timeout"
|
||||
msgstr "超时时间"
|
||||
|
||||
#: yass_window.cpp:159
|
||||
#: yass_window.cpp:143
|
||||
msgid "Auto Start"
|
||||
msgstr "随系统自启动"
|
||||
|
||||
#: yass_window.cpp:160
|
||||
#: yass_window.cpp:144
|
||||
msgid "System Proxy"
|
||||
msgstr "系统代理"
|
||||
|
||||
#: yass_window.cpp:263
|
||||
#: yass_window.cpp:275
|
||||
msgid "READY"
|
||||
msgstr "就绪"
|
||||
|
||||
#: yass_window.cpp:336 yass_window.cpp:369
|
||||
#: yass_window.cpp:348 yass_window.cpp:382
|
||||
msgid "Show"
|
||||
msgstr "唤醒"
|
||||
|
||||
#: yass_window.cpp:531
|
||||
#: yass_window.cpp:544
|
||||
msgid " tx rate: "
|
||||
msgstr " 上传速率: "
|
||||
|
||||
#: yass_window.cpp:534
|
||||
#: yass_window.cpp:547
|
||||
msgid " rx rate: "
|
||||
msgstr " 下载速率: "
|
||||
|
||||
#: yass_window.cpp:645
|
||||
#: yass_window.cpp:658
|
||||
msgid "YASS Option"
|
||||
msgstr "YASS 选项"
|
||||
|
||||
#: yass_window.cpp:656
|
||||
#: yass_window.cpp:669
|
||||
msgid "Last Change: "
|
||||
msgstr "最后改动: "
|
||||
|
||||
#: yass_window.cpp:659
|
||||
#: yass_window.cpp:672
|
||||
msgid "Enabled Feature: "
|
||||
msgstr "启用功能: "
|
||||
|
||||
#: yass_window.cpp:662
|
||||
#: yass_window.cpp:675
|
||||
msgid "GUI Variant: "
|
||||
msgstr "图形版本: "
|
||||
|
||||
#: yass_window.cpp:671
|
||||
#: yass_window.cpp:684
|
||||
msgid "official-site"
|
||||
msgstr "官方网站"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user