diff --git a/.github/update.log b/.github/update.log index 1294170851..f08d9d5458 100644 --- a/.github/update.log +++ b/.github/update.log @@ -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 diff --git a/clash-meta/.github/workflows/build.yml b/clash-meta/.github/workflows/build.yml index 94854f2748..b62e982000 100644 --- a/clash-meta/.github/workflows/build.yml +++ b/clash-meta/.github/workflows/build.yml @@ -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: | diff --git a/clash-meta/adapter/outbound/hysteria.go b/clash-meta/adapter/outbound/hysteria.go index dacffd106d..ccab16c12a 100644 --- a/clash-meta/adapter/outbound/hysteria.go +++ b/clash-meta/adapter/outbound/hysteria.go @@ -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 { diff --git a/clash-meta/adapter/outbound/hysteria2.go b/clash-meta/adapter/outbound/hysteria2.go index b8abf39cc2..c1a255a766 100644 --- a/clash-meta/adapter/outbound/hysteria2.go +++ b/clash-meta/adapter/outbound/hysteria2.go @@ -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) { diff --git a/clash-meta/adapter/outbound/hysteria2_test.go b/clash-meta/adapter/outbound/hysteria2_test.go new file mode 100644 index 0000000000..de7d82271d --- /dev/null +++ b/clash-meta/adapter/outbound/hysteria2_test.go @@ -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") + } +} diff --git a/clash-meta/adapter/outbound/hysteria_test.go b/clash-meta/adapter/outbound/hysteria_test.go new file mode 100644 index 0000000000..f2297c6035 --- /dev/null +++ b/clash-meta/adapter/outbound/hysteria_test.go @@ -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") + } +} diff --git a/clash-meta/adapter/outbound/wireguard.go b/clash-meta/adapter/outbound/wireguard.go index 2e34dd83cd..0382debb24 100644 --- a/clash-meta/adapter/outbound/wireguard.go +++ b/clash-meta/adapter/outbound/wireguard.go @@ -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) } diff --git a/clash-meta/adapter/outbound/wireguard_test.go b/clash-meta/adapter/outbound/wireguard_test.go index 20dbdbdd6b..2248bb7b1f 100644 --- a/clash-meta/adapter/outbound/wireguard_test.go +++ b/clash-meta/adapter/outbound/wireguard_test.go @@ -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 diff --git a/clash-meta/go.mod b/clash-meta/go.mod index b0c1b3dd96..dbfa9ba17c 100644 --- a/clash-meta/go.mod +++ b/clash-meta/go.mod @@ -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 diff --git a/clash-meta/go.sum b/clash-meta/go.sum index 8194b68adc..1bb4b5ba43 100644 --- a/clash-meta/go.sum +++ b/clash-meta/go.sum @@ -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= diff --git a/clash-meta/hub/executor/executor.go b/clash-meta/hub/executor/executor.go index d54d55b752..97072ec88d 100644 --- a/clash-meta/hub/executor/executor.go +++ b/clash-meta/hub/executor/executor.go @@ -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) } diff --git a/clash-meta/transport/hysteria/core/client.go b/clash-meta/transport/hysteria/core/client.go index 60db8fdf45..782948c018 100644 --- a/clash-meta/transport/hysteria/core/client.go +++ b/clash-meta/transport/hysteria/core/client.go @@ -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 } diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index 2e04bf4f7c..a5befb9961 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -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]] diff --git a/clash-nyanpasu/backend/tauri-plugin-deep-link/src/linux.rs b/clash-nyanpasu/backend/tauri-plugin-deep-link/src/linux.rs index 13778b6c8e..8104cdaf00 100644 --- a/clash-nyanpasu/backend/tauri-plugin-deep-link/src/linux.rs +++ b/clash-nyanpasu/backend/tauri-plugin-deep-link/src/linux.rs @@ -18,76 +18,79 @@ pub fn register(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::>() + .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(()) } diff --git a/clash-nyanpasu/backend/tauri-plugin-deep-link/src/windows.rs b/clash-nyanpasu/backend/tauri-plugin-deep-link/src/windows.rs index 27cf83c0db..15e5730a10 100644 --- a/clash-nyanpasu/backend/tauri-plugin-deep-link/src/windows.rs +++ b/clash-nyanpasu/backend/tauri-plugin-deep-link/src/windows.rs @@ -1,5 +1,4 @@ use std::{ - os::windows::thread, path::Path, sync::atomic::{AtomicU16, Ordering}, }; diff --git a/clash-nyanpasu/backend/tauri/Cargo.toml b/clash-nyanpasu/backend/tauri/Cargo.toml index d7f6cc785e..1115b5cabf 100644 --- a/clash-nyanpasu/backend/tauri/Cargo.toml +++ b/clash-nyanpasu/backend/tauri/Cargo.toml @@ -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 diff --git a/clash-nyanpasu/backend/tauri/icons/tray-icon.png b/clash-nyanpasu/backend/tauri/icons/tray-icon.png index 5108f89c7d..0e3f9d940b 100644 Binary files a/clash-nyanpasu/backend/tauri/icons/tray-icon.png and b/clash-nyanpasu/backend/tauri/icons/tray-icon.png differ diff --git a/clash-nyanpasu/backend/tauri/icons/win-tray-icon.png b/clash-nyanpasu/backend/tauri/icons/win-tray-icon.png index 5fb7c27aab..2f3d50e8db 100644 Binary files a/clash-nyanpasu/backend/tauri/icons/win-tray-icon.png and b/clash-nyanpasu/backend/tauri/icons/win-tray-icon.png differ diff --git a/clash-nyanpasu/backend/tauri/src/core/clash/proxies.rs b/clash-nyanpasu/backend/tauri/src/core/clash/proxies.rs index 0d68c3ccb8..5b3ee41d6a 100644 --- a/clash-nyanpasu/backend/tauri/src/core/clash/proxies.rs +++ b/clash-nyanpasu/backend/tauri/src/core/clash/proxies.rs @@ -73,7 +73,7 @@ impl Proxies { #[instrument] pub async fn fetch() -> Result { 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 diff --git a/clash-nyanpasu/backend/tauri/templates/installer.nsi b/clash-nyanpasu/backend/tauri/templates/installer.nsi index 035ea700dd..8da9d07d5e 100644 --- a/clash-nyanpasu/backend/tauri/templates/installer.nsi +++ b/clash-nyanpasu/backend/tauri/templates/installer.nsi @@ -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 diff --git a/clash-nyanpasu/frontend/nyanpasu/src/assets/image/logo-white.svg b/clash-nyanpasu/frontend/nyanpasu/src/assets/image/logo-white.svg new file mode 100644 index 0000000000..54db2849d8 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/assets/image/logo-white.svg @@ -0,0 +1,36 @@ + + + + + + + + + diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-provider.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-provider.tsx index ae42dd16cd..6c06748af4 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-provider.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/logs/log-provider.tsx @@ -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; }; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/profiles.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/profiles.tsx index ef146b7ee1..9d5f8b460c 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/profiles.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/profiles.tsx @@ -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={ -
+
setRuntimeConfigViewerOpen(false)} /> { setRuntimeConfigViewerOpen(true); }} > - + - {Object.entries(getCurrentMode).map(([key, value], index) => ( + {Object.entries(getCurrentMode).map(([key, enabled]) => ( ))} diff --git a/clash-nyanpasu/frontend/nyanpasu/tailwind.config.mjs b/clash-nyanpasu/frontend/nyanpasu/tailwind.config.mjs index 1c8f0fd30f..e7617f0090 100644 --- a/clash-nyanpasu/frontend/nyanpasu/tailwind.config.mjs +++ b/clash-nyanpasu/frontend/nyanpasu/tailwind.config.mjs @@ -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 */ diff --git a/clash-nyanpasu/frontend/ui/package.json b/clash-nyanpasu/frontend/ui/package.json index a95d26125a..9d815e89c2 100644 --- a/clash-nyanpasu/frontend/ui/package.json +++ b/clash-nyanpasu/frontend/ui/package.json @@ -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" diff --git a/clash-nyanpasu/frontend/ui/src/hooks/use-breakpoint.ts b/clash-nyanpasu/frontend/ui/src/hooks/use-breakpoint.ts index bbfbc71f9d..31253de400 100644 --- a/clash-nyanpasu/frontend/ui/src/hooks/use-breakpoint.ts +++ b/clash-nyanpasu/frontend/ui/src/hooks/use-breakpoint.ts @@ -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"; diff --git a/clash-nyanpasu/frontend/ui/src/materialYou/components/baseDialog/index.tsx b/clash-nyanpasu/frontend/ui/src/materialYou/components/baseDialog/index.tsx index 1d53b04b06..48893e04ba 100644 --- a/clash-nyanpasu/frontend/ui/src/materialYou/components/baseDialog/index.tsx +++ b/clash-nyanpasu/frontend/ui/src/materialYou/components/baseDialog/index.tsx @@ -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) { diff --git a/clash-nyanpasu/frontend/ui/src/materialYou/components/lazyImage/index.tsx b/clash-nyanpasu/frontend/ui/src/materialYou/components/lazyImage/index.tsx index c6327bedbc..e13977735d 100644 --- a/clash-nyanpasu/frontend/ui/src/materialYou/components/lazyImage/index.tsx +++ b/clash-nyanpasu/frontend/ui/src/materialYou/components/lazyImage/index.tsx @@ -5,7 +5,11 @@ export interface LazyImageProps extends React.ImgHTMLAttributes { 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) {
setLoading(false)} - className={cn(props.className, loading ? "hidden" : "inline-block")} + className={cn(className, loading ? "hidden" : "inline-block")} /> ); diff --git a/clash-nyanpasu/frontend/ui/src/materialYou/createTheme.ts b/clash-nyanpasu/frontend/ui/src/materialYou/createTheme.ts index df2e143939..1c60c15c50 100644 --- a/clash-nyanpasu/frontend/ui/src/materialYou/createTheme.ts +++ b/clash-nyanpasu/frontend/ui/src/materialYou/createTheme.ts @@ -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), diff --git a/clash-nyanpasu/frontend/ui/src/materialYou/themeComponents/MuiButtonGroup.ts b/clash-nyanpasu/frontend/ui/src/materialYou/themeComponents/MuiButtonGroup.ts index 99c4f90e33..17e6847c7f 100644 --- a/clash-nyanpasu/frontend/ui/src/materialYou/themeComponents/MuiButtonGroup.ts +++ b/clash-nyanpasu/frontend/ui/src/materialYou/themeComponents/MuiButtonGroup.ts @@ -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["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["MuiButtonGroup"] = { borderBottomRightRadius: 48, "&.MuiButton-sizeSmall": { - paddingRight: "14px", + paddingRight: "1.5em", }, "&.MuiButton-sizeMedium": { @@ -39,4 +56,4 @@ export const MuiButtonGroup: Components["MuiButtonGroup"] = { }, }, }, -}; +} satisfies Components["MuiButtonGroup"]; diff --git a/clash-nyanpasu/frontend/ui/src/materialYou/themeConsts.mjs b/clash-nyanpasu/frontend/ui/src/materialYou/themeConsts.mjs new file mode 100644 index 0000000000..cfd1df8366 --- /dev/null +++ b/clash-nyanpasu/frontend/ui/src/materialYou/themeConsts.mjs @@ -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, + }, +}; diff --git a/clash-nyanpasu/frontend/ui/tsconfig.json b/clash-nyanpasu/frontend/ui/tsconfig.json index 84810c960d..9298da0821 100644 --- a/clash-nyanpasu/frontend/ui/tsconfig.json +++ b/clash-nyanpasu/frontend/ui/tsconfig.json @@ -4,7 +4,7 @@ "target": "ESNext", "useDefineForClassFields": true, "lib": ["DOM", "DOM.Iterable", "ESNext"], - "allowJs": false, + "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 6d6db41293..e8a661b2cf 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -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" } diff --git a/clash-nyanpasu/package.json b/clash-nyanpasu/package.json index 15bb84c8b3..2c2ebb4911 100644 --- a/clash-nyanpasu/package.json +++ b/clash-nyanpasu/package.json @@ -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", diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 94e2162fee..15415a66b3 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -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 diff --git a/echo/cf-workers/ws/.prettierrc b/echo/.prettierrc similarity index 75% rename from echo/cf-workers/ws/.prettierrc rename to echo/.prettierrc index 5c7b5d3c7a..a5fd757c8c 100644 --- a/echo/cf-workers/ws/.prettierrc +++ b/echo/.prettierrc @@ -2,5 +2,5 @@ "printWidth": 140, "singleQuote": true, "semi": true, - "useTabs": true -} + "useTabs": false +} \ No newline at end of file diff --git a/echo/internal/cmgr/cmgr.go b/echo/internal/cmgr/cmgr.go index 18b05f2fa6..859eca294b 100644 --- a/echo/internal/cmgr/cmgr.go +++ b/echo/internal/cmgr/cmgr.go @@ -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 +} diff --git a/echo/internal/cmgr/metric_sync.go b/echo/internal/cmgr/metric_sync.go index 47137f7a5d..b25ba8e30b 100644 --- a/echo/internal/cmgr/metric_sync.go +++ b/echo/internal/cmgr/metric_sync.go @@ -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) } } diff --git a/echo/internal/config/config.go b/echo/internal/config/config.go index 0de55fa2c8..abce8cc6bd 100644 --- a/echo/internal/config/config.go +++ b/echo/internal/config/config.go @@ -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"` -} diff --git a/echo/internal/conn/relay_conn.go b/echo/internal/conn/relay_conn.go index 6415ba5dc3..cde0997d94 100644 --- a/echo/internal/conn/relay_conn.go +++ b/echo/internal/conn/relay_conn.go @@ -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 diff --git a/echo/internal/relay/conf/cfg.go b/echo/internal/relay/conf/cfg.go index b387bf396b..2df1da462c 100644 --- a/echo/internal/relay/conf/cfg.go +++ b/echo/internal/relay/conf/cfg.go @@ -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 } diff --git a/echo/internal/relay/server.go b/echo/internal/relay/server.go index 799c2dab28..2681a78f56 100644 --- a/echo/internal/relay/server.go +++ b/echo/internal/relay/server.go @@ -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) } diff --git a/echo/internal/web/handlers.go b/echo/internal/web/handlers.go index dc29ab62f9..8e59dff920 100644 --- a/echo/internal/web/handlers.go +++ b/echo/internal/web/handlers.go @@ -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) +} diff --git a/echo/internal/web/server.go b/echo/internal/web/server.go index 300f2cb8a2..18673a1e97 100644 --- a/echo/internal/web/server.go +++ b/echo/internal/web/server.go @@ -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 } diff --git a/echo/internal/web/templates/index.html b/echo/internal/web/templates/index.html index 1c1293f43e..9c01df9304 100644 --- a/echo/internal/web/templates/index.html +++ b/echo/internal/web/templates/index.html @@ -1,167 +1,125 @@ - - - - - - - Ehco - - - -
-
-
- -

