nntp_proxy/
network.rs

1//! Network socket optimization utilities
2//!
3//! This module provides utilities for optimizing TCP socket performance
4//! for high-throughput NNTP transfers.
5//!
6//! The module is organized into:
7//! - `optimizers`: Trait-based optimization strategies for different connection types
8//! - `SocketOptimizer`: Convenience wrappers for common optimization patterns
9
10pub mod optimizers;
11
12use crate::constants::socket::{HIGH_THROUGHPUT_RECV_BUFFER, HIGH_THROUGHPUT_SEND_BUFFER};
13use crate::stream::ConnectionStream;
14use std::io;
15use tokio::net::TcpStream;
16use tracing::debug;
17
18// Re-export the new optimizers for easier access
19pub use optimizers::{ConnectionOptimizer, NetworkOptimizer, TcpOptimizer, TlsOptimizer};
20
21/// Socket optimizer for high-throughput scenarios
22pub struct SocketOptimizer;
23
24impl SocketOptimizer {
25    /// Set socket optimizations for high-throughput transfers using socket2
26    pub fn optimize_for_throughput(stream: &TcpStream) -> Result<(), io::Error> {
27        use socket2::SockRef;
28
29        let sock_ref = SockRef::from(stream);
30
31        // Set larger buffer sizes for high throughput
32        sock_ref.set_recv_buffer_size(HIGH_THROUGHPUT_RECV_BUFFER)?;
33        sock_ref.set_send_buffer_size(HIGH_THROUGHPUT_SEND_BUFFER)?;
34
35        // Keep Nagle's algorithm enabled for large transfers to reduce packet overhead
36        // (socket2 doesn't expose some advanced TCP options like TCP_QUICKACK, TCP_CORK)
37        // but the basic optimizations are sufficient for most use cases
38
39        Ok(())
40    }
41
42    /// Apply optimizations using the new trait-based approach (recommended)
43    pub fn apply_to_connection_streams(
44        client_stream: &ConnectionStream,
45        backend_stream: &ConnectionStream,
46    ) -> Result<(), io::Error> {
47        debug!("Applying connection optimizations with trait-based approach");
48
49        let client_optimizer = ConnectionOptimizer::new(client_stream);
50        let backend_optimizer = ConnectionOptimizer::new(backend_stream);
51
52        client_optimizer.optimize()?;
53        backend_optimizer.optimize()?;
54
55        Ok(())
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn test_constants() {
65        assert_eq!(HIGH_THROUGHPUT_RECV_BUFFER, 16 * 1024 * 1024);
66        assert_eq!(HIGH_THROUGHPUT_SEND_BUFFER, 16 * 1024 * 1024);
67    }
68
69    #[test]
70    fn test_buffer_size_is_reasonable() {
71        // Buffer sizes should be large but not excessive
72        // Compile-time assertions
73        const _: () = assert!(HIGH_THROUGHPUT_RECV_BUFFER >= 1024 * 1024); // At least 1MB
74        const _: () = assert!(HIGH_THROUGHPUT_RECV_BUFFER <= 128 * 1024 * 1024); // At most 128MB
75
76        const _: () = assert!(HIGH_THROUGHPUT_SEND_BUFFER >= 1024 * 1024);
77        const _: () = assert!(HIGH_THROUGHPUT_SEND_BUFFER <= 128 * 1024 * 1024);
78    }
79
80    #[test]
81    fn test_buffer_sizes_are_equal() {
82        // Send and receive buffers should be the same for bidirectional transfers
83        assert_eq!(HIGH_THROUGHPUT_RECV_BUFFER, HIGH_THROUGHPUT_SEND_BUFFER);
84    }
85
86    #[test]
87    fn test_buffer_sizes_are_power_of_two_or_multiple() {
88        // Should be aligned to reasonable boundaries
89        let size = HIGH_THROUGHPUT_RECV_BUFFER;
90
91        // Should be a multiple of 1MB for efficient allocation
92        assert_eq!(size % (1024 * 1024), 0);
93    }
94
95    #[test]
96    fn test_socket_optimizer_exists() {
97        // Verify SocketOptimizer can be instantiated
98        let _ = SocketOptimizer;
99    }
100
101    #[tokio::test]
102    async fn test_optimize_for_throughput_with_real_socket() {
103        // Create a real TCP listener and connection
104        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
105        let addr = listener.local_addr().unwrap();
106
107        // Connect to it
108        let client_stream = tokio::net::TcpStream::connect(addr).await.unwrap();
109
110        // Try to optimize (might fail on some systems, but shouldn't panic)
111        let result = SocketOptimizer::optimize_for_throughput(&client_stream);
112
113        // On most systems this should succeed, but some might not support large buffers
114        // The important thing is it doesn't panic
115        match result {
116            Ok(()) => {
117                // Success - verify we can still use the socket
118                assert!(client_stream.peer_addr().is_ok());
119            }
120            Err(e) => {
121                // Some systems might not support these buffer sizes
122                println!(
123                    "Buffer size not supported (expected on some systems): {}",
124                    e
125                );
126            }
127        }
128    }
129
130    #[tokio::test]
131    async fn test_apply_to_connection_streams() {
132        use crate::stream::ConnectionStream;
133
134        // Create two TCP connections
135        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
136        let addr = listener.local_addr().unwrap();
137
138        let client_tcp = tokio::net::TcpStream::connect(addr).await.unwrap();
139        let (server_tcp, _) = listener.accept().await.unwrap();
140        
141        let client_stream = ConnectionStream::plain(client_tcp);
142        let server_stream = ConnectionStream::plain(server_tcp);
143
144        // Apply optimizations using the trait-based approach
145        let result = SocketOptimizer::apply_to_connection_streams(&client_stream, &server_stream);
146
147        // Should always succeed
148        assert!(result.is_ok());
149
150        // Streams should still be usable
151        assert!(client_stream.as_tcp_stream().unwrap().peer_addr().is_ok());
152        assert!(server_stream.as_tcp_stream().unwrap().peer_addr().is_ok());
153    }
154
155    #[tokio::test]
156    async fn test_connection_optimizer() {
157        use crate::stream::ConnectionStream;
158
159        // Create a test server and connection
160        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
161        let addr = listener.local_addr().unwrap();
162
163        let tcp_stream = std::net::TcpStream::connect(addr).unwrap();
164        tcp_stream.set_nonblocking(true).unwrap();
165        let tokio_stream = TcpStream::from_std(tcp_stream).unwrap();
166
167        let conn_stream = ConnectionStream::plain(tokio_stream);
168
169        // Should successfully optimize ConnectionStream using trait-based approach
170        let optimizer = ConnectionOptimizer::new(&conn_stream);
171        let result = optimizer.optimize();
172        assert!(result.is_ok());
173    }
174
175    #[test]
176    fn test_buffer_size_calculation() {
177        // Verify buffer sizes are calculated correctly
178        assert_eq!(HIGH_THROUGHPUT_RECV_BUFFER, 16 * 1024 * 1024);
179
180        // Verify it's 16MB in bytes
181        assert_eq!(HIGH_THROUGHPUT_RECV_BUFFER, 16_777_216);
182
183        // Verify relationship to KB/MB
184        assert_eq!(HIGH_THROUGHPUT_RECV_BUFFER / 1024, 16384); // KB
185        assert_eq!(HIGH_THROUGHPUT_RECV_BUFFER / (1024 * 1024), 16); // MB
186    }
187
188    #[test]
189    fn test_buffer_size_for_large_articles() {
190        // Typical large Usenet article is 1-100MB
191        // Our 16MB buffer should handle most efficiently
192        let typical_large_article = 10 * 1024 * 1024; // 10MB
193        let very_large_article = 100 * 1024 * 1024; // 100MB
194
195        // Buffer should be larger than typical article
196        assert!(HIGH_THROUGHPUT_RECV_BUFFER > typical_large_article);
197
198        // But we accept that very large articles will require multiple buffers
199        assert!(HIGH_THROUGHPUT_RECV_BUFFER < very_large_article);
200    }
201
202    #[tokio::test]
203    async fn test_new_trait_based_approach() {
204        use crate::stream::ConnectionStream;
205
206        // Create two TCP connections
207        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
208        let addr = listener.local_addr().unwrap();
209
210        let client_tcp = tokio::net::TcpStream::connect(addr).await.unwrap();
211        let (server_tcp, _) = listener.accept().await.unwrap();
212
213        let client_stream = ConnectionStream::plain(client_tcp);
214        let server_stream = ConnectionStream::plain(server_tcp);
215
216        // Use the new trait-based approach
217        let result = SocketOptimizer::apply_to_connection_streams(&client_stream, &server_stream);
218        assert!(result.is_ok());
219
220        // Test individual optimizers
221        let client_optimizer = ConnectionOptimizer::new(&client_stream);
222        let server_optimizer = ConnectionOptimizer::new(&server_stream);
223
224        assert!(client_optimizer.optimize().is_ok());
225        assert!(server_optimizer.optimize().is_ok());
226    }
227}