diff --git a/Cargo.lock b/Cargo.lock index 7049216b..2ddac4ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2288,6 +2288,7 @@ dependencies = [ "indoc", "itertools 0.14.0", "kcp-sys", + "lzokay-native", "machine-uid", "maplit", "mimalloc", @@ -4874,6 +4875,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lzokay-native" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "792ba667add2798c6c3e988e630f4eb921b5cbc735044825b7111ef1582c8730" +dependencies = [ + "byteorder", + "thiserror 1.0.63", +] + [[package]] name = "mac" version = "0.1.1" diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index 618b6613..a9960b86 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -221,6 +221,7 @@ async-ringbuf = "0.3.1" service-manager = { git = "https://github.com/EasyTier/service-manager-rs.git", branch = "main" } zstd = { version = "0.13", optional = true } +lzokay-native = { version = "0.1", optional = true } kcp-sys = { git = "https://github.com/EasyTier/kcp-sys", rev = "94964794caaed5d388463137da59b97499619e5f", optional = true } @@ -358,6 +359,7 @@ default = [ "faketcp", "magic-dns", "zstd", + "lzo", ] full = [ "websocket", @@ -372,6 +374,7 @@ full = [ "faketcp", "magic-dns", "zstd", + "lzo", ] wireguard = ["dep:boringtun", "dep:ring"] quic = ["dep:quinn", "dep:quinn-plaintext", "dep:rustls", "dep:rcgen"] @@ -402,5 +405,6 @@ tracing = ["tokio/tracing", "dep:console-subscriber"] magic-dns = ["dep:hickory-client", "dep:hickory-server"] faketcp = ["dep:flume"] zstd = ["dep:zstd"] +lzo = ["dep:lzokay-native"] # For Network Extension on macOS macos-ne = [] diff --git a/easytier/locales/app.yml b/easytier/locales/app.yml index 818329b5..b38436a8 100644 --- a/easytier/locales/app.yml +++ b/easytier/locales/app.yml @@ -194,8 +194,8 @@ core_clap: en: "the url of the ipv6 listener, e.g.: tcp://[::]:11010, if not set, will listen on random udp port" zh-CN: "IPv6 监听器的URL,例如:tcp://[::]:11010,如果未设置,将在随机UDP端口上监听" compression: - en: "compression algorithm to use, support none, zstd. default is none" - zh-CN: "要使用的压缩算法,支持 none、zstd。默认为 none" + en: "compression algorithm to use, support none, zstd, lzo. default is none" + zh-CN: "要使用的压缩算法,支持 none、zstd、lzo。默认为 none" mapped_listeners: en: "manually specify the public address of the listener, other nodes can use this address to connect to this node. e.g.: tcp://123.123.123.123:11223, can specify multiple." zh-CN: "手动指定监听器的公网地址,其他节点可以使用该地址连接到本节点。例如:tcp://123.123.123.123:11223,可以指定多个。" diff --git a/easytier/src/common/compressor.rs b/easytier/src/common/compressor.rs index d78972ee..86cd7ef7 100644 --- a/easytier/src/common/compressor.rs +++ b/easytier/src/common/compressor.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "zstd")] +#[cfg(any(feature = "zstd", feature = "lzo"))] use anyhow::Context; #[cfg(feature = "zstd")] use dashmap::DashMap; @@ -53,6 +53,13 @@ impl DefaultCompressor { ) }) }), + #[cfg(feature = "lzo")] + CompressorAlgo::Lzo => lzokay_native::compress(data).with_context(|| { + format!( + "Failed to compress data with algorithm: {:?}", + compress_algo + ) + }), CompressorAlgo::None => Ok(data.to_vec()), } } @@ -85,6 +92,13 @@ impl DefaultCompressor { compress_algo )) }), + #[cfg(feature = "lzo")] + CompressorAlgo::Lzo => lzokay_native::decompress_all(data, None).with_context(|| { + format!( + "Failed to decompress data with algorithm: {:?}", + compress_algo + ) + }), CompressorAlgo::None => Ok(data.to_vec()), } } @@ -181,14 +195,13 @@ thread_local! { static DCTX_MAP: RefCell>> = RefCell::new(DashMap::new()); } -#[cfg(all(test, feature = "zstd"))] +#[cfg(all(test, any(feature = "zstd", feature = "lzo")))] pub mod tests { use super::*; - #[tokio::test] - async fn test_compress() { - let text = b"12345670000000000000000000"; - let mut packet = ZCPacket::new_with_payload(text); + async fn test_compress_algo(compress_algo: CompressorAlgo) { + let text = vec![b'a'; 4096]; + let mut packet = ZCPacket::new_with_payload(&text); packet.fill_peer_manager_hdr(0, 0, 0); let compressor = DefaultCompressor {}; @@ -200,7 +213,7 @@ pub mod tests { ); compressor - .compress(&mut packet, CompressorAlgo::ZstdDefault) + .compress(&mut packet, compress_algo) .await .unwrap(); println!( @@ -215,8 +228,7 @@ pub mod tests { assert!(!packet.peer_manager_header().unwrap().is_compressed()); } - #[tokio::test] - async fn test_short_text_compress() { + async fn test_short_text_compress_algo(compress_algo: CompressorAlgo) { let text = b"1234"; let mut packet = ZCPacket::new_with_payload(text); packet.fill_peer_manager_hdr(0, 0, 0); @@ -225,7 +237,7 @@ pub mod tests { // short text can't be compressed compressor - .compress(&mut packet, CompressorAlgo::ZstdDefault) + .compress(&mut packet, compress_algo) .await .unwrap(); assert!(!packet.peer_manager_header().unwrap().is_compressed()); @@ -234,4 +246,28 @@ pub mod tests { assert_eq!(packet.payload(), text); assert!(!packet.peer_manager_header().unwrap().is_compressed()); } + + #[cfg(feature = "zstd")] + #[tokio::test] + async fn test_zstd_compress() { + test_compress_algo(CompressorAlgo::ZstdDefault).await; + } + + #[cfg(feature = "zstd")] + #[tokio::test] + async fn test_zstd_short_text_compress() { + test_short_text_compress_algo(CompressorAlgo::ZstdDefault).await; + } + + #[cfg(feature = "lzo")] + #[tokio::test] + async fn test_lzo_compress() { + test_compress_algo(CompressorAlgo::Lzo).await; + } + + #[cfg(feature = "lzo")] + #[tokio::test] + async fn test_lzo_short_text_compress() { + test_short_text_compress_algo(CompressorAlgo::Lzo).await; + } } diff --git a/easytier/src/core.rs b/easytier/src/core.rs index d29d7708..eded9bc8 100644 --- a/easytier/src/core.rs +++ b/easytier/src/core.rs @@ -37,6 +37,15 @@ use crate::tunnel::IpScheme; #[cfg(feature = "jemalloc-prof")] use jemalloc_ctl::{Access as _, AsName as _, epoch, stats}; +fn supported_compression_algorithms() -> &'static str { + cfg_select! { + all(feature = "zstd", feature = "lzo") => "none, zstd, lzo", + feature = "zstd" => "none, zstd", + feature = "lzo" => "none, lzo", + _ => "none", + } +} + #[cfg(target_os = "windows")] windows_service::define_windows_service!(ffi_service_main, win_service_main); @@ -1108,10 +1117,14 @@ impl NetworkOptions { if let Some(compression) = &self.compression { f.data_compress_algo = match compression.as_str() { "none" => CompressionAlgoPb::None, + #[cfg(feature = "zstd")] "zstd" => CompressionAlgoPb::Zstd, + #[cfg(feature = "lzo")] + "lzo" => CompressionAlgoPb::Lzo, _ => panic!( - "unknown compression algorithm: {}, supported: none, zstd", - compression + "unknown compression algorithm: {}, supported: {}", + compression, + supported_compression_algorithms() ), } .into(); diff --git a/easytier/src/proto/common.proto b/easytier/src/proto/common.proto index 15ed939a..533b70f4 100644 --- a/easytier/src/proto/common.proto +++ b/easytier/src/proto/common.proto @@ -105,6 +105,7 @@ enum CompressionAlgoPb { Invalid = 0; None = 1; Zstd = 2; + Lzo = 3; } message RpcCompressionInfo { diff --git a/easytier/src/proto/common.rs b/easytier/src/proto/common.rs index 067560ce..0af3b488 100644 --- a/easytier/src/proto/common.rs +++ b/easytier/src/proto/common.rs @@ -467,6 +467,8 @@ impl TryFrom for CompressorAlgo { match value { #[cfg(feature = "zstd")] CompressionAlgoPb::Zstd => Ok(CompressorAlgo::ZstdDefault), + #[cfg(feature = "lzo")] + CompressionAlgoPb::Lzo => Ok(CompressorAlgo::Lzo), CompressionAlgoPb::None => Ok(CompressorAlgo::None), _ => Err(anyhow::anyhow!("Invalid CompressionAlgoPb")), } @@ -480,6 +482,8 @@ impl TryFrom for CompressionAlgoPb { match value { #[cfg(feature = "zstd")] CompressorAlgo::ZstdDefault => Ok(CompressionAlgoPb::Zstd), + #[cfg(feature = "lzo")] + CompressorAlgo::Lzo => Ok(CompressionAlgoPb::Lzo), CompressorAlgo::None => Ok(CompressionAlgoPb::None), } } diff --git a/easytier/src/tunnel/packet_def.rs b/easytier/src/tunnel/packet_def.rs index 10cf5f4d..dfbd30d4 100644 --- a/easytier/src/tunnel/packet_def.rs +++ b/easytier/src/tunnel/packet_def.rs @@ -309,6 +309,8 @@ pub enum CompressorAlgo { None = 0, #[cfg(feature = "zstd")] ZstdDefault = 1, + #[cfg(feature = "lzo")] + Lzo = 2, } #[repr(C, packed)] @@ -323,6 +325,8 @@ impl CompressorTail { match self.algo { #[cfg(feature = "zstd")] 1 => Some(CompressorAlgo::ZstdDefault), + #[cfg(feature = "lzo")] + 2 => Some(CompressorAlgo::Lzo), _ => None, } }