From 8e818aae1f5feb0c45be894db791daf03732ccb3 Mon Sep 17 00:00:00 2001 From: drunkirishcoder Date: Mon, 29 Mar 2021 18:37:25 -0400 Subject: [PATCH] general cleanup --- bench/Cargo.toml | 7 +- bench/combinators.rs | 229 ------ bench/mbuf.rs | 2 +- bench/packets.rs | 6 +- core/Cargo.toml | 24 +- core/src/batch/emit.rs | 56 -- core/src/batch/filter.rs | 69 -- core/src/batch/filter_map.rs | 82 -- core/src/batch/for_each.rs | 63 -- core/src/batch/group_by.rs | 207 ----- core/src/batch/inspect.rs | 61 -- core/src/batch/map.rs | 67 -- core/src/batch/mod.rs | 736 ------------------ core/src/batch/poll.rs | 71 -- core/src/batch/replace.rs | 89 --- core/src/batch/rxtx.rs | 89 --- core/src/batch/send.rs | 151 ---- core/src/config.rs | 513 ------------ core/src/dpdk/kni.rs | 411 ---------- core/src/dpdk/mempool.rs | 180 ----- core/src/dpdk/mod.rs | 244 ------ core/src/dpdk/port.rs | 651 ---------------- core/src/dpdk/stats.rs | 130 ---- core/src/ffi/dpdk.rs | 33 +- core/src/ffi/mod.rs | 2 - core/src/ffi/pcap.rs | 41 +- core/src/lib.rs | 21 +- core/src/metrics.rs | 140 ---- core/src/packets/arp.rs | 19 +- core/src/packets/ethernet.rs | 11 +- core/src/packets/icmp/v4/echo_reply.rs | 7 +- core/src/packets/icmp/v4/echo_request.rs | 7 +- core/src/packets/icmp/v4/mod.rs | 8 +- core/src/packets/icmp/v4/redirect.rs | 7 +- core/src/packets/icmp/v4/time_exceeded.rs | 7 +- core/src/packets/icmp/v6/echo_reply.rs | 7 +- core/src/packets/icmp/v6/echo_request.rs | 7 +- core/src/packets/icmp/v6/mod.rs | 12 +- core/src/packets/icmp/v6/ndp/mod.rs | 19 +- .../packets/icmp/v6/ndp/neighbor_advert.rs | 7 +- .../packets/icmp/v6/ndp/neighbor_solicit.rs | 7 +- .../icmp/v6/ndp/options/link_layer_addr.rs | 7 +- core/src/packets/icmp/v6/ndp/options/mtu.rs | 7 +- .../icmp/v6/ndp/options/prefix_info.rs | 7 +- .../packets/icmp/v6/ndp/options/redirected.rs | 7 +- core/src/packets/icmp/v6/ndp/redirect.rs | 7 +- core/src/packets/icmp/v6/ndp/router_advert.rs | 7 +- .../src/packets/icmp/v6/ndp/router_solicit.rs | 7 +- core/src/packets/icmp/v6/time_exceeded.rs | 7 +- core/src/packets/icmp/v6/too_big.rs | 7 +- ...tination_unreachable.rs => unreachable.rs} | 7 +- core/src/packets/ip/v4.rs | 7 +- core/src/packets/ip/v6/fragment.rs | 8 +- core/src/packets/ip/v6/mod.rs | 7 +- core/src/packets/ip/v6/srh.rs | 9 +- core/src/{dpdk => packets}/mbuf.rs | 77 +- core/src/packets/mod.rs | 17 +- core/src/packets/size_of.rs | 85 ++ core/src/packets/tcp.rs | 10 +- core/src/packets/udp.rs | 10 +- core/src/pcap.rs | 340 -------- core/src/rt2/mod.rs | 234 ------ core/src/{rt2 => runtime}/config.rs | 2 +- core/src/runtime/core_map.rs | 386 --------- core/src/{rt2 => runtime}/lcore.rs | 2 +- core/src/{rt2 => runtime}/mempool.rs | 0 core/src/runtime/mod.rs | 718 ++++------------- core/src/{rt2 => runtime}/pcap_dump.rs | 3 + core/src/{rt2 => runtime}/port.rs | 38 +- core/src/testils/criterion.rs | 55 -- core/src/testils/mod.rs | 4 +- core/src/testils/packet.rs | 5 +- core/src/testils/proptest/arbitrary.rs | 2 +- core/src/testils/proptest/strategy.rs | 6 +- examples/kni/Cargo.toml | 4 +- examples/kni/main.rs | 8 +- examples/nat64/Cargo.toml | 4 +- examples/nat64/main.rs | 8 +- examples/ping4d/Cargo.toml | 4 +- examples/ping4d/main.rs | 7 +- examples/pktdump/Cargo.toml | 4 +- examples/pktdump/main.rs | 8 +- examples/skeleton/Cargo.toml | 4 +- examples/skeleton/main.rs | 4 +- examples/syn-flood/Cargo.toml | 4 +- examples/syn-flood/main.rs | 8 +- ffi/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- macros/src/derive_packet.rs | 2 + macros/src/lib.rs | 2 +- 90 files changed, 498 insertions(+), 6172 deletions(-) delete mode 100644 bench/combinators.rs delete mode 100644 core/src/batch/emit.rs delete mode 100644 core/src/batch/filter.rs delete mode 100644 core/src/batch/filter_map.rs delete mode 100644 core/src/batch/for_each.rs delete mode 100644 core/src/batch/group_by.rs delete mode 100644 core/src/batch/inspect.rs delete mode 100644 core/src/batch/map.rs delete mode 100644 core/src/batch/mod.rs delete mode 100644 core/src/batch/poll.rs delete mode 100644 core/src/batch/replace.rs delete mode 100644 core/src/batch/rxtx.rs delete mode 100644 core/src/batch/send.rs delete mode 100644 core/src/config.rs delete mode 100644 core/src/dpdk/kni.rs delete mode 100644 core/src/dpdk/mempool.rs delete mode 100644 core/src/dpdk/mod.rs delete mode 100644 core/src/dpdk/port.rs delete mode 100644 core/src/dpdk/stats.rs delete mode 100644 core/src/metrics.rs rename core/src/packets/icmp/v6/{destination_unreachable.rs => unreachable.rs} (98%) rename core/src/{dpdk => packets}/mbuf.rs (92%) create mode 100644 core/src/packets/size_of.rs delete mode 100644 core/src/pcap.rs delete mode 100644 core/src/rt2/mod.rs rename core/src/{rt2 => runtime}/config.rs (99%) delete mode 100644 core/src/runtime/core_map.rs rename core/src/{rt2 => runtime}/lcore.rs (98%) rename core/src/{rt2 => runtime}/mempool.rs (100%) rename core/src/{rt2 => runtime}/pcap_dump.rs (97%) rename core/src/{rt2 => runtime}/port.rs (96%) diff --git a/bench/Cargo.toml b/bench/Cargo.toml index e9d23606..2a4de6b8 100644 --- a/bench/Cargo.toml +++ b/bench/Cargo.toml @@ -11,7 +11,7 @@ Benchmarks for Capsule. [dev-dependencies] anyhow = "1.0" -capsule = { version = "0.1", path = "../core", features = ["testils"] } +capsule = { version = "0.2", path = "../core", features = ["testils"] } criterion = "0.3" proptest = "1.0" @@ -20,11 +20,6 @@ name = "packets" path = "packets.rs" harness = false -[[bench]] -name = "combinators" -path = "combinators.rs" -harness = false - [[bench]] name = "mbuf" path = "mbuf.rs" diff --git a/bench/combinators.rs b/bench/combinators.rs deleted file mode 100644 index 2866bc53..00000000 --- a/bench/combinators.rs +++ /dev/null @@ -1,229 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use anyhow::Result; -use capsule::batch::{Batch, Either}; -use capsule::packets::ip::v4::Ipv4; -use capsule::packets::{Ethernet, Packet}; -use capsule::testils::criterion::BencherExt; -use capsule::testils::proptest::*; -use capsule::{compose, Mbuf}; -use criterion::{criterion_group, criterion_main, Criterion}; -use proptest::prelude::*; -use proptest::strategy; - -const BATCH_SIZE: usize = 500; - -fn filter_true(batch: impl Batch) -> impl Batch { - batch.filter(|_p| true) -} - -fn filter_false(batch: impl Batch) -> impl Batch { - batch.filter(|_p| false) -} - -#[capsule::bench(mempool_capacity = 511)] -fn filters_batch(c: &mut Criterion) { - let mut group = c.benchmark_group("combinators::filter"); - - group.bench_function("combinators::filter_true", |b| { - let s = any::(); - b.iter_proptest_combinators(s, filter_true, BATCH_SIZE) - }); - - group.bench_function("combinators::filter_false", |b| { - let s = any::(); - b.iter_proptest_combinators(s, filter_false, BATCH_SIZE) - }); - - group.finish() -} - -fn filter_map(batch: impl Batch) -> impl Batch { - batch.filter_map(|p| { - let ethernet = p.parse::()?; - Ok(Either::Keep(ethernet)) - }) -} - -fn map_then_filter(batch: impl Batch) -> impl Batch { - batch.map(|p| p.parse::()).filter(|_p| true) -} - -#[capsule::bench(mempool_capacity = 511)] -fn filter_map_vs_map_then_filter_batch(c: &mut Criterion) { - let mut group = c.benchmark_group("combinators::filter_map_vs_map_then_filter"); - - group.bench_function("combinators::filter_map", |b| { - let s = v4_udp(); - b.iter_proptest_combinators(s, filter_map, BATCH_SIZE) - }); - - group.bench_function("combinators::map_then_filter", |b| { - let s = v4_udp(); - b.iter_proptest_combinators(s, map_then_filter, BATCH_SIZE) - }); - - group.finish() -} - -fn map(batch: impl Batch) -> impl Batch { - batch.map(|p| p.parse::()) -} - -fn no_batch_map(mbuf: Mbuf) -> Result { - mbuf.parse::() -} - -#[capsule::bench(mempool_capacity = 511)] -fn map_batch_vs_parse(c: &mut Criterion) { - let mut group = c.benchmark_group("combinators::map_batch_vs_parse"); - - group.bench_function("combinators::map", |b| { - let s = v4_udp(); - b.iter_proptest_combinators(s, map, BATCH_SIZE) - }); - - group.bench_function("combinators::no_batch_map", |b| { - let s = v4_udp(); - b.iter_proptest_batched(s, no_batch_map, BATCH_SIZE) - }); -} - -#[capsule::bench(mempool_capacity = 1023)] -fn map_batches(c: &mut Criterion) { - let mut group = c.benchmark_group("combinators::map_on_diff_batch_sizes"); - - group.bench_function("combinators::map_10", |b| { - let s = v4_udp(); - b.iter_proptest_combinators(s, map, 10) - }); - - group.bench_function("combinators::map_50", |b| { - let s = v4_udp(); - b.iter_proptest_combinators(s, map, 50) - }); - - group.bench_function("combinators::map_150", |b| { - let s = v4_udp(); - b.iter_proptest_combinators(s, map, 150) - }); - - group.bench_function("combinators::map_500", |b| { - let s = v4_udp(); - b.iter_proptest_combinators(s, map, 500) - }); - - group.bench_function("combinators::map_1000", |b| { - let s = v4_udp(); - b.iter_proptest_combinators(s, map, 1000) - }); - - group.finish() -} - -fn map_parse_errors(batch: impl Batch) -> impl Batch { - batch.map(|p| p.parse::()?.parse::()) -} - -#[capsule::bench(mempool_capacity = 511)] -fn map_errors(c: &mut Criterion) { - let mut group = c.benchmark_group("combinators::map_errors_vs_no_errors"); - - group.bench_function("combinators::map_no_errors", |b| { - let s = v4_udp(); - b.iter_proptest_combinators(s, map_parse_errors, BATCH_SIZE) - }); - - group.bench_function("combinators::map_with_errors", |b| { - let s = strategy::Union::new_weighted(vec![(8, v4_udp().boxed()), (2, v6_udp().boxed())]); - b.iter_proptest_combinators(s, map_parse_errors, BATCH_SIZE) - }); -} - -static mut COUNTER: u32 = 0; -fn group_by(batch: impl Batch) -> impl Batch { - unsafe { COUNTER += 1 }; - - unsafe { - batch.group_by( - |_p| COUNTER % 2, - |groups| { - compose!(groups { - 0 => |group| { - group - } - _ => |group| { - group - } - }) - }, - ) - } -} - -#[capsule::bench(mempool_capacity = 511)] -fn group_by_batch(c: &mut Criterion) { - c.bench_function("combinators::group_by", |b| { - let s = any::(); - b.iter_proptest_combinators(s, group_by, BATCH_SIZE) - }); -} - -fn replace(batch: impl Batch) -> impl Batch { - batch.replace(|_p| Mbuf::new()) -} - -fn no_batch_replace(_mbuf: Mbuf) -> Result { - Mbuf::new() -} - -#[capsule::bench(mempool_capacity = 511)] -fn replace_batch(c: &mut Criterion) { - let mut group = c.benchmark_group("combinators::replace_with_new_mbuf_vs_create_new_mbuf"); - - group.bench_function("combinators::replace", |b| { - let s = any::(); - b.iter_proptest_combinators(s, replace, BATCH_SIZE) - }); - - group.bench_function("combinators::no_batch_replace", |b| { - let s = any::(); - b.iter_proptest_batched(s, no_batch_replace, BATCH_SIZE) - }); - - group.finish() -} - -fn bench_config() -> Criterion { - Criterion::default().with_plots() -} - -criterion_group! { - name = benches; - config=bench_config(); - targets=filters_batch, - filter_map_vs_map_then_filter_batch, - map_batch_vs_parse, - group_by_batch, - replace_batch, - map_batches, - map_errors, -} - -criterion_main!(benches); diff --git a/bench/mbuf.rs b/bench/mbuf.rs index c8258e02..62f0cebd 100644 --- a/bench/mbuf.rs +++ b/bench/mbuf.rs @@ -17,7 +17,7 @@ */ use anyhow::Result; -use capsule::Mbuf; +use capsule::packets::Mbuf; use criterion::{criterion_group, criterion_main, Criterion}; const BATCH_SIZE: usize = 100; diff --git a/bench/packets.rs b/bench/packets.rs index a7a381c4..61ce7c65 100644 --- a/bench/packets.rs +++ b/bench/packets.rs @@ -17,13 +17,15 @@ */ use anyhow::Result; +use capsule::fieldmap; +use capsule::packets::ethernet::Ethernet; use capsule::packets::ip::v4::Ipv4; use capsule::packets::ip::v6::{Ipv6, SegmentRouting}; -use capsule::packets::{Ethernet, Packet, Udp4}; +use capsule::packets::udp::Udp4; +use capsule::packets::{Mbuf, Packet}; use capsule::testils::criterion::BencherExt; use capsule::testils::proptest::*; use capsule::testils::{PacketExt, Rvg}; -use capsule::{fieldmap, Mbuf}; use criterion::{criterion_group, criterion_main, Criterion}; use proptest::prelude::*; use std::net::Ipv6Addr; diff --git a/core/Cargo.toml b/core/Cargo.toml index abdfe1ac..951da38b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "capsule" -version = "0.1.5" +version = "0.2.0" authors = ["Capsule Developers "] license = "Apache-2.0" edition = "2018" @@ -23,24 +23,17 @@ doctest = false anyhow = "1.0" async-channel = "1.6" async-executor = "1.4" -capsule-ffi = { version = "0.1.5", path = "../ffi" } -capsule-macros = { version = "0.1.5", path = "../macros" } +capsule-ffi = { version = "0.2.0", path = "../ffi" } +capsule-macros = { version = "0.2.0", path = "../macros" } clap = "2.33" criterion = { version = "0.3", optional = true } -futures-lite = "1.11" -futures-preview = "=0.3.0-alpha.19" +futures-lite = "1.12" libc = "0.2" -metrics-core = { version = "0.5", optional = true } -metrics-runtime = { version = "0.13", optional = true, default-features = false } -once_cell = "1.7" +once_cell = "1.9" proptest = { version = "1.0", optional = true } -regex = "1" +regex = "1.5" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" -tokio = "=0.2.0-alpha.6" -tokio-executor = { version = "=0.2.0-alpha.6", features = ["current-thread", "threadpool"] } -tokio-net = { version = "=0.2.0-alpha.6", features = ["signal"] } -tokio-timer = "=0.3.0-alpha.6" toml = "0.5" tracing = "0.1" @@ -49,10 +42,9 @@ criterion = "0.3" proptest = { version = "1.0", default-features = false, features = ["default-code-coverage"] } [features] -default = ["metrics"] +default = [] compile_failure = [] # compiler tests to check mutability rules are followed -full = ["metrics", "pcap-dump", "testils"] -metrics = ["metrics-core", "metrics-runtime"] +full = ["pcap-dump", "testils"] pcap-dump = [] testils = ["criterion", "proptest"] diff --git a/core/src/batch/emit.rs b/core/src/batch/emit.rs deleted file mode 100644 index a7c677fd..00000000 --- a/core/src/batch/emit.rs +++ /dev/null @@ -1,56 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Batch, Disposition, PacketTx}; -use crate::packets::Packet; - -/// A batch that transmits the packets through the specified [`PacketTx`]. -/// -/// [`PacketTx`]: crate::batch::PacketTx -#[allow(missing_debug_implementations)] -pub struct Emit { - batch: B, - tx: Tx, -} - -impl Emit { - /// Creates a new `Emit` batch. - #[inline] - pub fn new(batch: B, tx: Tx) -> Self { - Emit { batch, tx } - } -} - -impl Batch for Emit { - type Item = B::Item; - - #[inline] - fn replenish(&mut self) { - self.batch.replenish(); - } - - #[inline] - fn next(&mut self) -> Option> { - self.batch.next().map(|disp| { - disp.map(|pkt| { - self.tx.transmit(vec![pkt.reset()]); - Disposition::Emit - }) - }) - } -} diff --git a/core/src/batch/filter.rs b/core/src/batch/filter.rs deleted file mode 100644 index 119d14a5..00000000 --- a/core/src/batch/filter.rs +++ /dev/null @@ -1,69 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Batch, Disposition}; -use crate::packets::Packet; - -/// A batch that filters the packets of the underlying batch. -/// -/// If the predicate evaluates to `false`, the packet is marked as dropped -/// and will short-circuit the remainder of the pipeline. -#[allow(missing_debug_implementations)] -pub struct Filter -where - P: FnMut(&B::Item) -> bool, -{ - batch: B, - predicate: P, -} - -impl Filter -where - P: FnMut(&B::Item) -> bool, -{ - /// Creates a new `Filter` batch. - #[inline] - pub fn new(batch: B, predicate: P) -> Self { - Filter { batch, predicate } - } -} - -impl Batch for Filter -where - P: FnMut(&B::Item) -> bool, -{ - type Item = B::Item; - - #[inline] - fn replenish(&mut self) { - self.batch.replenish(); - } - - #[inline] - fn next(&mut self) -> Option> { - self.batch.next().map(|disp| { - disp.map(|pkt| { - if (self.predicate)(&pkt) { - Disposition::Act(pkt) - } else { - Disposition::Drop(pkt.reset()) - } - }) - }) - } -} diff --git a/core/src/batch/filter_map.rs b/core/src/batch/filter_map.rs deleted file mode 100644 index ceebafba..00000000 --- a/core/src/batch/filter_map.rs +++ /dev/null @@ -1,82 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Batch, Disposition}; -use crate::packets::Packet; -use crate::Mbuf; -use anyhow::Result; - -/// The result of a [`filter_map`]. -/// -/// [`filter_map`]: crate::batch::Batch::filter_map -#[allow(missing_debug_implementations)] -pub enum Either { - /// Keeps the packet as mapped result. - Keep(T), - - /// Drops the packet. - Drop(Mbuf), -} - -/// A batch that both filters and maps the packets of the underlying batch. -/// -/// If the closure returns `Drop`, the packet is marked as dropped. On -/// error, the packet is marked as aborted. In both scenarios, it will -/// short-circuit the remainder of the pipeline. -#[allow(missing_debug_implementations)] -pub struct FilterMap -where - F: FnMut(B::Item) -> Result>, -{ - batch: B, - f: F, -} - -impl FilterMap -where - F: FnMut(B::Item) -> Result>, -{ - /// Creates a new `FilterMap` batch. - #[inline] - pub fn new(batch: B, f: F) -> Self { - FilterMap { batch, f } - } -} - -impl Batch for FilterMap -where - F: FnMut(B::Item) -> Result>, -{ - type Item = T; - - #[inline] - fn replenish(&mut self) { - self.batch.replenish(); - } - - #[inline] - fn next(&mut self) -> Option> { - self.batch.next().map(|disp| { - disp.map(|orig| match (self.f)(orig) { - Ok(Either::Keep(new)) => Disposition::Act(new), - Ok(Either::Drop(mbuf)) => Disposition::Drop(mbuf), - Err(e) => Disposition::Abort(e), - }) - }) - } -} diff --git a/core/src/batch/for_each.rs b/core/src/batch/for_each.rs deleted file mode 100644 index eb4c0f4c..00000000 --- a/core/src/batch/for_each.rs +++ /dev/null @@ -1,63 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Batch, Disposition}; -use anyhow::Result; - -/// A batch that calls a closure on packets in the underlying batch. -#[allow(missing_debug_implementations)] -pub struct ForEach -where - F: FnMut(&B::Item) -> Result<()>, -{ - batch: B, - f: F, -} - -impl ForEach -where - F: FnMut(&B::Item) -> Result<()>, -{ - /// Creates a new `ForEach` batch. - #[inline] - pub fn new(batch: B, f: F) -> Self { - ForEach { batch, f } - } -} - -impl Batch for ForEach -where - F: FnMut(&B::Item) -> Result<()>, -{ - type Item = B::Item; - - #[inline] - fn replenish(&mut self) { - self.batch.replenish(); - } - - #[inline] - fn next(&mut self) -> Option> { - self.batch.next().map(|disp| { - disp.map(|pkt| match (self.f)(&pkt) { - Ok(_) => Disposition::Act(pkt), - Err(e) => Disposition::Abort(e), - }) - }) - } -} diff --git a/core/src/batch/group_by.rs b/core/src/batch/group_by.rs deleted file mode 100644 index 3836f4e8..00000000 --- a/core/src/batch/group_by.rs +++ /dev/null @@ -1,207 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Batch, Disposition}; -use crate::packets::Packet; -use std::cell::Cell; -use std::collections::{HashMap, VecDeque}; -use std::hash::Hash; -use std::rc::Rc; - -/// A bridge between the main batch pipeline and the branch pipelines -/// created by the [`GroupBy`] combinator. Packets can be fed one at a time -/// through the bridge. Because the pipeline execution is depth first, -/// this is the most efficient way storage wise. -#[allow(missing_debug_implementations)] -#[derive(Default)] -pub struct Bridge(Rc>>); - -impl Bridge { - /// Creates a new, empty bridge. - pub fn new() -> Self { - Bridge(Rc::new(Cell::new(None))) - } - - /// Feeds a packet into the bridge container. - pub fn set(&self, pkt: T) { - self.0.set(Some(pkt)); - } -} - -impl Clone for Bridge { - fn clone(&self) -> Self { - Bridge(Rc::clone(&self.0)) - } -} - -impl Batch for Bridge { - type Item = T; - - fn replenish(&mut self) { - // nothing to do - } - - fn next(&mut self) -> Option> { - self.0.take().map(Disposition::Act) - } -} - -/// Builder closure for a sub batch from a bridge. -pub type GroupByBatchBuilder = dyn FnOnce(Bridge) -> Box>; - -/// A batch that splits the underlying batch into multiple sub batches. -/// -/// A closure is used to extract the discriminator used to determine how to -/// split the packets in the batch. If a packet is unmatched, it will be -/// marked as dropped. On error, the packet is marked as aborted. -/// -/// All the sub batches must have the same packet type as the underlying -/// batch. -#[allow(missing_debug_implementations)] -pub struct GroupBy -where - D: Eq + Clone + Hash, - S: Fn(&B::Item) -> D, -{ - batch: B, - selector: S, - bridge: Bridge, - groups: HashMap>>, - catchall: Box>, - fanouts: VecDeque>, -} - -impl GroupBy -where - D: Eq + Clone + Hash, - S: Fn(&B::Item) -> D, -{ - /// Creates a new `GroupBy` batch. - #[inline] - pub fn new(batch: B, selector: S, composer: C) -> Self - where - C: FnOnce(&mut HashMap, Box>>), - { - // get the builders for the sub batches - let mut builders = HashMap::new(); - composer(&mut builders); - - let bridge = Bridge::new(); - - // build the catchall batch pipeline - let catchall = builders.remove(&None).unwrap()(bridge.clone()); - - // build the rest of the batch pipelines - let groups = builders - .into_iter() - .map(|(key, build)| { - let key = key.unwrap(); - let group = build(bridge.clone()); - (key, group) - }) - .collect::>(); - - GroupBy { - batch, - selector, - bridge, - groups, - catchall, - fanouts: VecDeque::new(), - } - } -} - -impl Batch for GroupBy -where - D: Eq + Clone + Hash, - S: Fn(&B::Item) -> D, -{ - type Item = B::Item; - - #[inline] - fn replenish(&mut self) { - self.batch.replenish(); - } - - #[inline] - fn next(&mut self) -> Option> { - if let Some(disp) = self.fanouts.pop_front() { - Some(disp) - } else { - self.batch.next().map(|disp| { - disp.map(|pkt| { - // gets the discriminator key - let key = (self.selector)(&pkt); - - // feeds this packet through the bridge - self.bridge.set(pkt); - - // runs the packet through. the sub-batch could be a fanout - // that produces multiple packets from one input. they are - // temporarily stored in a queue and returned in the subsequent - // iterations. - let batch = match self.groups.get_mut(&key) { - Some(group) => group, - None => &mut self.catchall, - }; - - while let Some(next) = batch.next() { - self.fanouts.push_back(next) - } - - self.fanouts.pop_front().unwrap() - }) - }) - } - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __compose { - ($map:ident, $($key:expr => |$arg:tt| $body:block),*) => {{ - $( - $map.insert(Some($key), Box::new(|$arg| Box::new($body))); - )* - }}; -} - -/// Composes the batch builders for the [`group_by`] combinator. -/// -/// [`group_by`]: crate::batch::Batch::group_by -#[macro_export] -macro_rules! compose { - ($map:ident { $($key:expr => |$arg:tt| $body:block)+ }) => {{ - $crate::__compose!($map, $($key => |$arg| $body),*); - $map.insert(None, Box::new(|group| Box::new(group))); - }}; - ($map:ident { $($key:expr => |$arg:tt| $body:block)+ _ => |$_arg:tt| $_body:block }) => {{ - $crate::__compose!($map, $($key => |$arg| $body),*); - $map.insert(None, Box::new(|$_arg| Box::new($_body))); - }}; - ($map:ident { $($key:expr),+ => |$arg:tt| $body:block }) => {{ - $crate::compose!($map { $($key => |$arg| $body)+ }); - }}; - ($map:ident { $($key:expr),+ => |$arg:tt| $body:block _ => |$_arg:tt| $_body:block }) => {{ - $crate::compose!($map { $($key => |$arg| $body)+ _ => |$_arg| $_body }); - }}; - ($map:ident { _ => |$_arg:tt| $_body:block }) => {{ - $map.insert(None, Box::new(|$_arg| Box::new($_body))); - }}; -} diff --git a/core/src/batch/inspect.rs b/core/src/batch/inspect.rs deleted file mode 100644 index 82fa4395..00000000 --- a/core/src/batch/inspect.rs +++ /dev/null @@ -1,61 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Batch, Disposition}; - -/// A batch that calls a closure on packets in the underlying batch, including -/// ones that are already dropped, emitted or aborted. -#[allow(missing_debug_implementations)] -pub struct Inspect -where - F: FnMut(&Disposition), -{ - batch: B, - f: F, -} - -impl Inspect -where - F: FnMut(&Disposition), -{ - /// Creates a new `Inspect` batch. - #[inline] - pub fn new(batch: B, f: F) -> Self { - Inspect { batch, f } - } -} - -impl Batch for Inspect -where - F: FnMut(&Disposition), -{ - type Item = B::Item; - - #[inline] - fn replenish(&mut self) { - self.batch.replenish(); - } - - #[inline] - fn next(&mut self) -> Option> { - self.batch.next().map(|disp| { - (self.f)(&disp); - disp - }) - } -} diff --git a/core/src/batch/map.rs b/core/src/batch/map.rs deleted file mode 100644 index d52c4dfe..00000000 --- a/core/src/batch/map.rs +++ /dev/null @@ -1,67 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Batch, Disposition}; -use crate::packets::Packet; -use anyhow::Result; - -/// A batch that maps the packets of the underlying batch. -/// -/// On error, the packet is marked as `aborted` and will short-circuit the -/// remainder of the pipeline. -#[allow(missing_debug_implementations)] -pub struct Map -where - F: FnMut(B::Item) -> Result, -{ - batch: B, - f: F, -} - -impl Map -where - F: FnMut(B::Item) -> Result, -{ - /// Creates a new `Map` batch. - #[inline] - pub fn new(batch: B, f: F) -> Self { - Map { batch, f } - } -} - -impl Batch for Map -where - F: FnMut(B::Item) -> Result, -{ - type Item = T; - - #[inline] - fn replenish(&mut self) { - self.batch.replenish(); - } - - #[inline] - fn next(&mut self) -> Option> { - self.batch.next().map(|disp| { - disp.map(|orig| match (self.f)(orig) { - Ok(new) => Disposition::Act(new), - Err(e) => Disposition::Abort(e), - }) - }) - } -} diff --git a/core/src/batch/mod.rs b/core/src/batch/mod.rs deleted file mode 100644 index e1a82b80..00000000 --- a/core/src/batch/mod.rs +++ /dev/null @@ -1,736 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -//! Combinators that can be applied to batches of packets within a pipeline. - -mod emit; -mod filter; -mod filter_map; -mod for_each; -mod group_by; -mod inspect; -mod map; -mod poll; -mod replace; -mod rxtx; -mod send; - -pub use self::emit::*; -pub use self::filter::*; -pub use self::filter_map::*; -pub use self::for_each::*; -pub use self::group_by::*; -pub use self::inspect::*; -pub use self::map::*; -pub use self::poll::*; -pub use self::replace::*; -pub use self::rxtx::*; -pub use self::send::*; - -use crate::packets::Packet; -use crate::Mbuf; -use anyhow::{Error, Result}; -use std::collections::HashMap; -use std::hash::Hash; - -/// Way to categorize the packets of a batch inside a processing pipeline. -/// The disposition instructs the combinators how to process a packet. -#[allow(missing_debug_implementations)] -pub enum Disposition { - /// Indicating the packet should be processed. - Act(T), - - /// Indicating the packet has already been sent, possibly through a - /// different [`PacketTx`]. - /// - /// [`PacketTx`]: crate::batch::PacketTx - Emit, - - /// Indicating the packet is intentionally dropped from the output. - Drop(Mbuf), - - /// Indicating an error has occurred during processing. The packet will - /// be dropped from the output. Aborted packets are not bulk freed. - /// The packet is returned to mempool when it goes out of scope. - Abort(Error), -} - -impl Disposition { - /// Easy way to map a `Disposition` to a `Disposition` by reducing - /// it down to a map from `T` to `Disposition`. - fn map(self, f: F) -> Disposition - where - F: FnOnce(T) -> Disposition, - { - match self { - Disposition::Act(packet) => f(packet), - Disposition::Emit => Disposition::Emit, - Disposition::Drop(mbuf) => Disposition::Drop(mbuf), - Disposition::Abort(err) => Disposition::Abort(err), - } - } - - /// Returns whether the disposition is `Act`. - pub fn is_act(&self) -> bool { - matches!(self, Disposition::Act(_)) - } - - /// Returns whether the disposition is `Emit`. - pub fn is_emit(&self) -> bool { - matches!(self, Disposition::Emit) - } - - /// Returns whether the disposition is `Drop`. - pub fn is_drop(&self) -> bool { - matches!(self, Disposition::Drop(_)) - } - - /// Returns whether the disposition is `Abort`. - pub fn is_abort(&self) -> bool { - matches!(self, Disposition::Abort(_)) - } -} - -/// Types that can receive packets. -pub trait PacketRx { - /// Receives a batch of packets. - fn receive(&mut self) -> Vec; -} - -/// Types that can trasmit packets. -pub trait PacketTx { - /// Transmits a batch of packets. - fn transmit(&mut self, packets: Vec); -} - -/// Common behaviors to apply on batches of packets. -pub trait Batch { - /// The packet type. - type Item: Packet; - - /// Replenishes the batch with new packets from the source. - fn replenish(&mut self); - - /// Returns the disposition of the next packet in the batch. - /// - /// A value of `None` indicates that the batch is exhausted. To start - /// the next cycle, call [`replenish`] first. - /// - /// [`replenish`]: Batch::replenish - fn next(&mut self) -> Option>; - - /// Creates a batch that transmits all packets through the specified - /// [`PacketTx`]. - /// - /// Use when packets need to be delivered to a destination different - /// from the pipeline's main outbound queue. The send is immediate and - /// is not in batch. Packets sent with `emit` will be out of order - /// relative to other packets in the batch. - /// - /// # Example - /// - /// ``` - /// let (tx, _) = mpsc::channel(); - /// let mut batch = batch.emit(tx); - /// ``` - /// - /// [`PacketTx`]: crate::batch::PacketTx - fn emit(self, tx: Tx) -> Emit - where - Self: Sized, - { - Emit::new(self, tx) - } - - /// Creates a batch that uses a predicate to determine if a packet - /// should be processed or dropped. If the predicate evaluates to `false`, - /// the packet is marked as dropped. - /// - /// # Example - /// - /// ``` - /// let mut batch = batch.filter(|packet| { - /// let v4 = packet.parse::()?.parse::()?; - /// v4.ttl() > 0 - /// }); - /// ``` - #[inline] - fn filter

