Skip to content

Commit f9163dc

Browse files
committed
Added port-pool assignment support, version increased to 0.1.6
1 parent b999bd5 commit f9163dc

File tree

8 files changed

+304
-55
lines changed

8 files changed

+304
-55
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rperf"
3-
version = "0.1.5"
3+
version = "0.1.6"
44
description = "validates network throughput capacity and reliability"
55
authors = ["Neil Tallim <neiltallim@3d-p.com>"]
66
edition = "2018"
@@ -29,7 +29,7 @@ uuid = {version = "0.8", features = ["v4"]}
2929
#then "cargo deb" to build simple Debian packages for this project
3030
[package.metadata.deb]
3131
maintainer-scripts = "debian-maintainer-scripts/"
32-
copyright = "(C) 2021 Evtech Solutions, Ltd., dba 3D-P"
32+
copyright = "(C) 2022 Evtech Solutions, Ltd., dba 3D-P"
3333
license-file = ["COPYING", "0"]
3434
extended-description = """
3535
Rust-based iperf clone with a number of behavioural fixes and corrections, plus

README.md

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -77,34 +77,17 @@ welcome.
7777

7878
## usage
7979

80-
Everything is outlined in the output of `--help` and most users familiar with
81-
similar tools should feel comfortable immediately.
82-
83-
_rperf_ works much like _iperf3_, sharing a lot of concepts and even
84-
command-line flags. One key area where it differs is that the client drives
85-
all of the configuration process while the server just complies to the best
86-
of its ability and provides a stream of results. This means that the server
87-
will not present test-results directly via its interface and also that TCP
88-
and UDP tests can be run against the same instance, potentially by many clients
89-
simultaneously.
90-
91-
In its normal mode of operation, the client will upload data to the server;
92-
when the `reverse` flag is set, the client will receive data.
93-
94-
Unlike _iperf3_, _rperf_ does not make use of a reserved port-range. This is
95-
so it can support an arbitrary number of clients in parallel without
96-
resource contention on what can only practically be a small number of
97-
contiguous ports. In its intended capacity, this shouldn't be a problem, but
98-
it does make `reverse` incompatible with most non-permissive firewalls and
99-
NAT setups.
100-
101-
There also isn't a concept of testing throughput relative to a fixed quantity
102-
of data. Rather, the sole focus is on measuring throughput over a roughly
103-
known period of time.
104-
105-
Also of relevance is that, if the server is running in IPv6 mode and its
106-
host supports IPv4-mapping in a dual-stack configuration, both IPv4 and IPv6
107-
clients can connect to the same instance.
80+
Everything is outlined in the output of `--help` and most users familiar with similar tools should feel comfortable immediately.
81+
82+
_rperf_ works much like _iperf3_, sharing a lot of concepts and even command-line flags. One key area where it differs is that the client drives all of the configuration process while the server just complies to the best of its ability and provides a stream of results. This means that the server will not present test-results directly via its interface and also that TCP and UDP tests can be run against the same instance, potentially by many clients simultaneously.
83+
84+
In its normal mode of operation, the client will upload data to the server; when the `reverse` flag is set, the client will receive data.
85+
86+
Unlike _iperf3_, _rperf_ does not make use of a reserved port-range by default. This is so it can support an arbitrary number of clients in parallel without resource contention on what can only practically be a small number of contiguous ports. In its intended capacity, this shouldn't be a problem, but where non-permissive firewalls and NAT setups are concerned, the `--tcp[6]-port-pool` and `--udp[6]-port-pool` options may be used to allocate non-continguous ports to the set that will be used to receive traffic.
87+
88+
There also isn't a concept of testing throughput relative to a fixed quantity of data. Rather, the sole focus is on measuring throughput over a roughly known period of time.
89+
90+
Also of relevance is that, if the server is running in IPv6 mode and its host supports IPv4-mapping in a dual-stack configuration, both IPv4 and IPv6 clients can connect to the same instance.
10891

10992

11093
## building

src/client.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> {
9999
let mut complete = false;
100100

101101
//config-parsing and pre-connection setup
102+
let mut tcp_port_pool = tcp::receiver::TcpPortPool::new(
103+
args.value_of("tcp_port_pool").unwrap().to_string(),
104+
args.value_of("tcp6_port_pool").unwrap().to_string(),
105+
);
106+
let mut udp_port_pool = udp::receiver::UdpPortPool::new(
107+
args.value_of("udp_port_pool").unwrap().to_string(),
108+
args.value_of("udp6_port_pool").unwrap().to_string(),
109+
);
110+
102111
let cpu_affinity_manager = Arc::new(Mutex::new(crate::utils::cpu_affinity::CpuAffinityManager::new(args.value_of("affinity").unwrap())?));
103112

104113
let display_json:bool;
@@ -199,7 +208,7 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> {
199208
log::debug!("preparing UDP-receiver for stream {}...", stream_idx);
200209
let test = udp::receiver::UdpReceiver::new(
201210
test_definition.clone(), &(stream_idx as u8),
202-
&0,
211+
&mut udp_port_pool,
203212
&server_addr.ip(),
204213
&(download_config["receive_buffer"].as_i64().unwrap() as usize),
205214
)?;
@@ -214,7 +223,7 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> {
214223
log::debug!("preparing TCP-receiver for stream {}...", stream_idx);
215224
let test = tcp::receiver::TcpReceiver::new(
216225
test_definition.clone(), &(stream_idx as u8),
217-
&0,
226+
&mut tcp_port_pool,
218227
&server_addr.ip(),
219228
&(download_config["receive_buffer"].as_i64().unwrap() as usize),
220229
)?;

src/main.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,38 @@ fn main() {
201201
.short("N")
202202
.required(false)
203203
)
204+
.arg(
205+
Arg::with_name("tcp_port_pool")
206+
.help("an optional pool of IPv4 TCP ports over which data will be accepted; if omitted, any OS-assignable port is used; format: 1-10,19,21")
207+
.takes_value(true)
208+
.long("tcp-port-pool")
209+
.required(false)
210+
.default_value("")
211+
)
212+
.arg(
213+
Arg::with_name("tcp6_port_pool")
214+
.help("an optional pool of IPv6 TCP ports over which data will be accepted; if omitted, any OS-assignable port is used; format: 1-10,19,21")
215+
.takes_value(true)
216+
.long("tcp6-port-pool")
217+
.required(false)
218+
.default_value("")
219+
)
220+
.arg(
221+
Arg::with_name("udp_port_pool")
222+
.help("an optional pool of IPv4 UDP ports over which data will be accepted; if omitted, any OS-assignable port is used; format: 1-10,19,21")
223+
.takes_value(true)
224+
.long("udp-port-pool")
225+
.required(false)
226+
.default_value("")
227+
)
228+
.arg(
229+
Arg::with_name("udp6_port_pool")
230+
.help("an optional pool of IPv6 UDP ports over which data will be accepted; if omitted, any OS-assignable port is used; format: 1-10,19,21")
231+
.takes_value(true)
232+
.long("udp6-port-pool")
233+
.required(false)
234+
.default_value("")
235+
)
204236
.get_matches();
205237

206238
let mut env = env_logger::Env::default()

src/server.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* You should have received a copy of the GNU General Public License
1818
* along with rperf. If not, see <https://www.gnu.org/licenses/>.
1919
*/
20-
20+
2121
use std::error::Error;
2222
use std::io;
2323
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr};
@@ -53,7 +53,12 @@ static ALIVE:AtomicBool = AtomicBool::new(true);
5353
static CLIENTS:AtomicU16 = AtomicU16::new(0);
5454

5555

56-
fn handle_client(stream:&mut TcpStream, cpu_affinity_manager:Arc<Mutex<crate::utils::cpu_affinity::CpuAffinityManager>>) -> BoxResult<()> {
56+
fn handle_client(
57+
stream:&mut TcpStream,
58+
cpu_affinity_manager:Arc<Mutex<crate::utils::cpu_affinity::CpuAffinityManager>>,
59+
tcp_port_pool:Arc<Mutex<tcp::receiver::TcpPortPool>>,
60+
udp_port_pool:Arc<Mutex<udp::receiver::UdpPortPool>>,
61+
) -> BoxResult<()> {
5762
let mut started = false;
5863
let peer_addr = stream.peer_addr()?;
5964

@@ -95,12 +100,14 @@ fn handle_client(stream:&mut TcpStream, cpu_affinity_manager:Arc<Mutex<crate::ut
95100
if payload.get("family").unwrap_or(&serde_json::json!("tcp")).as_str().unwrap() == "udp" {
96101
log::info!("[{}] preparing for UDP test with {} streams...", &peer_addr, stream_count);
97102

103+
let mut c_udp_port_pool = udp_port_pool.lock().unwrap();
104+
98105
let test_definition = udp::UdpTestDefinition::new(&payload)?;
99106
for stream_idx in 0..stream_count {
100107
log::debug!("[{}] preparing UDP-receiver for stream {}...", &peer_addr, stream_idx);
101108
let test = udp::receiver::UdpReceiver::new(
102109
test_definition.clone(), &(stream_idx as u8),
103-
&0,
110+
&mut c_udp_port_pool,
104111
&peer_addr.ip(),
105112
&(payload["receive_buffer"].as_i64().unwrap() as usize),
106113
)?;
@@ -110,12 +117,14 @@ fn handle_client(stream:&mut TcpStream, cpu_affinity_manager:Arc<Mutex<crate::ut
110117
} else { //TCP
111118
log::info!("[{}] preparing for TCP test with {} streams...", &peer_addr, stream_count);
112119

120+
let mut c_tcp_port_pool = tcp_port_pool.lock().unwrap();
121+
113122
let test_definition = tcp::TcpTestDefinition::new(&payload)?;
114123
for stream_idx in 0..stream_count {
115124
log::debug!("[{}] preparing TCP-receiver for stream {}...", &peer_addr, stream_idx);
116125
let test = tcp::receiver::TcpReceiver::new(
117126
test_definition.clone(), &(stream_idx as u8),
118-
&0,
127+
&mut c_tcp_port_pool,
119128
&peer_addr.ip(),
120129
&(payload["receive_buffer"].as_i64().unwrap() as usize),
121130
)?;
@@ -274,6 +283,15 @@ impl Drop for ClientThreadMonitor {
274283

275284
pub fn serve(args:ArgMatches) -> BoxResult<()> {
276285
//config-parsing and pre-connection setup
286+
let tcp_port_pool = Arc::new(Mutex::new(tcp::receiver::TcpPortPool::new(
287+
args.value_of("tcp_port_pool").unwrap().to_string(),
288+
args.value_of("tcp6_port_pool").unwrap().to_string(),
289+
)));
290+
let udp_port_pool = Arc::new(Mutex::new(udp::receiver::UdpPortPool::new(
291+
args.value_of("udp_port_pool").unwrap().to_string(),
292+
args.value_of("udp6_port_pool").unwrap().to_string(),
293+
)));
294+
277295
let cpu_affinity_manager = Arc::new(Mutex::new(crate::utils::cpu_affinity::CpuAffinityManager::new(args.value_of("affinity").unwrap())?));
278296

279297
let client_limit:u16 = args.value_of("client_limit").unwrap().parse()?;
@@ -320,6 +338,8 @@ pub fn serve(args:ArgMatches) -> BoxResult<()> {
320338
CLIENTS.fetch_sub(1, Ordering::Relaxed);
321339
} else {
322340
let c_cam = cpu_affinity_manager.clone();
341+
let c_tcp_port_pool = tcp_port_pool.clone();
342+
let c_udp_port_pool = udp_port_pool.clone();
323343
let thread_builder = thread::Builder::new()
324344
.name(address.to_string().into());
325345
thread_builder.spawn(move || {
@@ -328,7 +348,7 @@ pub fn serve(args:ArgMatches) -> BoxResult<()> {
328348
client_address: address.to_string(),
329349
};
330350

331-
match handle_client(&mut stream, c_cam) {
351+
match handle_client(&mut stream, c_cam, c_tcp_port_pool, c_udp_port_pool) {
332352
Ok(_) => (),
333353
Err(e) => log::error!("error in client-handler: {}", e),
334354
}

src/stream/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,26 @@ pub trait TestStream {
3939
/// stops a running test
4040
fn stop(&mut self);
4141
}
42+
43+
fn parse_port_spec(port_spec:String) -> Vec<u16> {
44+
let mut ports = Vec::<u16>::new();
45+
if !port_spec.is_empty() {
46+
for range in port_spec.split(',') {
47+
if range.contains('-') {
48+
let mut range_spec = range.split('-');
49+
let range_first = range_spec.next().unwrap().parse::<u16>().unwrap();
50+
let range_last = range_spec.last().unwrap().parse::<u16>().unwrap();
51+
52+
for port in range_first..=range_last {
53+
ports.push(port);
54+
}
55+
} else {
56+
ports.push(range.parse::<u16>().unwrap());
57+
}
58+
}
59+
60+
ports.sort();
61+
}
62+
63+
return ports;
64+
}

0 commit comments

Comments
 (0)