- ehco is a network relay tool and a typo :) -

- -
-
-

- Build Info -

-
-
-
-
    -
  • Version: {{.Version}}
  • -
  • GitBranch: {{.GitBranch}}
  • -
  • GitRevision: {{.GitRevision}}
  • -
  • BuildTime: {{.BuildTime}}
  • -
  • StartTime: {{.StartTime}}
  • -
-
-
-
- -
-
-

- Quick Links -

-
-
-
- -
-
-
- - {{ if .SubConfigs }} -
-
-

- Clash Providers -

-
-
-
- - {{ end }} -
-
-
- {{ end }} - -
- -
+ + Ehco Web + + + + + + + + + + + +
+
+
+

Ehco Relay

+
+
+ +
+
+

Build Info

+
+
+
+
    +
  • Version: {{.Version}}
  • +
  • GitBranch: {{.GitBranch}}
  • +
  • GitRevision: {{.GitRevision}}
  • +
  • BuildTime: {{.BuildTime}}
  • +
  • StartTime: {{.StartTime}}
  • +
+
-
- -
-
-
- - + +
+ +
+
+

Quick Links

+
+ +
+
+
+ + {{template "metrics.html"}} +
+
+ + +
+ +
+ + + + diff --git a/echo/internal/web/templates/metrics.html b/echo/internal/web/templates/metrics.html new file mode 100644 index 0000000000..8900afc11c --- /dev/null +++ b/echo/internal/web/templates/metrics.html @@ -0,0 +1,284 @@ +
+
+