(self, predicate: P) -> Filter - where - P: FnMut(&Self::Item) -> bool, - Self: Sized, - { - Filter::new(self, predicate) - } - - /// Creates a batch that both [`filters`] and [`maps`]. - /// - /// # Example - /// - /// ``` - /// let mut batch = batch.filter_map(|packet| { - /// let v4 = packet.parse::()?.parse::()?; - /// if v4.protocol() == ProtocolNumbers::Udp { - /// Ok(Either::Keep(v4)) - /// } else { - /// Ok(Either::Drop(v4.reset())) - /// } - /// }); - /// ``` - /// - /// [`filters`]: Batch::filter - /// [`maps`]: Batch::map - #[inline] - fn filter_map(self, f: F) -> FilterMap - where - F: FnMut(Self::Item) -> Result>, - Self: Sized, - { - FilterMap::new(self, f) - } - - /// Creates a batch that maps the packets to another packet type. - /// - /// # Example - /// - /// ``` - /// let mut batch = batch.map(|packet| { - /// packet.parse::()?.parse::() - /// }); - /// ``` - #[inline] - fn map(self, f: F) -> Map - where - F: FnMut(Self::Item) -> Result, - Self: Sized, - { - Map::new(self, f) - } - - /// Calls a closure on each packet of the batch. - /// - /// Can be use for side-effect actions without the need to mutate the - /// packet. However, an error will abort the packet. - /// - /// # Example - /// - /// ``` - /// let mut batch = batch.for_each(|packet| { - /// println!("{:?}", packet); - /// Ok(()) - /// }); - /// ```` - #[inline] - fn for_each(self, f: F) -> ForEach - where - F: FnMut(&Self::Item) -> Result<()>, - Self: Sized, - { - ForEach::new(self, f) - } - - /// Calls a closure on each packet of the batch, including ones that are - /// already dropped, emitted or aborted. - /// - /// Unlike [`for_each`], `inspect` does not affect the packet disposition. - /// Useful as a debugging tool. - /// - /// # Example - /// - /// ``` - /// let mut batch = batch.inspect(|disp| { - /// if let Disposition::Act(v6) = disp { - /// if v6.hop_limit() > A_HOP_LIMIT { - /// debug!(...); - /// } - /// } - /// }); - /// ``` - /// - /// [`for_each`]: Batch::for_each - #[inline] - fn inspect(self, f: F) -> Inspect - where - F: FnMut(&Disposition), - Self: Sized, - { - Inspect::new(self, f) - } - - /// Splits the packets into multiple sub batches. Each sub batch runs - /// through a separate pipeline, and are then merged back together. - /// - /// `selector` is a closure that receives a reference to the packet and - /// evaluates to a discriminator value. The underlying batch will be split - /// into sub batches based on this value. - /// - /// `composer` is a closure that constructs a hash map of batch pipeline - /// builders for each individual sub pipeline. The [`compose!`] macro is an - /// ergonomic way to write the composer closure. The syntax of the macro - /// loosely resembles the std `match` expression. Each match arm consists of - /// a single discriminator value mapped to a builder closure. - /// - /// If a packet does not match with an arm, it will be passed through to - /// the next combinator. Use the catch all arm `_` to make the matching - /// exhaustive. - /// - /// # Example - /// - /// ``` - /// let mut batch = batch.group_by( - /// |packet| packet.protocol(), - /// |groups| { - /// compose!( groups { - /// ProtocolNumbers::Tcp => |group| { - /// group.map(do_tcp) - /// } - /// ProtocolNumbers::Udp => |group| { - /// group.map(do_udp) - /// } - /// _ => |group| { - /// group.map(unmatched) - /// } - /// }) - /// }, - /// ); - /// ``` - /// - /// [`compose!`]: macro@compose - #[inline] - fn group_by(self, selector: S, composer: C) -> GroupBy - where - D: Eq + Clone + Hash, - S: Fn(&Self::Item) -> D, - C: FnOnce(&mut HashMap, Box>>), - Self: Sized, - { - GroupBy::new(self, selector, composer) - } - - /// A batch that replaces each packet with another packet. - /// - /// Use for pipelines that generate new outbound packets based on inbound - /// packets and drop the inbound. - /// - /// # Example - /// - /// ``` - /// let mut batch = batch.replace(|request| { - /// let reply = Mbuf::new()?; - /// let ethernet = request.peek::()?; - /// let mut reply = reply.push::()?; - /// reply.set_src(ethernet.dst()); - /// reply.set_dst(ethernet.src()); - /// - /// ... - /// - /// Ok(reply) - /// }); - fn replace(self, f: F) -> Replace - where - F: FnMut(&Self::Item) -> Result, - Self: Sized, - { - Replace::new(self, f) - } - - /// Turns the batch pipeline into an executable task with default name. - /// - /// Send marks the end of the batch pipeline. No more combinators can be - /// appended after send. - /// - /// To give the pipeline a unique name, use - /// [`send_named`] instead. - /// - /// # Example - /// ``` - /// Poll::new(q.clone()).map(map_fn).send(q); - /// ``` - /// - /// [`send_named`]: Batch::send_named - #[inline] - fn send(self, tx: Tx) -> Send - where - Self: Sized, - { - Batch::send_named(self, "default", tx) - } - - /// Turns the batch pipeline into an executable task. - /// - /// `name` is used for logging and metrics. It does not need to be unique. - /// Multiple pipeline instances with the same name are aggregated together - /// into one set of metrics. Give each pipeline a different name to keep - /// metrics separated. - #[inline] - fn send_named(self, name: &str, tx: Tx) -> Send - where - Self: Sized, - { - Send::new(name.to_owned(), self, tx) - } -} - -/// Trait bound for batch pipelines. Can be used as a convenience for writing -/// pipeline installers. -/// -/// # Example -/// -/// ``` -/// fn install(q: PortQueue) -> impl Pipeline { -/// // install logic -/// } -/// ``` -pub trait Pipeline: futures::Future { - /// Returns the name of the pipeline. - fn name(&self) -> &str; - - /// Runs the pipeline once to process one batch of packets. - fn run_once(&mut self); -} - -/// Splices a [`PacketRx`] directly to a [`PacketTx`] without any intermediary -/// combinators. -/// -/// Useful for pipelines that perform simple forwarding without any packet -/// processing. -/// -/// # Example -/// -/// ``` -/// Runtime::build(config)? -/// .add_pipeline_to_port("kni0", |q| { -/// batch::splice(q.clone(), q.kni().unwrap().clone()) -/// }); -/// ``` -/// -/// [`PacketRx`]: crate::batch::PacketRx -/// [`PacketTx`]: crate::batch::PacketTx -pub fn splice(rx: Rx, tx: Tx) -> impl Pipeline { - Poll::new(rx).send(tx) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::compose; - use crate::packets::ip::v4::Ipv4; - use crate::packets::ip::ProtocolNumbers; - use crate::packets::Ethernet; - use crate::testils::byte_arrays::{ICMPV4_PACKET, IPV4_TCP_PACKET, IPV4_UDP_PACKET}; - use std::sync::mpsc::{self, TryRecvError}; - - fn new_batch(data: &[&[u8]]) -> impl Batch { - let packets = data - .iter() - .map(|bytes| Mbuf::from_bytes(bytes).unwrap()) - .collect::>(); - - let (mut tx, rx) = mpsc::channel(); - tx.transmit(packets); - let mut batch = Poll::new(rx); - batch.replenish(); - batch - } - - #[capsule::test] - fn emit_batch() { - let (tx, mut rx) = mpsc::channel(); - - let mut batch = new_batch(&[&IPV4_UDP_PACKET]) - .map(|p| p.parse::()) - .emit(tx) - .for_each(|_| panic!("emit broken!")); - - assert!(batch.next().unwrap().is_emit()); - - // sent to the tx - assert_eq!(1, rx.receive().len()); - } - - #[capsule::test] - fn filter_batch() { - let mut batch = new_batch(&[&IPV4_UDP_PACKET]).filter(|_| true); - assert!(batch.next().unwrap().is_act()); - - let mut batch = new_batch(&[&IPV4_UDP_PACKET]).filter(|_| false); - assert!(batch.next().unwrap().is_drop()); - } - - #[capsule::test] - fn filter_map_batch() { - let mut batch = new_batch(&[&IPV4_UDP_PACKET, &ICMPV4_PACKET]).filter_map(|p| { - let v4 = p.parse::()?.parse::()?; - if v4.protocol() == ProtocolNumbers::Udp { - Ok(Either::Keep(v4)) - } else { - Ok(Either::Drop(v4.reset())) - } - }); - - // udp is let through - assert!(batch.next().unwrap().is_act()); - // icmp is dropped - assert!(batch.next().unwrap().is_drop()); - // at the end - assert!(batch.next().is_none()); - } - - #[capsule::test] - fn map_batch() { - let mut batch = new_batch(&[&IPV4_UDP_PACKET]).map(|p| p.parse::()); - assert!(batch.next().unwrap().is_act()); - - // can't shrink the mbuf that much - let mut batch = new_batch(&[&IPV4_UDP_PACKET]).map(|mut p| { - p.shrink(0, 999_999)?; - Ok(p) - }); - assert!(batch.next().unwrap().is_abort()); - } - - #[capsule::test] - fn for_each_batch() { - let mut side_effect = false; - - let mut batch = new_batch(&[&IPV4_UDP_PACKET]).for_each(|_| { - side_effect = true; - Ok(()) - }); - - assert!(batch.next().unwrap().is_act()); - assert!(side_effect); - } - - #[capsule::test] - fn inspect_batch() { - let mut side_effect = false; - - let mut batch = new_batch(&[&IPV4_UDP_PACKET]).inspect(|_| { - side_effect = true; - }); - - assert!(batch.next().unwrap().is_act()); - assert!(side_effect); - } - - #[capsule::test] - fn group_by_batch() { - let mut batch = new_batch(&[&IPV4_TCP_PACKET, &IPV4_UDP_PACKET, &ICMPV4_PACKET]) - .map(|p| p.parse::()?.parse::()) - .group_by( - |p| p.protocol(), - |groups| { - compose!( groups { - ProtocolNumbers::Tcp => |group| { - group.map(|mut p| { - p.set_ttl(1); - Ok(p) - }) - } - ProtocolNumbers::Udp => |group| { - group.map(|mut p| { - p.set_ttl(2); - Ok(p) - }) - } - _ => |group| { - group.filter(|_| { - false - }) - } - }) - }, - ); - - // first one is the tcp arm - let disp = batch.next().unwrap(); - assert!(disp.is_act()); - if let Disposition::Act(pkt) = disp { - assert_eq!(1, pkt.ttl()); - } - - // next one is the udp arm - let disp = batch.next().unwrap(); - assert!(disp.is_act()); - if let Disposition::Act(pkt) = disp { - assert_eq!(2, pkt.ttl()); - } - - // last one is the catch all arm - assert!(batch.next().unwrap().is_drop()); - } - - #[capsule::test] - fn group_by_no_catchall() { - let mut batch = new_batch(&[&ICMPV4_PACKET]) - .map(|p| p.parse::()?.parse::()) - .group_by( - |p| p.protocol(), - |groups| { - compose!( groups { - ProtocolNumbers::Tcp => |group| { - group.filter(|_| false) - } - }) - }, - ); - - // did not match, passes through - assert!(batch.next().unwrap().is_act()); - } - - #[capsule::test] - fn group_by_or() { - let mut batch = new_batch(&[&IPV4_TCP_PACKET, &IPV4_UDP_PACKET, &ICMPV4_PACKET]) - .map(|p| p.parse::()?.parse::()) - .group_by( - |p| p.protocol(), - |groups| { - compose!( groups { - ProtocolNumbers::Tcp, ProtocolNumbers::Udp => |group| { - group.map(|mut p| { - p.set_ttl(1); - Ok(p) - }) - } - _ => |group| { - group.filter(|_| { - false - }) - } - }) - }, - ); - - // first one is the tcp arm - let disp = batch.next().unwrap(); - assert!(disp.is_act()); - if let Disposition::Act(pkt) = disp { - assert_eq!(1, pkt.ttl()); - } - - // next one is the udp arm - let disp = batch.next().unwrap(); - assert!(disp.is_act()); - if let Disposition::Act(pkt) = disp { - assert_eq!(1, pkt.ttl()); - } - - // last one is the catch all arm - assert!(batch.next().unwrap().is_drop()); - } - - #[capsule::test] - fn group_by_or_no_catchall() { - let mut batch = new_batch(&[&IPV4_TCP_PACKET, &IPV4_UDP_PACKET]) - .map(|p| p.parse::()?.parse::()) - .group_by( - |p| p.protocol(), - |groups| { - compose!( groups { - ProtocolNumbers::Tcp, ProtocolNumbers::Udp => |group| { - group.map(|mut p| { - p.set_ttl(1); - Ok(p) - }) - } - }) - }, - ); - - // first one is the tcp arm - let disp = batch.next().unwrap(); - assert!(disp.is_act()); - if let Disposition::Act(pkt) = disp { - assert_eq!(1, pkt.ttl()); - } - - // next one is the udp arm - let disp = batch.next().unwrap(); - assert!(disp.is_act()); - if let Disposition::Act(pkt) = disp { - assert_eq!(1, pkt.ttl()); - } - } - - #[capsule::test] - fn group_by_fanout() { - let mut batch = new_batch(&[&IPV4_TCP_PACKET]) - .map(|p| p.parse::()?.parse::()) - .group_by( - |p| p.protocol(), - |groups| { - compose!( groups { - ProtocolNumbers::Tcp => |group| { - group.replace(|_| { - Mbuf::from_bytes(&IPV4_UDP_PACKET)? - .parse::()? - .parse::() - }) - } - }) - }, - ); - - // replace inside group_by will produce a new UDP packet - // and marks the original TCP packet as dropped. - assert!(batch.next().unwrap().is_act()); - assert!(batch.next().unwrap().is_drop()); - assert!(batch.next().is_none()); - } - - #[capsule::test] - fn replace_batch() { - let mut batch = - new_batch(&[&IPV4_UDP_PACKET]).replace(|_| Mbuf::from_bytes(&IPV4_TCP_PACKET)); - - // first one is the replacement - assert!(batch.next().unwrap().is_act()); - // next one is the original - assert!(batch.next().unwrap().is_drop()); - // at the end - assert!(batch.next().is_none()); - } - - #[capsule::test] - fn poll_fn_batch() { - let mut batch = poll_fn(|| vec![Mbuf::new().unwrap()]); - batch.replenish(); - - assert!(batch.next().unwrap().is_act()); - assert!(batch.next().is_none()); - } - - #[capsule::test] - fn splice_pipeline() { - let (mut tx1, rx1) = mpsc::channel(); - let (tx2, rx2) = mpsc::channel(); - - // no packet yet - let mut pipeline = splice(rx1, tx2); - pipeline.run_once(); - assert_eq!(TryRecvError::Empty, rx2.try_recv().unwrap_err()); - - // send one packet - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); - tx1.transmit(vec![packet]); - pipeline.run_once(); - assert!(rx2.try_recv().is_ok()); - } -} diff --git a/core/src/batch/poll.rs b/core/src/batch/poll.rs deleted file mode 100644 index 72023c92..00000000 --- a/core/src/batch/poll.rs +++ /dev/null @@ -1,71 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Batch, Disposition, PacketRx, PollRx}; -use crate::Mbuf; -use std::collections::VecDeque; - -/// A batch that polls a receiving source for new packets. -/// -/// This marks the beginning of the pipeline. -#[allow(missing_debug_implementations)] -pub struct Poll { - rx: Rx, - packets: Option>, -} - -impl Poll { - /// Creates a new `Poll` batch. - #[inline] - pub fn new(rx: Rx) -> Self { - Poll { rx, packets: None } - } -} - -impl Batch for Poll { - type Item = Mbuf; - - /// Replenishes the batch with new packets from the RX source. - /// - /// If there are still packets left in the current queue, they are lost. - #[inline] - fn replenish(&mut self) { - // `VecDeque` is not the ideal structure here. We are relying on the - // conversion from `Vec` to `VecDeque` to be allocation-free. but - // unfortunately that's not always the case. We need an efficient and - // allocation-free data structure with pop semantic. - self.packets = Some(self.rx.receive().into()); - } - - #[inline] - fn next(&mut self) -> Option> { - if let Some(q) = self.packets.as_mut() { - q.pop_front().map(Disposition::Act) - } else { - None - } - } -} - -/// Creates a new poll batch from a closure. -pub fn poll_fn(f: F) -> Poll> -where - F: Fn() -> Vec, -{ - Poll::new(PollRx { f }) -} diff --git a/core/src/batch/replace.rs b/core/src/batch/replace.rs deleted file mode 100644 index 2efa0e7a..00000000 --- a/core/src/batch/replace.rs +++ /dev/null @@ -1,89 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Batch, Disposition}; -use crate::packets::Packet; -use anyhow::Result; - -/// A batch that replaces each packet of the batch with another packet. -/// -/// The original packet is dropped from the batch with the new packet in its -/// place. On error, the packet is `aborted` and will short-circuit the -/// remainder of the pipeline. -#[allow(missing_debug_implementations)] -pub struct Replace -where - F: FnMut(&B::Item) -> Result, -{ - batch: B, - f: F, - slot: Option, -} - -impl Replace -where - F: FnMut(&B::Item) -> Result, -{ - /// Creates a new `Replace` batch. - #[inline] - pub fn new(batch: B, f: F) -> Self { - Replace { - batch, - f, - slot: None, - } - } -} - -impl Batch for Replace -where - F: FnMut(&B::Item) -> Result, -{ - type Item = T; - - #[inline] - fn replenish(&mut self) { - self.batch.replenish(); - } - - #[inline] - fn next(&mut self) -> Option> { - // internally the replace combinator will add a new packet to the - // batch and mark the original as dropped. the iteration grows to - // 2x in length because each item becomes 2 items. - if let Some(pkt) = self.slot.take() { - // has a packet in the temp slot. marks it as dropped. - Some(Disposition::Drop(pkt.reset())) - } else { - // nothing in the slot, fetches a new packet from source. - self.batch.next().map(|disp| { - disp.map(|orig| { - match (self.f)(&orig) { - Ok(new) => { - // keeps the original in the temp slot, we will mark it dropped - // in the iteration that immediately follows. - self.slot.replace(orig); - Disposition::Act(new) - } - Err(e) => Disposition::Abort(e), - } - }) - }) - } - } -} diff --git a/core/src/batch/rxtx.rs b/core/src/batch/rxtx.rs deleted file mode 100644 index c78d65c0..00000000 --- a/core/src/batch/rxtx.rs +++ /dev/null @@ -1,89 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -//! Implementations of `PacketRx` and `PacketTx`. -//! -//! Implemented for `PortQueue`. -//! -//! `PacketRx` implemented for `KniRx`. -//! -//! `PacketTx` implemented for `KniTxQueue`. -//! -//! Implemented for the MPSC channel so it can be used as a batch source -//! mostly in tests. - -use super::{PacketRx, PacketTx}; -use crate::{KniRx, KniTxQueue, Mbuf, PortQueue}; -use std::iter; -use std::sync::mpsc::{Receiver, Sender}; - -impl PacketRx for PortQueue { - fn receive(&mut self) -> Vec { - PortQueue::receive(self) - } -} - -impl PacketTx for PortQueue { - fn transmit(&mut self, packets: Vec) { - PortQueue::transmit(self, packets) - } -} - -impl PacketRx for KniRx { - fn receive(&mut self) -> Vec { - KniRx::receive(self) - } -} - -impl PacketTx for KniTxQueue { - fn transmit(&mut self, packets: Vec) { - KniTxQueue::transmit(self, packets) - } -} - -impl PacketRx for Receiver { - fn receive(&mut self) -> Vec { - iter::from_fn(|| self.try_recv().ok()).collect::>() - } -} - -impl PacketTx for Sender { - fn transmit(&mut self, packets: Vec) { - packets.into_iter().for_each(|packet| { - let _ = self.send(packet); - }); - } -} - -/// A batch that polls a closure for packets. -#[allow(missing_debug_implementations)] -pub struct PollRx -where - F: Fn() -> Vec, -{ - pub(crate) f: F, -} - -impl PacketRx for PollRx -where - F: Fn() -> Vec, -{ - fn receive(&mut self) -> Vec { - (self.f)() - } -} diff --git a/core/src/batch/send.rs b/core/src/batch/send.rs deleted file mode 100644 index e96b67a7..00000000 --- a/core/src/batch/send.rs +++ /dev/null @@ -1,151 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Batch, Disposition, PacketTx, Pipeline}; -use crate::dpdk::CoreId; -#[cfg(feature = "metrics")] -use crate::metrics::{labels, Counter, SINK}; -use crate::packets::Packet; -use crate::Mbuf; -use futures::{future, Future}; -use std::pin::Pin; -use std::task::{Context, Poll}; -use tokio_executor::current_thread; - -/// Creates a new pipeline counter. -#[cfg(feature = "metrics")] -fn new_counter(name: &'static str, pipeline: &str) -> Counter { - SINK.scoped("pipeline").counter_with_labels( - name, - labels!( - "pipeline" => pipeline.to_owned(), - "core" => CoreId::current().raw().to_string(), - ), - ) -} - -/// A batch that can be executed as a runtime task. -#[allow(missing_debug_implementations)] -pub struct Send { - name: String, - batch: B, - tx: Tx, - #[cfg(feature = "metrics")] - runs: Counter, - #[cfg(feature = "metrics")] - processed: Counter, - #[cfg(feature = "metrics")] - dropped: Counter, - #[cfg(feature = "metrics")] - errors: Counter, -} - -impl Send { - /// Creates a new `Send` batch. - #[cfg(not(feature = "metrics"))] - #[inline] - pub fn new(name: String, batch: B, tx: Tx) -> Self { - Send { name, batch, tx } - } - - /// Creates a new `Send` batch. - #[cfg(feature = "metrics")] - #[inline] - pub fn new(name: String, batch: B, tx: Tx) -> Self { - let runs = new_counter("runs", &name); - let processed = new_counter("processed", &name); - let dropped = new_counter("dropped", &name); - let errors = new_counter("errors", &name); - Send { - name, - batch, - tx, - runs, - processed, - dropped, - errors, - } - } - - fn run(&mut self) { - // let's get a new batch - self.batch.replenish(); - - let mut transmit_q = Vec::with_capacity(64); - let mut drop_q = Vec::with_capacity(64); - let mut emitted = 0u64; - let mut aborted = 0u64; - - // consume the whole batch to completion - while let Some(disp) = self.batch.next() { - match disp { - Disposition::Act(packet) => transmit_q.push(packet.reset()), - Disposition::Drop(mbuf) => drop_q.push(mbuf), - Disposition::Emit => emitted += 1, - Disposition::Abort(_) => aborted += 1, - } - } - - #[cfg(feature = "metrics")] - { - self.runs.record(1); - self.processed.record(transmit_q.len() as u64 + emitted); - self.dropped.record(drop_q.len() as u64); - self.errors.record(aborted); - } - - if !transmit_q.is_empty() { - self.tx.transmit(transmit_q); - } - - if !drop_q.is_empty() { - Mbuf::free_bulk(drop_q); - } - } -} - -/// By implementing the `Future` trait, `Send` can be spawned onto the tokio -/// executor. Each time the future is polled, it processes one batch of -/// packets before returning the `Poll::Pending` status and yields. -impl Future for Send { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // executes a batch of packets. - self.get_mut().run(); - - // now schedules the waker as a future and yields the core so other - // futures have a chance to run. - let waker = cx.waker().clone(); - current_thread::spawn(future::lazy(|_| waker.wake())); - - Poll::Pending - } -} - -impl Pipeline for Send { - #[inline] - fn name(&self) -> &str { - &self.name - } - - #[inline] - fn run_once(&mut self) { - self.run() - } -} diff --git a/core/src/config.rs b/core/src/config.rs deleted file mode 100644 index 5ae8b14d..00000000 --- a/core/src/config.rs +++ /dev/null @@ -1,513 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -//! Toml-based configuration for use with Capsule applications. -//! -//! # Example -//! -//! A configuration from our [`pktdump`] example: -//! ``` -//! app_name = "pktdump" -//! master_core = 0 -//! duration = 5 -//! -//! [mempool] -//! capacity = 65535 -//! cache_size = 256 -//! -//! [[ports]] -//! name = "eth1" -//! device = "net_pcap0" -//! args = "rx_pcap=tcp4.pcap,tx_iface=lo" -//! cores = [0] -//! -//! [[ports]] -//! name = "eth2" -//! device = "net_pcap1" -//! args = "rx_pcap=tcp6.pcap,tx_iface=lo" -//! cores = [0] -//! ``` -//! -//! [`pktdump`]: https://github.com/capsule-rs/capsule/tree/master/examples/pktdump - -use crate::dpdk::CoreId; -use crate::net::{Ipv4Cidr, Ipv6Cidr, MacAddr}; -use anyhow::Result; -use clap::{clap_app, crate_version}; -use regex::Regex; -use serde::{de, Deserialize, Deserializer}; -use std::fmt; -use std::fs; -use std::str::FromStr; -use std::time::Duration; - -// make `CoreId` serde deserializable. -impl<'de> Deserialize<'de> for CoreId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let i = usize::deserialize(deserializer)?; - Ok(CoreId::new(i)) - } -} - -// make `MacAddr` serde deserializable. -impl<'de> Deserialize<'de> for MacAddr { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - MacAddr::from_str(&s).map_err(de::Error::custom) - } -} - -// make `Ipv4Cidr` serde deserializable. -impl<'de> Deserialize<'de> for Ipv4Cidr { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - Ipv4Cidr::from_str(&s).map_err(de::Error::custom) - } -} - -// make `Ipv6Cidr` serde deserializable. -impl<'de> Deserialize<'de> for Ipv6Cidr { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - Ipv6Cidr::from_str(&s).map_err(de::Error::custom) - } -} - -/// Deserializes a duration from seconds expressed as `u64`. -pub fn duration_from_secs<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let secs = u64::deserialize(deserializer)?; - Ok(Duration::from_secs(secs)) -} - -/// Deserializes an option of duration from seconds expressed as `u64`. -pub fn duration_option_from_secs<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - // for now this is the cleanest way to deserialize an option, till a better - // way is implemented, https://github.com/serde-rs/serde/issues/723 - #[derive(Deserialize)] - struct Wrapper(#[serde(deserialize_with = "duration_from_secs")] Duration); - - let option = Option::deserialize(deserializer)?.and_then(|Wrapper(dur)| { - if dur.as_secs() > 0 { - Some(dur) - } else { - None - } - }); - Ok(option) -} - -/// Runtime configuration settings. -#[derive(Clone, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct RuntimeConfig { - /// Application name. This must be unique if you want to run multiple - /// DPDK applications on the same system. - pub app_name: String, - - /// Indicating whether the process is a secondary process. Secondary - /// process cannot initialize shared memory, but can attach to pre- - /// initialized shared memory by the primary process and create objects - /// in it. Defaults to `false`. - #[serde(default)] - pub secondary: bool, - - /// Application group name. Use this to group primary and secondary - /// processes together in a multi-process setup; and allow them to share - /// the same memory regions. The default value is the `app_name`. Each - /// process works independently. - #[serde(default)] - pub app_group: Option, - - /// The identifier of the master core. This is the core the main thread - /// will run on. - pub master_core: CoreId, - - /// Additional cores that are available to the application, and can be - /// used for running general tasks. Packet pipelines cannot be run on - /// these cores unless the core is also assigned to a port separately. - /// Defaults to empty list. - #[serde(default)] - pub cores: Vec, - - /// Per mempool settings. On a system with multiple sockets, aka NUMA - /// nodes, one mempool will be allocated for each socket the apllication - /// uses. - #[serde(default)] - pub mempool: MempoolConfig, - - /// The ports to use for the application. Must have at least one. - pub ports: Vec, - - /// Additional DPDK [`parameters`] to pass on for EAL initialization. When - /// set, the values are passed through as is without validation. - /// - /// [`parameters`]: https://doc.dpdk.org/guides/linux_gsg/linux_eal_parameters.html - #[serde(default)] - pub dpdk_args: Option, - - /// If set, the application will stop after the duration expires. Useful - /// for setting a timeout for integration tests. - #[serde(default, deserialize_with = "duration_option_from_secs")] - pub duration: Option, -} - -impl RuntimeConfig { - /// Returns all the cores assigned to the runtime. - pub(crate) fn all_cores(&self) -> Vec { - let mut cores = vec![]; - cores.push(self.master_core); - cores.extend(self.cores.iter()); - - self.ports.iter().for_each(|port| { - cores.extend(port.cores.iter()); - }); - - cores.sort(); - cores.dedup(); - cores - } - - /// Extracts the EAL arguments from runtime settings. - pub(crate) fn to_eal_args(&self) -> Vec { - let mut eal_args = vec![]; - - // adds the app name - eal_args.push(self.app_name.clone()); - - let proc_type = if self.secondary { - "secondary".to_owned() - } else { - "primary".to_owned() - }; - - // adds the proc type - eal_args.push("--proc-type".to_owned()); - eal_args.push(proc_type); - - // adds the mem file prefix - let prefix = self.app_group.as_ref().unwrap_or(&self.app_name); - eal_args.push("--file-prefix".to_owned()); - eal_args.push(prefix.clone()); - - // adds all the ports - let pcie = Regex::new(r"^\d{4}:\d{2}:\d{2}\.\d$").unwrap(); - self.ports.iter().for_each(|port| { - if pcie.is_match(port.device.as_str()) { - eal_args.push("--pci-whitelist".to_owned()); - eal_args.push(port.device.clone()); - } else { - let vdev = if let Some(args) = &port.args { - format!("{},{}", port.device, args) - } else { - port.device.clone() - }; - eal_args.push("--vdev".to_owned()); - eal_args.push(vdev); - } - }); - - // adds the master core - eal_args.push("--master-lcore".to_owned()); - eal_args.push(self.master_core.raw().to_string()); - - // limits the EAL to only the master core. actual threads are - // managed by the runtime not the EAL. - eal_args.push("-l".to_owned()); - eal_args.push(self.master_core.raw().to_string()); - - // adds additional DPDK args - if let Some(args) = &self.dpdk_args { - eal_args.extend(args.split_ascii_whitespace().map(str::to_owned)); - } - - eal_args - } - - /// Returns the number of KNI enabled ports - pub(crate) fn num_knis(&self) -> usize { - self.ports.iter().filter(|p| p.kni).count() - } -} - -impl fmt::Debug for RuntimeConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut d = f.debug_struct("runtime"); - d.field("app_name", &self.app_name) - .field("secondary", &self.secondary) - .field( - "app_group", - self.app_group.as_ref().unwrap_or(&self.app_name), - ) - .field("master_core", &self.master_core) - .field("cores", &self.cores) - .field("mempool", &self.mempool) - .field("ports", &self.ports); - if let Some(dpdk_args) = &self.dpdk_args { - d.field("dpdk_args", dpdk_args); - } - if let Some(duration) = &self.duration { - d.field("duration", duration); - } - d.finish() - } -} - -/// Mempool configuration settings. -#[derive(Clone, Deserialize)] -pub struct MempoolConfig { - /// The maximum number of Mbufs the mempool can allocate. The optimum - /// size (in terms of memory usage) is when n is a power of two minus - /// one. Defaults to `65535` or `2 ^ 16 - 1`. - #[serde(default = "default_capacity")] - pub capacity: usize, - - /// The size of the per core object cache. If cache_size is non-zero, - /// the library will try to limit the accesses to the common lockless - /// pool. The cache can be disabled if the argument is set to 0. Defaults - /// to `0`. - #[serde(default = "default_cache_size")] - pub cache_size: usize, -} - -fn default_capacity() -> usize { - 65535 -} - -fn default_cache_size() -> usize { - 0 -} - -impl Default for MempoolConfig { - fn default() -> Self { - MempoolConfig { - capacity: default_capacity(), - cache_size: default_cache_size(), - } - } -} - -impl fmt::Debug for MempoolConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("mempool") - .field("capacity", &self.capacity) - .field("cache_size", &self.cache_size) - .finish() - } -} - -/// Port configuration settings. -#[derive(Clone, Deserialize)] -pub struct PortConfig { - /// The application assigned logical name of the port. - /// - /// For applications with more than one port, this name can be used to - /// identifer the port. - pub name: String, - - /// The device name of the port. It can be the following formats, - /// - /// * PCIe address, for example `0000:02:00.0` - /// * DPDK virtual device, for example `net_[pcap0|null0|tap0]` - pub device: String, - - /// Additional arguments to configure a virtual device. - #[serde(default)] - pub args: Option, - - /// The cores assigned to the port for running the pipelines. The values - /// can overlap with the runtime cores. - pub cores: Vec, - - /// The receive queue capacity. Defaults to `128`. - #[serde(default = "default_port_rxd")] - pub rxd: usize, - - /// The transmit queue capacity. Defaults to `128`. - #[serde(default = "default_port_txd")] - pub txd: usize, - - /// Whether promiscuous mode is enabled for this port. Defaults to `false`. - #[serde(default)] - pub promiscuous: bool, - - /// Whether multicast packet reception is enabled for this port. Defaults - /// to `true`. - #[serde(default = "default_multicast_mode")] - pub multicast: bool, - - /// Whether kernel NIC interface is enabled for this port. with KNI, this - /// port can exchange packets with the kernel networking stack. Defaults - /// to `false`. - #[serde(default)] - pub kni: bool, -} - -fn default_port_rxd() -> usize { - 128 -} - -fn default_port_txd() -> usize { - 128 -} - -fn default_multicast_mode() -> bool { - true -} - -impl fmt::Debug for PortConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut d = f.debug_struct("port"); - d.field("name", &self.name); - d.field("device", &self.device); - if let Some(args) = &self.args { - d.field("args", args); - } - d.field("cores", &self.cores) - .field("rxd", &self.rxd) - .field("txd", &self.txd) - .field("promiscuous", &self.promiscuous) - .field("multicast", &self.multicast) - .field("kni", &self.kni) - .finish() - } -} - -/// Loads the app config from a TOML file. -/// -/// # Example -/// -/// ``` -/// home$ ./myapp -f config.toml -/// ``` -pub fn load_config() -> Result { - let matches = clap_app!(capsule => - (version: crate_version!()) - (@arg file: -f --file +required +takes_value "configuration file") - ) - .get_matches(); - - let path = matches.value_of("file").unwrap(); - let content = fs::read_to_string(path)?; - toml::from_str(&content).map_err(|err| err.into()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn config_defaults() { - const CONFIG: &str = r#" - app_name = "myapp" - master_core = 0 - - [[ports]] - name = "eth0" - device = "0000:00:01.0" - cores = [2, 3] - "#; - - let config: RuntimeConfig = toml::from_str(CONFIG).unwrap(); - - assert_eq!(false, config.secondary); - assert_eq!(None, config.app_group); - assert!(config.cores.is_empty()); - assert_eq!(None, config.dpdk_args); - assert_eq!(default_capacity(), config.mempool.capacity); - assert_eq!(default_cache_size(), config.mempool.cache_size); - assert_eq!(None, config.ports[0].args); - assert_eq!(default_port_rxd(), config.ports[0].rxd); - assert_eq!(default_port_txd(), config.ports[0].txd); - assert_eq!(false, config.ports[0].promiscuous); - assert_eq!(default_multicast_mode(), config.ports[0].multicast); - assert_eq!(false, config.ports[0].kni); - } - - #[test] - fn config_to_eal_args() { - const CONFIG: &str = r#" - app_name = "myapp" - secondary = false - app_group = "mygroup" - master_core = 0 - cores = [1] - dpdk_args = "-v --log-level eal:8" - - [mempool] - capacity = 255 - cache_size = 16 - - [[ports]] - name = "eth0" - device = "0000:00:01.0" - cores = [2, 3] - rxd = 32 - txd = 32 - - [[ports]] - name = "eth1" - device = "net_pcap0" - args = "rx=lo,tx=lo" - cores = [0, 4] - rxd = 32 - txd = 32 - "#; - - let config: RuntimeConfig = toml::from_str(CONFIG).unwrap(); - - assert_eq!( - &[ - "myapp", - "--proc-type", - "primary", - "--file-prefix", - "mygroup", - "--pci-whitelist", - "0000:00:01.0", - "--vdev", - "net_pcap0,rx=lo,tx=lo", - "--master-lcore", - "0", - "-l", - "0", - "-v", - "--log-level", - "eal:8" - ], - config.to_eal_args().as_slice(), - ) - } -} diff --git a/core/src/dpdk/kni.rs b/core/src/dpdk/kni.rs deleted file mode 100644 index edf6919e..00000000 --- a/core/src/dpdk/kni.rs +++ /dev/null @@ -1,411 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Mbuf, PortId}; -use crate::dpdk::DpdkError; -use crate::ffi::{self, AsStr, ToResult}; - -#[cfg(feature = "metrics")] -use crate::metrics::{labels, Counter, SINK}; -use crate::net::MacAddr; -use crate::{debug, error, warn}; -use anyhow::Result; -use futures::{future, Future, StreamExt}; -use std::cmp; -use std::mem; -use std::os::raw; -use std::ptr::{self, NonNull}; -use thiserror::Error; -use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; - -/// Creates a new KNI counter. -#[cfg(feature = "metrics")] -fn new_counter(name: &'static str, kni: &str, dir: &'static str) -> Counter { - SINK.scoped("kni").counter_with_labels( - name, - labels!( - "kni" => kni.to_string(), - "dir" => dir, - ), - ) -} - -/// The KNI receive handle. Because the underlying interface is single -/// threaded, we must ensure that only one rx handle is created for each -/// interface. -#[allow(missing_debug_implementations)] -pub struct KniRx { - raw: NonNull, - #[cfg(feature = "metrics")] - packets: Counter, - #[cfg(feature = "metrics")] - octets: Counter, -} - -impl KniRx { - /// Creates a new `KniRx`. - #[cfg(not(feature = "metrics"))] - pub fn new(raw: NonNull) -> Self { - KniRx { raw } - } - - /// Creates a new `KniRx`. - #[cfg(feature = "metrics")] - pub fn new(raw: NonNull) -> Self { - let name = unsafe { ffi::rte_kni_get_name(raw.as_ref()).as_str().to_owned() }; - let packets = new_counter("packets", &name, "rx"); - let octets = new_counter("octets", &name, "rx"); - KniRx { - raw, - packets, - octets, - } - } - - /// Receives a burst of packets from the kernel, up to a maximum of - /// **32** packets. - pub fn receive(&mut self) -> Vec { - const RX_BURST_MAX: usize = 32; - let mut ptrs = Vec::with_capacity(RX_BURST_MAX); - - let len = unsafe { - ffi::rte_kni_rx_burst( - self.raw.as_mut(), - ptrs.as_mut_ptr(), - RX_BURST_MAX as raw::c_uint, - ) - }; - - let mbufs = unsafe { - ptrs.set_len(len as usize); - ptrs.into_iter() - .map(|ptr| Mbuf::from_ptr(ptr)) - .collect::>() - }; - - unsafe { - // checks if there are any link change requests, and handle them. - if let Err(err) = - ffi::rte_kni_handle_request(self.raw.as_mut()).into_result(|_| DpdkError::new()) - { - warn!(message = "failed to handle change link requests.", ?err); - } - } - - #[cfg(feature = "metrics")] - { - self.packets.record(mbufs.len() as u64); - - let bytes: usize = mbufs.iter().map(Mbuf::data_len).sum(); - self.octets.record(bytes as u64); - } - - mbufs - } -} - -/// In memory queue for the cores to deliver packets that are destined for -/// the kernel. Then another pipeline will collect these and forward them -/// on in a thread safe way. -#[allow(missing_debug_implementations)] -#[derive(Clone)] -pub struct KniTxQueue { - tx_enque: UnboundedSender>, -} - -impl KniTxQueue { - /// Transmits packets to the KNI tx queue. - pub fn transmit(&mut self, packets: Vec) { - if let Err(err) = self.tx_enque.try_send(packets) { - warn!(message = "failed to send to kni tx queue."); - Mbuf::free_bulk(err.into_inner()); - } - } -} - -/// The KNI transmit handle. Because the underlying interface is single -/// threaded, we must ensure that only one tx handle is created for each -/// interface. -pub(crate) struct KniTx { - raw: NonNull, - tx_deque: Option>>, - #[cfg(feature = "metrics")] - packets: Counter, - #[cfg(feature = "metrics")] - octets: Counter, - #[cfg(feature = "metrics")] - dropped: Counter, -} - -impl KniTx { - /// Creates a new `KniTx`. - #[cfg(not(feature = "metrics"))] - pub(crate) fn new(raw: NonNull, tx_deque: UnboundedReceiver>) -> Self { - KniTx { - raw, - tx_deque: Some(tx_deque), - } - } - - /// Creates a new `KniTx` with stats. - #[cfg(feature = "metrics")] - pub(crate) fn new(raw: NonNull, tx_deque: UnboundedReceiver>) -> Self { - let name = unsafe { ffi::rte_kni_get_name(raw.as_ref()).as_str().to_owned() }; - let packets = new_counter("packets", &name, "tx"); - let octets = new_counter("octets", &name, "tx"); - let dropped = new_counter("dropped", &name, "tx"); - KniTx { - raw, - tx_deque: Some(tx_deque), - packets, - octets, - dropped, - } - } - - /// Sends the packets to the kernel. - pub(crate) fn transmit(&mut self, packets: Vec) { - let mut ptrs = packets.into_iter().map(Mbuf::into_ptr).collect::>(); - - loop { - let to_send = ptrs.len() as raw::c_uint; - let sent = - unsafe { ffi::rte_kni_tx_burst(self.raw.as_mut(), ptrs.as_mut_ptr(), to_send) }; - - if sent > 0 { - #[cfg(feature = "metrics")] - { - self.packets.record(sent as u64); - - let bytes: u64 = ptrs[..sent as usize] - .iter() - .map(|&ptr| unsafe { (*ptr).data_len as u64 }) - .sum(); - self.octets.record(bytes); - } - - if to_send - sent > 0 { - // still have packets not sent. tx queue is full but still making - // progress. we will keep trying until all packets are sent. drains - // the ones already sent first and try again on the rest. - let _ = ptrs.drain(..sent as usize); - } else { - break; - } - } else { - // tx queue is full and we can't make progress, start dropping packets - // to avoid potentially stuck in an endless loop. - #[cfg(feature = "metrics")] - self.dropped.record(to_send as u64); - - super::mbuf_free_bulk(ptrs); - break; - } - } - } - - /// Converts the TX handle into a spawnable pipeline. - pub(crate) fn into_pipeline(mut self) -> impl Future { - self.tx_deque.take().unwrap().for_each(move |packets| { - self.transmit(packets); - future::ready(()) - }) - } -} - -// we need to send tx and rx across threads to run them. -unsafe impl Send for KniRx {} -unsafe impl Send for KniTx {} - -/// KNI errors. -#[derive(Debug, Error)] -pub(crate) enum KniError { - #[error("KNI is not enabled for the port.")] - Disabled, - - #[error("Another core owns the handle.")] - NotAcquired, -} - -/// Kernel NIC interface. This allows the DPDK application to exchange -/// packets with the kernel networking stack. -/// -/// The DPDK implementation is single-threaded TX and RX. Only one thread -/// can receive and one thread can transmit on the interface at a time. To -/// support a multi-queued port with a single virtual interface, a multi -/// producer, single consumer channel is used to collect all the kernel -/// bound packets onto one thread for transmit. -pub(crate) struct Kni { - raw: NonNull, - rx: Option, - tx: Option, - txq: KniTxQueue, -} - -impl Kni { - /// Creates a new KNI. - pub(crate) fn new(raw: NonNull) -> Kni { - let (send, recv) = mpsc::unbounded_channel(); - - // making 3 clones of the same raw pointer. but we know it is safe - // to do because rx and tx happen on two independent queues. so while - // each one is single-threaded, they can function in parallel. - let rx = KniRx::new(raw); - let tx = KniTx::new(raw, recv); - let txq = KniTxQueue { tx_enque: send }; - - Kni { - raw, - rx: Some(rx), - tx: Some(tx), - txq, - } - } - - /// Takes ownership of the RX handle. - pub(crate) fn take_rx(&mut self) -> Result { - self.rx.take().ok_or_else(|| KniError::NotAcquired.into()) - } - - /// Takes ownership of the TX handle. - pub(crate) fn take_tx(&mut self) -> Result { - self.tx.take().ok_or_else(|| KniError::NotAcquired.into()) - } - - /// Returns a TX queue handle to send packets to kernel. - pub(crate) fn txq(&self) -> KniTxQueue { - self.txq.clone() - } - - /// Returns the raw struct needed for FFI calls. - #[inline] - pub(crate) fn raw_mut(&mut self) -> &mut ffi::rte_kni { - unsafe { self.raw.as_mut() } - } -} - -impl Drop for Kni { - fn drop(&mut self) { - debug!("freeing kernel interface."); - - if let Err(err) = - unsafe { ffi::rte_kni_release(self.raw_mut()).into_result(|_| DpdkError::new()) } - { - error!(message = "failed to release KNI device.", ?err); - } - } -} - -/// Does not support changing the link MTU. -extern "C" fn change_mtu(port_id: u16, new_mtu: raw::c_uint) -> raw::c_int { - warn!("ignored change port {} mtu to {}.", port_id, new_mtu); - -1 -} - -/// Does not change the link up/down status, but will return 0 so the -/// command succeeds. -extern "C" fn config_network_if(port_id: u16, if_up: u8) -> raw::c_int { - warn!("ignored change port {} status to {}.", port_id, if_up); - 0 -} - -/// Does not support changing the link MAC address. -extern "C" fn config_mac_address(port_id: u16, _mac_addr: *mut u8) -> raw::c_int { - warn!("ignored change port {} mac address.", port_id); - -1 -} - -/// Does not support changing the link promiscusity. -extern "C" fn config_promiscusity(port_id: u16, to_on: u8) -> raw::c_int { - warn!("ignored change port {} promiscusity to {}.", port_id, to_on); - -1 -} - -/// Builds a KNI device from the configuration values. -pub(crate) struct KniBuilder<'a> { - mempool: &'a mut ffi::rte_mempool, - conf: ffi::rte_kni_conf, - ops: ffi::rte_kni_ops, -} - -impl<'a> KniBuilder<'a> { - /// Creates a new KNI device builder with the mempool for allocating - /// new packets. - pub(crate) fn new(mempool: &'a mut ffi::rte_mempool) -> Self { - KniBuilder { - mempool, - conf: ffi::rte_kni_conf::default(), - ops: ffi::rte_kni_ops::default(), - } - } - - pub(crate) fn name(&mut self, name: &str) -> &mut Self { - unsafe { - self.conf.name = mem::zeroed(); - ptr::copy( - name.as_ptr(), - self.conf.name.as_mut_ptr() as *mut u8, - cmp::min(name.len(), self.conf.name.len()), - ); - } - self - } - - pub(crate) fn port_id(&mut self, port_id: PortId) -> &mut Self { - self.conf.group_id = port_id.raw(); - self.ops.port_id = port_id.raw(); - self - } - - pub(crate) fn mac_addr(&mut self, mac: MacAddr) -> &mut Self { - unsafe { - self.conf.mac_addr = mem::transmute(mac); - } - self - } - - pub(crate) fn finish(&mut self) -> Result { - self.conf.mbuf_size = ffi::RTE_MBUF_DEFAULT_BUF_SIZE; - self.ops.change_mtu = Some(change_mtu); - self.ops.config_network_if = Some(config_network_if); - self.ops.config_mac_address = Some(config_mac_address); - self.ops.config_promiscusity = Some(config_promiscusity); - - unsafe { - ffi::rte_kni_alloc(self.mempool, &self.conf, &mut self.ops) - .into_result(|_| DpdkError::new()) - .map(Kni::new) - } - } -} - -/// Initializes and preallocates the KNI subsystem. -pub(crate) fn kni_init(max: usize) -> Result<()> { - unsafe { - ffi::rte_kni_init(max as raw::c_uint) - .into_result(DpdkError::from_errno) - .map(|_| ()) - } -} - -/// Closes the KNI subsystem. -pub(crate) fn kni_close() { - unsafe { - ffi::rte_kni_close(); - } -} diff --git a/core/src/dpdk/mempool.rs b/core/src/dpdk/mempool.rs deleted file mode 100644 index be3f2d78..00000000 --- a/core/src/dpdk/mempool.rs +++ /dev/null @@ -1,180 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::SocketId; -use crate::dpdk::DpdkError; -use crate::ffi::{self, AsStr, ToCString, ToResult}; -use crate::{debug, info}; -use anyhow::Result; -use std::cell::Cell; -use std::collections::HashMap; -use std::fmt; -use std::os::raw; -use std::ptr::{self, NonNull}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use thiserror::Error; - -/// A memory pool is an allocator of message buffers, or `Mbuf`. For best -/// performance, each socket should have a dedicated `Mempool`. -pub(crate) struct Mempool { - raw: NonNull, -} - -impl Mempool { - /// Creates a new `Mempool` for `Mbuf`. - /// - /// `capacity` is the maximum number of `Mbuf` the `Mempool` can hold. - /// The optimum size (in terms of memory usage) is when n is a power - /// of two minus one. - /// - /// `cache_size` is the per core object cache. If cache_size is non-zero, - /// the library will try to limit the accesses to the common lockless - /// pool. The cache can be disabled if the argument is set to 0. - /// - /// `socket_id` is the socket where the memory should be allocated. The - /// value can be `SocketId::ANY` if there is no constraint. - /// - /// # Errors - /// - /// If allocation fails, then `DpdkError` is returned. - pub(crate) fn new(capacity: usize, cache_size: usize, socket_id: SocketId) -> Result { - static MEMPOOL_COUNT: AtomicUsize = AtomicUsize::new(0); - let n = MEMPOOL_COUNT.fetch_add(1, Ordering::Relaxed); - let name = format!("mempool{}", n); - - let raw = unsafe { - ffi::rte_pktmbuf_pool_create( - name.clone().into_cstring().as_ptr(), - capacity as raw::c_uint, - cache_size as raw::c_uint, - 0, - ffi::RTE_MBUF_DEFAULT_BUF_SIZE as u16, - socket_id.raw(), - ) - .into_result(|_| DpdkError::new())? - }; - - info!("created {}.", name); - Ok(Self { raw }) - } - - /// Returns the raw struct needed for FFI calls. - #[inline] - pub(crate) fn raw(&self) -> &ffi::rte_mempool { - unsafe { self.raw.as_ref() } - } - - /// Returns the raw struct needed for FFI calls. - #[inline] - pub(crate) fn raw_mut(&mut self) -> &mut ffi::rte_mempool { - unsafe { self.raw.as_mut() } - } - - /// Returns the name of the `Mempool`. - #[inline] - pub(crate) fn name(&self) -> &str { - self.raw().name[..].as_str() - } - - #[cfg(feature = "metrics")] - pub(crate) fn stats(&self) -> super::MempoolStats { - super::MempoolStats::build(self) - } -} - -impl fmt::Debug for Mempool { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let raw = self.raw(); - f.debug_struct(self.name()) - .field("capacity", &raw.size) - .field("cache_size", &raw.cache_size) - .field("flags", &format_args!("{:#x}", raw.flags)) - .field("socket", &raw.socket_id) - .finish() - } -} - -impl Drop for Mempool { - fn drop(&mut self) { - debug!("freeing {}.", self.name()); - - unsafe { - ffi::rte_mempool_free(self.raw_mut()); - } - } -} - -thread_local! { - /// `Mempool` on the same socket as the current core. - /// - /// It's set when the core is first initialized. New `Mbuf` is allocated - /// from this `Mempool` when executed on this core. - pub static MEMPOOL: Cell<*mut ffi::rte_mempool> = Cell::new(ptr::null_mut()); -} - -/// Error indicating the `Mempool` is not found or is exhaused. -#[derive(Debug, Error)] -pub(crate) enum MempoolError { - #[error("Cannot allocate a new mbuf from mempool")] - Exhausted, - - #[error("Mempool for {0:?} not found.")] - NotFound(SocketId), -} - -/// A specialized hash map of `SocketId` to `&mut Mempool`. -#[derive(Debug)] -pub(crate) struct MempoolMap<'a> { - inner: HashMap, -} - -impl<'a> MempoolMap<'a> { - /// Creates a new map from a mutable slice. - pub(crate) fn new(mempools: &'a mut [Mempool]) -> Self { - let map = mempools - .iter_mut() - .map(|pool| { - let socket = SocketId(pool.raw().socket_id); - (socket, pool) - }) - .collect::>(); - - Self { inner: map } - } - - /// Returns a mutable reference to the raw mempool corresponding to the - /// socket id. - /// - /// # Errors - /// - /// If the value is not found, `MempoolError::NotFound` is returned. - pub(crate) fn get_raw(&mut self, socket_id: SocketId) -> Result<&mut ffi::rte_mempool> { - self.inner - .get_mut(&socket_id) - .ok_or_else(|| MempoolError::NotFound(socket_id).into()) - .map(|pool| pool.raw_mut()) - } -} - -impl<'a> Default for MempoolMap<'a> { - fn default() -> MempoolMap<'a> { - MempoolMap { - inner: HashMap::new(), - } - } -} diff --git a/core/src/dpdk/mod.rs b/core/src/dpdk/mod.rs deleted file mode 100644 index b0b7929a..00000000 --- a/core/src/dpdk/mod.rs +++ /dev/null @@ -1,244 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -mod kni; -mod mbuf; -mod mempool; -mod port; -#[cfg(feature = "metrics")] -mod stats; - -#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 -pub use self::kni::*; -#[allow(unreachable_pub)] -pub use self::mbuf::*; -pub(crate) use self::mempool::*; -#[allow(unreachable_pub)] -pub use self::port::*; -#[cfg(feature = "metrics")] -pub(crate) use self::stats::*; - -use crate::debug; -use crate::ffi::{self, AsStr, ToCString, ToResult}; -use crate::net::MacAddr; -use anyhow::Result; -use std::cell::Cell; -use std::fmt; -use std::mem; -use std::os::raw; -use thiserror::Error; - -/// An error generated in `libdpdk`. -/// -/// When an FFI call fails, the `errno` is translated into `DpdkError`. -#[derive(Debug, Error)] -#[error("{0}")] -pub(crate) struct DpdkError(String); - -impl DpdkError { - /// Returns the `DpdkError` for the most recent failure on the current - /// thread. - #[inline] - pub(crate) fn new() -> Self { - DpdkError::from_errno(-1) - } - - /// Returns the `DpdkError` for a specific `errno`. - #[inline] - fn from_errno(errno: raw::c_int) -> Self { - let errno = if errno == -1 { - unsafe { ffi::_rte_errno() } - } else { - -errno - }; - DpdkError(unsafe { ffi::rte_strerror(errno).as_str().into() }) - } -} - -/// An opaque identifier for a physical CPU socket. -/// -/// A socket is also known as a NUMA node. On a multi-socket system, for best -/// performance, ensure that the cores and memory used for packet processing -/// are in the same socket as the network interface card. -#[derive(Copy, Clone, Eq, Hash, PartialEq)] -pub struct SocketId(raw::c_int); - -impl SocketId { - /// A socket ID representing any NUMA node. - pub const ANY: Self = SocketId(-1); - - /// Returns the ID of the socket the current core is on. - #[inline] - pub fn current() -> SocketId { - unsafe { SocketId(ffi::rte_socket_id() as raw::c_int) } - } - - /// Returns all the socket IDs detected on the system. - #[inline] - pub fn all() -> Vec { - unsafe { - (0..ffi::rte_socket_count()) - .map(|idx| ffi::rte_socket_id_by_idx(idx)) - .filter(|&sid| sid != -1) - .map(SocketId) - .collect::>() - } - } - - /// Returns the raw value needed for FFI calls. - #[allow(clippy::trivially_copy_pass_by_ref)] - #[inline] - pub(crate) fn raw(&self) -> raw::c_int { - self.0 - } -} - -impl fmt::Debug for SocketId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "socket{}", self.0) - } -} - -/// An opaque identifier for a physical CPU core. -#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct CoreId(usize); - -impl CoreId { - /// Any lcore to indicate that no thread affinity is set. - pub const ANY: Self = CoreId(std::usize::MAX); - - /// Creates a new CoreId from the numeric ID assigned to the core - /// by the system. - #[inline] - pub(crate) fn new(i: usize) -> CoreId { - CoreId(i) - } - - /// Returns the ID of the current core. - #[inline] - pub fn current() -> CoreId { - CURRENT_CORE_ID.with(|tls| tls.get()) - } - - /// Returns the ID of the socket the core is on. - #[allow(clippy::trivially_copy_pass_by_ref)] - #[inline] - pub fn socket_id(&self) -> SocketId { - unsafe { SocketId(ffi::numa_node_of_cpu(self.0 as raw::c_int)) } - } - - /// Returns the raw value. - #[allow(clippy::trivially_copy_pass_by_ref)] - #[inline] - pub(crate) fn raw(&self) -> usize { - self.0 - } - - /// Sets the current thread's affinity to this core. - #[allow(clippy::trivially_copy_pass_by_ref)] - #[inline] - pub(crate) fn set_thread_affinity(&self) -> Result<()> { - unsafe { - // the two types that represent `cpu_set` have identical layout, - // hence it is safe to transmute between them. - let mut set: libc::cpu_set_t = mem::zeroed(); - libc::CPU_SET(self.0, &mut set); - let mut set: ffi::rte_cpuset_t = mem::transmute(set); - ffi::rte_thread_set_affinity(&mut set).into_result(DpdkError::from_errno)?; - } - - CURRENT_CORE_ID.with(|tls| tls.set(*self)); - Ok(()) - } -} - -impl fmt::Debug for CoreId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "core{}", self.0) - } -} - -thread_local! { - static CURRENT_CORE_ID: Cell = Cell::new(CoreId::ANY); -} - -/// Initializes the Environment Abstraction Layer (EAL). -pub(crate) fn eal_init(args: Vec) -> Result<()> { - debug!(arguments=?args); - - let len = args.len() as raw::c_int; - let args = args - .into_iter() - .map(|s| s.into_cstring()) - .collect::>(); - let mut ptrs = args - .iter() - .map(|s| s.as_ptr() as *mut raw::c_char) - .collect::>(); - - let res = unsafe { ffi::rte_eal_init(len, ptrs.as_mut_ptr()) }; - debug!("EAL parsed {} arguments.", res); - - res.into_result(DpdkError::from_errno).map(|_| ()) -} - -/// Cleans up the Environment Abstraction Layer (EAL). -pub(crate) fn eal_cleanup() -> Result<()> { - unsafe { - ffi::rte_eal_cleanup() - .into_result(DpdkError::from_errno) - .map(|_| ()) - } -} - -/// Returns the `MacAddr` of a port. -fn eth_macaddr_get(port_id: u16) -> MacAddr { - let mut addr = ffi::rte_ether_addr::default(); - unsafe { - ffi::rte_eth_macaddr_get(port_id, &mut addr); - } - addr.addr_bytes.into() -} - -/// Frees the `rte_mbuf` in bulk. -pub(crate) fn mbuf_free_bulk(mbufs: Vec<*mut ffi::rte_mbuf>) { - assert!(!mbufs.is_empty()); - - let mut to_free = Vec::with_capacity(mbufs.len()); - let pool = unsafe { (*mbufs[0]).pool }; - - for mbuf in mbufs.into_iter() { - if pool == unsafe { (*mbuf).pool } { - to_free.push(mbuf as *mut raw::c_void); - } else { - unsafe { - let len = to_free.len(); - ffi::_rte_mempool_put_bulk(pool, to_free.as_ptr(), len as u32); - to_free.set_len(0); - } - - to_free.push(mbuf as *mut raw::c_void); - } - } - - unsafe { - let len = to_free.len(); - ffi::_rte_mempool_put_bulk(pool, to_free.as_ptr(), len as u32); - to_free.set_len(0); - } -} diff --git a/core/src/dpdk/port.rs b/core/src/dpdk/port.rs deleted file mode 100644 index 302051ec..00000000 --- a/core/src/dpdk/port.rs +++ /dev/null @@ -1,651 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{CoreId, Kni, KniBuilder, KniTxQueue, Mbuf, Mempool, MempoolMap, SocketId}; -use crate::dpdk::DpdkError; -use crate::ffi::{self, AsStr, ToCString, ToResult}; -#[cfg(feature = "metrics")] -use crate::metrics::{labels, Counter, SINK}; -use crate::net::MacAddr; -#[cfg(feature = "pcap-dump")] -use crate::pcap; -use crate::{debug, ensure, info, warn}; -use anyhow::Result; -use std::collections::HashMap; -use std::fmt; -use std::os::raw; -use std::ptr; -use thiserror::Error; - -const DEFAULT_RSS_HF: u64 = - (ffi::ETH_RSS_IP | ffi::ETH_RSS_TCP | ffi::ETH_RSS_UDP | ffi::ETH_RSS_SCTP) as u64; - -/// An opaque identifier for an Ethernet device port. -#[derive(Copy, Clone)] -pub(crate) struct PortId(u16); - -impl PortId { - /// Returns the ID of the socket the port is connected to. - /// - /// Virtual devices do not have real socket IDs. The value returned - /// will be discarded if it does not match any of the system's physical - /// socket IDs. - #[inline] - pub(crate) fn socket_id(self) -> Option { - let id = unsafe { SocketId(ffi::rte_eth_dev_socket_id(self.0)) }; - if SocketId::all().contains(&id) { - Some(id) - } else { - None - } - } - - /// Returns the raw value needed for FFI calls. - #[allow(clippy::trivially_copy_pass_by_ref)] - #[inline] - pub(crate) fn raw(&self) -> u16 { - self.0 - } -} - -impl fmt::Debug for PortId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "port{}", self.0) - } -} - -/// The index of a receive queue. -#[derive(Copy, Clone)] -pub(crate) struct RxQueueIndex(u16); - -impl RxQueueIndex { - /// Returns the raw value needed for FFI calls. - #[allow(clippy::trivially_copy_pass_by_ref, dead_code)] - #[inline] - pub(crate) fn raw(&self) -> u16 { - self.0 - } -} - -/// The index of a transmit queue. -#[derive(Copy, Clone)] -pub(crate) struct TxQueueIndex(u16); - -impl TxQueueIndex { - /// Returns the raw value needed for FFI calls. - #[allow(clippy::trivially_copy_pass_by_ref, dead_code)] - #[inline] - pub(crate) fn raw(&self) -> u16 { - self.0 - } -} - -/// Either queue type (receive or transmit) with associated index. -#[allow(dead_code)] -pub(crate) enum RxTxQueue { - Rx(RxQueueIndex), - Tx(TxQueueIndex), -} - -/// The receive and transmit queue abstraction. Instead of modeling them -/// as two standalone queues, in the run-to-completion mode, they are modeled -/// as a queue pair associated with the core that runs the pipeline from -/// receive to send. -#[allow(missing_debug_implementations)] -#[derive(Clone)] -pub struct PortQueue { - port_id: PortId, - rxq: RxQueueIndex, - txq: TxQueueIndex, - kni: Option, - #[cfg(feature = "metrics")] - received: Option, - #[cfg(feature = "metrics")] - transmitted: Option, - #[cfg(feature = "metrics")] - dropped: Option, -} - -impl PortQueue { - #[cfg(not(feature = "metrics"))] - fn new(port: PortId, rxq: RxQueueIndex, txq: TxQueueIndex) -> Self { - PortQueue { - port_id: port, - rxq, - txq, - kni: None, - } - } - - #[cfg(feature = "metrics")] - fn new(port: PortId, rxq: RxQueueIndex, txq: TxQueueIndex) -> Self { - PortQueue { - port_id: port, - rxq, - txq, - kni: None, - received: None, - transmitted: None, - dropped: None, - } - } - /// Receives a burst of packets from the receive queue, up to a maximum - /// of 32 packets. - pub(crate) fn receive(&self) -> Vec { - const RX_BURST_MAX: usize = 32; - let mut ptrs = Vec::with_capacity(RX_BURST_MAX); - - let len = unsafe { - ffi::_rte_eth_rx_burst( - self.port_id.0, - self.rxq.0, - ptrs.as_mut_ptr(), - RX_BURST_MAX as u16, - ) - }; - - #[cfg(feature = "metrics")] - self.received.as_ref().unwrap().record(len as u64); - - unsafe { - ptrs.set_len(len as usize); - ptrs.into_iter() - .map(|ptr| Mbuf::from_ptr(ptr)) - .collect::>() - } - } - - /// Sends the packets to the transmit queue. - pub(crate) fn transmit(&self, packets: Vec) { - let mut ptrs = packets.into_iter().map(Mbuf::into_ptr).collect::>(); - - loop { - let to_send = ptrs.len() as u16; - let sent = unsafe { - ffi::_rte_eth_tx_burst(self.port_id.0, self.txq.0, ptrs.as_mut_ptr(), to_send) - }; - - if sent > 0 { - #[cfg(feature = "metrics")] - self.transmitted.as_ref().unwrap().record(sent as u64); - - if to_send - sent > 0 { - // still have packets not sent. tx queue is full but still making - // progress. we will keep trying until all packets are sent. drains - // the ones already sent first and try again on the rest. - let _drained = ptrs.drain(..sent as usize).collect::>(); - } else { - break; - } - } else { - // tx queue is full and we can't make progress, start dropping packets - // to avoid potentially stuck in an endless loop. - #[cfg(feature = "metrics")] - self.dropped.as_ref().unwrap().record(ptrs.len() as u64); - - super::mbuf_free_bulk(ptrs); - break; - } - } - } - - /// Returns a handle to send packets to the associated KNI interface. - pub fn kni(&self) -> Option<&KniTxQueue> { - self.kni.as_ref() - } - - /// Sets the TX queue for the KNI interface. - fn set_kni(&mut self, kni: KniTxQueue) { - self.kni = Some(kni); - } - - /// Sets the per queue counters. Some device drivers don't track TX - /// and RX packets per queue. Instead we will track them here for all - /// devices. Additionally we also track the TX packet drops when the - /// TX queue is full. - #[cfg(feature = "metrics")] - fn set_counters(&mut self, port: &str, core_id: CoreId) { - let counter = SINK.scoped("port").counter_with_labels( - "packets", - labels!( - "port" => port.to_owned(), - "dir" => "rx", - "core" => core_id.0.to_string(), - ), - ); - self.received = Some(counter); - - let counter = SINK.scoped("port").counter_with_labels( - "packets", - labels!( - "port" => port.to_owned(), - "dir" => "tx", - "core" => core_id.0.to_string(), - ), - ); - self.transmitted = Some(counter); - - let counter = SINK.scoped("port").counter_with_labels( - "dropped", - labels!( - "port" => port.to_owned(), - "dir" => "tx", - "core" => core_id.0.to_string(), - ), - ); - self.dropped = Some(counter); - } - - /// Returns the MAC address of the port. - pub fn mac_addr(&self) -> MacAddr { - super::eth_macaddr_get(self.port_id.0) - } -} - -/// Error indicating failed to initialize the port. -#[derive(Debug, Error)] -pub(crate) enum PortError { - /// Port is not found. - #[error("Port {0} is not found.")] - NotFound(String), - - #[error("Port is not bound to any cores.")] - CoreNotBound, - - /// The maximum number of RX queues is less than the number of cores - /// assigned to the port. - #[error("Insufficient number of RX queues '{0}'.")] - InsufficientRxQueues(usize), - - /// The maximum number of TX queues is less than the number of cores - /// assigned to the port. - #[error("Insufficient number of TX queues '{0}'.")] - InsufficientTxQueues(usize), -} - -/// An Ethernet device port. -pub(crate) struct Port { - id: PortId, - name: String, - device: String, - queues: HashMap, - kni: Option, - dev_info: ffi::rte_eth_dev_info, -} - -impl Port { - /// Returns the port id. - pub(crate) fn id(&self) -> PortId { - self.id - } - - /// Returns the application assigned logical name of the port. - /// - /// For applications with more than one port, this name can be used to - /// identifer the port. - pub(crate) fn name(&self) -> &str { - self.name.as_str() - } - - /// Returns the MAC address of the port. - pub(crate) fn mac_addr(&self) -> MacAddr { - super::eth_macaddr_get(self.id.0) - } - - /// Returns the available port queues. - pub(crate) fn queues(&self) -> &HashMap { - &self.queues - } - - /// Returns the KNI. - pub(crate) fn kni(&mut self) -> Option<&mut Kni> { - self.kni.as_mut() - } - - /// Starts the port. This is the final step before packets can be - /// received or transmitted on this port. Promiscuous mode is also - /// enabled automatically. - /// - /// # Errors - /// - /// If the port fails to start, `DpdkError` is returned. - pub(crate) fn start(&mut self) -> Result<()> { - unsafe { - ffi::rte_eth_dev_start(self.id.0).into_result(DpdkError::from_errno)?; - } - - info!("started port {}.", self.name()); - Ok(()) - } - - /// Stops the port. - pub(crate) fn stop(&mut self) { - unsafe { - ffi::rte_eth_dev_stop(self.id.0); - } - - info!("stopped port {}.", self.name()); - } - - #[cfg(feature = "metrics")] - pub(crate) fn stats(&self) -> super::PortStats { - super::PortStats::build(self) - } -} - -impl fmt::Debug for Port { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let info = self.dev_info; - f.debug_struct(&self.name()) - .field("device", &self.device) - .field("port", &self.id.0) - .field("mac", &format_args!("\"{}\"", self.mac_addr())) - .field("driver", &info.driver_name.as_str()) - .field("rx_offload", &format_args!("{:#x}", info.rx_offload_capa)) - .field("tx_offload", &format_args!("{:#x}", info.tx_offload_capa)) - .field("max_rxq", &info.max_rx_queues) - .field("max_txq", &info.max_tx_queues) - .field("socket", &self.id.socket_id().map_or(-1, |s| s.0)) - .finish() - } -} - -impl Drop for Port { - fn drop(&mut self) { - debug!("freeing {}.", self.name); - - unsafe { - ffi::rte_eth_dev_close(self.id.0); - } - } -} - -/// Builds a port from the configuration values. -pub(crate) struct PortBuilder<'a> { - name: String, - device: String, - port_id: PortId, - dev_info: ffi::rte_eth_dev_info, - cores: Vec, - mempools: MempoolMap<'a>, - rxd: u16, - txd: u16, -} - -impl<'a> PortBuilder<'a> { - /// Creates a new `PortBuilder` with a logical name and device name. - /// - /// The device name can be the following - /// * PCIe address, for example `0000:02:00.0` - /// * DPDK virtual device, for example `net_[pcap0|null0|tap0]` - /// - /// # Errors - /// - /// If the device is not found, `DpdkError` is returned. - pub(crate) fn new(name: String, device: String) -> Result { - let mut port_id = 0u16; - unsafe { - ffi::rte_eth_dev_get_port_by_name(device.clone().into_cstring().as_ptr(), &mut port_id) - .into_result(DpdkError::from_errno)?; - } - - let port_id = PortId(port_id); - debug!("{} is {:?}.", name, port_id); - - let mut dev_info = ffi::rte_eth_dev_info::default(); - unsafe { - ffi::rte_eth_dev_info_get(port_id.0, &mut dev_info); - } - - Ok(PortBuilder { - name, - device, - port_id, - dev_info, - cores: vec![CoreId::new(0)], - mempools: Default::default(), - rxd: 0, - txd: 0, - }) - } - - /// Sets the processing cores assigned to the port. - /// - /// Each core assigned will receive from and transmit through the port - /// independently using the run-to-completion model. - /// - /// # Errors - /// - /// If either the maximum number of RX or TX queues is less than the - /// number of cores assigned, `PortError` is returned. - pub(crate) fn cores(&mut self, cores: &[CoreId]) -> Result<&mut Self> { - ensure!(!cores.is_empty(), PortError::CoreNotBound); - - let mut cores = cores.to_vec(); - cores.sort(); - cores.dedup(); - let len = cores.len() as u16; - - ensure!( - self.dev_info.max_rx_queues >= len, - PortError::InsufficientRxQueues(self.dev_info.max_rx_queues as usize) - ); - ensure!( - self.dev_info.max_tx_queues >= len, - PortError::InsufficientTxQueues(self.dev_info.max_tx_queues as usize) - ); - - self.cores = cores; - Ok(self) - } - - /// Sets the receive and transmit queues' capacity. - /// - /// `rxd` is the receive queue capacity and `txd` is the trasmit queue - /// capacity. The values are checked against the descriptor limits of - /// the Ethernet device, and are adjusted if they exceed the boundaries. - /// - /// # Errors - /// - /// If the adjustment failed, `DpdkError` is returned. - pub(crate) fn rx_tx_queue_capacity(&mut self, rxd: usize, txd: usize) -> Result<&mut Self> { - let mut rxd2 = rxd as u16; - let mut txd2 = txd as u16; - - unsafe { - ffi::rte_eth_dev_adjust_nb_rx_tx_desc(self.port_id.0, &mut rxd2, &mut txd2) - .into_result(DpdkError::from_errno)?; - } - - info!( - cond: rxd2 != rxd as u16, - message = "adjusted rxd.", - before = rxd, - after = rxd2 - ); - info!( - cond: txd2 != txd as u16, - message = "adjusted txd.", - before = txd, - after = txd2 - ); - - self.rxd = rxd2; - self.txd = txd2; - Ok(self) - } - - /// Sets the available mempools. - pub(crate) fn mempools(&'a mut self, mempools: &'a mut [Mempool]) -> &'a mut Self { - self.mempools = MempoolMap::new(mempools); - self - } - - /// Creates the `Port`. - #[allow(clippy::cognitive_complexity)] - pub(crate) fn finish( - &mut self, - promiscuous: bool, - multicast: bool, - with_kni: bool, - ) -> anyhow::Result { - let len = self.cores.len() as u16; - let mut conf = ffi::rte_eth_conf::default(); - - // turns on receive side scaling if port has multiple cores. - if len > 1 { - conf.rxmode.mq_mode = ffi::rte_eth_rx_mq_mode::ETH_MQ_RX_RSS; - conf.rx_adv_conf.rss_conf.rss_hf = - DEFAULT_RSS_HF & self.dev_info.flow_type_rss_offloads; - } - - // turns on optimization for fast release of mbufs. - if self.dev_info.tx_offload_capa & ffi::DEV_TX_OFFLOAD_MBUF_FAST_FREE as u64 > 0 { - conf.txmode.offloads |= ffi::DEV_TX_OFFLOAD_MBUF_FAST_FREE as u64; - debug!("turned on optimization for fast release of mbufs."); - } - - // must configure the device first before everything else. - unsafe { - ffi::rte_eth_dev_configure(self.port_id.0, len, len, &conf) - .into_result(DpdkError::from_errno)?; - } - - // if the port is virtual, we will allocate it to the socket of - // the first assigned core. - let socket_id = self - .port_id - .socket_id() - .unwrap_or_else(|| self.cores[0].socket_id()); - debug!("{} connected to {:?}.", self.name, socket_id); - - // the socket determines which pool to allocate mbufs from. - let mempool = self.mempools.get_raw(socket_id)?; - - // if the port has kni enabled, we will allocate an interface. - let kni = if with_kni { - let kni = KniBuilder::new(mempool) - .name(&self.name) - .port_id(self.port_id) - .mac_addr(super::eth_macaddr_get(self.port_id.raw())) - .finish()?; - Some(kni) - } else { - None - }; - - let mut queues = HashMap::new(); - - // for each core, we setup a rx/tx queue pair. for simplicity, we - // will use the same index for both queues. - for (idx, &core_id) in self.cores.iter().enumerate() { - // for best performance, the port and cores should connect to - // the same socket. - warn!( - cond: core_id.socket_id() != socket_id, - message = "core socket does not match port socket.", - core = ?core_id, - core_socket = core_id.socket_id().0, - port_socket = socket_id.0 - ); - - // configures the RX queue with defaults - let rxq = RxQueueIndex(idx as u16); - unsafe { - ffi::rte_eth_rx_queue_setup( - self.port_id.0, - rxq.0, - self.rxd, - socket_id.0 as raw::c_uint, - ptr::null(), - mempool, - ) - .into_result(DpdkError::from_errno)?; - } - - // configures the TX queue with defaults - let txq = TxQueueIndex(idx as u16); - unsafe { - ffi::rte_eth_tx_queue_setup( - self.port_id.0, - txq.0, - self.txd, - socket_id.0 as raw::c_uint, - ptr::null(), - ) - .into_result(DpdkError::from_errno)?; - } - - #[cfg(feature = "pcap-dump")] - { - pcap::capture_queue( - self.port_id, - self.name.as_str(), - core_id, - RxTxQueue::Rx(rxq), - )?; - - pcap::capture_queue( - self.port_id, - self.name.as_str(), - core_id, - RxTxQueue::Tx(txq), - )?; - } - - let mut q = PortQueue::new(self.port_id, rxq, txq); - - if let Some(kni) = &kni { - q.set_kni(kni.txq()); - } - - #[cfg(feature = "metrics")] - q.set_counters(&self.name, core_id); - - queues.insert(core_id, q); - debug!("initialized port queue for {:?}.", core_id); - } - - unsafe { - // sets the port's promiscuous mode. - if promiscuous { - ffi::rte_eth_promiscuous_enable(self.port_id.0); - } else { - ffi::rte_eth_promiscuous_disable(self.port_id.0); - } - - // sets the port's multicast mode. - if multicast { - ffi::rte_eth_allmulticast_enable(self.port_id.0); - } else { - ffi::rte_eth_allmulticast_disable(self.port_id.0); - } - } - - info!("initialized port {}.", self.name); - - Ok(Port { - id: self.port_id, - name: self.name.clone(), - device: self.device.clone(), - queues, - kni, - dev_info: self.dev_info, - }) - } -} diff --git a/core/src/dpdk/stats.rs b/core/src/dpdk/stats.rs deleted file mode 100644 index a12f41e8..00000000 --- a/core/src/dpdk/stats.rs +++ /dev/null @@ -1,130 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use super::{Mempool, Port, PortId}; -use crate::dpdk::DpdkError; -use crate::ffi::{self, AsStr, ToResult}; -use crate::metrics::{labels, Key, Measurement}; -use anyhow::Result; -use std::ptr::NonNull; - -/// Port stats collector. -pub(crate) struct PortStats { - id: PortId, - name: String, -} - -impl PortStats { - /// Builds a collector from the port. - pub(crate) fn build(port: &Port) -> Self { - PortStats { - id: port.id(), - name: port.name().to_owned(), - } - } - - /// Returns the port name. - pub(crate) fn name(&self) -> &str { - self.name.as_str() - } - - /// Returns a counter with port and direction labels. - fn new_counter(&self, name: &'static str, value: u64, dir: &'static str) -> (Key, Measurement) { - ( - Key::from_name_and_labels( - name, - labels!( - "port" => self.name.clone(), - "dir" => dir, - ), - ), - Measurement::Counter(value), - ) - } - - /// Collects the port stats tracked by DPDK. - pub(crate) fn collect(&self) -> Result> { - let mut stats = ffi::rte_eth_stats::default(); - unsafe { - ffi::rte_eth_stats_get(self.id.raw(), &mut stats).into_result(DpdkError::from_errno)?; - } - - let mut values = Vec::new(); - - values.push(self.new_counter("octets", stats.ibytes, "rx")); - values.push(self.new_counter("octets", stats.obytes, "tx")); - values.push(self.new_counter("dropped", stats.imissed, "rx")); - values.push(self.new_counter("errors", stats.ierrors, "rx")); - values.push(self.new_counter("errors", stats.oerrors, "tx")); - values.push(self.new_counter("no_mbuf", stats.rx_nombuf, "rx")); - - Ok(values) - } -} - -/// Mempool stats collector. -pub(crate) struct MempoolStats { - raw: NonNull, -} - -impl MempoolStats { - /// Builds a collector from the port. - pub(crate) fn build(mempool: &Mempool) -> Self { - MempoolStats { - raw: unsafe { - NonNull::new_unchecked( - mempool.raw() as *const ffi::rte_mempool as *mut ffi::rte_mempool - ) - }, - } - } - - fn raw(&self) -> &ffi::rte_mempool { - unsafe { self.raw.as_ref() } - } - - /// Returns the name of the `Mempool`. - fn name(&self) -> &str { - self.raw().name[..].as_str() - } - - /// Returns a gauge. - fn new_gauge(&self, name: &'static str, value: i64) -> (Key, Measurement) { - ( - Key::from_name_and_labels( - name, - labels!( - "pool" => self.name().to_string(), - ), - ), - Measurement::Gauge(value), - ) - } - - /// Collects the mempool stats. - pub(crate) fn collect(&self) -> Vec<(Key, Measurement)> { - let used = unsafe { ffi::rte_mempool_in_use_count(self.raw()) as i64 }; - let free = self.raw().size as i64 - used; - - vec![self.new_gauge("used", used), self.new_gauge("free", free)] - } -} - -/// Send mempool stats across threads. -unsafe impl Send for MempoolStats {} -unsafe impl Sync for MempoolStats {} diff --git a/core/src/ffi/dpdk.rs b/core/src/ffi/dpdk.rs index 1f54ff31..8c586a13 100644 --- a/core/src/ffi/dpdk.rs +++ b/core/src/ffi/dpdk.rs @@ -67,13 +67,8 @@ pub(crate) struct SocketId(raw::c_int); impl SocketId { /// A socket ID representing any NUMA socket. + #[allow(dead_code)] pub(crate) const ANY: Self = SocketId(-1); - - /// Returns the ID of the socket the current core is on. - #[inline] - pub(crate) fn current() -> SocketId { - unsafe { SocketId(cffi::rte_socket_id() as raw::c_int) } - } } impl fmt::Debug for SocketId { @@ -137,6 +132,7 @@ pub(crate) fn mempool_lookup>(name: S) -> Result { } /// Returns the number of elements which have been allocated from the mempool. +#[allow(dead_code)] pub(crate) fn mempool_in_use_count(mp: &MempoolPtr) -> usize { unsafe { cffi::rte_mempool_in_use_count(mp.deref()) as usize } } @@ -152,6 +148,7 @@ pub(crate) struct LcoreId(raw::c_uint); impl LcoreId { /// Any lcore to indicate that no thread affinity is set. + #[cfg(test)] pub(crate) const ANY: Self = LcoreId(raw::c_uint::MAX); /// Returns the ID of the current execution unit or `LcoreId::ANY` when @@ -201,14 +198,14 @@ pub(crate) fn get_next_lcore( match unsafe { cffi::rte_get_next_lcore(i, skip_master, wrap) } { cffi::RTE_MAX_LCORE => None, - id @ _ => Some(LcoreId(id)), + id => Some(LcoreId(id)), } } /// The function passed to `rte_eal_remote_launch`. unsafe extern "C" fn lcore_fn(arg: *mut raw::c_void) -> raw::c_int where - F: FnOnce() -> () + Send + 'static, + F: FnOnce() + Send + 'static, { let f = Box::from_raw(arg as *mut F); @@ -225,7 +222,7 @@ where /// Launches a function on another lcore. pub(crate) fn eal_remote_launch(worker_id: LcoreId, f: F) -> Result<()> where - F: FnOnce() -> () + Send + 'static, + F: FnOnce() + Send + 'static, { let ptr = Box::into_raw(Box::new(f)) as *mut raw::c_void; @@ -303,11 +300,10 @@ pub(crate) fn eth_dev_adjust_nb_rx_tx_desc( /// Returns the value of promiscuous mode for a device. pub(crate) fn eth_promiscuous_get(port_id: PortId) -> bool { - match unsafe { cffi::rte_eth_promiscuous_get(port_id.0).into_result(DpdkError::from_errno) } { - Ok(1) => true, - // assuming port_id is valid, we treat error as mode disabled. - _ => false, - } + let mode = + unsafe { cffi::rte_eth_promiscuous_get(port_id.0).into_result(DpdkError::from_errno) }; + // assuming port_id is valid, treats Ok(0) and Err(_) both as disabled. + matches!(mode, Ok(1)) } /// Enables receipt in promiscuous mode for a device. @@ -330,11 +326,10 @@ pub(crate) fn eth_promiscuous_disable(port_id: PortId) -> Result<()> { /// Returns the value of allmulticast mode for a device. pub(crate) fn eth_allmulticast_get(port_id: PortId) -> bool { - match unsafe { cffi::rte_eth_allmulticast_get(port_id.0).into_result(DpdkError::from_errno) } { - Ok(1) => true, - // assuming port_id is valid, we treat error as mode disabled. - _ => false, - } + let mode = + unsafe { cffi::rte_eth_allmulticast_get(port_id.0).into_result(DpdkError::from_errno) }; + // assuming port_id is valid, treats Ok(0) and Err(_) both as disabled. + matches!(mode, Ok(1)) } /// Enables the receipt of any multicast frame by a device. diff --git a/core/src/ffi/mod.rs b/core/src/ffi/mod.rs index b0cac1a1..d1bcc259 100644 --- a/core/src/ffi/mod.rs +++ b/core/src/ffi/mod.rs @@ -20,8 +20,6 @@ pub(crate) mod dpdk; #[cfg(feature = "pcap-dump")] pub(crate) mod pcap; -pub(crate) use capsule_ffi::*; - use crate::warn; use anyhow::Result; use std::error::Error; diff --git a/core/src/ffi/pcap.rs b/core/src/ffi/pcap.rs index 812b043a..6eeb74a9 100644 --- a/core/src/ffi/pcap.rs +++ b/core/src/ffi/pcap.rs @@ -20,7 +20,6 @@ use super::{AsStr, EasyPtr, ToCString, ToResult}; use crate::ffi::dpdk::MbufPtr; use anyhow::Result; use capsule_ffi as cffi; -use libc; use std::ops::DerefMut; use std::os::raw; use std::ptr; @@ -104,6 +103,26 @@ pub(crate) fn close(handle: &mut PcapPtr) { } } +/// An error generated in `libpcap`. +#[derive(Debug, Error)] +#[error("{0}")] +pub(crate) struct PcapError(String); + +impl PcapError { + /// Returns the `PcapError` with the given error message. + #[inline] + fn new(msg: &str) -> Self { + PcapError(msg.into()) + } + + /// Returns the `PcapError` pertaining to the last `libpcap` error. + #[inline] + fn get_error(handle: &mut PcapPtr) -> Self { + let msg = unsafe { cffi::pcap_geterr(handle.deref_mut()) }; + PcapError::new((msg as *const raw::c_char).as_str()) + } +} + /// Opens a saved capture file for reading. #[cfg(test)] pub(crate) fn open_offline>(filename: S) -> Result { @@ -134,23 +153,3 @@ pub(crate) fn next(handle: &mut PcapPtr) -> Result<&[u8]> { } } } - -/// An error generated in `libpcap`. -#[derive(Debug, Error)] -#[error("{0}")] -pub(crate) struct PcapError(String); - -impl PcapError { - /// Returns the `PcapError` with the given error message. - #[inline] - fn new(msg: &str) -> Self { - PcapError(msg.into()) - } - - /// Returns the `PcapError` pertaining to the last `libpcap` error. - #[inline] - fn get_error(handle: &mut PcapPtr) -> Self { - let msg = unsafe { cffi::pcap_geterr(handle.deref_mut()) }; - PcapError::new((msg as *const raw::c_char).as_str()) - } -} diff --git a/core/src/lib.rs b/core/src/lib.rs index b42ac8ab..8fdeb4b2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -82,8 +82,7 @@ //! //! ## Feature flags //! -//! - `default`: Enables metrics by default. -//! - `metrics`: Enables automatic [`metrics`] collection. +//! - `default`: None of the features are enabled. //! - `pcap-dump`: Enables capturing port traffic to `pcap` files. //! - `testils`: Enables utilities for unit testing and benchmarking. //! - `full`: Enables all features. @@ -94,7 +93,6 @@ //! - [nat64]: IPv6 to IPv4 NAT gateway example. //! - [ping4d]: Ping4 daemon example. //! - [pktdump]: Packet dump example. -//! - [signals]: Linux signal handling example. //! - [skeleton]: Base skeleton example. //! - [syn-flood]: TCP SYN flood example. //! @@ -109,40 +107,25 @@ //! [rr]: https://rr-project.org/ //! [README]: https://github.com/capsule-rs/capsule/blob/master/README.md //! [sandbox repo]: https://github.com/capsule-rs/sandbox -//! [`metrics`]: crate::metrics //! [kni]: https://github.com/capsule-rs/capsule/tree/master/examples/kni //! [nat64]: https://github.com/capsule-rs/capsule/tree/master/examples/nat64 //! [ping4d]: https://github.com/capsule-rs/capsule/tree/master/examples/ping4d //! [pktdump]: https://github.com/capsule-rs/capsule/tree/master/examples/pktdump -//! [signals]: https://github.com/capsule-rs/capsule/tree/master/examples/signals //! [skeleton]: https://github.com/capsule-rs/capsule/tree/master/examples/skeleton //! [syn-flood]: https://github.com/capsule-rs/capsule/tree/master/examples/syn-flood // alias for the macros extern crate self as capsule; -pub mod batch; -pub mod config; -mod dpdk; pub(crate) mod ffi; mod macros; -#[cfg(feature = "metrics")] -#[cfg_attr(docsrs, doc(cfg(all(feature = "default", feature = "metrics"))))] -pub mod metrics; pub mod net; pub mod packets; -#[cfg(feature = "pcap-dump")] -#[cfg_attr(docsrs, doc(cfg(feature = "pcap-dump")))] -mod pcap; -pub mod rt2; -mod runtime; +pub mod runtime; #[cfg(any(test, feature = "testils"))] #[cfg_attr(docsrs, doc(cfg(feature = "testils")))] pub mod testils; -pub use self::dpdk::{KniRx, KniTxQueue, Mbuf, PortQueue, SizeOf}; -pub use self::runtime::{Runtime, UnixSignal}; -pub use capsule_macros::SizeOf; #[cfg(any(test, feature = "testils"))] #[cfg_attr(docsrs, doc(cfg(feature = "testils")))] pub use capsule_macros::{bench, test}; diff --git a/core/src/metrics.rs b/core/src/metrics.rs deleted file mode 100644 index 83232974..00000000 --- a/core/src/metrics.rs +++ /dev/null @@ -1,140 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -//! Exposes framework metrics, including port, kni, mempool, and pipeline -//! metrics. -//! -//! # Port Metrics -//! -//! * `port.packets`, total number of successfully received or transmitted -//! packets. -//! * `port.octets`, total number of successfully received or transmitted -//! bytes. -//! * `port.dropped`, total number of packets dropped because the receive -//! or transmit queues are full. -//! * `port.errors`, total number of erroneous received packets or packets -//! failed to transmit. -//! * `port.no_mbuf`, total number of packets dropped due to mbuf allocation -//! failures. -//! -//! Each metric is labeled with the port name and a direction, which can be -//! either RX or TX. `port.packets` and `port.dropped` are tracked per core -//! and labeled with the core id. The others are tracked by only the overall -//! metrics. -//! -//! -//! # KNI Metrics -//! -//! * `kni.packets`, total number of successfully received or transmitted -//! packets. -//! * `kni.octets`, total number of successfully received or transmitted bytes. -//! * `kni.dropped`, total number of packets dropped because the transmit -//! queue is full. -//! -//! Each metric is labeled with the KNI interface name and a direction, which -//! can be either RX or TX. -//! -//! -//! # Mempool Metrics -//! -//! * `mempool.used`, total number of mbufs which have been allocated from -//! the mempool. -//! * `mempool.free`, total number of mbufs available for allocation. -//! -//! Each metric is labeled with the mempool name. -//! -//! -//! # Pipeline Metrics -//! -//! * `pipeline.runs`, total number of times the pipeline executes. -//! * `pipeline.processed`, total number of successfully processed packets. -//! * `pipeline.dropped`, total number of packets intentionally dropped. -//! * `pipeline.errors`, total number of packet dropped due to processing -//! errors. -//! -//! Each metric is tracked per core and labeled with the core id and the -//! pipeline name. If the pipeline doesn't have a name, it will be labeled -//! as "default". - -// re-export some metrics types to make feature gated imports easier. -pub(crate) use metrics_core::{labels, Key}; -pub(crate) use metrics_runtime::data::Counter; -pub(crate) use metrics_runtime::Measurement; - -use crate::dpdk::{Mempool, MempoolStats, Port}; -use crate::warn; -use anyhow::{anyhow, Result}; -use metrics_runtime::{Receiver, Sink}; -use once_cell::sync::{Lazy, OnceCell}; - -/// The metrics store. -static RECEIVER: OnceCell = OnceCell::new(); - -/// Safely initializes the metrics store. Because the receiver builder could -/// potentially fail, the `Lazy` convenience type is not safe. -/// -/// Also very important that `init` is not called twice. -pub(crate) fn init() -> Result<()> { - let receiver = Receiver::builder().build()?; - - RECEIVER - .set(receiver) - .map_err(|_| anyhow!("already initialized."))?; - Ok(()) -} - -/// Registers DPDK collected port stats with the metrics store. -pub(crate) fn register_port_stats(ports: &[Port]) { - let stats = ports.iter().map(Port::stats).collect::>(); - SINK.clone().proxy("port", move || { - stats - .iter() - .flat_map(|s| { - s.collect().unwrap_or_else(|err| { - warn!(message = "failed to collect stats.", port = s.name(), ?err); - Vec::new() - }) - }) - .collect() - }); -} - -/// Registers collected mempool stats with the metrics store. -pub(crate) fn register_mempool_stats(mempools: &[Mempool]) { - let stats = mempools.iter().map(Mempool::stats).collect::>(); - SINK.clone().proxy("mempool", move || { - stats.iter().flat_map(MempoolStats::collect).collect() - }); -} - -/// Returns the global metrics store. -/// -/// Metrics are managed using [metrics-rs]. The application can use this to -/// access framework metrics or to add new application metrics. -/// -/// # Panics -/// -/// Panics if `Runtime::build` is not called first. -/// -/// [metrics-rs]: https://github.com/metrics-rs -pub fn global() -> &'static Receiver { - unsafe { RECEIVER.get_unchecked() } -} - -/// The root sink for all framework metrics. -pub(crate) static SINK: Lazy = Lazy::new(|| global().sink().scoped("capsule")); diff --git a/core/src/packets/arp.rs b/core/src/packets/arp.rs index d6744044..ab2d1041 100644 --- a/core/src/packets/arp.rs +++ b/core/src/packets/arp.rs @@ -18,10 +18,11 @@ //! Address Resolution Protocol. +use crate::ensure; use crate::net::MacAddr; +use crate::packets::ethernet::{EtherTypes, Ethernet}; use crate::packets::types::u16be; -use crate::packets::{EtherTypes, Ethernet, Internal, Packet}; -use crate::{ensure, SizeOf}; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::net::Ipv4Addr; @@ -533,12 +534,6 @@ pub trait HardwareAddr: SizeOf + Copy + fmt::Display { fn default() -> Self; } -impl SizeOf for MacAddr { - fn size_of() -> usize { - 6 - } -} - impl HardwareAddr for MacAddr { fn addr_type() -> HardwareType { HardwareTypes::Ethernet @@ -561,12 +556,6 @@ pub trait ProtocolAddr: SizeOf + Copy + fmt::Display { fn default() -> Self; } -impl SizeOf for Ipv4Addr { - fn size_of() -> usize { - 4 - } -} - impl ProtocolAddr for Ipv4Addr { fn addr_type() -> ProtocolType { ProtocolTypes::Ipv4 @@ -631,8 +620,8 @@ impl Default for ArpHeader { #[cfg(test)] mod tests { use super::*; + use crate::packets::Mbuf; use crate::testils::byte_arrays::ARP4_PACKET; - use crate::Mbuf; #[test] fn size_of_arp_header() { diff --git a/core/src/packets/ethernet.rs b/core/src/packets/ethernet.rs index eb3a6e93..6f6c6475 100644 --- a/core/src/packets/ethernet.rs +++ b/core/src/packets/ethernet.rs @@ -16,12 +16,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -use crate::dpdk::BufferError; +//! Ethernet Protocol. + +use crate::ensure; use crate::net::MacAddr; use crate::packets::types::u16be; -use crate::packets::{Internal, Packet}; -use crate::{ensure, Mbuf, SizeOf}; -use anyhow::Result; +use crate::packets::{Internal, Mbuf, Packet, SizeOf}; +use anyhow::{anyhow, Result}; use std::fmt; use std::ptr::NonNull; @@ -293,7 +294,7 @@ impl Packet for Ethernet { // header will cause a panic. ensure!( packet.mbuf().data_len() >= packet.header_len(), - BufferError::OutOfBuffer(packet.header_len(), packet.mbuf().data_len()) + anyhow!("header size exceeds remaining buffer size.") ); Ok(packet) diff --git a/core/src/packets/icmp/v4/echo_reply.rs b/core/src/packets/icmp/v4/echo_reply.rs index 71c3ac6a..35738ba5 100644 --- a/core/src/packets/icmp/v4/echo_reply.rs +++ b/core/src/packets/icmp/v4/echo_reply.rs @@ -18,8 +18,7 @@ use crate::packets::icmp::v4::{Icmpv4, Icmpv4Message, Icmpv4Packet, Icmpv4Type, Icmpv4Types}; use crate::packets::types::u16be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::ptr::NonNull; @@ -219,9 +218,9 @@ struct EchoReplyBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v4::Ipv4; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::Mbuf; #[test] fn size_of_echo_reply_body() { diff --git a/core/src/packets/icmp/v4/echo_request.rs b/core/src/packets/icmp/v4/echo_request.rs index cd08593a..e388abdb 100644 --- a/core/src/packets/icmp/v4/echo_request.rs +++ b/core/src/packets/icmp/v4/echo_request.rs @@ -18,8 +18,7 @@ use crate::packets::icmp::v4::{Icmpv4, Icmpv4Message, Icmpv4Packet, Icmpv4Type, Icmpv4Types}; use crate::packets::types::u16be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::ptr::NonNull; @@ -220,9 +219,9 @@ struct EchoRequestBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v4::Ipv4; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::Mbuf; #[test] fn size_of_echo_request_body() { diff --git a/core/src/packets/icmp/v4/mod.rs b/core/src/packets/icmp/v4/mod.rs index 511e872c..43753583 100644 --- a/core/src/packets/icmp/v4/mod.rs +++ b/core/src/packets/icmp/v4/mod.rs @@ -29,11 +29,11 @@ pub use self::redirect::*; pub use self::time_exceeded::*; pub use capsule_macros::Icmpv4Packet; +use crate::ensure; use crate::packets::ip::v4::Ipv4; use crate::packets::ip::ProtocolNumbers; use crate::packets::types::u16be; -use crate::packets::{checksum, Internal, Packet}; -use crate::{ensure, SizeOf}; +use crate::packets::{checksum, Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::ptr::NonNull; @@ -446,10 +446,10 @@ pub trait Icmpv4Packet { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v4::Ipv4; - use crate::packets::Ethernet; + use crate::packets::Mbuf; use crate::testils::byte_arrays::{ICMPV4_PACKET, IPV4_UDP_PACKET}; - use crate::Mbuf; #[test] fn size_of_icmpv4_header() { diff --git a/core/src/packets/icmp/v4/redirect.rs b/core/src/packets/icmp/v4/redirect.rs index 8de0c154..9177800e 100644 --- a/core/src/packets/icmp/v4/redirect.rs +++ b/core/src/packets/icmp/v4/redirect.rs @@ -18,8 +18,7 @@ use crate::packets::icmp::v4::{Icmpv4, Icmpv4Message, Icmpv4Packet, Icmpv4Type, Icmpv4Types}; use crate::packets::ip::v4::IPV4_MIN_MTU; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::net::Ipv4Addr; @@ -216,10 +215,10 @@ impl Default for RedirectBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v4::Ipv4; - use crate::packets::Ethernet; + use crate::packets::Mbuf; use crate::testils::byte_arrays::IPV4_TCP_PACKET; - use crate::Mbuf; #[test] fn size_of_redirect_body() { diff --git a/core/src/packets/icmp/v4/time_exceeded.rs b/core/src/packets/icmp/v4/time_exceeded.rs index affd0ad9..8a5e697b 100644 --- a/core/src/packets/icmp/v4/time_exceeded.rs +++ b/core/src/packets/icmp/v4/time_exceeded.rs @@ -19,8 +19,7 @@ use crate::packets::icmp::v4::{Icmpv4, Icmpv4Message, Icmpv4Packet, Icmpv4Type, Icmpv4Types}; use crate::packets::ip::v4::IPV4_MIN_MTU; use crate::packets::types::u32be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::ptr::NonNull; @@ -180,10 +179,10 @@ struct TimeExceededBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v4::Ipv4; - use crate::packets::Ethernet; + use crate::packets::Mbuf; use crate::testils::byte_arrays::IPV4_TCP_PACKET; - use crate::Mbuf; #[test] fn size_of_time_exceeded_body() { diff --git a/core/src/packets/icmp/v6/echo_reply.rs b/core/src/packets/icmp/v6/echo_reply.rs index c2ab433d..02d2d435 100644 --- a/core/src/packets/icmp/v6/echo_reply.rs +++ b/core/src/packets/icmp/v6/echo_reply.rs @@ -19,8 +19,7 @@ use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; use crate::packets::ip::v6::Ipv6Packet; use crate::packets::types::u16be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::ptr::NonNull; @@ -218,9 +217,9 @@ struct EchoReplyBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::Mbuf; #[test] fn size_of_echo_reply_body() { diff --git a/core/src/packets/icmp/v6/echo_request.rs b/core/src/packets/icmp/v6/echo_request.rs index 5e098131..d2885215 100644 --- a/core/src/packets/icmp/v6/echo_request.rs +++ b/core/src/packets/icmp/v6/echo_request.rs @@ -19,8 +19,7 @@ use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; use crate::packets::ip::v6::Ipv6Packet; use crate::packets::types::u16be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::ptr::NonNull; @@ -219,9 +218,9 @@ struct EchoRequestBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::Mbuf; #[test] fn size_of_echo_request_body() { diff --git a/core/src/packets/icmp/v6/mod.rs b/core/src/packets/icmp/v6/mod.rs index 2fa68144..bdd33631 100644 --- a/core/src/packets/icmp/v6/mod.rs +++ b/core/src/packets/icmp/v6/mod.rs @@ -18,25 +18,25 @@ //! Internet Control Message Protocol for IPv6. -mod destination_unreachable; mod echo_reply; mod echo_request; pub mod ndp; mod time_exceeded; mod too_big; +mod unreachable; -pub use self::destination_unreachable::*; pub use self::echo_reply::*; pub use self::echo_request::*; pub use self::time_exceeded::*; pub use self::too_big::*; +pub use self::unreachable::*; pub use capsule_macros::Icmpv6Packet; +use crate::ensure; use crate::packets::ip::v6::Ipv6Packet; use crate::packets::ip::ProtocolNumbers; use crate::packets::types::u16be; -use crate::packets::{checksum, Internal, Packet}; -use crate::{ensure, SizeOf}; +use crate::packets::{checksum, Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::ptr::NonNull; @@ -498,11 +498,11 @@ pub trait Icmpv6Packet { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::icmp::v6::ndp::RouterAdvertisement; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; + use crate::packets::Mbuf; use crate::testils::byte_arrays::{ICMPV6_PACKET, IPV6_TCP_PACKET, ROUTER_ADVERT_PACKET}; - use crate::Mbuf; #[test] fn size_of_icmpv6_header() { diff --git a/core/src/packets/icmp/v6/ndp/mod.rs b/core/src/packets/icmp/v6/ndp/mod.rs index f9558210..80ad51b0 100644 --- a/core/src/packets/icmp/v6/ndp/mod.rs +++ b/core/src/packets/icmp/v6/ndp/mod.rs @@ -43,10 +43,9 @@ pub use self::redirect::*; pub use self::router_advert::*; pub use self::router_solicit::*; -use crate::dpdk::BufferError; -use crate::packets::{Immutable, Internal, Packet}; -use crate::{ensure, Mbuf, SizeOf}; -use anyhow::Result; +use crate::ensure; +use crate::packets::{Immutable, Internal, Mbuf, Packet, SizeOf}; +use anyhow::{anyhow, Result}; use std::fmt; use std::marker::PhantomData; use std::ptr::NonNull; @@ -174,8 +173,7 @@ impl<'a> ImmutableNdpOption<'a> { /// /// # Errors /// - /// Returns `BufferError::OutOfBuffer` if the buffer does not have - /// enough free space. + /// Returns an error if the buffer does not have enough free space. #[inline] fn new(mbuf: &'a mut Mbuf, offset: usize) -> Result { let tuple = mbuf.read_data(offset)?; @@ -189,7 +187,7 @@ impl<'a> ImmutableNdpOption<'a> { // indicated by the length field stored in the option itself ensure!( option.mbuf.len() >= option.end_offset(), - BufferError::OutOfBuffer(option.end_offset(), option.mbuf.len()) + anyhow!("option size exceeds remaining buffer size.") ); Ok(option) @@ -297,8 +295,7 @@ impl<'a> MutableNdpOption<'a> { /// /// # Errors /// - /// Returns `BufferError::OutOfBuffer` if the buffer does not have - /// enough free space. + /// Returns an error if the buffer does not have enough free space. #[inline] fn new(mbuf: &'a mut Mbuf, offset: usize) -> Result { let tuple = mbuf.read_data(offset)?; @@ -312,7 +309,7 @@ impl<'a> MutableNdpOption<'a> { // indicated by the length field stored in the option itself ensure!( option.mbuf.len() >= option.end_offset(), - BufferError::OutOfBuffer(option.end_offset(), option.mbuf.len()) + anyhow!("option size exceeds remaining buffer size.") ); Ok(option) @@ -554,8 +551,8 @@ pub trait NdpOption<'a> { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; use crate::testils::byte_arrays::ROUTER_ADVERT_PACKET; #[capsule::test] diff --git a/core/src/packets/icmp/v6/ndp/neighbor_advert.rs b/core/src/packets/icmp/v6/ndp/neighbor_advert.rs index c8c93655..9c532fa7 100644 --- a/core/src/packets/icmp/v6/ndp/neighbor_advert.rs +++ b/core/src/packets/icmp/v6/ndp/neighbor_advert.rs @@ -20,8 +20,7 @@ use super::NdpPacket; use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; use crate::packets::ip::v6::Ipv6Packet; use crate::packets::types::u16be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::net::Ipv6Addr; @@ -271,9 +270,9 @@ impl Default for NeighborAdvertisementBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::Mbuf; #[test] fn size_of_neighbor_advertisement_body() { diff --git a/core/src/packets/icmp/v6/ndp/neighbor_solicit.rs b/core/src/packets/icmp/v6/ndp/neighbor_solicit.rs index bc96e6b9..18f09f50 100644 --- a/core/src/packets/icmp/v6/ndp/neighbor_solicit.rs +++ b/core/src/packets/icmp/v6/ndp/neighbor_solicit.rs @@ -20,8 +20,7 @@ use super::NdpPacket; use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; use crate::packets::ip::v6::Ipv6Packet; use crate::packets::types::u32be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::net::Ipv6Addr; @@ -186,9 +185,9 @@ impl Default for NeighborSolicitationBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::Mbuf; #[test] fn size_of_neighbor_solicitation_body() { diff --git a/core/src/packets/icmp/v6/ndp/options/link_layer_addr.rs b/core/src/packets/icmp/v6/ndp/options/link_layer_addr.rs index 1c5eeb9c..c196745c 100644 --- a/core/src/packets/icmp/v6/ndp/options/link_layer_addr.rs +++ b/core/src/packets/icmp/v6/ndp/options/link_layer_addr.rs @@ -16,10 +16,10 @@ * SPDX-License-Identifier: Apache-2.0 */ +use crate::ensure; use crate::net::MacAddr; use crate::packets::icmp::v6::ndp::{NdpOption, NdpOptionType, NdpOptionTypes}; -use crate::packets::Internal; -use crate::{ensure, Mbuf, SizeOf}; +use crate::packets::{Internal, Mbuf, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::ptr::NonNull; @@ -190,9 +190,10 @@ impl Default for LinkLayerAddressFields { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::icmp::v6::ndp::{NdpPacket, RouterAdvertisement}; use crate::packets::ip::v6::Ipv6; - use crate::packets::{Ethernet, Packet}; + use crate::packets::Packet; use crate::testils::byte_arrays::ROUTER_ADVERT_PACKET; #[test] diff --git a/core/src/packets/icmp/v6/ndp/options/mtu.rs b/core/src/packets/icmp/v6/ndp/options/mtu.rs index 0d2f8e2b..dc533af7 100644 --- a/core/src/packets/icmp/v6/ndp/options/mtu.rs +++ b/core/src/packets/icmp/v6/ndp/options/mtu.rs @@ -16,10 +16,10 @@ * SPDX-License-Identifier: Apache-2.0 */ +use crate::ensure; use crate::packets::icmp::v6::ndp::{NdpOption, NdpOptionType, NdpOptionTypes}; use crate::packets::types::{u16be, u32be}; -use crate::packets::Internal; -use crate::{ensure, Mbuf, SizeOf}; +use crate::packets::{Internal, Mbuf, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::ptr::NonNull; @@ -170,9 +170,10 @@ impl Default for MtuFields { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::icmp::v6::ndp::{NdpPacket, RouterAdvertisement}; use crate::packets::ip::v6::Ipv6; - use crate::packets::{Ethernet, Packet}; + use crate::packets::Packet; use crate::testils::byte_arrays::ROUTER_ADVERT_PACKET; #[test] diff --git a/core/src/packets/icmp/v6/ndp/options/prefix_info.rs b/core/src/packets/icmp/v6/ndp/options/prefix_info.rs index bcd3167a..d721ca52 100644 --- a/core/src/packets/icmp/v6/ndp/options/prefix_info.rs +++ b/core/src/packets/icmp/v6/ndp/options/prefix_info.rs @@ -16,10 +16,10 @@ * SPDX-License-Identifier: Apache-2.0 */ +use crate::ensure; use crate::packets::icmp::v6::ndp::{NdpOption, NdpOptionType, NdpOptionTypes}; use crate::packets::types::u32be; -use crate::packets::Internal; -use crate::{ensure, Mbuf, SizeOf}; +use crate::packets::{Internal, Mbuf, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::net::Ipv6Addr; @@ -308,9 +308,10 @@ impl Default for PrefixInformationFields { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::icmp::v6::ndp::{NdpPacket, RouterAdvertisement}; use crate::packets::ip::v6::Ipv6; - use crate::packets::{Ethernet, Packet}; + use crate::packets::Packet; use crate::testils::byte_arrays::ROUTER_ADVERT_PACKET; #[test] diff --git a/core/src/packets/icmp/v6/ndp/options/redirected.rs b/core/src/packets/icmp/v6/ndp/options/redirected.rs index b16e094f..6ba9d199 100644 --- a/core/src/packets/icmp/v6/ndp/options/redirected.rs +++ b/core/src/packets/icmp/v6/ndp/options/redirected.rs @@ -16,10 +16,10 @@ * SPDX-License-Identifier: Apache-2.0 */ +use crate::ensure; use crate::packets::icmp::v6::ndp::{NdpOption, NdpOptionType, NdpOptionTypes}; use crate::packets::types::{u16be, u32be}; -use crate::packets::Internal; -use crate::{ensure, Mbuf, SizeOf}; +use crate::packets::{Internal, Mbuf, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::ptr::NonNull; @@ -232,9 +232,10 @@ impl Default for RedirectedHeaderFields { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::icmp::v6::ndp::{NdpPacket, Redirect}; use crate::packets::ip::v6::Ipv6; - use crate::packets::{Ethernet, Packet}; + use crate::packets::Packet; #[test] fn size_of_redirected_header_fields() { diff --git a/core/src/packets/icmp/v6/ndp/redirect.rs b/core/src/packets/icmp/v6/ndp/redirect.rs index 3c253827..1f3fe658 100644 --- a/core/src/packets/icmp/v6/ndp/redirect.rs +++ b/core/src/packets/icmp/v6/ndp/redirect.rs @@ -20,8 +20,7 @@ use super::{NdpPacket, RedirectedHeader}; use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; use crate::packets::ip::v6::{Ipv6Packet, IPV6_MIN_MTU}; use crate::packets::types::u32be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::net::Ipv6Addr; @@ -242,9 +241,9 @@ impl Default for RedirectBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::Mbuf; #[test] fn size_of_redirect_body() { diff --git a/core/src/packets/icmp/v6/ndp/router_advert.rs b/core/src/packets/icmp/v6/ndp/router_advert.rs index 630af35e..4e1d1291 100644 --- a/core/src/packets/icmp/v6/ndp/router_advert.rs +++ b/core/src/packets/icmp/v6/ndp/router_advert.rs @@ -20,8 +20,7 @@ use super::NdpPacket; use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; use crate::packets::ip::v6::Ipv6Packet; use crate::packets::types::{u16be, u32be}; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::ptr::NonNull; @@ -302,9 +301,9 @@ struct RouterAdvertisementBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::Mbuf; #[test] fn size_of_router_advertisement_body() { diff --git a/core/src/packets/icmp/v6/ndp/router_solicit.rs b/core/src/packets/icmp/v6/ndp/router_solicit.rs index adf9a03a..bf122b05 100644 --- a/core/src/packets/icmp/v6/ndp/router_solicit.rs +++ b/core/src/packets/icmp/v6/ndp/router_solicit.rs @@ -20,8 +20,7 @@ use super::NdpPacket; use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; use crate::packets::ip::v6::Ipv6Packet; use crate::packets::types::u32be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::ptr::NonNull; @@ -144,9 +143,9 @@ struct RouterSolicitationBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::Mbuf; #[test] fn size_of_router_solicitation_body() { diff --git a/core/src/packets/icmp/v6/time_exceeded.rs b/core/src/packets/icmp/v6/time_exceeded.rs index e1c1e269..57e572dc 100644 --- a/core/src/packets/icmp/v6/time_exceeded.rs +++ b/core/src/packets/icmp/v6/time_exceeded.rs @@ -19,8 +19,7 @@ use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; use crate::packets::ip::v6::{Ipv6Packet, IPV6_MIN_MTU}; use crate::packets::types::u32be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::ptr::NonNull; @@ -164,10 +163,10 @@ struct TimeExceededBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; + use crate::packets::Mbuf; use crate::testils::byte_arrays::IPV6_TCP_PACKET; - use crate::Mbuf; #[test] fn size_of_time_exceeded_body() { diff --git a/core/src/packets/icmp/v6/too_big.rs b/core/src/packets/icmp/v6/too_big.rs index bc5c5cd3..71d1cf54 100644 --- a/core/src/packets/icmp/v6/too_big.rs +++ b/core/src/packets/icmp/v6/too_big.rs @@ -19,8 +19,7 @@ use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; use crate::packets::ip::v6::{Ipv6Packet, IPV6_MIN_MTU}; use crate::packets::types::u32be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::ptr::NonNull; @@ -189,10 +188,10 @@ struct PacketTooBigBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; + use crate::packets::Mbuf; use crate::testils::byte_arrays::IPV6_TCP_PACKET; - use crate::Mbuf; #[test] fn size_of_packet_too_big() { diff --git a/core/src/packets/icmp/v6/destination_unreachable.rs b/core/src/packets/icmp/v6/unreachable.rs similarity index 98% rename from core/src/packets/icmp/v6/destination_unreachable.rs rename to core/src/packets/icmp/v6/unreachable.rs index e23d29a1..827682e4 100644 --- a/core/src/packets/icmp/v6/destination_unreachable.rs +++ b/core/src/packets/icmp/v6/unreachable.rs @@ -19,8 +19,7 @@ use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; use crate::packets::ip::v6::{Ipv6Packet, IPV6_MIN_MTU}; use crate::packets::types::u32be; -use crate::packets::{Internal, Packet}; -use crate::SizeOf; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::ptr::NonNull; @@ -164,10 +163,10 @@ struct DestinationUnreachableBody { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; + use crate::packets::Mbuf; use crate::testils::byte_arrays::IPV6_TCP_PACKET; - use crate::Mbuf; #[test] fn size_of_destination_unreachable_body() { diff --git a/core/src/packets/ip/v4.rs b/core/src/packets/ip/v4.rs index 3605e3bc..e2c6f1e6 100644 --- a/core/src/packets/ip/v4.rs +++ b/core/src/packets/ip/v4.rs @@ -18,11 +18,12 @@ //! Internet Protocol v4. +use crate::ensure; use crate::packets::checksum::{self, PseudoHeader}; +use crate::packets::ethernet::{EtherTypes, Ethernet}; use crate::packets::ip::{IpPacket, ProtocolNumber, DEFAULT_IP_TTL}; use crate::packets::types::u16be; -use crate::packets::{EtherTypes, Ethernet, Internal, Packet}; -use crate::{ensure, SizeOf}; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::net::{IpAddr, Ipv4Addr}; @@ -612,8 +613,8 @@ impl Default for Ipv4Header { mod tests { use super::*; use crate::packets::ip::ProtocolNumbers; + use crate::packets::Mbuf; use crate::testils::byte_arrays::{IPV4_UDP_PACKET, IPV6_TCP_PACKET}; - use crate::Mbuf; #[test] fn size_of_ipv4_header() { diff --git a/core/src/packets/ip/v6/fragment.rs b/core/src/packets/ip/v6/fragment.rs index 15e90ce7..7d523c4d 100644 --- a/core/src/packets/ip/v6/fragment.rs +++ b/core/src/packets/ip/v6/fragment.rs @@ -16,12 +16,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +use crate::ensure; use crate::packets::checksum::PseudoHeader; use crate::packets::ip::v6::Ipv6Packet; use crate::packets::ip::{IpPacket, ProtocolNumber, ProtocolNumbers}; use crate::packets::types::{u16be, u32be}; -use crate::packets::{Internal, Packet}; -use crate::{ensure, SizeOf}; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::net::IpAddr; @@ -329,10 +329,10 @@ struct FragmentHeader { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; + use crate::packets::Mbuf; use crate::testils::byte_arrays::{IPV6_FRAGMENT_PACKET, IPV6_TCP_PACKET}; - use crate::Mbuf; #[test] fn size_of_fragment_header() { diff --git a/core/src/packets/ip/v6/mod.rs b/core/src/packets/ip/v6/mod.rs index 5cf78f79..5c10414c 100644 --- a/core/src/packets/ip/v6/mod.rs +++ b/core/src/packets/ip/v6/mod.rs @@ -24,11 +24,12 @@ mod srh; pub use self::fragment::*; pub use self::srh::*; +use crate::ensure; use crate::packets::checksum::PseudoHeader; +use crate::packets::ethernet::{EtherTypes, Ethernet}; use crate::packets::ip::{IpPacket, ProtocolNumber, DEFAULT_IP_TTL}; use crate::packets::types::{u16be, u32be}; -use crate::packets::{EtherTypes, Ethernet, Internal, Packet}; -use crate::{ensure, SizeOf}; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::net::{IpAddr, Ipv6Addr}; @@ -467,8 +468,8 @@ impl Default for Ipv6Header { mod tests { use super::*; use crate::packets::ip::ProtocolNumbers; + use crate::packets::Mbuf; use crate::testils::byte_arrays::{IPV4_UDP_PACKET, IPV6_TCP_PACKET}; - use crate::Mbuf; #[test] fn size_of_ipv6_header() { diff --git a/core/src/packets/ip/v6/srh.rs b/core/src/packets/ip/v6/srh.rs index 9cefbb9d..bdad10b6 100644 --- a/core/src/packets/ip/v6/srh.rs +++ b/core/src/packets/ip/v6/srh.rs @@ -16,12 +16,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +use crate::ensure; use crate::packets::checksum::PseudoHeader; use crate::packets::ip::v6::Ipv6Packet; use crate::packets::ip::{IpPacket, ProtocolNumber, ProtocolNumbers}; use crate::packets::types::u16be; -use crate::packets::{Internal, Packet}; -use crate::{ensure, SizeOf}; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::net::{IpAddr, Ipv6Addr}; @@ -523,11 +523,12 @@ impl Default for SegmentRoutingHeader { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::Ipv6; use crate::packets::ip::ProtocolNumbers; - use crate::packets::{Ethernet, Tcp, Tcp6}; + use crate::packets::tcp::{Tcp, Tcp6}; + use crate::packets::Mbuf; use crate::testils::byte_arrays::{IPV6_TCP_PACKET, SR_TCP_PACKET}; - use crate::Mbuf; #[test] fn size_of_segment_routing_header() { diff --git a/core/src/dpdk/mbuf.rs b/core/src/packets/mbuf.rs similarity index 92% rename from core/src/dpdk/mbuf.rs rename to core/src/packets/mbuf.rs index 74ed1b75..79d76d0c 100644 --- a/core/src/dpdk/mbuf.rs +++ b/core/src/packets/mbuf.rs @@ -17,8 +17,8 @@ */ use crate::ffi::dpdk::{self, MbufPtr}; -use crate::packets::{Internal, Packet}; -use crate::rt2::Mempool; +use crate::packets::{Internal, Packet, SizeOf}; +use crate::runtime::Mempool; use crate::{ensure, trace}; use anyhow::Result; use capsule_ffi as cffi; @@ -28,58 +28,6 @@ use std::ptr::{self, NonNull}; use std::slice; use thiserror::Error; -/// A trait for returning the size of a type in bytes. -/// -/// Size of the structs are used for bound checks when reading and writing -/// packets. -/// -/// -/// # Derivable -/// -/// The `SizeOf` trait can be used with `#[derive]` and defaults to -/// `std::mem::size_of::()`. -/// -/// ``` -/// #[derive(SizeOf)] -/// pub struct Ipv4Header { -/// ... -/// } -/// ``` -pub trait SizeOf { - /// Returns the size of a type in bytes. - fn size_of() -> usize; -} - -impl SizeOf for () { - fn size_of() -> usize { - std::mem::size_of::<()>() - } -} - -impl SizeOf for u8 { - fn size_of() -> usize { - std::mem::size_of::() - } -} - -impl SizeOf for [u8; 2] { - fn size_of() -> usize { - std::mem::size_of::<[u8; 2]>() - } -} - -impl SizeOf for [u8; 16] { - fn size_of() -> usize { - std::mem::size_of::<[u8; 16]>() - } -} - -impl SizeOf for ::std::net::Ipv6Addr { - fn size_of() -> usize { - std::mem::size_of::() - } -} - /// Error indicating buffer access failures. #[derive(Debug, Error)] pub(crate) enum BufferError { @@ -178,14 +126,6 @@ impl Mbuf { } } - /// Creates a new `Mbuf` from a raw pointer. - #[inline] - pub(crate) unsafe fn from_ptr(ptr: *mut cffi::rte_mbuf) -> Self { - Mbuf { - inner: MbufInner::Original(NonNull::new_unchecked(ptr)), - } - } - /// Returns the raw struct needed for FFI calls. #[inline] fn raw(&self) -> &cffi::rte_mbuf { @@ -425,22 +365,11 @@ impl Mbuf { /// free the raw pointer after use. Otherwise the buffer is leaked. #[inline] pub(crate) fn into_easyptr(self) -> MbufPtr { - let ptr = self.inner.ptr().clone(); + let ptr = *self.inner.ptr(); mem::forget(self); ptr.into() } - /// Acquires the underlying raw struct pointer. - /// - /// The `Mbuf` is consumed. It is the caller's the responsibility to - /// free the raw pointer after use. Otherwise the buffer is leaked. - #[inline] - pub(crate) fn into_ptr(self) -> *mut cffi::rte_mbuf { - let ptr = self.inner.ptr().as_ptr(); - mem::forget(self); - ptr - } - /// Allocates a Vec of `Mbuf`s of `len` size. /// /// # Errors diff --git a/core/src/packets/mod.rs b/core/src/packets/mod.rs index de17b304..39a5758a 100644 --- a/core/src/packets/mod.rs +++ b/core/src/packets/mod.rs @@ -20,17 +20,18 @@ pub mod arp; pub mod checksum; -mod ethernet; +pub mod ethernet; pub mod icmp; pub mod ip; -mod tcp; +mod mbuf; +mod size_of; +pub mod tcp; pub mod types; -mod udp; +pub mod udp; -pub use self::ethernet::*; -pub use self::tcp::*; -pub use self::udp::*; -pub use crate::dpdk::Mbuf; +pub use self::mbuf::*; +pub use self::size_of::*; +pub use capsule_macros::SizeOf; use anyhow::{Context, Result}; use std::fmt; @@ -354,7 +355,9 @@ pub enum Postmark { mod tests { use super::*; use crate::net::MacAddr; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v4::Ipv4; + use crate::packets::udp::Udp4; use crate::testils::byte_arrays::IPV4_UDP_PACKET; #[capsule::test] diff --git a/core/src/packets/size_of.rs b/core/src/packets/size_of.rs new file mode 100644 index 00000000..f7a6a308 --- /dev/null +++ b/core/src/packets/size_of.rs @@ -0,0 +1,85 @@ +/* +* Copyright 2019 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + +use crate::net::MacAddr; +use std::mem; +use std::net::{Ipv4Addr, Ipv6Addr}; + +/// A trait for returning the size of a type in bytes. +/// +/// Size of the structs are used for bound checks when reading and writing +/// packets. +/// +/// +/// # Derivable +/// +/// The `SizeOf` trait can be used with `#[derive]` and defaults to +/// `std::mem::size_of::()`. +/// +/// ``` +/// #[derive(SizeOf)] +/// pub struct Ipv4Header { +/// ... +/// } +/// ``` +pub trait SizeOf { + /// Returns the size of a type in bytes. + fn size_of() -> usize; +} + +impl SizeOf for () { + fn size_of() -> usize { + mem::size_of::<()>() + } +} + +impl SizeOf for u8 { + fn size_of() -> usize { + mem::size_of::() + } +} + +impl SizeOf for [u8; 2] { + fn size_of() -> usize { + mem::size_of::<[u8; 2]>() + } +} + +impl SizeOf for [u8; 16] { + fn size_of() -> usize { + mem::size_of::<[u8; 16]>() + } +} + +impl SizeOf for MacAddr { + fn size_of() -> usize { + mem::size_of::() + } +} + +impl SizeOf for Ipv4Addr { + fn size_of() -> usize { + mem::size_of::() + } +} + +impl SizeOf for Ipv6Addr { + fn size_of() -> usize { + mem::size_of::() + } +} diff --git a/core/src/packets/tcp.rs b/core/src/packets/tcp.rs index d0cec42e..b7d11e93 100644 --- a/core/src/packets/tcp.rs +++ b/core/src/packets/tcp.rs @@ -16,12 +16,14 @@ * SPDX-License-Identifier: Apache-2.0 */ +//! Transmission Control Protocol. + +use crate::ensure; use crate::packets::ip::v4::Ipv4; use crate::packets::ip::v6::Ipv6; use crate::packets::ip::{Flow, IpPacket, ProtocolNumbers}; use crate::packets::types::{u16be, u32be}; -use crate::packets::{checksum, Internal, Packet}; -use crate::{ensure, SizeOf}; +use crate::packets::{checksum, Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::net::IpAddr; @@ -664,10 +666,10 @@ impl Default for TcpHeader { #[cfg(test)] mod tests { use super::*; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v6::SegmentRouting; - use crate::packets::Ethernet; + use crate::packets::Mbuf; use crate::testils::byte_arrays::{IPV4_TCP_PACKET, IPV4_UDP_PACKET, SR_TCP_PACKET}; - use crate::Mbuf; use std::net::{Ipv4Addr, Ipv6Addr}; #[test] diff --git a/core/src/packets/udp.rs b/core/src/packets/udp.rs index c146328b..541fd1db 100644 --- a/core/src/packets/udp.rs +++ b/core/src/packets/udp.rs @@ -16,12 +16,14 @@ * SPDX-License-Identifier: Apache-2.0 */ +//! User Datagram Protocol. + +use crate::ensure; use crate::packets::ip::v4::Ipv4; use crate::packets::ip::v6::Ipv6; use crate::packets::ip::{Flow, IpPacket, ProtocolNumbers}; use crate::packets::types::u16be; -use crate::packets::{checksum, Internal, Packet}; -use crate::{ensure, SizeOf}; +use crate::packets::{checksum, Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::net::IpAddr; @@ -386,9 +388,9 @@ struct UdpHeader { #[cfg(test)] mod tests { use super::*; - use crate::packets::Ethernet; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; use crate::testils::byte_arrays::{IPV4_TCP_PACKET, IPV4_UDP_PACKET}; - use crate::Mbuf; use std::net::{Ipv4Addr, Ipv6Addr}; #[test] diff --git a/core/src/pcap.rs b/core/src/pcap.rs deleted file mode 100644 index 0be10325..00000000 --- a/core/src/pcap.rs +++ /dev/null @@ -1,340 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use crate::dpdk::{CoreId, DpdkError, PortId, RxTxQueue}; -use crate::ffi::{self, AsStr, ToCString, ToResult}; -use crate::{debug, error}; -use anyhow::Result; -use std::fmt; -use std::os::raw; -use std::ptr::NonNull; -use thiserror::Error; - -// DLT_EN10MB; LINKTYPE_ETHERNET=1; 10MB is historical -const DLT_EN10MB: raw::c_int = 1; -const PCAP_SNAPSHOT_LEN: raw::c_int = ffi::RTE_MBUF_DEFAULT_BUF_SIZE as raw::c_int; - -/// An error generated in `libpcap`. -/// -/// When an FFI call fails, either a specified error message or an `errno` is -/// translated into a `PcapError`. -#[derive(Debug, Error)] -#[error("{0}")] -struct PcapError(String); - -impl PcapError { - /// Returns the `PcapError` with the given error message. - #[inline] - fn new(msg: &str) -> Self { - PcapError(msg.into()) - } - - /// Returns the `PcapError` pertaining to the last `libpcap` error. - #[inline] - fn get_error(handle: NonNull) -> Self { - let msg = unsafe { ffi::pcap_geterr(handle.as_ptr()) }; - PcapError::new((msg as *const raw::c_char).as_str()) - } -} - -/// Packet Capture (`pcap`) writer/dumper for packets -struct Pcap { - path: String, - handle: NonNull, - dumper: NonNull, -} - -impl Pcap { - /// Creates a file for dumping packets into from a given file path. - fn create(path: &str) -> Result { - unsafe { - let handle = ffi::pcap_open_dead(DLT_EN10MB, PCAP_SNAPSHOT_LEN) - .into_result(|_| PcapError::new("Cannot create packet capture handle."))?; - let dumper = ffi::pcap_dump_open(handle.as_ptr(), path.into_cstring().as_ptr()) - .into_result(|_| PcapError::get_error(handle)) - .map_err(|err| { - ffi::pcap_close(handle.as_ptr()); - err - })?; - - debug!("PCAP file {} created", path); - - Ok(Pcap { - path: path.to_string(), - handle, - dumper, - }) - } - } - - /// Append to already-existing file for dumping packets into from a given - /// file path. - fn append(path: &str) -> Result { - if !std::path::Path::new(path).exists() { - return Err(PcapError::new("Pcap filename path does not exist.").into()); - } - - unsafe { - let handle = ffi::pcap_open_dead(DLT_EN10MB, PCAP_SNAPSHOT_LEN) - .into_result(|_| PcapError::new("Cannot create packet capture handle."))?; - let dumper = ffi::pcap_dump_open_append(handle.as_ptr(), path.into_cstring().as_ptr()) - .into_result(|_| PcapError::get_error(handle)) - .map_err(|err| { - ffi::pcap_close(handle.as_ptr()); - err - })?; - Ok(Pcap { - path: path.to_string(), - handle, - dumper, - }) - } - } - - /// Write packets to `pcap` file handler. - unsafe fn write(&self, ptrs: &[*mut ffi::rte_mbuf]) -> Result<()> { - ptrs.iter().for_each(|&p| self.dump_packet(p)); - - self.flush() - } - - unsafe fn dump_packet(&self, ptr: *mut ffi::rte_mbuf) { - let mut pcap_hdr = ffi::pcap_pkthdr::default(); - pcap_hdr.len = (*ptr).data_len as u32; - pcap_hdr.caplen = pcap_hdr.len; - - // If this errors, we'll still want to write packet(s) to the pcap, - let _ = libc::gettimeofday( - &mut pcap_hdr.ts as *mut ffi::timeval as *mut libc::timeval, - std::ptr::null_mut(), - ); - - ffi::pcap_dump( - self.dumper.as_ptr() as *mut raw::c_uchar, - &pcap_hdr, - ((*ptr).buf_addr as *mut u8).offset((*ptr).data_off as isize), - ); - } - - fn flush(&self) -> Result<()> { - unsafe { - ffi::pcap_dump_flush(self.dumper.as_ptr()) - .into_result(|_| PcapError::new("Cannot flush packets to packet capture")) - .map(|_| ()) - } - } -} - -impl<'a> fmt::Debug for Pcap { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("pcap").field("path", &self.path).finish() - } -} - -impl Drop for Pcap { - fn drop(&mut self) { - unsafe { - ffi::pcap_dump_close(self.dumper.as_ptr()); - ffi::pcap_close(self.handle.as_ptr()); - } - } -} - -/// Default formatting for pcap files. -fn format_pcap_file(port_name: &str, core_id: usize, tx_or_rx: &str) -> String { - format!("port-{}-core{}-{}.pcap", port_name, core_id, tx_or_rx) -} - -/// Generate PCAP files for rx/tx queues per port and per core. -pub(crate) fn capture_queue( - port_id: PortId, - port_name: &str, - core: CoreId, - q: RxTxQueue, -) -> Result<()> { - match q { - RxTxQueue::Rx(rxq) => { - Pcap::create(&format_pcap_file(port_name, core.raw(), "rx"))?; - unsafe { - ffi::rte_eth_add_rx_callback( - port_id.raw(), - rxq.raw(), - Some(append_and_write_rx), - port_name.into_cstring().into_raw() as *mut raw::c_void, - ) - .into_result(|_| DpdkError::new())?; - } - } - RxTxQueue::Tx(txq) => { - Pcap::create(&format_pcap_file(port_name, core.raw(), "tx"))?; - unsafe { - ffi::rte_eth_add_tx_callback( - port_id.raw(), - txq.raw(), - Some(append_and_write_tx), - port_name.into_cstring().into_raw() as *mut raw::c_void, - ) - .into_result(|_| DpdkError::new())?; - } - } - } - - Ok(()) -} - -/// Callback fn passed to `rte_eth_add_rx_callback`, which is called on RX -/// with a burst of packets that have been received on a given port and queue. -unsafe extern "C" fn append_and_write_rx( - _port_id: u16, - _queue_id: u16, - pkts: *mut *mut ffi::rte_mbuf, - num_pkts: u16, - _max_pkts: u16, - user_param: *mut raw::c_void, -) -> u16 { - append_and_write( - (user_param as *const raw::c_char).as_str(), - "rx", - std::slice::from_raw_parts_mut(pkts, num_pkts as usize), - ); - num_pkts -} - -/// Callback fn passed to `rte_eth_add_tx_callback`, which is called on TX -/// with a burst of packets immediately before the packets are put onto -/// the hardware queue for transmission. -unsafe extern "C" fn append_and_write_tx( - _port_id: u16, - _queue_id: u16, - pkts: *mut *mut ffi::rte_mbuf, - num_pkts: u16, - user_param: *mut raw::c_void, -) -> u16 { - append_and_write( - (user_param as *const raw::c_char).as_str(), - "tx", - std::slice::from_raw_parts_mut(pkts, num_pkts as usize), - ); - num_pkts -} - -/// Executed within the rx/tx callback functions for writing out to pcap -/// file(s). -fn append_and_write(port: &str, tx_or_rx: &str, ptrs: &[*mut ffi::rte_mbuf]) { - let path = format_pcap_file(port, CoreId::current().raw(), tx_or_rx); - if let Err(err) = Pcap::append(path.as_str()).and_then(|pcap| unsafe { pcap.write(&ptrs) }) { - error!( - message = "Cannot write/append to pcap file.", - pcap = path.as_str(), - ?err - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::testils::byte_arrays::IPV4_UDP_PACKET; - use crate::Mbuf; - use std::fs; - use std::ptr; - - fn read_pcap_plen(path: &str) -> u32 { - let mut errbuf = [0i8; ffi::RTE_MBUF_DEFAULT_BUF_SIZE as usize]; - let handle = - unsafe { ffi::pcap_open_offline(path.into_cstring().as_ptr(), errbuf.as_mut_ptr()) }; - - let mut header: *mut ffi::pcap_pkthdr = ptr::null_mut(); - let mut buf: *const libc::c_uchar = ptr::null(); - - let mut ret = 0; - - while let 1 = unsafe { ffi::pcap_next_ex(handle, &mut header, &mut buf) } { - ret += unsafe { (*header).caplen } - } - - unsafe { - ffi::pcap_close(handle); - } - - ret - } - - fn cleanup(path: &str) { - fs::remove_file(path).unwrap(); - } - - #[capsule::test] - fn create_pcap_and_write_packet() { - let writer = Pcap::create("foo.pcap").unwrap(); - let udp = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); - let data_len = udp.data_len(); - - let res = unsafe { writer.write(&[udp.into_ptr()]) }; - - assert!(res.is_ok()); - let len = read_pcap_plen("foo.pcap"); - assert_eq!(data_len as u32, len); - cleanup("foo.pcap"); - } - - #[capsule::test] - fn create_pcap_and_write_packets() { - let writer = Pcap::create("foo1.pcap").unwrap(); - let udp = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); - let data_len1 = udp.data_len(); - let udp2 = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); - let data_len2 = udp2.data_len(); - - let packets = vec![udp.into_ptr(), udp2.into_ptr()]; - let res = unsafe { writer.write(&packets) }; - assert!(res.is_ok()); - let len = read_pcap_plen("foo1.pcap"); - assert_eq!((data_len1 + data_len2) as u32, len); - cleanup("foo1.pcap"); - } - - #[capsule::test] - fn append_to_pcap_and_write_packet() { - let open = Pcap::create("foo2.pcap"); - assert!(open.is_ok()); - - let udp = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); - let data_len = udp.data_len(); - - let writer = Pcap::append("foo2.pcap").unwrap(); - let res = unsafe { writer.write(&[udp.into_ptr()]) }; - - assert!(res.is_ok()); - let len = read_pcap_plen("foo2.pcap"); - assert_eq!(data_len as u32, len); - cleanup("foo2.pcap"); - } - - #[capsule::test] - fn append_to_wrong_pcap() { - let open = Pcap::create("foo3.pcap"); - assert!(open.is_ok()); - - // fails on append to uninitiated pcap - let res = Pcap::append("foo4.pcap"); - assert!(res.is_err()); - - cleanup("foo3.pcap"); - } -} diff --git a/core/src/rt2/mod.rs b/core/src/rt2/mod.rs deleted file mode 100644 index cd63ef92..00000000 --- a/core/src/rt2/mod.rs +++ /dev/null @@ -1,234 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -//! Capsule runtime. - -mod config; -mod lcore; -mod mempool; -#[cfg(feature = "pcap-dump")] -#[cfg_attr(docsrs, doc(cfg(feature = "pcap-dump")))] -mod pcap_dump; -mod port; - -pub use self::config::*; -pub(crate) use self::lcore::*; -pub use self::lcore::{Lcore, LcoreMap, LcoreNotFound}; -pub use self::mempool::Mempool; -pub(crate) use self::mempool::*; -pub use self::port::{Outbox, Port, PortError, PortMap}; - -use crate::ffi::dpdk::{self, LcoreId}; -use crate::packets::{Mbuf, Postmark}; -use crate::{debug, info}; -use anyhow::Result; -use async_channel::{self, Receiver, Sender}; -use std::fmt; -use std::mem::ManuallyDrop; -use std::ops::DerefMut; - -/// Trigger for the shutdown. -pub(crate) struct ShutdownTrigger(Sender<()>, Receiver<()>); - -impl ShutdownTrigger { - /// Creates a new shutdown trigger. - /// - /// Leverages the behavior of an async channel. When the sender is dropped - /// from scope, it closes the channel and causes the receiver side future - /// in the executor queue to resolve. - pub(crate) fn new() -> Self { - let (s, r) = async_channel::unbounded(); - Self(s, r) - } - - /// Returns a wait handle. - pub(crate) fn get_wait(&self) -> ShutdownWait { - ShutdownWait(self.1.clone()) - } - - /// Returns whether the trigger is being waited on. - pub(crate) fn is_waited(&self) -> bool { - // a receiver count greater than 1 indicating that there are receiver - // clones in scope, hence the trigger is being waited on. - self.0.receiver_count() > 1 - } - - /// Triggers the shutdown. - pub(crate) fn fire(self) { - drop(self.0) - } -} - -/// Shutdown wait handle. -pub(crate) struct ShutdownWait(Receiver<()>); - -impl ShutdownWait { - /// A future that waits till the shutdown trigger is fired. - pub(crate) async fn wait(&self) { - self.0.recv().await.unwrap_or(()) - } -} - -/// The Capsule runtime. -/// -/// The runtime initializes the underlying DPDK environment, and it also manages -/// the task scheduler that executes the packet processing tasks. -pub struct Runtime { - mempool: ManuallyDrop, - lcores: ManuallyDrop, - ports: ManuallyDrop, - #[cfg(feature = "pcap-dump")] - pcap_dump: ManuallyDrop, -} - -impl Runtime { - /// Returns the mempool. - /// - /// For simplicity, we currently only support one global Mempool. Multi- - /// socket support may be added in the future. - pub fn mempool(&self) -> &Mempool { - &self.mempool - } - - /// Returns the lcores. - pub fn lcores(&self) -> &LcoreMap { - &self.lcores - } - - /// Returns the configured ports. - pub fn ports(&self) -> &PortMap { - &self.ports - } - - /// Initializes a new runtime from config settings. - pub fn from_config(config: RuntimeConfig) -> Result { - info!("starting runtime."); - - debug!("initializing EAL ..."); - dpdk::eal_init(config.to_eal_args())?; - - debug!("initializing mempool ..."); - let socket = LcoreId::main().socket(); - let mut mempool = Mempool::new( - "mempool", - config.mempool.capacity, - config.mempool.cache_size, - socket, - )?; - debug!(?mempool); - - debug!("initializing lcore schedulers ..."); - let lcores = self::lcore_pool(); - - for lcore in lcores.iter() { - let mut ptr = mempool.ptr_mut().clone(); - lcore.block_on(async move { MEMPOOL.with(|tls| tls.set(ptr.deref_mut())) }); - } - - info!("initializing ports ..."); - let mut ports = Vec::new(); - for port in config.ports.iter() { - let mut port = port::Builder::for_device(&port.name, &port.device)? - .set_rxqs_txqs(port.rxqs, port.txqs)? - .set_promiscuous(port.promiscuous)? - .set_multicast(port.multicast)? - .set_rx_lcores(port.rx_cores.clone())? - .set_tx_lcores(port.tx_cores.clone())? - .build(&mut mempool)?; - - debug!(?port); - - if !port.tx_lcores().is_empty() { - port.spawn_tx_loops(&lcores)?; - } - - port.start()?; - ports.push(port); - } - let ports: PortMap = ports.into(); - - #[cfg(feature = "pcap-dump")] - let pcap_dump = self::pcap_dump::enable_pcap_dump(&config.data_dir(), &ports, &lcores)?; - - info!("runtime ready."); - - Ok(Runtime { - mempool: ManuallyDrop::new(mempool), - lcores: ManuallyDrop::new(lcores), - ports: ManuallyDrop::new(ports), - #[cfg(feature = "pcap-dump")] - pcap_dump: ManuallyDrop::new(pcap_dump), - }) - } - - /// Sets the packet processing pipeline for port. - pub fn set_port_pipeline(&self, port: &str, f: F) -> Result<()> - where - F: Fn(Mbuf) -> Result + Clone + Send + Sync + 'static, - { - let port = self.ports.get(port)?; - port.spawn_rx_loops(f, &self.lcores)?; - Ok(()) - } - - /// Starts the runtime execution. - pub fn execute(self) -> Result { - Ok(RuntimeGuard { runtime: self }) - } -} - -impl fmt::Debug for Runtime { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Runtime") - .field("mempool", &self.mempool) - .finish() - } -} - -/// The RAII guard to stop and cleanup the runtime resources on drop. -pub struct RuntimeGuard { - runtime: Runtime, -} - -impl Drop for RuntimeGuard { - fn drop(&mut self) { - info!("shutting down runtime."); - - for port in self.runtime.ports.iter_mut() { - port.stop(); - } - - unsafe { - #[cfg(feature = "pcap-dump")] - ManuallyDrop::drop(&mut self.runtime.pcap_dump); - ManuallyDrop::drop(&mut self.runtime.ports); - ManuallyDrop::drop(&mut self.runtime.lcores); - ManuallyDrop::drop(&mut self.runtime.mempool); - } - - debug!("freeing EAL ..."); - let _ = dpdk::eal_cleanup(); - info!("runtime shutdown."); - } -} - -impl fmt::Debug for RuntimeGuard { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "RuntimeGuard") - } -} diff --git a/core/src/rt2/config.rs b/core/src/runtime/config.rs similarity index 99% rename from core/src/rt2/config.rs rename to core/src/runtime/config.rs index c5b201e3..6aaf30c3 100644 --- a/core/src/rt2/config.rs +++ b/core/src/runtime/config.rs @@ -123,7 +123,7 @@ impl RuntimeConfig { } }); - cores.sort(); + cores.sort_unstable(); cores.dedup(); cores } diff --git a/core/src/runtime/core_map.rs b/core/src/runtime/core_map.rs deleted file mode 100644 index bbf10ff6..00000000 --- a/core/src/runtime/core_map.rs +++ /dev/null @@ -1,386 +0,0 @@ -/* -* Copyright 2019 Comcast Cable Communications Management, LLC -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -use crate::dpdk::{CoreId, Mempool, MempoolMap, MEMPOOL}; -use crate::{debug, error, ffi, info}; -use anyhow::Result; -use futures::Future; -use std::collections::{HashMap, HashSet}; -use std::sync::mpsc::{self, Receiver, SyncSender}; -use std::thread::{self, JoinHandle}; -use thiserror::Error; -use tokio::sync::oneshot; -use tokio_executor::current_thread::{self, CurrentThread}; -use tokio_executor::park::ParkThread; -use tokio_net::driver::{self, Reactor}; -use tokio_timer::timer::{self, Timer}; - -/// A sync-channel based park handle. -/// -/// This is designed to be a single use handle. We only need to park the -/// core one time at initialization time. Once unparked, we will never -/// park the core again. -pub(crate) struct Park { - core_id: CoreId, - sender: SyncSender<()>, - receiver: Receiver<()>, -} - -impl Park { - fn new(core_id: CoreId) -> Self { - let (sender, receiver) = mpsc::sync_channel(0); - Park { - core_id, - sender, - receiver, - } - } - - fn unpark(&self) -> Unpark { - Unpark { - core_id: self.core_id, - sender: self.sender.clone(), - } - } - - fn park(&self) { - if let Err(err) = self.receiver.recv() { - // we are not expecting failures, but we will log it in case. - error!(message = "park failed.", core=?self.core_id, ?err); - } - } -} - -/// A sync-channel based unpark handle. -/// -/// This is designed to be a single use handle. We will unpark a core one -/// time after all initialization completes. Do not reinvoke this. -pub(crate) struct Unpark { - core_id: CoreId, - sender: SyncSender<()>, -} - -impl Unpark { - pub(crate) fn unpark(&self) { - if let Err(err) = self.sender.send(()) { - // we are not expecting failures, but we will log it in case. - error!(message = "unpark failed.", core=?self.core_id, ?err); - } - } -} - -/// A tokio oneshot channel based shutdown mechanism. -pub(crate) struct Shutdown { - receiver: oneshot::Receiver<()>, -} - -impl Shutdown { - fn new(core_id: CoreId) -> (Self, ShutdownTrigger) { - let (sender, receiver) = oneshot::channel(); - let shutdown = Shutdown { receiver }; - let trigger = ShutdownTrigger { core_id, sender }; - (shutdown, trigger) - } - - fn into_task(self) -> impl Future { - self.receiver - } -} - -/// A sync-channel based shutdown trigger to terminate a background thread. -pub(crate) struct ShutdownTrigger { - core_id: CoreId, - sender: oneshot::Sender<()>, -} - -impl ShutdownTrigger { - pub(crate) fn shutdown(self) { - if let Err(err) = self.sender.send(()) { - // we are not expecting failures, but we will log it in case. - error!(message = "shutdown failed.", core=?self.core_id, ?err); - } - } -} - -/// A abstraction used to interact with the master/main thread. -/// -/// This is an additional handle to the master thread for performing tasks. -/// Use this `thread` handle to run the main loop. Use the `reactor` handle -/// to catch Unix signals to terminate the main loop. Use the `timer` handle -/// to create new time based tasks with either a `Delay` or `Interval`. -pub(crate) struct MasterExecutor { - pub(crate) reactor: driver::Handle, - pub(crate) timer: timer::Handle, - pub(crate) thread: CurrentThread>, -} - -/// A thread/core abstraction used to interact with a background thread -/// from the master/main thread. -/// -/// When a background thread is first spawned, it is parked and waiting for -/// tasks. Use the `timer` handle to create new time based tasks with either -/// a `Delay` or `Interval`. Use the thread handle to spawn tasks onto the -/// background thread. Use `unpark` when they are ready to execute tasks. -/// -/// The master thread also has an associated `CoreExecutor`, but `unpark` -/// won't do anything because the thread is not parked. Tasks can be spawned -/// onto it with this handle just like a background thread. -pub(crate) struct CoreExecutor { - pub(crate) timer: timer::Handle, - pub(crate) thread: current_thread::Handle, - pub(crate) unpark: Option, - pub(crate) shutdown: Option, - pub(crate) join: Option>, -} - -/// Core errors. -#[derive(Debug, Error)] -pub(crate) enum CoreError { - /// Core is not found. - #[error("{0:?} is not found.")] - NotFound(CoreId), - - /// Core is not assigned to any ports. - #[error("{0:?} is not assigned to any ports.")] - NotAssigned(CoreId), -} - -/// Map of all the core handles. -pub(crate) struct CoreMap { - pub(crate) master_core: MasterExecutor, - pub(crate) cores: HashMap, -} - -/// By default, raw pointers do not implement `Send`. We need a simple -/// wrapper so we can send the mempool pointer to a background thread and -/// assigned it to that thread. Otherwise, we wont' be able to create new -/// `Mbuf`s on the background threads. -struct SendablePtr(*mut ffi::rte_mempool); - -unsafe impl std::marker::Send for SendablePtr {} - -/// Builder for core map. -pub(crate) struct CoreMapBuilder<'a> { - app_name: String, - cores: HashSet, - master_core: CoreId, - mempools: MempoolMap<'a>, -} - -impl<'a> CoreMapBuilder<'a> { - pub(crate) fn new() -> Self { - CoreMapBuilder { - app_name: String::new(), - cores: Default::default(), - master_core: CoreId::new(0), - mempools: Default::default(), - } - } - - pub(crate) fn app_name(&mut self, app_name: &str) -> &mut Self { - self.app_name = app_name.to_owned(); - self - } - - pub(crate) fn cores(&mut self, cores: &[CoreId]) -> &mut Self { - self.cores = cores.iter().cloned().collect(); - self - } - - pub(crate) fn master_core(&mut self, master_core: CoreId) -> &mut Self { - self.master_core = master_core; - self - } - - pub(crate) fn mempools(&'a mut self, mempools: &'a mut [Mempool]) -> &'a mut Self { - self.mempools = MempoolMap::new(mempools); - self - } - - #[allow(clippy::cognitive_complexity)] - pub(crate) fn finish(&'a mut self) -> Result { - let mut map = HashMap::new(); - - // first initializes the master core, which the current running - // thread should be affinitized to. - let socket_id = self.master_core.socket_id(); - let mempool = self.mempools.get_raw(socket_id)?; - - let (master_thread, core_executor) = init_master_core(self.master_core, mempool)?; - - // adds the master core to the map. tasks can be spawned onto the - // master core like any other cores. - map.insert(self.master_core, core_executor); - - info!("initialized master on {:?}.", self.master_core); - - // the core list may also include the master core, to avoid double - // init, let's try remove it just in case. - self.cores.remove(&self.master_core); - - // next initializes all the cores other than the master core - for &core_id in self.cores.iter() { - // finds the mempool that matches the core's socket, and wraps the - // reference in a sendable pointer because we are sending it to - // a background thread - let socket_id = core_id.socket_id(); - let mempool = self.mempools.get_raw(socket_id)?; - let ptr = SendablePtr(mempool); - - // creates a synchronous channel so we can retrieve the executor for - // the background core. - let (sender, receiver) = mpsc::sync_channel(0); - - // spawns a new background thread and initializes a core executor on - // that thread. - let join = thread::Builder::new() - .name(format!("{}-{:?}", self.app_name, core_id)) - .spawn(move || { - debug!("spawned background thread {:?}.", thread::current().id()); - - match init_background_core(core_id, ptr.0) { - Ok((mut thread, park, shutdown, executor)) => { - info!("initialized thread on {:?}.", core_id); - - // keeps a timer handle for later use. - let timer_handle = executor.timer.clone(); - - // sends the executor back to the master core. it's safe to unwrap - // the result because the receiving end is guaranteed to be in scope. - sender.send(Ok(executor)).unwrap(); - - info!("parking {:?}.", core_id); - - // sleeps the thread for now since there's nothing to be done yet. - // once new tasks are spawned, the master core can unpark this and - // let the execution continue. - park.park(); - - info!("unparked {:?}.", core_id); - - // once the thread wakes up, we will run all the spawned tasks and - // wait until a shutdown is triggered from the master core. - let _timer = timer::set_default(&timer_handle); - let _ = thread.block_on(shutdown.into_task()); - - info!("unblocked {:?}.", core_id); - } - // propogates the error back to the master core. - Err(err) => sender.send(Err(err)).unwrap(), - } - })?; - - // blocks and waits for the background thread to finish initialize. - // when done, we add the executor to the map. - let mut executor = receiver.recv().unwrap()?; - executor.join = Some(join); - map.insert(core_id, executor); - } - - Ok(CoreMap { - master_core: master_thread, - cores: map, - }) - } -} - -fn init_master_core( - id: CoreId, - mempool: *mut ffi::rte_mempool, -) -> Result<(MasterExecutor, CoreExecutor)> { - // affinitize the running thread to this core. - id.set_thread_affinity()?; - - // sets the mempool - MEMPOOL.with(|tls| tls.set(mempool)); - - // starts a reactor so we can receive signals on the master core. - let reactor = Reactor::new()?; - let reactor_handle = reactor.handle(); - - // starts a per-core timer so we can schedule timed tasks. - let timer = Timer::new(reactor); - let timer_handle = timer.handle(); - - // starts the single-threaded executor, we can use this handle - // to spawn tasks onto this core from the master core. - let thread = CurrentThread::new_with_park(timer); - let thread_handle = thread.handle(); - - let main = MasterExecutor { - reactor: reactor_handle, - timer: timer_handle.clone(), - thread, - }; - - let executor = CoreExecutor { - timer: timer_handle, - thread: thread_handle, - unpark: None, - shutdown: None, - join: None, - }; - - Ok((main, executor)) -} - -fn init_background_core( - id: CoreId, - mempool: *mut ffi::rte_mempool, -) -> Result<( - CurrentThread>, - Park, - Shutdown, - CoreExecutor, -)> { - // affinitize the running thread to this core. - id.set_thread_affinity()?; - - // sets the mempool - MEMPOOL.with(|tls| tls.set(mempool)); - - // starts a per-core timer so we can schedule timed tasks. - let park = ParkThread::new(); - let timer = Timer::new(park); - let timer_handle = timer.handle(); - - // starts the single-threaded executor, we can use this handle - // to spawn tasks onto this core from the master core. - let thread = CurrentThread::new_with_park(timer); - let thread_handle = thread.handle(); - - // problem with using the regular thread park is when a task is - // spawned, the handle will implicitly unpark the thread. we have - // no way to control that behavior. so instead, we use a channel - // based unpark mechanism to block the thread from further - // execution until we are ready to proceed. - let park = Park::new(id); - - // shutdown handle for the core. - let (shutdown, trigger) = Shutdown::new(id); - - let executor = CoreExecutor { - timer: timer_handle, - thread: thread_handle, - unpark: Some(park.unpark()), - shutdown: Some(trigger), - join: None, - }; - - Ok((thread, park, shutdown, executor)) -} diff --git a/core/src/rt2/lcore.rs b/core/src/runtime/lcore.rs similarity index 98% rename from core/src/rt2/lcore.rs rename to core/src/runtime/lcore.rs index 15a353dd..7fb11595 100644 --- a/core/src/rt2/lcore.rs +++ b/core/src/runtime/lcore.rs @@ -156,7 +156,7 @@ mod tests { #[capsule::test] fn get_current_lcore_id_from_non_eal() { - let lcore_id = thread::spawn(|| LcoreId::current()).join().expect("panic!"); + let lcore_id = thread::spawn(LcoreId::current).join().expect("panic!"); assert_eq!(LcoreId::ANY, lcore_id); } diff --git a/core/src/rt2/mempool.rs b/core/src/runtime/mempool.rs similarity index 100% rename from core/src/rt2/mempool.rs rename to core/src/runtime/mempool.rs diff --git a/core/src/runtime/mod.rs b/core/src/runtime/mod.rs index 351d54b3..cd63ef92 100644 --- a/core/src/runtime/mod.rs +++ b/core/src/runtime/mod.rs @@ -16,615 +16,219 @@ * SPDX-License-Identifier: Apache-2.0 */ -mod core_map; - -pub(crate) use self::core_map::*; - -use crate::batch::Pipeline; -use crate::config::RuntimeConfig; -use crate::dpdk::{ - self, CoreId, KniError, KniRx, Mempool, Port, PortBuilder, PortError, PortQueue, -}; -use crate::{debug, ensure, info}; +//! Capsule runtime. + +mod config; +mod lcore; +mod mempool; +#[cfg(feature = "pcap-dump")] +#[cfg_attr(docsrs, doc(cfg(feature = "pcap-dump")))] +mod pcap_dump; +mod port; + +pub use self::config::*; +pub(crate) use self::lcore::*; +pub use self::lcore::{Lcore, LcoreMap, LcoreNotFound}; +pub use self::mempool::Mempool; +pub(crate) use self::mempool::*; +pub use self::port::{Outbox, Port, PortError, PortMap}; + +use crate::ffi::dpdk::{self, LcoreId}; +use crate::packets::{Mbuf, Postmark}; +use crate::{debug, info}; use anyhow::Result; -use futures::{future, stream, StreamExt}; -use std::collections::{HashMap, HashSet}; +use async_channel::{self, Receiver, Sender}; use std::fmt; use std::mem::ManuallyDrop; -use std::sync::Arc; -use std::time::{Duration, Instant}; -use tokio_executor::current_thread; -use tokio_net::driver; -use tokio_net::signal::unix::{self, SignalKind}; -use tokio_timer::{timer, Interval}; - -/// Supported [Unix signals]. -/// -/// [Unix signals]: https://en.wikipedia.org/wiki/Signal_(IPC)#POSIX_signals -#[derive(Copy, Clone, Debug)] -pub enum UnixSignal { - /// This signal is sent to a process when its controlling terminal is closed. - /// In modern systems, this signal usually means that the controlling pseudo - /// or virtual terminal has been closed. Many daemons will reload their - /// configuration files and reopen their log files instead of exiting when - /// receiving this signal. `nohup` is a command to make a command ignore the - /// signal. - SIGHUP = libc::SIGHUP as isize, - /// This signal is sent to a process by its controlling terminal when a user - /// wishes to interrupt the process. This is typically initiated by pressing - /// `Ctrl-C`, but on some systems, the "delete" character or "break" key can - /// be used. - SIGINT = libc::SIGINT as isize, - /// This signal is sent to a process to request its termination. Unlike the - /// `SIGKILL` signal, it can be caught and interpreted or ignored by the - /// process. This allows the process to perform nice termination releasing - /// resources and saving state if appropriate. `SIGINT` is nearly identical - /// to `SIGTERM`. - SIGTERM = libc::SIGTERM as isize, -} +use std::ops::DerefMut; -/// The Capsule runtime. -/// -/// The runtime initializes the underlying DPDK environment, and it also manages -/// the task scheduler that executes the packet processing pipelines. -pub struct Runtime { - ports: ManuallyDrop>, - mempools: ManuallyDrop>, - core_map: CoreMap, - on_signal: Arc bool>, - config: RuntimeConfig, -} - -impl Runtime { - /// Builds a runtime from config settings. - #[allow(clippy::cognitive_complexity)] - pub fn build(config: RuntimeConfig) -> Result { - info!("initializing EAL..."); - dpdk::eal_init(config.to_eal_args())?; - - #[cfg(feature = "metrics")] - { - info!("initializing metrics subsystem..."); - crate::metrics::init()?; - } - - let cores = config.all_cores(); - - info!("initializing mempools..."); - let sockets = cores.iter().map(CoreId::socket_id).collect::>(); - let mut mempools = vec![]; - for socket in sockets { - let mempool = Mempool::new(config.mempool.capacity, config.mempool.cache_size, socket)?; - debug!(?mempool); - mempools.push(mempool); - } - - info!("intializing cores..."); - let core_map = CoreMapBuilder::new() - .app_name(&config.app_name) - .cores(&cores) - .master_core(config.master_core) - .mempools(&mut mempools) - .finish()?; - - let len = config.num_knis(); - if len > 0 { - info!("initializing KNI subsystem..."); - dpdk::kni_init(len)?; - } - - info!("initializing ports..."); - let mut ports = vec![]; - for conf in config.ports.iter() { - let port = PortBuilder::new(conf.name.clone(), conf.device.clone())? - .cores(&conf.cores)? - .mempools(&mut mempools) - .rx_tx_queue_capacity(conf.rxd, conf.txd)? - .finish(conf.promiscuous, conf.multicast, conf.kni)?; - - debug!(?port); - ports.push(port); - } - - #[cfg(feature = "metrics")] - { - crate::metrics::register_port_stats(&ports); - crate::metrics::register_mempool_stats(&mempools); - } - - info!("runtime ready."); - - Ok(Runtime { - ports: ManuallyDrop::new(ports), - mempools: ManuallyDrop::new(mempools), - core_map, - on_signal: Arc::new(|_| true), - config, - }) - } +/// Trigger for the shutdown. +pub(crate) struct ShutdownTrigger(Sender<()>, Receiver<()>); - #[inline] - fn get_port(&self, name: &str) -> Result<&Port> { - self.ports - .iter() - .find(|p| p.name() == name) - .ok_or_else(|| PortError::NotFound(name.to_owned()).into()) - } - - #[inline] - fn get_port_mut(&mut self, name: &str) -> Result<&mut Port> { - self.ports - .iter_mut() - .find(|p| p.name() == name) - .ok_or_else(|| PortError::NotFound(name.to_owned()).into()) +impl ShutdownTrigger { + /// Creates a new shutdown trigger. + /// + /// Leverages the behavior of an async channel. When the sender is dropped + /// from scope, it closes the channel and causes the receiver side future + /// in the executor queue to resolve. + pub(crate) fn new() -> Self { + let (s, r) = async_channel::unbounded(); + Self(s, r) } - #[inline] - fn get_core(&self, core_id: CoreId) -> Result<&CoreExecutor> { - self.core_map - .cores - .get(&core_id) - .ok_or_else(|| CoreError::NotFound(core_id).into()) + /// Returns a wait handle. + pub(crate) fn get_wait(&self) -> ShutdownWait { + ShutdownWait(self.1.clone()) } - #[inline] - fn get_port_qs(&self, core_id: CoreId) -> Result> { - let map = self - .ports - .iter() - .filter_map(|p| { - p.queues() - .get(&core_id) - .map(|q| (p.name().to_owned(), q.clone())) - }) - .collect::>(); - - ensure!(!map.is_empty(), CoreError::NotAssigned(core_id)); - - Ok(map) + /// Returns whether the trigger is being waited on. + pub(crate) fn is_waited(&self) -> bool { + // a receiver count greater than 1 indicating that there are receiver + // clones in scope, hence the trigger is being waited on. + self.0.receiver_count() > 1 } - /// Sets the Unix signal handler. - /// - /// `SIGHUP`, `SIGINT` and `SIGTERM` are the supported Unix signals. - /// The return of the handler determines whether to terminate the - /// process. `true` indicates the signal is received and the process - /// should be terminated. `false` indicates to discard the signal and - /// keep the process running. - /// - /// # Example - /// - /// ``` - /// Runtime::build(&config)?; - /// .set_on_signal(|signal| match signal { - /// SIGHUP => { - /// reload_config(); - /// false - /// } - /// _ => true, - /// }) - /// .execute(); - /// ``` - pub fn set_on_signal(&mut self, f: F) -> &mut Self - where - F: Fn(UnixSignal) -> bool + 'static, - { - self.on_signal = Arc::new(f); - self + /// Triggers the shutdown. + pub(crate) fn fire(self) { + drop(self.0) } +} - /// Installs a pipeline to a port. The pipeline will run on all the - /// cores assigned to the port. - /// - /// `port` is the logical name that identifies the port. The `installer` - /// is a closure that takes in a [`PortQueue`] and returns a [`Pipeline`] - /// that will be spawned onto the thread executor. - /// - /// # Example - /// - /// ``` - /// Runtime::build(config)? - /// .add_add_pipeline_to_port("eth1", install)? - /// .execute() - /// ``` - /// - /// [`PortQueue`]: crate::PortQueue - /// [`Pipeline`]: crate::batch::Pipeline - pub fn add_pipeline_to_port( - &mut self, - port: &str, - installer: F, - ) -> Result<&mut Self> - where - F: Fn(PortQueue) -> T + Send + Sync + 'static, - { - let port = self.get_port(port)?; - let f = Arc::new(installer); - - for (core_id, port_q) in port.queues() { - let f = f.clone(); - let port_q = port_q.clone(); - let thread = &self.get_core(*core_id)?.thread; - - // spawns the bootstrap. we want the bootstrapping to execute on the - // target core instead of the master core. that way the actual task - // is spawned locally and the type bounds are less restricting. - thread.spawn(future::lazy(move |_| { - let fut = f(port_q); - debug!("spawned pipeline {}.", fut.name()); - current_thread::spawn(fut); - }))?; - - debug!("installed pipeline on port_q for {:?}.", core_id); - } - - info!("installed pipeline for port {}.", port.name()); - - Ok(self) - } +/// Shutdown wait handle. +pub(crate) struct ShutdownWait(Receiver<()>); - /// Installs a pipeline to a KNI enabled port to receive packets coming - /// from the kernel. This pipeline will run on a randomly select core - /// that's assigned to the port. - /// - /// # Remarks - /// - /// This function has be to invoked once per port. Otherwise the packets - /// coming from the kernel will be silently dropped. For the most common - /// use case where the application only needs simple packet forwarding, - /// use [`batch::splice`] to join the kernel's RX with the port's TX. - /// - /// # Example - /// - /// ``` - /// Runtime::build(config)? - /// .add_add_pipeline_to_port("kni0", install)? - /// .add_kni_rx_pipeline_to_port("kni0", batch::splice)? - /// .execute() - /// ``` - /// - /// [`batch::splice`]: crate::batch::splice - pub fn add_kni_rx_pipeline_to_port( - &mut self, - port: &str, - installer: F, - ) -> Result<&mut Self> - where - F: FnOnce(KniRx, PortQueue) -> T + Send + Sync + 'static, - { - // takes ownership of the kni rx handle. - let kni_rx = self - .get_port_mut(port)? - .kni() - .ok_or(KniError::Disabled)? - .take_rx()?; - - // selects a core to run a rx pipeline for this port. the selection is - // randomly choosing the last core we find. if the port has more than one - // core assigned, this will be different from the core that's running the - // tx pipeline. - let port = self.get_port(port)?; - let core_id = port.queues().keys().last().unwrap(); - let port_q = port.queues()[core_id].clone(); - let thread = &self.get_core(*core_id)?.thread; - - // spawns the bootstrap. we want the bootstrapping to execute on the - // target core instead of the master core. - thread.spawn(future::lazy(move |_| { - let fut = installer(kni_rx, port_q); - debug!("spawned kni rx pipeline {}.", fut.name()); - current_thread::spawn(fut); - }))?; - - info!("installed kni rx pipeline for port {}.", port.name()); - - Ok(self) +impl ShutdownWait { + /// A future that waits till the shutdown trigger is fired. + pub(crate) async fn wait(&self) { + self.0.recv().await.unwrap_or(()) } +} - /// Installs a pipeline to a core. All the ports the core is assigned - /// to will be available to the pipeline. - /// - /// `core` is the logical id that identifies the core. The `installer` - /// is a closure that takes in a hashmap of [`PortQueues`] and returns a - /// [`Pipeline`] that will be spawned onto the thread executor of the core. - /// - /// # Example - /// - /// ``` - /// Runtime::build(config)? - /// .add_pipeline_to_core(1, install)? - /// .execute() - /// ``` - /// - /// [`PortQueues`]: crate::PortQueue - /// [`Pipeline`]: crate::batch::Pipeline - pub fn add_pipeline_to_core( - &mut self, - core: usize, - installer: F, - ) -> Result<&mut Self> - where - F: FnOnce(HashMap) -> T + Send + Sync + 'static, - { - let core_id = CoreId::new(core); - let thread = &self.get_core(core_id)?.thread; - let port_qs = self.get_port_qs(core_id)?; - - // spawns the bootstrap. we want the bootstrapping to execute on the - // target core instead of the master core. - thread.spawn(future::lazy(move |_| { - let fut = installer(port_qs); - debug!("spawned pipeline {}.", fut.name()); - current_thread::spawn(fut); - }))?; - - info!("installed pipeline for {:?}.", core_id); - - Ok(self) - } +/// The Capsule runtime. +/// +/// The runtime initializes the underlying DPDK environment, and it also manages +/// the task scheduler that executes the packet processing tasks. +pub struct Runtime { + mempool: ManuallyDrop, + lcores: ManuallyDrop, + ports: ManuallyDrop, + #[cfg(feature = "pcap-dump")] + pcap_dump: ManuallyDrop, +} - /// Installs a periodic pipeline to a core. - /// - /// `core` is the logical id that identifies the core. The `installer` is a - /// closure that takes in a hashmap of [`PortQueues`] and returns a - /// [`Pipeline`] that will be run periodically every `dur` interval. - /// - /// # Remarks - /// - /// All the ports the core is assigned to will be available to this - /// pipeline. However they should only be used to transmit packets. This - /// variant is for pipelines that generate new packets periodically. - /// A new packet batch can be created with [`batch::poll_fn`] and ingested - /// into the pipeline. - /// - /// # Example - /// - /// ``` - /// Runtime::build(config)? - /// .add_periodic_pipeline_to_core(1, install, Duration::from_millis(10))? - /// .execute() - /// ``` +impl Runtime { + /// Returns the mempool. /// - /// [`PortQueues`]: crate::PortQueue - /// [`Pipeline`]: crate::batch::Pipeline - /// [`batch::poll_fn`]: crate::batch::poll_fn - pub fn add_periodic_pipeline_to_core( - &mut self, - core: usize, - installer: F, - dur: Duration, - ) -> Result<&mut Self> - where - F: FnOnce(HashMap) -> T + Send + Sync + 'static, - { - let core_id = CoreId::new(core); - let thread = &self.get_core(core_id)?.thread; - let port_qs = self.get_port_qs(core_id)?; - - // spawns the bootstrap. we want the bootstrapping to execute on the - // target core instead of the master core so the periodic task is - // associated with the correct timer instance. - thread.spawn(future::lazy(move |_| { - let mut pipeline = installer(port_qs); - debug!("spawned periodic pipeline {}.", pipeline.name()); - let fut = Interval::new_interval(dur).for_each(move |_| { - pipeline.run_once(); - future::ready(()) - }); - current_thread::spawn(fut); - }))?; - - info!("installed periodic pipeline for {:?}.", core_id); - - Ok(self) + /// For simplicity, we currently only support one global Mempool. Multi- + /// socket support may be added in the future. + pub fn mempool(&self) -> &Mempool { + &self.mempool } - /// Installs a periodic task to a core. - /// - /// `core` is the logical id that identifies the core. `task` is the - /// closure to execute. The task will rerun every `dur` interval. - /// - /// # Example - /// - /// ``` - /// Runtime::build(config)? - /// .add_periodic_task_to_core(0, print_stats, Duration::from_secs(1))? - /// .execute() - /// ``` - pub fn add_periodic_task_to_core( - &mut self, - core: usize, - mut task: F, - dur: Duration, - ) -> Result<&mut Self> - where - F: FnMut() + Send + Sync + 'static, - { - let core_id = CoreId::new(core); - let thread = &self.get_core(core_id)?.thread; - - // spawns the bootstrap. we want the bootstrapping to execute on the - // target core instead of the master core so the periodic task is - // associated with the correct timer instance. - thread.spawn(future::lazy(move |_| { - let fut = Interval::new_interval(dur).for_each(move |_| { - task(); - future::ready(()) - }); - debug!("spawned periodic task."); - current_thread::spawn(fut); - }))?; - - info!("installed periodic task for {:?}.", core_id); - - Ok(self) + /// Returns the lcores. + pub fn lcores(&self) -> &LcoreMap { + &self.lcores } - /// Blocks the main thread until a timeout expires. - /// - /// This mode is useful for running integration tests. The timeout - /// duration can be set in `RuntimeSettings`. - fn wait_for_timeout(&mut self, timeout: Duration) { - let MasterExecutor { - ref timer, - ref mut thread, - .. - } = self.core_map.master_core; - - let when = Instant::now() + timeout; - let delay = timer.delay(when); - - debug!("waiting for {:?}...", timeout); - let _timer = timer::set_default(&timer); - thread.block_on(delay); - info!("timed out after {:?}.", timeout); + /// Returns the configured ports. + pub fn ports(&self) -> &PortMap { + &self.ports } - /// Blocks the main thread until receives a signal to terminate. - fn wait_for_signal(&mut self) -> Result<()> { - let sighup = unix::signal(SignalKind::hangup())?.map(|_| UnixSignal::SIGHUP); - let sigint = unix::signal(SignalKind::interrupt())?.map(|_| UnixSignal::SIGINT); - let sigterm = unix::signal(SignalKind::terminate())?.map(|_| UnixSignal::SIGTERM); - - // combines the streams together - let stream = stream::select(stream::select(sighup, sigint), sigterm); - - // passes each signal through the `on_signal` closure, and discard - // any that shouldn't stop the execution. - let f = self.on_signal.clone(); - let mut stream = stream.filter(|&signal| future::ready(f(signal))); - - let MasterExecutor { - ref reactor, - ref timer, - ref mut thread, - .. - } = self.core_map.master_core; - - // sets the reactor so we receive the signals and runs the future - // on the master core. the execution stops on the first signal that - // wasn't filtered out. - debug!("waiting for a Unix signal..."); - let _guard = driver::set_default(&reactor); - let _timer = timer::set_default(&timer); - let _ = thread.block_on(stream.next()); - info!("signaled to stop."); + /// Initializes a new runtime from config settings. + pub fn from_config(config: RuntimeConfig) -> Result { + info!("starting runtime."); - Ok(()) - } + debug!("initializing EAL ..."); + dpdk::eal_init(config.to_eal_args())?; - /// Installs the KNI TX pipelines. - fn add_kni_tx_pipelines(&mut self) -> Result<()> { - let mut map = HashMap::new(); - for port in self.ports.iter_mut() { - // selects a core if we need to run a tx pipeline for this port. the - // selection is randomly choosing the first core we find. if the port - // has more than one core assigned, this will be different from the - // core that's running the rx pipeline. - let core_id = *port.queues().keys().next().unwrap(); - - // if the port is kni enabled, then we will take ownership of the - // tx handle. - if let Some(kni) = port.kni() { - map.insert(core_id, kni.take_tx()?); - } + debug!("initializing mempool ..."); + let socket = LcoreId::main().socket(); + let mut mempool = Mempool::new( + "mempool", + config.mempool.capacity, + config.mempool.cache_size, + socket, + )?; + debug!(?mempool); + + debug!("initializing lcore schedulers ..."); + let lcores = self::lcore_pool(); + + for lcore in lcores.iter() { + let mut ptr = mempool.ptr_mut().clone(); + lcore.block_on(async move { MEMPOOL.with(|tls| tls.set(ptr.deref_mut())) }); } - // spawns all the pipelines. - for (core_id, kni_tx) in map.into_iter() { - let thread = &self.get_core(core_id)?.thread; - thread.spawn(kni_tx.into_pipeline())?; + info!("initializing ports ..."); + let mut ports = Vec::new(); + for port in config.ports.iter() { + let mut port = port::Builder::for_device(&port.name, &port.device)? + .set_rxqs_txqs(port.rxqs, port.txqs)? + .set_promiscuous(port.promiscuous)? + .set_multicast(port.multicast)? + .set_rx_lcores(port.rx_cores.clone())? + .set_tx_lcores(port.tx_cores.clone())? + .build(&mut mempool)?; - info!("installed kni tx pipeline on {:?}.", core_id); - } + debug!(?port); - Ok(()) - } + if !port.tx_lcores().is_empty() { + port.spawn_tx_loops(&lcores)?; + } - /// Starts all the ports to receive packets. - fn start_ports(&mut self) -> Result<()> { - for port in self.ports.iter_mut() { port.start()?; + ports.push(port); } + let ports: PortMap = ports.into(); - Ok(()) - } + #[cfg(feature = "pcap-dump")] + let pcap_dump = self::pcap_dump::enable_pcap_dump(&config.data_dir(), &ports, &lcores)?; - /// Unparks all the cores to start task execution. - fn unpark_cores(&mut self) { - for core in self.core_map.cores.values() { - if let Some(unpark) = &core.unpark { - unpark.unpark(); - } - } - } + info!("runtime ready."); - /// Shuts down all the cores to stop task execution. - #[allow(clippy::cognitive_complexity)] - fn shutdown_cores(&mut self) { - for (core_id, core) in &mut self.core_map.cores { - if let Some(trigger) = core.shutdown.take() { - debug!("shutting down {:?}.", core_id); - trigger.shutdown(); - debug!("sent {:?} shutdown trigger.", core_id); - let handle = core.join.take().unwrap(); - let _ = handle.join(); - info!("terminated {:?}.", core_id); - } - } + Ok(Runtime { + mempool: ManuallyDrop::new(mempool), + lcores: ManuallyDrop::new(lcores), + ports: ManuallyDrop::new(ports), + #[cfg(feature = "pcap-dump")] + pcap_dump: ManuallyDrop::new(pcap_dump), + }) } - /// Stops all the ports. - fn stop_ports(&mut self) { - for port in self.ports.iter_mut() { - port.stop(); - } + /// Sets the packet processing pipeline for port. + pub fn set_port_pipeline(&self, port: &str, f: F) -> Result<()> + where + F: Fn(Mbuf) -> Result + Clone + Send + Sync + 'static, + { + let port = self.ports.get(port)?; + port.spawn_rx_loops(f, &self.lcores)?; + Ok(()) } - /// Executes the pipeline(s) until a stop signal is received. - pub fn execute(&mut self) -> Result<()> { - self.add_kni_tx_pipelines()?; - self.start_ports()?; - self.unpark_cores(); - - // runs the app until main loop finishes. - match self.config.duration { - None => self.wait_for_signal()?, - Some(d) => self.wait_for_timeout(d), - }; - - self.shutdown_cores(); - self.stop_ports(); - info!("runtime terminated."); - - Ok(()) + /// Starts the runtime execution. + pub fn execute(self) -> Result { + Ok(RuntimeGuard { runtime: self }) } } -impl<'a> fmt::Debug for Runtime { +impl fmt::Debug for Runtime { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("runtime") - .field("runtime configuration", &format!("{:?}", self.config)) + f.debug_struct("Runtime") + .field("mempool", &self.mempool) .finish() } } -impl Drop for Runtime { +/// The RAII guard to stop and cleanup the runtime resources on drop. +pub struct RuntimeGuard { + runtime: Runtime, +} + +impl Drop for RuntimeGuard { fn drop(&mut self) { - // the default rust drop order is self before fields, which is the wrong - // order for what EAL needs. To control the order, we manually drop the - // fields first. - unsafe { - ManuallyDrop::drop(&mut self.ports); - ManuallyDrop::drop(&mut self.mempools); + info!("shutting down runtime."); + + for port in self.runtime.ports.iter_mut() { + port.stop(); } - if self.config.num_knis() > 0 { - debug!("freeing KNI subsystem."); - dpdk::kni_close(); + unsafe { + #[cfg(feature = "pcap-dump")] + ManuallyDrop::drop(&mut self.runtime.pcap_dump); + ManuallyDrop::drop(&mut self.runtime.ports); + ManuallyDrop::drop(&mut self.runtime.lcores); + ManuallyDrop::drop(&mut self.runtime.mempool); } - debug!("freeing EAL."); - dpdk::eal_cleanup().unwrap(); + debug!("freeing EAL ..."); + let _ = dpdk::eal_cleanup(); + info!("runtime shutdown."); + } +} + +impl fmt::Debug for RuntimeGuard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "RuntimeGuard") } } diff --git a/core/src/rt2/pcap_dump.rs b/core/src/runtime/pcap_dump.rs similarity index 97% rename from core/src/rt2/pcap_dump.rs rename to core/src/runtime/pcap_dump.rs index 8c0a39bf..f8a72f47 100644 --- a/core/src/rt2/pcap_dump.rs +++ b/core/src/runtime/pcap_dump.rs @@ -72,6 +72,9 @@ impl Drop for CaptureFile { /// The pcap dump manager. pub(crate) struct PcapDump { output_dir: String, + // we need the extra level of indirection because we need stable + // pointers to pass to ffi code. + #[allow(clippy::vec_box)] captures: Vec>, } diff --git a/core/src/rt2/port.rs b/core/src/runtime/port.rs similarity index 96% rename from core/src/rt2/port.rs rename to core/src/runtime/port.rs index 3e65a68a..0a941e5f 100644 --- a/core/src/rt2/port.rs +++ b/core/src/runtime/port.rs @@ -90,7 +90,7 @@ impl Port { self.outbox .as_ref() .map(|s| Outbox(self.name.clone(), s.clone())) - .ok_or(PortError::TxNotEnabled.into()) + .ok_or_else(|| PortError::TxNotEnabled.into()) } /// Spawns the port receiving loop. @@ -102,7 +102,7 @@ impl Port { let shutdown = self.shutdown.as_ref().unwrap(); // can't run loop without assigned rx cores. - ensure!(self.rx_lcores.len() > 0, PortError::RxNotEnabled); + ensure!(!self.rx_lcores.is_empty(), PortError::RxNotEnabled); // pipeline already set if the trigger is waited on. ensure!(!shutdown.is_waited(), PortError::PipelineSet); @@ -292,29 +292,23 @@ async fn tx_loop( let txq = PortTxQueue { port_id, queue_id }; let mut ptrs = Vec::with_capacity(batch_size); - loop { - if let Ok(ptr) = receiver.recv().await { - ptrs.push(ptr); - - // try to batch the packets up to batch size. - for _ in 1..batch_size { - match receiver.try_recv() { - Ok(ptr) => ptrs.push(ptr), - // no more packets to batch, ready to transmit. - Err(_) => break, - } + while let Ok(ptr) = receiver.recv().await { + ptrs.push(ptr); + + // try to batch the packets up to batch size. + for _ in 1..batch_size { + match receiver.try_recv() { + Ok(ptr) => ptrs.push(ptr), + // no more packets to batch, ready to transmit. + Err(_) => break, } + } - txq.transmit(&mut ptrs); + txq.transmit(&mut ptrs); - // cooperatively moves to the back of the execution queue, - // making room for other tasks before transmitting again. - future::yield_now().await; - } else { - // this branch can only be reached if the channel is closed, - // indicating the tx loop should exit. - break; - } + // cooperatively moves to the back of the execution queue, + // making room for other tasks before transmitting again. + future::yield_now().await; } debug!(port = ?port_name, lcore = ?LcoreId::current(), "tx loop exited."); diff --git a/core/src/testils/criterion.rs b/core/src/testils/criterion.rs index 8bd66dc6..d45268dd 100644 --- a/core/src/testils/criterion.rs +++ b/core/src/testils/criterion.rs @@ -22,12 +22,9 @@ //! [criterion]: https://crates.io/crates/criterion use super::Rvg; -use crate::batch::{Batch, PacketTx, Poll}; -use crate::Mbuf; use criterion::{black_box, Bencher}; use proptest::strategy::Strategy; use std::cmp; -use std::sync::mpsc::{self, Receiver}; use std::time::{Duration, Instant}; /// Criterion `Bencher` extension trait. @@ -42,19 +39,6 @@ pub trait BencherExt { where R: FnMut(S::Value) -> O, S: Strategy; - - /// Times a `routine` with an input generated via a `proptest strategy` - /// batch of input that can be polled for benchmarking pipeline combinators - /// in [`Batch`] and then times the iteration of the benchmark - /// over the input. See [`BatchSize`] for details on choosing the batch size. - /// - /// [`BatchSize`]: https://docs.rs/criterion/latest/criterion/enum.BatchSize.html - /// [`Batch`]: crate::batch::Batch - fn iter_proptest_combinators(&mut self, strategy: S, routine: R, batch_size: usize) - where - R: FnMut(Poll>) -> O, - S: Strategy, - O: Batch; } impl BencherExt for Bencher<'_> { @@ -84,43 +68,4 @@ impl BencherExt for Bencher<'_> { total_elapsed }) } - - fn iter_proptest_combinators, O: Batch>( - &mut self, - strategy: S, - mut routine: R, - batch_size: usize, - ) where - R: FnMut(Poll>) -> O, - { - self.iter_custom(|mut iters| { - let mut total_elapsed = Duration::from_secs(0); - let mut gen = Rvg::deterministic(); - while iters > 0 { - let batch_size = cmp::min(batch_size, iters as usize); - let inputs = black_box(gen.generate_vec(&strategy, batch_size)); - let mut outputs = Vec::with_capacity(batch_size); - - let (mut tx, rx) = mpsc::channel(); - tx.transmit(inputs.into_iter().collect::>()); - let mut new_batch = Poll::new(rx); - new_batch.replenish(); - - let start = Instant::now(); - let mut batch = routine(new_batch); - - while let Some(disp) = batch.next() { - outputs.push(disp) - } - - total_elapsed += start.elapsed(); - - black_box(batch); - black_box(outputs); - - iters -= batch_size as u64; - } - total_elapsed - }) - } } diff --git a/core/src/testils/mod.rs b/core/src/testils/mod.rs index 24455756..da4393e4 100644 --- a/core/src/testils/mod.rs +++ b/core/src/testils/mod.rs @@ -28,8 +28,7 @@ pub use self::packet::*; pub use self::rvg::*; use crate::ffi::dpdk::{self, SocketId}; -use crate::metrics; -use crate::rt2::{Mempool, MEMPOOL}; +use crate::runtime::{Mempool, MEMPOOL}; use std::ops::DerefMut; use std::ptr; use std::sync::Once; @@ -62,7 +61,6 @@ pub fn cargo_test_init() { "net_tap0", ]) .unwrap(); - let _ = metrics::init(); }); } diff --git a/core/src/testils/packet.rs b/core/src/testils/packet.rs index fbdb710e..c0e4d86c 100644 --- a/core/src/testils/packet.rs +++ b/core/src/testils/packet.rs @@ -16,9 +16,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +use crate::packets::ethernet::Ethernet; use crate::packets::ip::v4::Ipv4; use crate::packets::ip::v6::{Ipv6, SegmentRouting}; -use crate::packets::{Ethernet, Packet, Tcp, Tcp4, Tcp6, Udp4, Udp6}; +use crate::packets::tcp::{Tcp, Tcp4, Tcp6}; +use crate::packets::udp::{Udp4, Udp6}; +use crate::packets::Packet; /// [`Packet`] extension trait. /// diff --git a/core/src/testils/proptest/arbitrary.rs b/core/src/testils/proptest/arbitrary.rs index b4b3a399..35c24aed 100644 --- a/core/src/testils/proptest/arbitrary.rs +++ b/core/src/testils/proptest/arbitrary.rs @@ -19,8 +19,8 @@ //! Implementations of `proptest.arbitrary.Arbitrary` trait for //! various types. -use crate::dpdk::Mbuf; use crate::net::MacAddr; +use crate::packets::Mbuf; use proptest::arbitrary::{any, Arbitrary, StrategyFor}; use proptest::strategy::{MapInto, Strategy}; diff --git a/core/src/testils/proptest/strategy.rs b/core/src/testils/proptest/strategy.rs index c70c0645..12f07076 100644 --- a/core/src/testils/proptest/strategy.rs +++ b/core/src/testils/proptest/strategy.rs @@ -19,12 +19,14 @@ //! Proptest strategies. use crate::net::MacAddr; +use crate::packets::ethernet::{EtherType, EtherTypes, Ethernet}; use crate::packets::ip::v4::Ipv4; use crate::packets::ip::v6::{Ipv6, Ipv6Packet, SegmentRouting}; use crate::packets::ip::{Flow, IpPacket, ProtocolNumber, ProtocolNumbers}; -use crate::packets::{EtherType, EtherTypes, Ethernet, Packet, Tcp, Udp}; +use crate::packets::tcp::Tcp; +use crate::packets::udp::Udp; +use crate::packets::{Mbuf, Packet}; use crate::testils::Rvg; -use crate::Mbuf; use proptest::arbitrary::{any, Arbitrary}; use proptest::collection::vec; use proptest::prop_oneof; diff --git a/examples/kni/Cargo.toml b/examples/kni/Cargo.toml index a27935c4..317d5a39 100644 --- a/examples/kni/Cargo.toml +++ b/examples/kni/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kni" -version = "0.1.0" +version = "0.2.0" authors = ["Capsule Developers "] license = "Apache-2.0" edition = "2018" @@ -17,7 +17,7 @@ doctest = false [dependencies] anyhow = "1.0" -capsule = { version = "0.1", path = "../../core" } +capsule = { version = "0.2", path = "../../core" } colored = "2.0" signal-hook = "0.3" tracing = "0.1" diff --git a/examples/kni/main.rs b/examples/kni/main.rs index 8b0ac94b..15c9a7b0 100644 --- a/examples/kni/main.rs +++ b/examples/kni/main.rs @@ -17,11 +17,13 @@ */ use anyhow::Result; +use capsule::packets::ethernet::Ethernet; use capsule::packets::icmp::v6::Icmpv6; use capsule::packets::ip::v6::{Ipv6, Ipv6Packet}; use capsule::packets::ip::ProtocolNumbers; -use capsule::packets::{Ethernet, Mbuf, Packet, Postmark, Udp6}; -use capsule::rt2::{self, Outbox, Runtime}; +use capsule::packets::udp::Udp6; +use capsule::packets::{Mbuf, Packet, Postmark}; +use capsule::runtime::{self, Outbox, Runtime}; use colored::Colorize; use signal_hook::consts; use signal_hook::flag; @@ -75,7 +77,7 @@ fn main() -> Result<()> { .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = rt2::load_config()?; + let config = runtime::load_config()?; let runtime = Runtime::from_config(config)?; let kni0 = runtime.ports().get("kni0")?.outbox()?; diff --git a/examples/nat64/Cargo.toml b/examples/nat64/Cargo.toml index c9bf0d1c..8cc66e3e 100644 --- a/examples/nat64/Cargo.toml +++ b/examples/nat64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nat64" -version = "0.1.0" +version = "0.2.0" authors = ["Capsule Developers "] license = "Apache-2.0" edition = "2018" @@ -18,7 +18,7 @@ doctest = false [dependencies] anyhow = "1.0" bimap = "0.6" -capsule = { version = "0.1", path = "../../core" } +capsule = { version = "0.2", path = "../../core" } colored = "2.0" once_cell = "1.7" signal-hook = "0.3" diff --git a/examples/nat64/main.rs b/examples/nat64/main.rs index 4b68d21c..fcac7249 100644 --- a/examples/nat64/main.rs +++ b/examples/nat64/main.rs @@ -19,11 +19,13 @@ use anyhow::Result; use bimap::BiMap; use capsule::net::MacAddr; +use capsule::packets::ethernet::Ethernet; use capsule::packets::ip::v4::Ipv4; use capsule::packets::ip::v6::{Ipv6, Ipv6Packet}; use capsule::packets::ip::ProtocolNumbers; -use capsule::packets::{Ethernet, Mbuf, Packet, Postmark, Tcp4, Tcp6}; -use capsule::rt2::{self, Outbox, Runtime}; +use capsule::packets::tcp::{Tcp4, Tcp6}; +use capsule::packets::{Mbuf, Packet, Postmark}; +use capsule::runtime::{self, Outbox, Runtime}; use colored::Colorize; use once_cell::sync::Lazy; use signal_hook::consts; @@ -184,7 +186,7 @@ fn main() -> Result<()> { .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = rt2::load_config()?; + let config = runtime::load_config()?; let runtime = Runtime::from_config(config)?; let cap1 = runtime.ports().get("cap1")?.outbox()?; diff --git a/examples/ping4d/Cargo.toml b/examples/ping4d/Cargo.toml index 708cba93..8c4935c8 100644 --- a/examples/ping4d/Cargo.toml +++ b/examples/ping4d/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ping4d" -version = "0.1.0" +version = "0.2.0" authors = ["Capsule Developers "] license = "Apache-2.0" edition = "2018" @@ -17,7 +17,7 @@ doctest = false [dependencies] anyhow = "1.0" -capsule = { version = "0.1", path = "../../core" } +capsule = { version = "0.2", path = "../../core" } signal-hook = "0.3" tracing = "0.1" tracing-subscriber = "0.2" diff --git a/examples/ping4d/main.rs b/examples/ping4d/main.rs index 14fde502..acbf9cd5 100644 --- a/examples/ping4d/main.rs +++ b/examples/ping4d/main.rs @@ -17,10 +17,11 @@ */ use anyhow::Result; +use capsule::packets::ethernet::Ethernet; use capsule::packets::icmp::v4::{EchoReply, EchoRequest}; use capsule::packets::ip::v4::Ipv4; -use capsule::packets::{Ethernet, Mbuf, Packet, Postmark}; -use capsule::rt2::{self, Outbox, Runtime}; +use capsule::packets::{Mbuf, Packet, Postmark}; +use capsule::runtime::{self, Outbox, Runtime}; use signal_hook::consts; use signal_hook::flag; use std::sync::atomic::{AtomicBool, Ordering}; @@ -63,7 +64,7 @@ fn main() -> Result<()> { .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = rt2::load_config()?; + let config = runtime::load_config()?; let runtime = Runtime::from_config(config)?; let outbox = runtime.ports().get("cap0")?.outbox()?; diff --git a/examples/pktdump/Cargo.toml b/examples/pktdump/Cargo.toml index f421f28e..be1920e4 100644 --- a/examples/pktdump/Cargo.toml +++ b/examples/pktdump/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pktdump" -version = "0.1.0" +version = "0.2.0" authors = ["Capsule Developers "] license = "Apache-2.0" edition = "2018" @@ -17,7 +17,7 @@ doctest = false [dependencies] anyhow = "1.0" -capsule = { version = "0.1", path = "../../core" } +capsule = { version = "0.2", path = "../../core" } colored = "2.0" signal-hook = "0.3" tracing = "0.1" diff --git a/examples/pktdump/main.rs b/examples/pktdump/main.rs index fbaeaf32..cd768cc9 100644 --- a/examples/pktdump/main.rs +++ b/examples/pktdump/main.rs @@ -17,11 +17,13 @@ */ use anyhow::{anyhow, Result}; +use capsule::packets::ethernet::{EtherTypes, Ethernet}; use capsule::packets::ip::v4::Ipv4; use capsule::packets::ip::v6::Ipv6; use capsule::packets::ip::IpPacket; -use capsule::packets::{EtherTypes, Ethernet, Mbuf, Packet, Postmark, Tcp, Tcp4, Tcp6}; -use capsule::rt2::{self, Runtime}; +use capsule::packets::tcp::{Tcp, Tcp4, Tcp6}; +use capsule::packets::{Mbuf, Packet, Postmark}; +use capsule::runtime::{self, Runtime}; use colored::Colorize; use signal_hook::consts; use signal_hook::flag; @@ -81,7 +83,7 @@ fn main() -> Result<()> { .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = rt2::load_config()?; + let config = runtime::load_config()?; let runtime = Runtime::from_config(config)?; runtime.set_port_pipeline("cap0", dump_pkt)?; runtime.set_port_pipeline("cap1", dump_pkt)?; diff --git a/examples/skeleton/Cargo.toml b/examples/skeleton/Cargo.toml index cbc69250..3906f04b 100644 --- a/examples/skeleton/Cargo.toml +++ b/examples/skeleton/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "skeleton" -version = "0.1.0" +version = "0.2.0" authors = ["Capsule Developers "] license = "Apache-2.0" edition = "2018" @@ -17,6 +17,6 @@ doctest = false [dependencies] anyhow = "1.0" -capsule = { version = "0.1", path = "../../core" } +capsule = { version = "0.2", path = "../../core" } tracing = "0.1" tracing-subscriber = "0.2" diff --git a/examples/skeleton/main.rs b/examples/skeleton/main.rs index 0763f8a6..ccb0de88 100644 --- a/examples/skeleton/main.rs +++ b/examples/skeleton/main.rs @@ -17,7 +17,7 @@ */ use anyhow::Result; -use capsule::rt2::{self, Runtime}; +use capsule::runtime::{self, Runtime}; use tracing::{debug, Level}; use tracing_subscriber::fmt; @@ -27,7 +27,7 @@ fn main() -> Result<()> { .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = rt2::load_config()?; + let config = runtime::load_config()?; debug!(?config); let runtime = Runtime::from_config(config)?; diff --git a/examples/syn-flood/Cargo.toml b/examples/syn-flood/Cargo.toml index 355dd441..f9fa98b1 100644 --- a/examples/syn-flood/Cargo.toml +++ b/examples/syn-flood/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syn-flood" -version = "0.1.0" +version = "0.2.0" authors = ["Capsule Developers "] license = "Apache-2.0" edition = "2018" @@ -18,7 +18,7 @@ doctest = false [dependencies] anyhow = "1.0" async-io = "1.3" -capsule = { version = "0.1", path = "../../core" } +capsule = { version = "0.2", path = "../../core" } futures-lite = "1.11" rand = "0.8" signal-hook = "0.3" diff --git a/examples/syn-flood/main.rs b/examples/syn-flood/main.rs index c0062a48..8f9accbc 100644 --- a/examples/syn-flood/main.rs +++ b/examples/syn-flood/main.rs @@ -19,9 +19,11 @@ use anyhow::Result; use async_io::Timer; use capsule::net::MacAddr; +use capsule::packets::ethernet::Ethernet; use capsule::packets::ip::v4::Ipv4; -use capsule::packets::{Ethernet, Mbuf, Packet, Tcp4}; -use capsule::rt2::{self, Outbox, Runtime}; +use capsule::packets::tcp::Tcp4; +use capsule::packets::{Mbuf, Packet}; +use capsule::runtime::{self, Outbox, Runtime}; use futures_lite::stream::StreamExt; use signal_hook::consts; use signal_hook::flag; @@ -79,7 +81,7 @@ fn main() -> Result<()> { .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = rt2::load_config()?; + let config = runtime::load_config()?; let runtime = Runtime::from_config(config)?; let term = Arc::new(AtomicBool::new(false)); diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index d72a4a5e..7ed51aad 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "capsule-ffi" -version = "0.1.5" +version = "0.2.0" authors = ["Capsule Developers "] license = "Apache-2.0" edition = "2018" diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 99267022..99562296 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "capsule-macros" -version = "0.1.5" +version = "0.2.0" authors = ["Capsule Developers "] license = "Apache-2.0" edition = "2018" diff --git a/macros/src/derive_packet.rs b/macros/src/derive_packet.rs index 76baf650..5e7de693 100644 --- a/macros/src/derive_packet.rs +++ b/macros/src/derive_packet.rs @@ -82,6 +82,7 @@ pub fn gen_icmpv6(input: syn::DeriveInput) -> TokenStream { fn try_push(mut envelope: Self::Envelope, internal: Internal) -> ::anyhow::Result { use ::capsule::packets::icmp::v6::{Icmpv6, Icmpv6Header, Icmpv6Message}; use ::capsule::packets::ip::{IpPacket, ProtocolNumbers}; + use ::capsule::packets::SizeOf; let offset = envelope.payload_offset(); let mbuf = envelope.mbuf_mut(); @@ -180,6 +181,7 @@ pub fn gen_icmpv4(input: syn::DeriveInput) -> TokenStream { fn try_push(mut envelope: Self::Envelope, internal: ::capsule::packets::Internal) -> ::anyhow::Result { use ::capsule::packets::icmp::v4::{Icmpv4, Icmpv4Header, Icmpv4Message}; use ::capsule::packets::ip::{IpPacket, ProtocolNumbers}; + use ::capsule::packets::SizeOf; let offset = envelope.payload_offset(); let mbuf = envelope.mbuf_mut(); diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 0cfca412..23b816ec 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -40,7 +40,7 @@ pub fn derive_size_of(input: TokenStream) -> TokenStream { let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let expanded = quote! { - impl #impl_generics SizeOf for #name #ty_generics #where_clause { + impl #impl_generics ::capsule::packets::SizeOf for #name #ty_generics #where_clause { fn size_of() -> usize { std::mem::size_of::() }