Node Metrics

+
+ +
+
+
+
+
+
+

CPU

+ +
+
+

Memory

+ +
+
+

Disk

+ +
+ +
+

Network

+ +
+
+

Ping

+ +
+
+
+
+ + +
diff --git a/echo/internal/web/templates/rule_list.html b/echo/internal/web/templates/rule_list.html index dae01e5775..3881f56208 100644 --- a/echo/internal/web/templates/rule_list.html +++ b/echo/internal/web/templates/rule_list.html @@ -1,86 +1,79 @@ - - - - - - - - Rules - - -
-
-

Rules

- - - - - - - - - - - - - {{range .Configs}} - - - - - - - - - {{end}} - -
LabelListenListen TypeTransport TypeRemoteActions
{{.Label}}{{.Listen}}{{.ListenType}}{{.TransportType}}{{.GetTCPRemotes}} - -
-
-
+ + + + + + + + Rules + + +
+
+

Rules

+ + + + + + + + + + + + + {{range .Configs}} + + + + + + + + + {{end}} + +
LabelListenListen TypeTransport TypeRemoteActions
{{.Label}}{{.Listen}}{{.ListenType}}{{.TransportType}}{{.Remotes}} + +
+
+
- - + }, + 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 + }, + }); + } + + diff --git a/echo/pkg/metric_reader/reader.go b/echo/pkg/metric_reader/reader.go index 7e8c72a960..2b1c7564b0 100644 --- a/echo/pkg/metric_reader/reader.go +++ b/echo/pkg/metric_reader/reader.go @@ -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 } diff --git a/echo/pkg/metric_reader/types.go b/echo/pkg/metric_reader/types.go index 3f691511d6..1195a7ceaf 100644 --- a/echo/pkg/metric_reader/types.go +++ b/echo/pkg/metric_reader/types.go @@ -29,7 +29,7 @@ type NodeMetrics struct { // ping PingMetrics []PingMetric `json:"ping_metrics"` - syncTime time.Time + SyncTime time.Time } type PingMetric struct { diff --git a/mieru/README.md b/mieru/README.md index 21f96fb029..be909e415a 100644 --- a/mieru/README.md +++ b/mieru/README.md @@ -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 diff --git a/mieru/README.zh_CN.md b/mieru/README.zh_CN.md index c78ceec621..9fcbf99704 100644 --- a/mieru/README.zh_CN.md +++ b/mieru/README.zh_CN.md @@ -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) ## 分享 diff --git a/mieru/docs/compile.md b/mieru/docs/compile.md new file mode 100644 index 0000000000..8e6e6cd6a6 --- /dev/null +++ b/mieru/docs/compile.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.** diff --git a/mieru/docs/compile.zh_CN.md b/mieru/docs/compile.zh_CN.md new file mode 100644 index 0000000000..d67dc900db --- /dev/null +++ b/mieru/docs/compile.zh_CN.md @@ -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 之外的操作系统中运行。** diff --git a/mieru/pkg/congestion/bbr_sender.go b/mieru/pkg/congestion/bbr_sender.go index 5c4dcc6988..e2dba78fef 100644 --- a/mieru/pkg/congestion/bbr_sender.go +++ b/mieru/pkg/congestion/bbr_sender.go @@ -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] } } diff --git a/mieru/pkg/congestion/pacer.go b/mieru/pkg/congestion/pacer.go index 8093403999..ab32f4c6b9 100644 --- a/mieru/pkg/congestion/pacer.go +++ b/mieru/pkg/congestion/pacer.go @@ -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() { diff --git a/mieru/pkg/congestion/pacer_test.go b/mieru/pkg/congestion/pacer_test.go new file mode 100644 index 0000000000..2e909ddbc4 --- /dev/null +++ b/mieru/pkg/congestion/pacer_test.go @@ -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 . + +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) + } +} diff --git a/mieru/pkg/protocolv2/session.go b/mieru/pkg/protocolv2/session.go index 34a6fe1cb5..8787ed586f 100644 --- a/mieru/pkg/protocolv2/session.go +++ b/mieru/pkg/protocolv2/session.go @@ -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. diff --git a/mihomo/.github/workflows/build.yml b/mihomo/.github/workflows/build.yml index 94854f2748..b62e982000 100644 --- a/mihomo/.github/workflows/build.yml +++ b/mihomo/.github/workflows/build.yml @@ -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: | diff --git a/mihomo/adapter/outbound/hysteria.go b/mihomo/adapter/outbound/hysteria.go index dacffd106d..ccab16c12a 100644 --- a/mihomo/adapter/outbound/hysteria.go +++ b/mihomo/adapter/outbound/hysteria.go @@ -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 { diff --git a/mihomo/adapter/outbound/hysteria2.go b/mihomo/adapter/outbound/hysteria2.go index b8abf39cc2..c1a255a766 100644 --- a/mihomo/adapter/outbound/hysteria2.go +++ b/mihomo/adapter/outbound/hysteria2.go @@ -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) { diff --git a/mihomo/adapter/outbound/hysteria2_test.go b/mihomo/adapter/outbound/hysteria2_test.go new file mode 100644 index 0000000000..de7d82271d --- /dev/null +++ b/mihomo/adapter/outbound/hysteria2_test.go @@ -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") + } +} diff --git a/mihomo/adapter/outbound/hysteria_test.go b/mihomo/adapter/outbound/hysteria_test.go new file mode 100644 index 0000000000..f2297c6035 --- /dev/null +++ b/mihomo/adapter/outbound/hysteria_test.go @@ -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") + } +} diff --git a/mihomo/adapter/outbound/wireguard.go b/mihomo/adapter/outbound/wireguard.go index 2e34dd83cd..0382debb24 100644 --- a/mihomo/adapter/outbound/wireguard.go +++ b/mihomo/adapter/outbound/wireguard.go @@ -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) } diff --git a/mihomo/adapter/outbound/wireguard_test.go b/mihomo/adapter/outbound/wireguard_test.go index 20dbdbdd6b..2248bb7b1f 100644 --- a/mihomo/adapter/outbound/wireguard_test.go +++ b/mihomo/adapter/outbound/wireguard_test.go @@ -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 diff --git a/mihomo/go.mod b/mihomo/go.mod index b0c1b3dd96..dbfa9ba17c 100644 --- a/mihomo/go.mod +++ b/mihomo/go.mod @@ -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 diff --git a/mihomo/go.sum b/mihomo/go.sum index 8194b68adc..1bb4b5ba43 100644 --- a/mihomo/go.sum +++ b/mihomo/go.sum @@ -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= diff --git a/mihomo/hub/executor/executor.go b/mihomo/hub/executor/executor.go index d54d55b752..97072ec88d 100644 --- a/mihomo/hub/executor/executor.go +++ b/mihomo/hub/executor/executor.go @@ -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) } diff --git a/mihomo/transport/hysteria/core/client.go b/mihomo/transport/hysteria/core/client.go index 60db8fdf45..782948c018 100644 --- a/mihomo/transport/hysteria/core/client.go +++ b/mihomo/transport/hysteria/core/client.go @@ -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 } diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua index a02f7846e6..effc8fa2d3 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua @@ -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"), "" .. 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!") .. diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/haproxy.lua b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/haproxy.lua index 3e794b578c..67366ae1f3 100644 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/haproxy.lua +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/haproxy.lua @@ -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 diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh index 870ffb575f..dcf2879af5 100755 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh @@ -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) diff --git a/shadowsocks-rust/Cargo.lock b/shadowsocks-rust/Cargo.lock index abef076062..0a0ee17529 100644 --- a/shadowsocks-rust/Cargo.lock +++ b/shadowsocks-rust/Cargo.lock @@ -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]] diff --git a/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml b/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml index 9ab209b952..287965b2f8 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml +++ b/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml @@ -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 } diff --git a/sing-box/outbound/wireguard.go b/sing-box/outbound/wireguard.go index 7805e165a1..d34023653a 100644 --- a/sing-box/outbound/wireguard.go +++ b/sing-box/outbound/wireguard.go @@ -180,7 +180,6 @@ func (w *WireGuard) Close() error { if w.pauseCallback != nil { w.pauseManager.UnregisterCallback(w.pauseCallback) } - w.tunDevice.Close() return nil } diff --git a/sing-box/transport/wireguard/device_stack.go b/sing-box/transport/wireguard/device_stack.go index 7f57b7c73a..d5770419e2 100644 --- a/sing-box/transport/wireguard/device_stack.go +++ b/sing-box/transport/wireguard/device_stack.go @@ -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 } diff --git a/sing-box/transport/wireguard/device_system.go b/sing-box/transport/wireguard/device_system.go index 49acc5b90e..2c16c53dfc 100644 --- a/sing-box/transport/wireguard/device_system.go +++ b/sing-box/transport/wireguard/device_system.go @@ -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() } diff --git a/small/luci-app-bypass/luasrc/controller/bypass.lua b/small/luci-app-bypass/luasrc/controller/bypass.lua index dcb971386d..7cabd3e74c 100644 --- a/small/luci-app-bypass/luasrc/controller/bypass.lua +++ b/small/luci-app-bypass/luasrc/controller/bypass.lua @@ -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) diff --git a/small/luci-app-bypass/luasrc/view/bypass/status_bottom.htm b/small/luci-app-bypass/luasrc/view/bypass/status_bottom.htm index ab695367cd..207b6b0693 100644 --- a/small/luci-app-bypass/luasrc/view/bypass/status_bottom.htm +++ b/small/luci-app-bypass/luasrc/view/bypass/status_bottom.htm @@ -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 + "
" + data.country; + document.querySelector(".status-info").innerHTML = data.ip + "
" + 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 => { diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua index a02f7846e6..effc8fa2d3 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua @@ -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"), "" .. 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!") .. diff --git a/small/luci-app-passwall/root/usr/share/passwall/haproxy.lua b/small/luci-app-passwall/root/usr/share/passwall/haproxy.lua index 3e794b578c..67366ae1f3 100644 --- a/small/luci-app-passwall/root/usr/share/passwall/haproxy.lua +++ b/small/luci-app-passwall/root/usr/share/passwall/haproxy.lua @@ -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 diff --git a/small/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh b/small/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh index 870ffb575f..dcf2879af5 100755 --- a/small/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh +++ b/small/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh @@ -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) diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index 9067aa194a..5b5ce7db5d 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -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 diff --git a/v2rayn/v2rayN/ServiceLib/Common/Utils.cs b/v2rayn/v2rayN/ServiceLib/Common/Utils.cs index 1f90e0cbc2..968de934ca 100644 --- a/v2rayn/v2rayN/ServiceLib/Common/Utils.cs +++ b/v2rayn/v2rayN/ServiceLib/Common/Utils.cs @@ -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 diff --git a/v2rayn/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayn/v2rayN/ServiceLib/Handler/CoreHandler.cs index 6eddc956d9..78185563ed 100644 --- a/v2rayn/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayn/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -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)) { diff --git a/xray-core/.github/workflows/release.yml b/xray-core/.github/workflows/release.yml index d044a5c165..77e668b49c 100644 --- a/xray-core/.github/workflows/release.yml +++ b/xray-core/.github/workflows/release.yml @@ -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 diff --git a/xray-core/.github/workflows/test.yml b/xray-core/.github/workflows/test.yml index b2d91b0917..1e531d527a 100644 --- a/xray-core/.github/workflows/test.yml +++ b/xray-core/.github/workflows/test.yml @@ -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 diff --git a/xray-core/README.md b/xray-core/README.md index 026d149115..9485f944c9 100644 --- a/xray-core/README.md +++ b/xray-core/README.md @@ -22,6 +22,8 @@ [Project X Channel](https://t.me/projectXtls) +[Project VLESS](https://t.me/projectVless) (non-Chinese) + ## Installation - Linux Script diff --git a/xray-core/common/mux/server.go b/xray-core/common/mux/server.go index 5a4e9974d9..480175ba06 100644 --- a/xray-core/common/mux/server.go +++ b/xray-core/common/mux/server.go @@ -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) diff --git a/xray-core/common/mux/server_test.go b/xray-core/common/mux/server_test.go new file mode 100644 index 0000000000..4158bf46f1 --- /dev/null +++ b/xray-core/common/mux/server_test.go @@ -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()) + } +} diff --git a/xray-core/common/session/context.go b/xray-core/common/session/context.go index 3fed0151b8..b7af69cc35 100644 --- a/xray-core/common/session/context.go +++ b/xray-core/common/session/context.go @@ -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 diff --git a/xray-core/go.mod b/xray-core/go.mod index 874d26d358..b234a1372f 100644 --- a/xray-core/go.mod +++ b/xray-core/go.mod @@ -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 diff --git a/xray-core/go.sum b/xray-core/go.sum index 2942ecb8c6..1847c4b9f5 100644 --- a/xray-core/go.sum +++ b/xray-core/go.sum @@ -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= diff --git a/yass/src/config/config.cpp b/yass/src/config/config.cpp index ffb40728c7..9e88b11a39 100644 --- a/yass/src/config/config.cpp +++ b/yass/src/config/config.cpp @@ -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(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); diff --git a/yass/src/config/config_network.cpp b/yass/src/config/config_network.cpp index a36fed6629..e00fd278ce 100644 --- a/yass/src/config/config_network.cpp +++ b/yass/src/config/config_network.cpp @@ -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"); diff --git a/yass/src/config/config_network.hpp b/yass/src/config/config_network.hpp index 71f4268588..9ca21d0571 100644 --- a/yass/src/config/config_network.hpp +++ b/yass/src/config/config_network.hpp @@ -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); diff --git a/yass/src/gtk/option_dialog.cpp b/yass/src/gtk/option_dialog.cpp index d8e5305717..f44286e867 100644 --- a/yass/src/gtk/option_dialog.cpp +++ b/yass/src/gtk/option_dialog.cpp @@ -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; } diff --git a/yass/src/gtk/option_dialog.hpp b/yass/src/gtk/option_dialog.hpp index 3ccc4acffd..a4b8513901 100644 --- a/yass/src/gtk/option_dialog.hpp +++ b/yass/src/gtk/option_dialog.hpp @@ -6,6 +6,7 @@ #include #include +#include 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 algorithms_; GtkButton* okay_button_; GtkButton* cancel_button_; diff --git a/yass/src/gtk/yass_en.po b/yass/src/gtk/yass_en.po index 3d4d1dd02c..fef372f9d8 100644 --- a/yass/src/gtk/yass_en.po +++ b/yass/src/gtk/yass_en.po @@ -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 \n" "Language-Team: Chilledheart \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" diff --git a/yass/src/gtk/yass_zh_CN.po b/yass/src/gtk/yass_zh_CN.po index a7fb025190..94ceccf4ce 100644 --- a/yass/src/gtk/yass_zh_CN.po +++ b/yass/src/gtk/yass_zh_CN.po @@ -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 \n" "Language-Team: Chilledheart \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 "官方网站" diff --git a/yass/src/gtk4/option_dialog.cpp b/yass/src/gtk4/option_dialog.cpp index e065d37ef0..5bccd31fb6 100644 --- a/yass/src/gtk4/option_dialog.cpp +++ b/yass/src/gtk4/option_dialog.cpp @@ -9,6 +9,7 @@ #include "core/logging.hpp" #include "core/utils.hpp" #include "gtk/utils.hpp" +#include "net/network.hpp" extern "C" { @@ -20,6 +21,7 @@ struct _OptionGtkDialog { GtkWidget* tcp_keep_alive_idle_timeout; GtkWidget* tcp_keep_alive_interval; GtkWidget* enable_post_quantum_kyber; + GtkWidget* tcp_congestion_algorithm; GtkWidget* okay_button; GtkWidget* cancel_button; @@ -44,6 +46,7 @@ static void option_dialog_dispose(GObject* object) { gtk_widget_unparent(dialog->tcp_keep_alive_idle_timeout); gtk_widget_unparent(dialog->tcp_keep_alive_interval); gtk_widget_unparent(dialog->enable_post_quantum_kyber); + gtk_widget_unparent(dialog->tcp_congestion_algorithm); gtk_widget_unparent(dialog->okay_button); gtk_widget_unparent(dialog->cancel_button); @@ -60,6 +63,7 @@ static void option_dialog_class_init(OptionGtkDialogClass* cls) { gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(cls), OptionGtkDialog, tcp_keep_alive_idle_timeout); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(cls), OptionGtkDialog, tcp_keep_alive_interval); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(cls), OptionGtkDialog, enable_post_quantum_kyber); + gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(cls), OptionGtkDialog, tcp_congestion_algorithm); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(cls), OptionGtkDialog, okay_button); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(cls), OptionGtkDialog, cancel_button); @@ -88,6 +92,14 @@ OptionGtkDialog* option_dialog_new(const gchar* title, GtkWindow* parent, GtkDia OptionDialog::OptionDialog(const std::string& title, GtkWindow* parent, bool modal) : impl_(option_dialog_new(title.c_str(), parent, modal ? GTK_DIALOG_MODAL : GTK_DIALOG_DESTROY_WITH_PARENT)) { + algorithms_ = net::GetTCPAvailableCongestionAlgorithms(); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + GtkComboBoxText* tcp_congestion_algorithm = GTK_COMBO_BOX_TEXT(impl_->tcp_congestion_algorithm); + for (const auto& algorithm : algorithms_) { + gtk_combo_box_text_append_text(tcp_congestion_algorithm, algorithm.c_str()); + } + G_GNUC_END_IGNORE_DEPRECATIONS + auto okay_callback = [](GtkButton* self, gpointer pointer) { OptionDialog* window = (OptionDialog*)pointer; window->OnOkayButtonClicked(); @@ -142,6 +154,21 @@ void OptionDialog::LoadChanges() { gtk_check_button_set_active(GTK_CHECK_BUTTON(impl_->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; + } + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + gtk_combo_box_set_active(GTK_COMBO_BOX(impl_->tcp_congestion_algorithm), i); + G_GNUC_END_IGNORE_DEPRECATIONS } bool OptionDialog::OnSave() { @@ -165,5 +192,14 @@ bool OptionDialog::OnSave() { absl::SetFlag(&FLAGS_tcp_keep_alive_interval, tcp_keep_alive_interval.value()); absl::SetFlag(&FLAGS_enable_post_quantum_kyber, enable_post_quantum_kyber); + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + gchar* algorithm_cstr = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(impl_->tcp_congestion_algorithm)); + G_GNUC_END_IGNORE_DEPRECATIONS + 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; } diff --git a/yass/src/gtk4/option_dialog.hpp b/yass/src/gtk4/option_dialog.hpp index 38816d1605..f37f02fcf5 100644 --- a/yass/src/gtk4/option_dialog.hpp +++ b/yass/src/gtk4/option_dialog.hpp @@ -6,6 +6,7 @@ #include #include +#include extern "C" { #define OPTION_DIALOG_TYPE (option_dialog_get_type()) @@ -32,6 +33,7 @@ class OptionDialog { private: friend class YASSWindow; OptionGtkDialog* impl_; + std::vector algorithms_; }; // OptionDialog #endif // OPTION_DIALOG_H diff --git a/yass/src/gtk4/option_dialog.ui b/yass/src/gtk4/option_dialog.ui index 50651a7fb8..d54ce57c75 100644 --- a/yass/src/gtk4/option_dialog.ui +++ b/yass/src/gtk4/option_dialog.ui @@ -63,6 +63,16 @@ + + + TCP Congestion Algorithm + 0.0 + + 0 + 5 + + + @@ -103,12 +113,20 @@ + + + + 1 + 5 + + + Okay 0 - 5 + 6 @@ -117,7 +135,7 @@ Cancel 1 - 5 + 6 diff --git a/yass/src/gtk4/yass_en.po b/yass/src/gtk4/yass_en.po index cb96f41ccd..a4ede16120 100644 --- a/yass/src/gtk4/yass_en.po +++ b/yass/src/gtk4/yass_en.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-25 13:13+0800\n" +"POT-Creation-Date: 2024-08-26 14:25+0800\n" "PO-Revision-Date: 2023-05-24 14:35+0800\n" "Last-Translator: Chilledheart \n" "Language-Team: Chilledheart \n" @@ -17,63 +17,63 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: yass.cpp:302 +#: yass.cpp:297 msgid "Connected with conns: " msgstr "Connected with conns: " -#: yass.cpp:304 +#: yass.cpp:299 msgid "Connecting" msgstr "Connecting" -#: yass.cpp:306 +#: yass.cpp:301 msgid "Failed to connect due to " msgstr "Failed to connect due to " -#: yass.cpp:308 +#: yass.cpp:303 msgid "Disconnecting" msgstr "Disconnecting" -#: yass.cpp:310 +#: yass.cpp:305 msgid "Disconnected with " msgstr "Disconnected with " -#: yass.cpp:421 -msgid "Last Change: " -msgstr "Last Change: " - -#: yass.cpp:424 -msgid "Enabled Feature: " -msgstr "Enabled Feature: " - -#: yass.cpp:427 -msgid "GUI Variant: " -msgstr "GUI Variant: " - -#: yass.cpp:436 -msgid "official-site" -msgstr "official-site" - -#: yass.cpp:442 -msgid "YASS Option" -msgstr "YASS Option" - -#: yass_window.cpp:192 +#: yass_window.cpp:193 msgid "READY" msgstr "READY" -#: yass_window.cpp:319 +#: yass_window.cpp:266 +msgid "Last Change: " +msgstr "Last Change: " + +#: yass_window.cpp:269 +msgid "Enabled Feature: " +msgstr "Enabled Feature: " + +#: yass_window.cpp:272 +msgid "GUI Variant: " +msgstr "GUI Variant: " + +#: yass_window.cpp:281 +msgid "official-site" +msgstr "official-site" + +#: yass_window.cpp:298 +msgid "YASS Option" +msgstr "YASS Option" + +#: yass_window.cpp:397 msgid " tx rate: " msgstr " tx rate: " -#: yass_window.cpp:322 +#: yass_window.cpp:400 msgid " rx rate: " msgstr " rx rate: " -#: yass_window.cpp:357 +#: yass_window.cpp:435 msgid "Start Failed" msgstr "Start Failed" -#: yass_window.cpp:358 +#: yass_window.cpp:436 msgid "OK" msgstr "OK" @@ -113,11 +113,15 @@ msgstr "TCP keep alive interval" msgid "Kyber post-quantum key agreement for TLS" msgstr "Kyber post-quantum key agreement for TLS" -#: option_dialog.ui:108 +#: option_dialog.ui:68 +msgid "TCP Congestion Algorithm" +msgstr "TCP Congestion Algorithm" + +#: option_dialog.ui:126 msgid "Okay" msgstr "Okay" -#: option_dialog.ui:117 +#: option_dialog.ui:135 msgid "Cancel" msgstr "Cancel" @@ -185,6 +189,6 @@ msgstr "System Proxy" msgid "Start" msgstr "Start" -#: yass_window.ui:291 +#: yass_window.ui:292 msgid "Stop" msgstr "Stop" diff --git a/yass/src/gtk4/yass_zh_CN.po b/yass/src/gtk4/yass_zh_CN.po index 0b2f06f988..e40ea56971 100644 --- a/yass/src/gtk4/yass_zh_CN.po +++ b/yass/src/gtk4/yass_zh_CN.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: 1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-25 13:13+0800\n" +"POT-Creation-Date: 2024-08-26 14:25+0800\n" "PO-Revision-Date: 2023-09-15 11:28+0800\n" "Last-Translator: Chilledheart \n" "Language-Team: Chilledheart \n" @@ -18,63 +18,63 @@ msgstr "" "X-Generator: Poedit 2.2.4\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: yass.cpp:302 +#: yass.cpp:297 msgid "Connected with conns: " msgstr "已产生连接: " -#: yass.cpp:304 +#: yass.cpp:299 msgid "Connecting" msgstr "连接中" -#: yass.cpp:306 +#: yass.cpp:301 msgid "Failed to connect due to " msgstr "无法连接因为 " -#: yass.cpp:308 +#: yass.cpp:303 msgid "Disconnecting" msgstr "断开连接中" -#: yass.cpp:310 +#: yass.cpp:305 msgid "Disconnected with " msgstr "断开连接于服务器 " -#: yass.cpp:421 -msgid "Last Change: " -msgstr "最后改动: " - -#: yass.cpp:424 -msgid "Enabled Feature: " -msgstr "启用功能: " - -#: yass.cpp:427 -msgid "GUI Variant: " -msgstr "图形版本: " - -#: yass.cpp:436 -msgid "official-site" -msgstr "官方网站" - -#: yass.cpp:442 -msgid "YASS Option" -msgstr "YASS 选项" - -#: yass_window.cpp:192 +#: yass_window.cpp:193 msgid "READY" msgstr "就绪" -#: yass_window.cpp:319 +#: yass_window.cpp:266 +msgid "Last Change: " +msgstr "最后改动: " + +#: yass_window.cpp:269 +msgid "Enabled Feature: " +msgstr "启用功能: " + +#: yass_window.cpp:272 +msgid "GUI Variant: " +msgstr "图形版本: " + +#: yass_window.cpp:281 +msgid "official-site" +msgstr "官方网站" + +#: yass_window.cpp:298 +msgid "YASS Option" +msgstr "YASS 选项" + +#: yass_window.cpp:397 msgid " tx rate: " msgstr " 上传速率: " -#: yass_window.cpp:322 +#: yass_window.cpp:400 msgid " rx rate: " msgstr " 下载速率: " -#: yass_window.cpp:357 +#: yass_window.cpp:435 msgid "Start Failed" msgstr "启动失败" -#: yass_window.cpp:358 +#: yass_window.cpp:436 msgid "OK" msgstr "确认" @@ -114,11 +114,15 @@ msgstr "TCP 保活间隔" msgid "Kyber post-quantum key agreement for TLS" msgstr "TLS的Kyber后量子密钥协商" -#: option_dialog.ui:108 +#: option_dialog.ui:68 +msgid "TCP Congestion Algorithm" +msgstr "TCP 拥塞算法" + +#: option_dialog.ui:126 msgid "Okay" msgstr "确认" -#: option_dialog.ui:117 +#: option_dialog.ui:135 msgid "Cancel" msgstr "取消" @@ -186,6 +190,6 @@ msgstr "系统代理" msgid "Start" msgstr "启动" -#: yass_window.ui:291 +#: yass_window.ui:292 msgid "Stop" msgstr "停止" diff --git a/yass/src/net/network.cpp b/yass/src/net/network.cpp index 81da9b3ba2..d4e05880c8 100644 --- a/yass/src/net/network.cpp +++ b/yass/src/net/network.cpp @@ -15,6 +15,7 @@ #endif #include +#include #include #include #include "config/config_network.hpp" @@ -44,9 +45,9 @@ void SetSOReusePort(asio::ip::tcp::acceptor::native_handle_type handle, asio::er #ifdef __linux__ static void PrintTcpAllowedCongestionControls() { - constexpr std::string_view kAllowedCongestionControl = "/proc/sys/net/ipv4/tcp_allowed_congestion_control"; + const std::string procfs = "/proc/sys/net/ipv4/tcp_allowed_congestion_control"; uint8_t buf[256]; - auto ret = ReadFileToBuffer(std::string(kAllowedCongestionControl), make_span(buf)); + auto ret = ReadFileToBuffer(procfs, buf); if (ret < 0) { return; } @@ -54,11 +55,32 @@ static void PrintTcpAllowedCongestionControls() { } #endif +std::vector GetTCPAvailableCongestionAlgorithms() { + std::vector ret; + ret.push_back(std::string()); // unspec +#ifdef __linux__ + uint8_t buf[4096]; + const std::string procfs = "/proc/sys/net/ipv4/tcp_available_congestion_control"; + ssize_t bytes = ReadFileToBuffer(procfs, buf); + if (bytes > 0 && bytes < (ssize_t)sizeof(buf)) { + std::string_view sbuf = std::string_view((const char*)buf, bytes); + LOG(INFO) << "Available TCP Congestion Algorithms: " << sbuf; + auto algorithms = absl::StrSplit(sbuf, absl::ByAnyChar(" \n\t\r")); + for (const auto& algorithm : algorithms) { + if (!algorithm.empty()) { + ret.push_back(std::string(algorithm)); + } + } + } +#endif + return ret; +} + void SetTCPCongestion(asio::ip::tcp::acceptor::native_handle_type handle, asio::error_code& ec) { (void)handle; ec = asio::error_code(); #ifdef __linux__ - const std::string new_algo = absl::GetFlag(FLAGS_congestion_algorithm); + const std::string new_algo = absl::GetFlag(FLAGS_tcp_congestion_algorithm); if (new_algo.empty()) { return; } @@ -70,7 +92,7 @@ void SetTCPCongestion(asio::ip::tcp::acceptor::native_handle_type handle, asio:: if (ret < 0 && (errno == EPROTONOSUPPORT || errno == ENOPROTOOPT)) { PLOG(WARNING) << "TCP_CONGESTION is not supported on this platform"; LOG(WARNING) << "Ignore congestion algorithm settings"; - absl::SetFlag(&FLAGS_congestion_algorithm, std::string()); + absl::SetFlag(&FLAGS_tcp_congestion_algorithm, std::string()); return; } if (ret < 0) { @@ -88,7 +110,7 @@ void SetTCPCongestion(asio::ip::tcp::acceptor::native_handle_type handle, asio:: PrintTcpAllowedCongestionControls(); LOG(WARNING) << "Please load the algorithm kernel module before use!"; LOG(WARNING) << "Ignore congestion algorithm settings"; - absl::SetFlag(&FLAGS_congestion_algorithm, std::string()); + absl::SetFlag(&FLAGS_tcp_congestion_algorithm, std::string()); return; } VLOG(2) << "Previous congestion algorithm: " << old_algo; diff --git a/yass/src/net/network.hpp b/yass/src/net/network.hpp index 39eac13afe..f79d0c383b 100644 --- a/yass/src/net/network.hpp +++ b/yass/src/net/network.hpp @@ -8,12 +8,17 @@ #include #endif +#include +#include + #include "net/asio.hpp" namespace net { void SetSOReusePort(asio::ip::tcp::acceptor::native_handle_type handle, asio::error_code&); +std::vector GetTCPAvailableCongestionAlgorithms(); + void SetTCPCongestion(asio::ip::tcp::acceptor::native_handle_type handle, asio::error_code&); void SetTCPFastOpen(asio::ip::tcp::acceptor::native_handle_type handle, asio::error_code&); diff --git a/yass/src/qt6/lang/yass_en.ts b/yass/src/qt6/lang/yass_en.ts index a330dda6d2..a9c0520a41 100644 --- a/yass/src/qt6/lang/yass_en.ts +++ b/yass/src/qt6/lang/yass_en.ts @@ -35,6 +35,10 @@ Cancel + + TCP Congestion Algorithm + + TrayIcon diff --git a/yass/src/qt6/lang/yass_zh_CN.qm b/yass/src/qt6/lang/yass_zh_CN.qm index 1183cef562..51a3b21a4e 100644 Binary files a/yass/src/qt6/lang/yass_zh_CN.qm and b/yass/src/qt6/lang/yass_zh_CN.qm differ diff --git a/yass/src/qt6/lang/yass_zh_CN.ts b/yass/src/qt6/lang/yass_zh_CN.ts index a7236bd852..047439d157 100644 --- a/yass/src/qt6/lang/yass_zh_CN.ts +++ b/yass/src/qt6/lang/yass_zh_CN.ts @@ -35,6 +35,10 @@ Cancel 取消 + + TCP Congestion Algorithm + TCP 拥塞算法 + TrayIcon diff --git a/yass/src/qt6/option_dialog.cpp b/yass/src/qt6/option_dialog.cpp index d8a92984b6..3f8ab643d6 100644 --- a/yass/src/qt6/option_dialog.cpp +++ b/yass/src/qt6/option_dialog.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include "config/config.hpp" #include "core/logging.hpp" #include "core/utils.hpp" +#include "net/network.hpp" OptionDialog::OptionDialog(QWidget* parent) : QDialog(parent) { setWindowTitle(tr("YASS Option")); @@ -29,12 +31,14 @@ OptionDialog::OptionDialog(QWidget* parent) : QDialog(parent) { auto tcp_keep_alive_interval_label = new QLabel(tr("TCP keep alive interval")); auto enable_post_quantum_kyber_label = new QLabel(tr("Kyber post-quantum key agreement for TLS")); + auto tcp_congestion_algorithm_label = new QLabel(tr("TCP Congestion Algorithm")); grid->addWidget(tcp_keep_alive_label, 0, 0); grid->addWidget(tcp_keep_alive_cnt_label, 1, 0); grid->addWidget(tcp_keep_alive_idle_timeout_label, 2, 0); grid->addWidget(tcp_keep_alive_interval_label, 3, 0); grid->addWidget(enable_post_quantum_kyber_label, 4, 0); + grid->addWidget(tcp_congestion_algorithm_label, 5, 0); tcp_keep_alive_ = new QCheckBox; tcp_keep_alive_cnt_ = new QLineEdit; @@ -44,12 +48,20 @@ OptionDialog::OptionDialog(QWidget* parent) : QDialog(parent) { tcp_keep_alive_interval_ = new QLineEdit; tcp_keep_alive_interval_->setValidator(new QIntValidator(0, INT32_MAX, this)); enable_post_quantum_kyber_ = new QCheckBox; + tcp_congestion_algorithm_ = new QComboBox; + + algorithms_ = net::GetTCPAvailableCongestionAlgorithms(); + + for (const auto& algorithm : algorithms_) { + tcp_congestion_algorithm_->addItem(algorithm.c_str()); + } grid->addWidget(tcp_keep_alive_, 0, 1); grid->addWidget(tcp_keep_alive_cnt_, 1, 1); grid->addWidget(tcp_keep_alive_idle_timeout_, 2, 1); grid->addWidget(tcp_keep_alive_interval_, 3, 1); grid->addWidget(enable_post_quantum_kyber_, 4, 1); + grid->addWidget(tcp_congestion_algorithm_, 5, 1); okay_button_ = new QPushButton(tr("Okay")); connect(okay_button_, &QPushButton::clicked, this, &OptionDialog::OnOkayButtonClicked); @@ -57,8 +69,8 @@ OptionDialog::OptionDialog(QWidget* parent) : QDialog(parent) { cancel_button_ = new QPushButton(tr("Cancel")); connect(cancel_button_, &QPushButton::clicked, this, &OptionDialog::OnCancelButtonClicked); - grid->addWidget(okay_button_, 5, 0); - grid->addWidget(cancel_button_, 5, 1); + grid->addWidget(okay_button_, 6, 0); + grid->addWidget(cancel_button_, 6, 1); setLayout(grid); @@ -89,6 +101,20 @@ void OptionDialog::LoadChanges() { QString::fromUtf8(tcp_keep_alive_interval_str.c_str(), tcp_keep_alive_interval_str.size())); enable_post_quantum_kyber_->setChecked(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; + } + + tcp_congestion_algorithm_->setCurrentIndex(i); } bool OptionDialog::OnSave() { @@ -112,5 +138,10 @@ bool OptionDialog::OnSave() { absl::SetFlag(&FLAGS_enable_post_quantum_kyber, enable_post_quantum_kyber); + int i = tcp_congestion_algorithm_->currentIndex(); + DCHECK_GE(i, 0); + DCHECK_LE(i, (int)algorithms_.size()); + absl::SetFlag(&FLAGS_tcp_congestion_algorithm, algorithms_[i]); + return true; } diff --git a/yass/src/qt6/option_dialog.hpp b/yass/src/qt6/option_dialog.hpp index 3e222301a3..16b9f58740 100644 --- a/yass/src/qt6/option_dialog.hpp +++ b/yass/src/qt6/option_dialog.hpp @@ -5,7 +5,11 @@ #include +#include +#include + class QCheckBox; +class QComboBox; class QLineEdit; class QPushButton; class OptionDialog : public QDialog { @@ -27,6 +31,8 @@ class OptionDialog : public QDialog { QLineEdit* tcp_keep_alive_idle_timeout_; QLineEdit* tcp_keep_alive_interval_; QCheckBox* enable_post_quantum_kyber_; + QComboBox* tcp_congestion_algorithm_; + std::vector algorithms_; QPushButton* okay_button_; QPushButton* cancel_button_;