diff --git a/Cargo.toml b/Cargo.toml index b68afeee..78c9c2b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ members = [ "examples/nat64", "examples/ping4d", "examples/pktdump", - "examples/signals", "examples/skeleton", "examples/syn-flood", "ffi", 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..7f8cca75 100644 --- a/bench/packets.rs +++ b/bench/packets.rs @@ -17,26 +17,28 @@ */ 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; const BATCH_SIZE: usize = 500; -fn single_parse_udp(ipv4: Ipv4) -> Udp4 { - ipv4.parse::().unwrap() +fn single_parse_udp(ip4: Ipv4) -> Udp4 { + ip4.parse::().unwrap() } -fn single_peek_udp(ipv4: Ipv4) -> Ipv4 { - ipv4.peek::().unwrap(); - ipv4 +fn single_peek_udp(ip4: Ipv4) -> Ipv4 { + ip4.peek::().unwrap(); + ip4 } #[capsule::bench(mempool_capacity = 511)] @@ -44,16 +46,16 @@ fn single_peek_vs_parse(c: &mut Criterion) { let mut group = c.benchmark_group("packets::single_peek_vs_parse_on_udp"); group.bench_function("packets::single_parse_udp", |b| { - let s = v4_udp().prop_map(|v| { - let packet = v.into_v4_udp(); + let s = udp4().prop_map(|v| { + let packet = v.into_udp4(); packet.deparse() }); b.iter_proptest_batched(s, single_parse_udp, BATCH_SIZE) }); group.bench_function("packets::single_peek_udp", |b| { - let s = v4_udp().prop_map(|v| { - let packet = v.into_v4_udp(); + let s = udp4().prop_map(|v| { + let packet = v.into_udp4(); packet.deparse() }); b.iter_proptest_batched(s, single_peek_udp, BATCH_SIZE) @@ -64,14 +66,14 @@ fn single_peek_vs_parse(c: &mut Criterion) { fn multi_parse_udp(mbuf: Mbuf) -> Udp4 { let ethernet = mbuf.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - ipv4.parse::().unwrap() + let ip4 = ethernet.parse::().unwrap(); + ip4.parse::().unwrap() } fn multi_peek_udp(mbuf: Mbuf) -> Mbuf { let ethernet = mbuf.peek::().unwrap(); - let ipv4 = ethernet.peek::().unwrap(); - ipv4.peek::().unwrap(); + let ip4 = ethernet.peek::().unwrap(); + ip4.peek::().unwrap(); mbuf } @@ -80,70 +82,70 @@ fn multi_peek_vs_parse(c: &mut Criterion) { let mut group = c.benchmark_group("packets::multi_peek_vs_parse_on_udp_packets"); group.bench_function("packets::multi_parse_udp", |b| { - let s = v4_udp(); + let s = udp4(); b.iter_proptest_batched(s, multi_parse_udp, BATCH_SIZE) }); group.bench_function("packets::multi_peek_udp", |b| { - let s = v4_udp(); + let s = udp4(); b.iter_proptest_batched(s, multi_peek_udp, BATCH_SIZE) }); group.finish() } -fn single_parse_srh(ipv6: Ipv6) -> SegmentRouting { - ipv6.parse::>().unwrap() +fn single_parse_sr(ip6: Ipv6) -> SegmentRouting { + ip6.parse::>().unwrap() } #[capsule::bench(mempool_capacity = 511)] -fn single_parse_srh_segments_sizes(c: &mut Criterion) { - let mut group = c.benchmark_group("packets::parsing_on_SRH_across_segment_sizes"); +fn single_parse_sr_segments_sizes(c: &mut Criterion) { + let mut group = c.benchmark_group("packets::parsing_on_sr_across_segment_sizes"); let mut rvg = Rvg::new(); - group.bench_function("packets::single_parse_srh::size=1", |b| { + group.bench_function("packets::single_parse_sr::size=1", |b| { let segments = rvg.generate_vec(&any::(), 1); let s = sr_tcp_with(fieldmap! {field::sr_segments => segments}) .prop_map(|v| v.into_sr().deparse()); - b.iter_proptest_batched(s, single_parse_srh, BATCH_SIZE) + b.iter_proptest_batched(s, single_parse_sr, BATCH_SIZE) }); - group.bench_function("packets::single_parse_srh::size=2", |b| { + group.bench_function("packets::single_parse_sr::size=2", |b| { let segments = rvg.generate_vec(&any::(), 2); let s = sr_tcp_with(fieldmap! {field::sr_segments => segments}) .prop_map(|v| v.into_sr().deparse()); - b.iter_proptest_batched(s, single_parse_srh, BATCH_SIZE) + b.iter_proptest_batched(s, single_parse_sr, BATCH_SIZE) }); - group.bench_function("packets::single_parse_srh::size=4", |b| { + group.bench_function("packets::single_parse_sr::size=4", |b| { let segments = rvg.generate_vec(&any::(), 4); let s = sr_tcp_with(fieldmap! {field::sr_segments => segments}) .prop_map(|v| v.into_sr().deparse()); - b.iter_proptest_batched(s, single_parse_srh, BATCH_SIZE) + b.iter_proptest_batched(s, single_parse_sr, BATCH_SIZE) }); - group.bench_function("packets::single_parse_srh::size=8", |b| { + group.bench_function("packets::single_parse_sr::size=8", |b| { let segments = rvg.generate_vec(&any::(), 8); let s = sr_tcp_with(fieldmap! {field::sr_segments => segments}) .prop_map(|v| v.into_sr().deparse()); - b.iter_proptest_batched(s, single_parse_srh, BATCH_SIZE) + b.iter_proptest_batched(s, single_parse_sr, BATCH_SIZE) }); group.finish() } -fn multi_parse_srh(mbuf: Mbuf) -> SegmentRouting { +fn multi_parse_sr(mbuf: Mbuf) -> SegmentRouting { let ethernet = mbuf.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - ipv6.parse::>().unwrap() + let ip6 = ethernet.parse::().unwrap(); + ip6.parse::>().unwrap() } #[capsule::bench(mempool_capacity = 511)] -fn multi_parse_upto_variable_srh(c: &mut Criterion) { - c.bench_function("packets::multi_parse_srh", |b| { +fn multi_parse_upto_variable_sr(c: &mut Criterion) { + c.bench_function("packets::multi_parse_sr", |b| { let s = sr_tcp(); - b.iter_proptest_batched(s, multi_parse_srh, BATCH_SIZE) + b.iter_proptest_batched(s, multi_parse_sr, BATCH_SIZE) }); } @@ -156,7 +158,7 @@ fn deparse_udp(udp: Udp4) -> Mbuf { #[capsule::bench(mempool_capacity = 511)] fn deparse(c: &mut Criterion) { c.bench_function("packets::deparse_udp", |b| { - let s = v4_udp().prop_map(|v| v.into_v4_udp()); + let s = udp4().prop_map(|v| v.into_udp4()); b.iter_proptest_batched(s, deparse_udp, BATCH_SIZE) }); } @@ -168,15 +170,15 @@ fn reset_udp(udp: Udp4) -> Mbuf { #[capsule::bench(mempool_capacity = 511)] fn reset(c: &mut Criterion) { c.bench_function("packets::reset_udp", |b| { - let s = v4_udp().prop_map(|v| v.into_v4_udp()); + let s = udp4().prop_map(|v| v.into_udp4()); b.iter_proptest_batched(s, reset_udp, BATCH_SIZE) }); } fn multi_push_udp(mbuf: Mbuf) -> Udp4 { let ethernet = mbuf.push::().unwrap(); - let ipv4 = ethernet.push::().unwrap(); - ipv4.push::().unwrap() + let ip4 = ethernet.push::().unwrap(); + ip4.push::().unwrap() } #[capsule::bench(mempool_capacity = 511)] @@ -187,15 +189,15 @@ fn multi_push(c: &mut Criterion) { }); } -fn single_push_udp(ipv4: Ipv4) -> Udp4 { - ipv4.push::().unwrap() +fn single_push_udp(ip4: Ipv4) -> Udp4 { + ip4.push::().unwrap() } #[capsule::bench(mempool_capacity = 511)] fn single_push(c: &mut Criterion) { c.bench_function("packets::single_push_udp", |b| { - let s = v4_udp().prop_map(|v| { - let udp = v.into_v4_udp(); + let s = udp4().prop_map(|v| { + let udp = v.into_udp4(); udp.remove().unwrap() }); b.iter_proptest_batched(s, single_push_udp, BATCH_SIZE) @@ -209,57 +211,57 @@ fn single_remove_udp(udp: Udp4) -> Ipv4 { #[capsule::bench(mempool_capacity = 511)] fn single_remove(c: &mut Criterion) { c.bench_function("packets::single_remove_from_udp", |b| { - let s = v4_udp().prop_map(|v| v.into_v4_udp()); + let s = udp4().prop_map(|v| v.into_udp4()); b.iter_proptest_batched(s, single_remove_udp, BATCH_SIZE) }); } fn multi_remove_udp(udp: Udp4) -> Mbuf { - let ipv4 = udp.remove().unwrap(); - let ethernet = ipv4.remove().unwrap(); + let ip4 = udp.remove().unwrap(); + let ethernet = ip4.remove().unwrap(); ethernet.remove().unwrap() } #[capsule::bench(mempool_capacity = 511)] fn multi_remove(c: &mut Criterion) { c.bench_function("packets::multi_remove_from_udp", |b| { - let s = v4_udp().prop_map(|v| v.into_v4_udp()); + let s = udp4().prop_map(|v| v.into_udp4()); b.iter_proptest_batched(s, multi_remove_udp, BATCH_SIZE) }); } -fn set_srh_segments(mut args: (SegmentRouting, Vec)) -> Result<()> { +fn set_sr_segments(mut args: (SegmentRouting, Vec)) -> Result<()> { args.0.set_segments(&args.1) } #[capsule::bench(mempool_capacity = 511)] -fn set_srh_segments_sizes(c: &mut Criterion) { - let mut group = c.benchmark_group("packets::setting_segments_on_SRH_across_segment_sizes"); +fn set_sr_segments_sizes(c: &mut Criterion) { + let mut group = c.benchmark_group("packets::setting_segments_on_sr_across_segment_sizes"); let mut rvg = Rvg::new(); - group.bench_function("packets::set_srh_segments::size=1", |b| { + group.bench_function("packets::set_sr_segments::size=1", |b| { let segments = rvg.generate_vec(&any::(), 1); let s = (sr_tcp().prop_map(|v| v.into_sr()), Just(segments)); - b.iter_proptest_batched(s, set_srh_segments, BATCH_SIZE) + b.iter_proptest_batched(s, set_sr_segments, BATCH_SIZE) }); - group.bench_function("packets::set_srh_segments::size=2", |b| { + group.bench_function("packets::set_sr_segments::size=2", |b| { let segments = rvg.generate_vec(&any::(), 2); let s = (sr_tcp().prop_map(|v| v.into_sr()), Just(segments)); - b.iter_proptest_batched(s, set_srh_segments, BATCH_SIZE) + b.iter_proptest_batched(s, set_sr_segments, BATCH_SIZE) }); - group.bench_function("packets::set_srh_segments::size=4", |b| { + group.bench_function("packets::set_sr_segments::size=4", |b| { let segments = rvg.generate_vec(&any::(), 4); let s = (sr_tcp().prop_map(|v| v.into_sr()), Just(segments)); - b.iter_proptest_batched(s, set_srh_segments, BATCH_SIZE) + b.iter_proptest_batched(s, set_sr_segments, BATCH_SIZE) }); - group.bench_function("packets::set_srh_segments::size=8", |b| { + group.bench_function("packets::set_sr_segments::size=8", |b| { let segments = rvg.generate_vec(&any::(), 8); let s = (sr_tcp().prop_map(|v| v.into_sr()), Just(segments)); - b.iter_proptest_batched(s, set_srh_segments, BATCH_SIZE) + b.iter_proptest_batched(s, set_sr_segments, BATCH_SIZE) }); group.finish() @@ -274,9 +276,9 @@ criterion_group! { config=bench_config(); targets=single_peek_vs_parse, multi_peek_vs_parse, - single_parse_srh_segments_sizes, - multi_parse_upto_variable_srh, - set_srh_segments_sizes, + single_parse_sr_segments_sizes, + multi_parse_upto_variable_sr, + set_sr_segments_sizes, deparse, single_push, multi_push, diff --git a/core/Cargo.toml b/core/Cargo.toml index 17b923ad..6daf60a3 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" @@ -21,23 +21,20 @@ doctest = false [dependencies] anyhow = "1.0" -capsule-ffi = { version = "0.1.5", path = "../ffi" } -capsule-macros = { version = "0.1.5", path = "../macros" } +async-channel = "1.6" +async-executor = "1.4" +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-preview = "=0.3.0-alpha.19" +futures-lite = "1.11" libc = "0.2" -metrics-core = { version = "0.5", optional = true } -metrics-runtime = { version = "0.13", optional = true, default-features = false } +metrics = "0.14" once_cell = "1.7" proptest = { version = "1.0", optional = true } regex = "1" 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" @@ -46,10 +43,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/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 new file mode 100644 index 00000000..8c586a13 --- /dev/null +++ b/core/src/ffi/dpdk.rs @@ -0,0 +1,677 @@ +/* +* 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::{AsStr, EasyPtr, ToCString, ToResult}; +use crate::net::MacAddr; +use crate::{debug, error}; +use anyhow::Result; +use capsule_ffi as cffi; +use std::fmt; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::os::raw; +use std::panic::{self, AssertUnwindSafe}; +use std::ptr; +use thiserror::Error; + +/// Initializes the Environment Abstraction Layer (EAL). +pub(crate) fn eal_init>(args: Vec) -> Result<()> { + let args = args + .into_iter() + .map(|s| Into::::into(s).into_cstring()) + .collect::>(); + debug!(arguments=?args); + + let mut ptrs = args + .iter() + .map(|s| s.as_ptr() as *mut raw::c_char) + .collect::>(); + let len = ptrs.len() as raw::c_int; + + let parsed = + unsafe { cffi::rte_eal_init(len, ptrs.as_mut_ptr()).into_result(DpdkError::from_errno)? }; + debug!("EAL parsed {} arguments.", parsed); + + Ok(()) +} + +/// Cleans up the Environment Abstraction Layer (EAL). +pub(crate) fn eal_cleanup() -> Result<()> { + unsafe { cffi::rte_eal_cleanup() } + .into_result(DpdkError::from_errno) + .map(|_| ()) +} + +/// 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(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); +} + +impl fmt::Debug for SocketId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "socket{}", self.0) + } +} + +impl From for SocketId { + fn from(id: raw::c_int) -> Self { + SocketId(id) + } +} + +/// A `rte_mempool` pointer. +pub(crate) type MempoolPtr = EasyPtr; + +impl Clone for MempoolPtr { + fn clone(&self) -> Self { + self.0.clone().into() + } +} + +// Allows the pointer to go across thread/lcore boundaries. +unsafe impl Send for MempoolPtr {} + +/// Creates a mbuf pool. +pub(crate) fn pktmbuf_pool_create>( + name: S, + capacity: usize, + cache_size: usize, + socket_id: SocketId, +) -> Result { + let name: String = name.into(); + + let ptr = unsafe { + cffi::rte_pktmbuf_pool_create( + name.into_cstring().as_ptr(), + capacity as raw::c_uint, + cache_size as raw::c_uint, + 0, + cffi::RTE_MBUF_DEFAULT_BUF_SIZE as u16, + socket_id.0, + ) + .into_result(|_| DpdkError::new())? + }; + + Ok(EasyPtr(ptr)) +} + +/// Looks up a mempool by the name. +#[cfg(test)] +pub(crate) fn mempool_lookup>(name: S) -> Result { + let name: String = name.into(); + + let ptr = unsafe { + cffi::rte_mempool_lookup(name.into_cstring().as_ptr()).into_result(|_| DpdkError::new())? + }; + + Ok(EasyPtr(ptr)) +} + +/// 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 } +} + +/// Frees a mempool. +pub(crate) fn mempool_free(mp: &mut MempoolPtr) { + unsafe { cffi::rte_mempool_free(mp.deref_mut()) }; +} + +/// An opaque identifier for a logical execution unit of the processor. +#[derive(Copy, Clone, Eq, Hash, PartialEq)] +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 + /// called from a non-EAL thread. + #[inline] + pub(crate) fn current() -> LcoreId { + unsafe { LcoreId(cffi::_rte_lcore_id()) } + } + + /// Returns the ID of the main lcore. + #[inline] + pub(crate) fn main() -> LcoreId { + unsafe { LcoreId(cffi::rte_get_master_lcore()) } + } + + /// Returns the ID of the physical CPU socket of the lcore. + #[allow(clippy::trivially_copy_pass_by_ref)] + #[inline] + pub(crate) fn socket(&self) -> SocketId { + unsafe { (cffi::rte_lcore_to_socket_id(self.0) as raw::c_int).into() } + } + + /// Returns the raw value. + pub(crate) fn raw(&self) -> usize { + self.0 as usize + } +} + +impl fmt::Debug for LcoreId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "lcore{}", self.0) + } +} + +/// Gets the next enabled lcore ID. +pub(crate) fn get_next_lcore( + id: Option, + skip_master: bool, + wrap: bool, +) -> Option { + let (i, wrap) = match id { + Some(id) => (id.0, wrap as raw::c_int), + None => (raw::c_uint::MAX, 1), + }; + + let skip_master = skip_master as raw::c_int; + + match unsafe { cffi::rte_get_next_lcore(i, skip_master, wrap) } { + cffi::RTE_MAX_LCORE => None, + 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, +{ + let f = Box::from_raw(arg as *mut F); + + // in case the closure panics, let's not crash the app. + let result = panic::catch_unwind(AssertUnwindSafe(f)); + + if let Err(err) = result { + error!(lcore = ?LcoreId::current(), error = ?err, "failed to execute closure."); + } + + 0 +} + +/// Launches a function on another lcore. +pub(crate) fn eal_remote_launch(worker_id: LcoreId, f: F) -> Result<()> +where + F: FnOnce() + Send + 'static, +{ + let ptr = Box::into_raw(Box::new(f)) as *mut raw::c_void; + + unsafe { + cffi::rte_eal_remote_launch(Some(lcore_fn::), ptr, worker_id.0) + .into_result(DpdkError::from_errno) + .map(|_| ()) + } +} + +/// An opaque identifier for a PMD device port. +#[derive(Copy, Clone)] +pub(crate) struct PortId(u16); + +impl PortId { + /// Returns the ID of the socket the port is connected to. + #[inline] + pub(crate) fn socket(self) -> SocketId { + unsafe { cffi::rte_eth_dev_socket_id(self.0).into() } + } +} + +impl fmt::Debug for PortId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "port{}", self.0) + } +} + +/// Gets the port id from device name. +pub(crate) fn eth_dev_get_port_by_name>(name: S) -> Result { + let name: String = name.into(); + let mut port_id = 0u16; + unsafe { + cffi::rte_eth_dev_get_port_by_name(name.into_cstring().as_ptr(), &mut port_id) + .into_result(DpdkError::from_errno)?; + } + Ok(PortId(port_id)) +} + +/// Retrieves the Ethernet address of a device. +pub(crate) fn eth_macaddr_get(port_id: PortId) -> Result { + let mut addr = cffi::rte_ether_addr::default(); + unsafe { + cffi::rte_eth_macaddr_get(port_id.0, &mut addr).into_result(DpdkError::from_errno)?; + } + Ok(addr.addr_bytes.into()) +} + +/// Retrieves the contextual information of a device. +pub(crate) fn eth_dev_info_get(port_id: PortId) -> Result { + let mut port_info = cffi::rte_eth_dev_info::default(); + unsafe { + cffi::rte_eth_dev_info_get(port_id.0, &mut port_info).into_result(DpdkError::from_errno)?; + } + Ok(port_info) +} + +/// Checks that numbers of Rx and Tx descriptors satisfy descriptors limits +/// from the ethernet device information, otherwise adjust them to boundaries. +pub(crate) fn eth_dev_adjust_nb_rx_tx_desc( + port_id: PortId, + nb_rx_desc: usize, + nb_tx_desc: usize, +) -> Result<(usize, usize)> { + let mut nb_rx_desc = nb_rx_desc as u16; + let mut nb_tx_desc = nb_tx_desc as u16; + + unsafe { + cffi::rte_eth_dev_adjust_nb_rx_tx_desc(port_id.0, &mut nb_rx_desc, &mut nb_tx_desc) + .into_result(DpdkError::from_errno)?; + } + + Ok((nb_rx_desc as usize, nb_tx_desc as usize)) +} + +/// Returns the value of promiscuous mode for a device. +pub(crate) fn eth_promiscuous_get(port_id: PortId) -> bool { + 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. +pub(crate) fn eth_promiscuous_enable(port_id: PortId) -> Result<()> { + unsafe { + cffi::rte_eth_promiscuous_enable(port_id.0) + .into_result(DpdkError::from_errno) + .map(|_| ()) + } +} + +/// Disables receipt in promiscuous mode for a device. +pub(crate) fn eth_promiscuous_disable(port_id: PortId) -> Result<()> { + unsafe { + cffi::rte_eth_promiscuous_disable(port_id.0) + .into_result(DpdkError::from_errno) + .map(|_| ()) + } +} + +/// Returns the value of allmulticast mode for a device. +pub(crate) fn eth_allmulticast_get(port_id: PortId) -> bool { + 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. +pub(crate) fn eth_allmulticast_enable(port_id: PortId) -> Result<()> { + unsafe { + cffi::rte_eth_allmulticast_enable(port_id.0) + .into_result(DpdkError::from_errno) + .map(|_| ()) + } +} + +/// Disables the receipt of any multicast frame by a device. +pub(crate) fn eth_allmulticast_disable(port_id: PortId) -> Result<()> { + unsafe { + cffi::rte_eth_allmulticast_disable(port_id.0) + .into_result(DpdkError::from_errno) + .map(|_| ()) + } +} + +/// Configures a device. +pub(crate) fn eth_dev_configure( + port_id: PortId, + nb_rx_queue: usize, + nb_tx_queue: usize, + eth_conf: &cffi::rte_eth_conf, +) -> Result<()> { + unsafe { + cffi::rte_eth_dev_configure(port_id.0, nb_rx_queue as u16, nb_tx_queue as u16, eth_conf) + .into_result(DpdkError::from_errno) + .map(|_| ()) + } +} + +/// An opaque identifier for a port's receive queue. +#[derive(Copy, Clone)] +pub(crate) struct PortRxQueueId(u16); + +impl fmt::Debug for PortRxQueueId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "rxq{}", self.0) + } +} + +impl From for PortRxQueueId { + fn from(id: usize) -> Self { + PortRxQueueId(id as u16) + } +} + +/// Allocates and sets up a receive queue for a device. +pub(crate) fn eth_rx_queue_setup( + port_id: PortId, + rx_queue_id: PortRxQueueId, + nb_rx_desc: usize, + socket_id: SocketId, + rx_conf: Option<&cffi::rte_eth_rxconf>, + mb_pool: &mut MempoolPtr, +) -> Result<()> { + unsafe { + cffi::rte_eth_rx_queue_setup( + port_id.0, + rx_queue_id.0, + nb_rx_desc as u16, + socket_id.0 as raw::c_uint, + rx_conf.map_or(ptr::null(), |conf| conf), + mb_pool.deref_mut(), + ) + .into_result(DpdkError::from_errno) + .map(|_| ()) + } +} + +/// Removes an RX or TX packet callback from a given port and queue. +#[allow(dead_code)] +pub(crate) enum RxTxCallbackGuard { + Rx(PortId, PortRxQueueId, *const cffi::rte_eth_rxtx_callback), + Tx(PortId, PortTxQueueId, *const cffi::rte_eth_rxtx_callback), +} + +impl Drop for RxTxCallbackGuard { + fn drop(&mut self) { + if let Err(error) = match self { + RxTxCallbackGuard::Rx(port_id, queue_id, ptr) => { + debug!(port = ?port_id, rxq = ?queue_id, "remove rx callback."); + unsafe { + cffi::rte_eth_remove_rx_callback(port_id.0, queue_id.0, *ptr) + .into_result(DpdkError::from_errno) + } + } + RxTxCallbackGuard::Tx(port_id, queue_id, ptr) => { + debug!(port = ?port_id, txq = ?queue_id, "remove tx callback."); + unsafe { + cffi::rte_eth_remove_tx_callback(port_id.0, queue_id.0, *ptr) + .into_result(DpdkError::from_errno) + } + } + } { + error!(?error); + } + } +} + +/// Adds a callback to be called on packet RX on a given port and queue. +#[allow(dead_code)] +pub(crate) fn eth_add_rx_callback( + port_id: PortId, + queue_id: PortRxQueueId, + callback: cffi::rte_rx_callback_fn, + user_param: &mut T, +) -> Result { + let ptr = unsafe { + cffi::rte_eth_add_rx_callback( + port_id.0, + queue_id.0, + callback, + user_param as *mut T as *mut raw::c_void, + ) + .into_result(|_| DpdkError::new())? + }; + + Ok(RxTxCallbackGuard::Rx(port_id, queue_id, ptr)) +} + +/// Retrieves a burst of input packets from a receive queue of a device. +pub(crate) fn eth_rx_burst(port_id: PortId, queue_id: PortRxQueueId, rx_pkts: &mut Vec) { + let nb_pkts = rx_pkts.capacity(); + + unsafe { + let len = cffi::_rte_eth_rx_burst( + port_id.0, + queue_id.0, + rx_pkts.as_mut_ptr() as *mut *mut cffi::rte_mbuf, + nb_pkts as u16, + ); + + rx_pkts.set_len(len as usize); + } +} + +/// An opaque identifier for a port's transmit queue. +#[derive(Copy, Clone)] +pub(crate) struct PortTxQueueId(u16); + +impl fmt::Debug for PortTxQueueId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "txq{}", self.0) + } +} + +impl From for PortTxQueueId { + fn from(id: usize) -> Self { + PortTxQueueId(id as u16) + } +} + +/// Allocates and sets up a transmit queue for a device. +pub(crate) fn eth_tx_queue_setup( + port_id: PortId, + tx_queue_id: PortTxQueueId, + nb_tx_desc: usize, + socket_id: SocketId, + tx_conf: Option<&cffi::rte_eth_txconf>, +) -> Result<()> { + unsafe { + cffi::rte_eth_tx_queue_setup( + port_id.0, + tx_queue_id.0, + nb_tx_desc as u16, + socket_id.0 as raw::c_uint, + tx_conf.map_or(ptr::null(), |conf| conf), + ) + .into_result(DpdkError::from_errno) + .map(|_| ()) + } +} + +/// Adds a callback to be called on packet TX on a given port and queue. +#[allow(dead_code)] +pub(crate) fn eth_add_tx_callback( + port_id: PortId, + queue_id: PortTxQueueId, + callback: cffi::rte_tx_callback_fn, + user_param: &mut T, +) -> Result { + let ptr = unsafe { + cffi::rte_eth_add_tx_callback( + port_id.0, + queue_id.0, + callback, + user_param as *mut T as *mut raw::c_void, + ) + .into_result(|_| DpdkError::new())? + }; + + Ok(RxTxCallbackGuard::Tx(port_id, queue_id, ptr)) +} + +/// Sends a burst of output packets on a transmit queue of a device. +pub(crate) fn eth_tx_burst( + port_id: PortId, + queue_id: PortTxQueueId, + tx_pkts: &mut Vec, +) -> usize { + let nb_pkts = tx_pkts.len(); + + let sent = unsafe { + cffi::_rte_eth_tx_burst( + port_id.0, + queue_id.0, + tx_pkts.as_mut_ptr() as *mut *mut cffi::rte_mbuf, + nb_pkts as u16, + ) + } as usize; + + if nb_pkts > sent { + // wasn't able to send everything. + mem::forget(tx_pkts.drain(..sent)); + } else { + unsafe { + tx_pkts.set_len(0); + } + } + + sent +} + +/// Starts a device. +pub(crate) fn eth_dev_start(port_id: PortId) -> Result<()> { + unsafe { + cffi::rte_eth_dev_start(port_id.0) + .into_result(DpdkError::from_errno) + .map(|_| ()) + } +} + +/// Stops a device. +pub(crate) fn eth_dev_stop(port_id: PortId) { + unsafe { + cffi::rte_eth_dev_stop(port_id.0); + } +} + +/// A `rte_mbuf` pointer. +pub(crate) type MbufPtr = EasyPtr; + +// Allows the pointer to go across thread/lcore boundaries. +unsafe impl Send for MbufPtr {} + +/// Allocates a new mbuf from a mempool. +pub(crate) fn pktmbuf_alloc(mp: &mut MempoolPtr) -> Result { + let ptr = + unsafe { cffi::_rte_pktmbuf_alloc(mp.deref_mut()).into_result(|_| DpdkError::new())? }; + + Ok(EasyPtr(ptr)) +} + +/// Allocates a bulk of mbufs. +pub(crate) fn pktmbuf_alloc_bulk(mp: &mut MempoolPtr, mbufs: &mut Vec) -> Result<()> { + let len = mbufs.capacity(); + + unsafe { + cffi::_rte_pktmbuf_alloc_bulk( + mp.deref_mut(), + mbufs.as_mut_ptr() as *mut *mut cffi::rte_mbuf, + len as raw::c_uint, + ) + .into_result(DpdkError::from_errno)?; + + mbufs.set_len(len); + } + + Ok(()) +} + +/// Frees a packet mbuf back into its original mempool. +pub(crate) fn pktmbuf_free(mut m: MbufPtr) { + unsafe { + cffi::_rte_pktmbuf_free(m.deref_mut()); + } +} + +/// Frees a bulk of packet mbufs back into their original mempools. +pub(crate) fn pktmbuf_free_bulk(mbufs: &mut Vec) { + assert!(!mbufs.is_empty()); + + let mut to_free = Vec::with_capacity(mbufs.len()); + let mut pool = mbufs[0].pool; + + for mbuf in mbufs.drain(..) { + if pool == mbuf.pool { + to_free.push(mbuf); + } else { + unsafe { + let len = to_free.len(); + cffi::_rte_mempool_put_bulk( + pool, + to_free.as_ptr() as *const *mut raw::c_void, + len as u32, + ); + to_free.set_len(0); + } + + pool = mbuf.pool; + to_free.push(mbuf); + } + } + + unsafe { + let len = to_free.len(); + cffi::_rte_mempool_put_bulk( + pool, + to_free.as_ptr() as *const *mut raw::c_void, + len as u32, + ); + to_free.set_len(0); + } +} + +/// 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 { cffi::_rte_errno() } + } else { + -errno + }; + DpdkError(unsafe { cffi::rte_strerror(errno).as_str().into() }) + } +} diff --git a/core/src/ffi.rs b/core/src/ffi/mod.rs similarity index 80% rename from core/src/ffi.rs rename to core/src/ffi/mod.rs index 5bb7baaa..d1bcc259 100644 --- a/core/src/ffi.rs +++ b/core/src/ffi/mod.rs @@ -16,12 +16,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -pub(crate) use capsule_ffi::*; +pub(crate) mod dpdk; +#[cfg(feature = "pcap-dump")] +pub(crate) mod pcap; use crate::warn; use anyhow::Result; use std::error::Error; use std::ffi::{CStr, CString}; +use std::ops::{Deref, DerefMut}; use std::os::raw; use std::ptr::NonNull; @@ -139,3 +142,35 @@ impl ToResult for raw::c_int { } } } + +/// Makes NonNull even easier to use with the downside that it hides the +/// unsafeness of the underlying pointer access. +pub(crate) struct EasyPtr(NonNull); + +impl Deref for EasyPtr { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { self.0.as_ref() } + } +} + +impl DerefMut for EasyPtr { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.0.as_mut() } + } +} + +// delete later? +impl From> for EasyPtr { + fn from(ptr: NonNull) -> Self { + EasyPtr(ptr) + } +} + +// delete later? +impl From> for NonNull { + fn from(ptr: EasyPtr) -> Self { + ptr.0 + } +} diff --git a/core/src/ffi/pcap.rs b/core/src/ffi/pcap.rs new file mode 100644 index 00000000..f009db49 --- /dev/null +++ b/core/src/ffi/pcap.rs @@ -0,0 +1,155 @@ +/* +* 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::{AsStr, EasyPtr, ToCString, ToResult}; +use crate::ffi::dpdk::MbufPtr; +use anyhow::Result; +use capsule_ffi as cffi; +use std::ops::DerefMut; +use std::os::raw; +use std::ptr; +use thiserror::Error; + +// Ethernet (10Mb, 100Mb, 1000Mb, and up); the 10MB in the DLT_ name is historical. +const DLT_EN10MB: raw::c_int = 1; + +// https://github.com/the-tcpdump-group/libpcap/blob/master/pcap/pcap.h#L152 +#[allow(dead_code)] +const PCAP_ERRBUF_SIZE: usize = 256; + +/// A `pcap_t` pointer. +pub(crate) type PcapPtr = EasyPtr; + +/// Creates a `libpcap` handle needed to call other functions. +pub(crate) fn open_dead() -> Result { + let ptr = unsafe { + cffi::pcap_open_dead(DLT_EN10MB, cffi::RTE_MBUF_DEFAULT_BUF_SIZE as raw::c_int) + .into_result(|_| PcapError::new("Cannot create libpcap handle."))? + }; + + Ok(EasyPtr(ptr)) +} + +/// A `pcap_dumper_t` pointer. +pub(crate) type DumperPtr = EasyPtr; + +/// Opens a file to which to write packets. +pub(crate) fn dump_open>(handle: &mut PcapPtr, filename: S) -> Result { + let filename: String = filename.into(); + let ptr = unsafe { + cffi::pcap_dump_open(handle.deref_mut(), filename.into_cstring().as_ptr()) + .into_result(|_| PcapError::get_error(handle))? + }; + + Ok(EasyPtr(ptr)) +} + +/// Writes a packet to a capture file. +pub(crate) fn dump(dumper: &mut DumperPtr, mbuf: &MbufPtr) { + let mut pkthdr = cffi::pcap_pkthdr::default(); + pkthdr.len = mbuf.data_len as u32; + pkthdr.caplen = pkthdr.len; + + unsafe { + // If this errors, we'll still want to write packet(s) to the pcap, + let _ = libc::gettimeofday( + &mut pkthdr.ts as *mut cffi::timeval as *mut libc::timeval, + ptr::null_mut(), + ); + + cffi::pcap_dump( + dumper.deref_mut() as *mut cffi::pcap_dumper_t as *mut raw::c_uchar, + &pkthdr, + (mbuf.buf_addr as *mut u8).offset(mbuf.data_off as isize), + ); + } +} + +/// Flushes to a savefile packets dumped. +pub(crate) fn dump_flush(dumper: &mut DumperPtr) -> Result<()> { + unsafe { + cffi::pcap_dump_flush(dumper.deref_mut()) + .into_result(|_| PcapError::new("Cannot flush packets to capture file.")) + .map(|_| ()) + } +} + +/// Closes a savefile being written to. +pub(crate) fn dump_close(dumper: &mut DumperPtr) { + unsafe { + cffi::pcap_dump_close(dumper.deref_mut()); + } +} + +/// Closes a capture device or savefile +pub(crate) fn close(handle: &mut PcapPtr) { + unsafe { + cffi::pcap_close(handle.deref_mut()); + } +} + +/// Opens a saved capture file for reading. +#[cfg(test)] +pub(crate) fn open_offline>(filename: S) -> Result { + let filename: String = filename.into(); + let mut errbuf: [raw::c_char; PCAP_ERRBUF_SIZE] = [0; PCAP_ERRBUF_SIZE]; + + let ptr = unsafe { + cffi::pcap_open_offline(filename.into_cstring().as_ptr(), errbuf.as_mut_ptr()) + .into_result(|_| PcapError::new(errbuf.as_str()))? + }; + + Ok(EasyPtr(ptr)) +} + +/// Reads the next packet from a `pcap_t` handle. +#[cfg(test)] +pub(crate) fn next(handle: &mut PcapPtr) -> Result<&[u8]> { + let mut pkthdr: *mut cffi::pcap_pkthdr = ptr::null_mut(); + let mut pktdata: *const raw::c_uchar = ptr::null(); + + unsafe { + match cffi::pcap_next_ex(handle.deref_mut(), &mut pkthdr, &mut pktdata) { + 1 => Ok(std::slice::from_raw_parts( + pktdata, + (*pkthdr).caplen as usize, + )), + _ => Err(PcapError::get_error(handle).into()), + } + } +} + +/// 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 762b545d..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,39 +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; -mod ffi; +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; -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/net/mac.rs b/core/src/net/mac.rs index fa698e60..14297049 100644 --- a/core/src/net/mac.rs +++ b/core/src/net/mac.rs @@ -32,7 +32,7 @@ impl MacAddr { /// Creates a MAC address from 6 octets. #[allow(clippy::many_single_char_names)] - pub fn new(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8) -> Self { + pub const fn new(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8) -> Self { MacAddr([a, b, c, d, e, f]) } diff --git a/core/src/packets/arp.rs b/core/src/packets/arp.rs index d6744044..030032d6 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::{EtherType, EtherTypes, Ethernet}; use crate::packets::types::u16be; -use crate::packets::{EtherTypes, Ethernet, Internal, Packet}; -use crate::{ensure, SizeOf}; +use crate::packets::{Datalink, Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::net::Ipv4Addr; @@ -83,13 +84,13 @@ use std::ptr::NonNull; /// defined by *P Length*. /// /// [IETF RFC 826]: https://tools.ietf.org/html/rfc826 -pub struct Arp { - envelope: Ethernet, +pub struct Arp { + envelope: E, header: NonNull>, offset: usize, } -impl Arp { +impl Arp { #[inline] fn header(&self) -> &ArpHeader { unsafe { self.header.as_ref() } @@ -113,14 +114,20 @@ impl Arp { } /// Returns the protocol type. + /// + /// [IANA] assigned Protocol type numbers share the same space as + /// [`EtherTypes`]. + /// + /// [IANA]: https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml#arp-parameters-3 + /// [`EtherTypes`]: crate::packets::ethernet::EtherTypes #[inline] - pub fn protocol_type(&self) -> ProtocolType { - ProtocolType::new(self.header().protocol_type.into()) + pub fn protocol_type(&self) -> EtherType { + EtherType::new(self.header().protocol_type.into()) } /// Sets the protocol type. #[inline] - fn set_protocol_type(&mut self, protocol_type: ProtocolType) { + fn set_protocol_type(&mut self, protocol_type: EtherType) { self.header_mut().protocol_type = protocol_type.0.into() } @@ -209,7 +216,7 @@ impl Arp { } } -impl fmt::Debug for Arp { +impl fmt::Debug for Arp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("arp") .field("hardware_type", &format!("{}", self.hardware_type())) @@ -240,9 +247,8 @@ impl fmt::Debug for Arp { } } -impl Packet for Arp { - /// The preceding type for ARP must be `Ethernet`. - type Envelope = Ethernet; +impl Packet for Arp { + type Envelope = E; #[inline] fn envelope(&self) -> &Self::Envelope { @@ -273,7 +279,7 @@ impl Packet for Arp { } } - /// Parses the Ethernet payload as an ARP packet. + /// Parses the payload as an ARP packet. /// /// # Errors /// @@ -291,7 +297,7 @@ impl Packet for Arp { #[inline] fn try_parse(envelope: Self::Envelope, _internal: Internal) -> Result { ensure!( - envelope.ether_type() == EtherTypes::Arp, + envelope.protocol_type() == EtherTypes::Arp, anyhow!("not an ARP packet.") ); @@ -361,7 +367,7 @@ impl Packet for Arp { mbuf.extend(offset, ArpHeader::::size_of())?; let header = mbuf.write_data(offset, &ArpHeader::::default())?; - envelope.set_ether_type(EtherTypes::Arp); + envelope.set_protocol_type(EtherTypes::Arp); let mut packet = Arp { envelope, @@ -426,49 +432,6 @@ impl fmt::Display for HardwareType { } } -/// [IANA] assigned protocol type. -/// -/// See [`ProtocolTypes`] for which are current supported. -/// -/// [IANA]: https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml#arp-parameters-3 -/// [`ProtocolTypes`]: crate::packets::arp::ProtocolTypes -#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] -#[repr(C, packed)] -pub struct ProtocolType(u16); - -impl ProtocolType { - /// Creates a new protocol type. - pub fn new(value: u16) -> Self { - ProtocolType(value) - } -} - -/// Supported protocol types. -#[allow(non_snake_case)] -#[allow(non_upper_case_globals)] -pub mod ProtocolTypes { - use super::ProtocolType; - - /// Internet protocol version 4. - pub const Ipv4: ProtocolType = ProtocolType(0x0800); -} - -impl fmt::Display for ProtocolType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match *self { - ProtocolTypes::Ipv4 => "IPv4".to_string(), - _ => { - let t = self.0; - format!("0x{:04x}", t) - } - } - ) - } -} - /// [IANA] assigned operation code. /// /// See [`OperationCodes`] for which are current supported. @@ -533,12 +496,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 @@ -552,7 +509,7 @@ impl HardwareAddr for MacAddr { /// A trait implemented by ARP protocol address types. pub trait ProtocolAddr: SizeOf + Copy + fmt::Display { /// Returns the associated protocol type of the given address. - fn addr_type() -> ProtocolType; + fn addr_type() -> EtherType; /// Returns the default value. /// @@ -561,15 +518,9 @@ 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 + fn addr_type() -> EtherType { + EtherTypes::Ipv4 } fn default() -> Self { @@ -577,9 +528,6 @@ impl ProtocolAddr for Ipv4Addr { } } -/// A type alias for an IPv4 ARP packet. -pub type Arp4 = Arp; - /// ARP header. #[allow(missing_debug_implementations)] #[derive(Copy, SizeOf)] @@ -631,8 +579,8 @@ impl Default for ArpHeader { #[cfg(test)] mod tests { use super::*; - use crate::testils::byte_arrays::ARP4_PACKET; - use crate::Mbuf; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::ARP_PACKET; #[test] fn size_of_arp_header() { @@ -641,48 +589,48 @@ mod tests { #[capsule::test] fn parse_arp_packet() { - let packet = Mbuf::from_bytes(&ARP4_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&ARP_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let arp4 = ethernet.parse::().unwrap(); + let arp = ethernet.parse::().unwrap(); - assert_eq!(HardwareTypes::Ethernet, arp4.hardware_type()); - assert_eq!(ProtocolTypes::Ipv4, arp4.protocol_type()); - assert_eq!(6, arp4.hardware_addr_len()); - assert_eq!(4, arp4.protocol_addr_len()); - assert_eq!(OperationCodes::Request, arp4.operation_code()); - assert_eq!("00:00:00:00:00:01", arp4.sender_hardware_addr().to_string()); - assert_eq!("139.133.217.110", arp4.sender_protocol_addr().to_string()); - assert_eq!("00:00:00:00:00:00", arp4.target_hardware_addr().to_string()); - assert_eq!("139.133.233.2", arp4.target_protocol_addr().to_string()); + assert_eq!(HardwareTypes::Ethernet, arp.hardware_type()); + assert_eq!(EtherTypes::Ipv4, arp.protocol_type()); + assert_eq!(6, arp.hardware_addr_len()); + assert_eq!(4, arp.protocol_addr_len()); + assert_eq!(OperationCodes::Request, arp.operation_code()); + assert_eq!("00:00:00:00:00:01", arp.sender_hardware_addr().to_string()); + assert_eq!("139.133.217.110", arp.sender_protocol_addr().to_string()); + assert_eq!("00:00:00:00:00:00", arp.target_hardware_addr().to_string()); + assert_eq!("139.133.233.2", arp.target_protocol_addr().to_string()); } #[capsule::test] fn push_arp_packet() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let mut arp4 = ethernet.push::().unwrap(); + let mut arp = ethernet.push::().unwrap(); - assert_eq!(ArpHeader::::size_of(), arp4.len()); + assert_eq!(ArpHeader::::size_of(), arp.len()); // make sure types are set properly - assert_eq!(HardwareTypes::Ethernet, arp4.hardware_type()); - assert_eq!(ProtocolTypes::Ipv4, arp4.protocol_type()); - assert_eq!(6, arp4.hardware_addr_len()); - assert_eq!(4, arp4.protocol_addr_len()); + assert_eq!(HardwareTypes::Ethernet, arp.hardware_type()); + assert_eq!(EtherTypes::Ipv4, arp.protocol_type()); + assert_eq!(6, arp.hardware_addr_len()); + assert_eq!(4, arp.protocol_addr_len()); // check the setters - arp4.set_operation_code(OperationCodes::Reply); - assert_eq!(OperationCodes::Reply, arp4.operation_code()); - arp4.set_sender_hardware_addr(MacAddr::new(0, 0, 0, 0, 0, 1)); - assert_eq!("00:00:00:00:00:01", arp4.sender_hardware_addr().to_string()); - arp4.set_sender_protocol_addr(Ipv4Addr::new(10, 0, 0, 1)); - assert_eq!("10.0.0.1", arp4.sender_protocol_addr().to_string()); - arp4.set_target_hardware_addr(MacAddr::new(0, 0, 0, 0, 0, 2)); - assert_eq!("00:00:00:00:00:02", arp4.target_hardware_addr().to_string()); - arp4.set_target_protocol_addr(Ipv4Addr::new(10, 0, 0, 2)); - assert_eq!("10.0.0.2", arp4.target_protocol_addr().to_string()); + arp.set_operation_code(OperationCodes::Reply); + assert_eq!(OperationCodes::Reply, arp.operation_code()); + arp.set_sender_hardware_addr(MacAddr::new(0, 0, 0, 0, 0, 1)); + assert_eq!("00:00:00:00:00:01", arp.sender_hardware_addr().to_string()); + arp.set_sender_protocol_addr(Ipv4Addr::new(10, 0, 0, 1)); + assert_eq!("10.0.0.1", arp.sender_protocol_addr().to_string()); + arp.set_target_hardware_addr(MacAddr::new(0, 0, 0, 0, 0, 2)); + assert_eq!("00:00:00:00:00:02", arp.target_hardware_addr().to_string()); + arp.set_target_protocol_addr(Ipv4Addr::new(10, 0, 0, 2)); + assert_eq!("10.0.0.2", arp.target_protocol_addr().to_string()); // make sure the ether type is fixed - assert_eq!(EtherTypes::Arp, arp4.envelope().ether_type()); + assert_eq!(EtherTypes::Arp, arp.envelope().ether_type()); } } diff --git a/core/src/packets/ethernet.rs b/core/src/packets/ethernet.rs index eb3a6e93..67a06b3c 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::{Datalink, 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) @@ -330,6 +331,18 @@ impl Packet for Ethernet { } } +impl Datalink for Ethernet { + #[inline] + fn protocol_type(&self) -> EtherType { + self.ether_type() + } + + #[inline] + fn set_protocol_type(&mut self, ether_type: EtherType) { + self.set_ether_type(ether_type) + } +} + /// The protocol identifier of the Ethernet frame payload. #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] #[repr(C, packed)] @@ -478,7 +491,7 @@ impl SizeOf for EthernetHeader { #[cfg(test)] mod tests { use super::*; - use crate::testils::byte_arrays::{IPV4_UDP_PACKET, VLAN_DOT1Q_PACKET, VLAN_QINQ_PACKET}; + use crate::testils::byte_arrays::{UDP4_PACKET, VLAN_DOT1Q_PACKET, VLAN_QINQ_PACKET}; #[test] fn size_of_ethernet_header() { @@ -495,7 +508,7 @@ mod tests { #[capsule::test] fn parse_ethernet_packet() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); assert_eq!("00:00:00:00:00:01", ethernet.dst().to_string()); @@ -529,7 +542,7 @@ mod tests { #[capsule::test] fn swap_addresses() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let mut ethernet = packet.parse::().unwrap(); ethernet.swap_addresses(); diff --git a/core/src/packets/icmp/v4/echo_reply.rs b/core/src/packets/icmp/v4/echo_reply.rs index 71c3ac6a..c1a4e5fe 100644 --- a/core/src/packets/icmp/v4/echo_reply.rs +++ b/core/src/packets/icmp/v4/echo_reply.rs @@ -17,9 +17,9 @@ */ use crate::packets::icmp::v4::{Icmpv4, Icmpv4Message, Icmpv4Packet, Icmpv4Type, Icmpv4Types}; +use crate::packets::ip::v4::{Ipv4, Ipv4Packet}; 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; @@ -47,12 +47,12 @@ use std::ptr::NonNull; /// /// [IETF RFC 792]: https://tools.ietf.org/html/rfc792 #[derive(Icmpv4Packet)] -pub struct EchoReply { - icmp: Icmpv4, +pub struct EchoReply { + icmp: Icmpv4, body: NonNull, } -impl EchoReply { +impl EchoReply { #[inline] fn body(&self) -> &EchoReplyBody { unsafe { self.body.as_ref() } @@ -128,7 +128,7 @@ impl EchoReply { } } -impl fmt::Debug for EchoReply { +impl fmt::Debug for EchoReply { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("EchoReply") .field("type", &format!("{}", self.msg_type())) @@ -143,24 +143,26 @@ impl fmt::Debug for EchoReply { } } -impl Icmpv4Message for EchoReply { +impl Icmpv4Message for EchoReply { + type Envelope = E; + #[inline] fn msg_type() -> Icmpv4Type { Icmpv4Types::EchoReply } #[inline] - fn icmp(&self) -> &Icmpv4 { + fn icmp(&self) -> &Icmpv4 { &self.icmp } #[inline] - fn icmp_mut(&mut self) -> &mut Icmpv4 { + fn icmp_mut(&mut self) -> &mut Icmpv4 { &mut self.icmp } #[inline] - fn into_icmp(self) -> Icmpv4 { + fn into_icmp(self) -> Icmpv4 { self.icmp } @@ -179,7 +181,7 @@ impl Icmpv4Message for EchoReply { /// Returns an error if the payload does not have sufficient data for /// the echo reply message body. #[inline] - fn try_parse(icmp: Icmpv4, _internal: Internal) -> Result { + fn try_parse(icmp: Icmpv4, _internal: Internal) -> Result { let mbuf = icmp.mbuf(); let offset = icmp.payload_offset(); let body = mbuf.read_data(offset)?; @@ -194,7 +196,7 @@ impl Icmpv4Message for EchoReply { /// /// Returns an error if the buffer does not have enough free space. #[inline] - fn try_push(mut icmp: Icmpv4, _internal: Internal) -> Result { + fn try_push(mut icmp: Icmpv4, _internal: Internal) -> Result { let offset = icmp.payload_offset(); let mbuf = icmp.mbuf_mut(); @@ -219,9 +221,8 @@ struct EchoReplyBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v4::Ipv4; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; #[test] fn size_of_echo_reply_body() { @@ -232,8 +233,8 @@ mod tests { fn push_and_set_echo_reply() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv4 = ethernet.push::().unwrap(); - let mut echo = ipv4.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); + let mut echo = ip4.push::().unwrap(); assert_eq!(4, echo.header_len()); assert_eq!(EchoReplyBody::size_of(), echo.payload_len()); diff --git a/core/src/packets/icmp/v4/echo_request.rs b/core/src/packets/icmp/v4/echo_request.rs index cd08593a..480bbece 100644 --- a/core/src/packets/icmp/v4/echo_request.rs +++ b/core/src/packets/icmp/v4/echo_request.rs @@ -17,9 +17,9 @@ */ use crate::packets::icmp::v4::{Icmpv4, Icmpv4Message, Icmpv4Packet, Icmpv4Type, Icmpv4Types}; +use crate::packets::ip::v4::{Ipv4, Ipv4Packet}; 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; @@ -48,12 +48,12 @@ use std::ptr::NonNull; /// /// [IETF RFC 792]: https://tools.ietf.org/html/rfc792 #[derive(Icmpv4Packet)] -pub struct EchoRequest { - icmp: Icmpv4, +pub struct EchoRequest { + icmp: Icmpv4, body: NonNull, } -impl EchoRequest { +impl EchoRequest { #[inline] fn body(&self) -> &EchoRequestBody { unsafe { self.body.as_ref() } @@ -129,7 +129,7 @@ impl EchoRequest { } } -impl fmt::Debug for EchoRequest { +impl fmt::Debug for EchoRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("EchoRequest") .field("type", &format!("{}", self.msg_type())) @@ -144,24 +144,26 @@ impl fmt::Debug for EchoRequest { } } -impl Icmpv4Message for EchoRequest { +impl Icmpv4Message for EchoRequest { + type Envelope = E; + #[inline] fn msg_type() -> Icmpv4Type { Icmpv4Types::EchoRequest } #[inline] - fn icmp(&self) -> &Icmpv4 { + fn icmp(&self) -> &Icmpv4 { &self.icmp } #[inline] - fn icmp_mut(&mut self) -> &mut Icmpv4 { + fn icmp_mut(&mut self) -> &mut Icmpv4 { &mut self.icmp } #[inline] - fn into_icmp(self) -> Icmpv4 { + fn into_icmp(self) -> Icmpv4 { self.icmp } @@ -180,7 +182,7 @@ impl Icmpv4Message for EchoRequest { /// Returns an error if the payload does not have sufficient data for /// the echo request message body. #[inline] - fn try_parse(icmp: Icmpv4, _internal: Internal) -> Result { + fn try_parse(icmp: Icmpv4, _internal: Internal) -> Result { let mbuf = icmp.mbuf(); let offset = icmp.payload_offset(); let body = mbuf.read_data(offset)?; @@ -195,7 +197,7 @@ impl Icmpv4Message for EchoRequest { /// /// Returns an error if the buffer does not have enough free space. #[inline] - fn try_push(mut icmp: Icmpv4, _internal: Internal) -> Result { + fn try_push(mut icmp: Icmpv4, _internal: Internal) -> Result { let offset = icmp.payload_offset(); let mbuf = icmp.mbuf_mut(); @@ -220,9 +222,8 @@ struct EchoRequestBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v4::Ipv4; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; #[test] fn size_of_echo_request_body() { @@ -233,8 +234,8 @@ mod tests { fn push_and_set_echo_request() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv4 = ethernet.push::().unwrap(); - let mut echo = ipv4.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); + let mut echo = ip4.push::().unwrap(); assert_eq!(4, echo.header_len()); assert_eq!(EchoRequestBody::size_of(), echo.payload_len()); diff --git a/core/src/packets/icmp/v4/mod.rs b/core/src/packets/icmp/v4/mod.rs index 511e872c..ab725cc8 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::packets::ip::v4::Ipv4; +use crate::ensure; +use crate::packets::ip::v4::{Ipv4, Ipv4Packet}; 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; @@ -72,13 +72,13 @@ use std::ptr::NonNull; /// /// [IETF RFC 792]: https://tools.ietf.org/html/rfc792 /// [`Icmpv4Message`]: Icmpv4Message -pub struct Icmpv4 { - envelope: Ipv4, +pub struct Icmpv4 { + envelope: E, header: NonNull, offset: usize, } -impl Icmpv4 { +impl Icmpv4 { #[inline] fn header(&self) -> &Icmpv4Header { unsafe { self.header.as_ref() } @@ -135,7 +135,7 @@ impl Icmpv4 { /// Returns an error if the message type in the packet header does not /// match the assigned message type for `T`. #[inline] - pub fn downcast(self) -> Result { + pub fn downcast>(self) -> Result { ensure!( self.msg_type() == T::msg_type(), anyhow!("the ICMPv4 packet is not {}.", T::msg_type()) @@ -145,7 +145,7 @@ impl Icmpv4 { } } -impl fmt::Debug for Icmpv4 { +impl fmt::Debug for Icmpv4 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("icmpv4") .field("type", &format!("{}", self.msg_type())) @@ -158,9 +158,8 @@ impl fmt::Debug for Icmpv4 { } } -impl Packet for Icmpv4 { - /// The preceding type for ICMPv4 packet must be IPv4. - type Envelope = Ipv4; +impl Packet for Icmpv4 { + type Envelope = E; #[inline] fn envelope(&self) -> &Self::Envelope { @@ -204,7 +203,7 @@ impl Packet for Icmpv4 { #[inline] fn try_parse(envelope: Self::Envelope, _internal: Internal) -> Result { ensure!( - envelope.protocol() == ProtocolNumbers::Icmpv4, + envelope.next_protocol() == ProtocolNumbers::Icmpv4, anyhow!("not an ICMPv4 packet.") ); @@ -336,17 +335,20 @@ pub struct Icmpv4Header { /// [`Packet`]: Packet /// [`Icmpv4Packet`]: Icmpv4Packet pub trait Icmpv4Message { + /// The preceding packet type that encapsulates this message. + type Envelope: Ipv4Packet; + /// Returns the assigned message type. fn msg_type() -> Icmpv4Type; /// Returns a reference to the generic ICMPv4 packet. - fn icmp(&self) -> &Icmpv4; + fn icmp(&self) -> &Icmpv4; /// Returns a mutable reference to the generic ICMPv4 packet. - fn icmp_mut(&mut self) -> &mut Icmpv4; + fn icmp_mut(&mut self) -> &mut Icmpv4; /// Converts the message back to the generic ICMPv4 packet. - fn into_icmp(self) -> Icmpv4; + fn into_icmp(self) -> Icmpv4; /// Returns a copy of the message. /// @@ -368,7 +370,7 @@ pub trait Icmpv4Message { /// /// [`Icmpv4::downcast`]: Icmpv4::downcast /// [`msg_type`]: Icmpv4::msg_type - fn try_parse(icmp: Icmpv4, internal: Internal) -> Result + fn try_parse(icmp: Icmpv4, internal: Internal) -> Result where Self: Sized; @@ -386,7 +388,7 @@ pub trait Icmpv4Message { /// /// [`msg_type`]: Icmpv4::msg_type /// [`Packet::push`]: Packet::push - fn try_push(icmp: Icmpv4, internal: Internal) -> Result + fn try_push(icmp: Icmpv4, internal: Internal) -> Result where Self: Sized; @@ -446,10 +448,9 @@ pub trait Icmpv4Packet { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v4::Ipv4; - use crate::packets::Ethernet; - use crate::testils::byte_arrays::{ICMPV4_PACKET, IPV4_UDP_PACKET}; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{ICMPV4_PACKET, UDP4_PACKET}; #[test] fn size_of_icmpv4_header() { @@ -460,16 +461,16 @@ mod tests { fn parse_icmpv4_packet() { let packet = Mbuf::from_bytes(&ICMPV4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let icmpv4 = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let icmp4 = ip4.parse::().unwrap(); // parses the generic header - assert_eq!(Icmpv4Types::EchoRequest, icmpv4.msg_type()); - assert_eq!(0, icmpv4.code()); - assert_eq!(0x2a5c, icmpv4.checksum()); + assert_eq!(Icmpv4Types::EchoRequest, icmp4.msg_type()); + assert_eq!(0, icmp4.code()); + assert_eq!(0x2a5c, icmp4.checksum()); // downcasts to specific message - let echo = icmpv4.downcast::().unwrap(); + let echo = icmp4.downcast::().unwrap(); assert_eq!(Icmpv4Types::EchoRequest, echo.msg_type()); assert_eq!(0, echo.code()); assert_eq!(0x2a5c, echo.checksum()); @@ -477,48 +478,48 @@ mod tests { assert_eq!(0x2100, echo.seq_no()); // also can one-step parse - let ipv4 = echo.deparse(); - assert!(ipv4.parse::().is_ok()); + let ip4 = echo.deparse(); + assert!(ip4.parse::().is_ok()); } #[capsule::test] fn parse_wrong_icmpv4_type() { let packet = Mbuf::from_bytes(&ICMPV4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let icmpv4 = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let icmp4 = ip4.parse::().unwrap(); - assert!(icmpv4.downcast::().is_err()); + assert!(icmp4.downcast::().is_err()); } #[capsule::test] fn parse_non_icmpv4_packet() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); - assert!(ipv4.parse::().is_err()); + assert!(ip4.parse::().is_err()); } #[capsule::test] fn compute_checksum() { let packet = Mbuf::from_bytes(&ICMPV4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let mut icmpv4 = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let mut icmp4 = ip4.parse::().unwrap(); - let expected = icmpv4.checksum(); + let expected = icmp4.checksum(); // no payload change but force a checksum recompute anyway - icmpv4.reconcile_all(); - assert_eq!(expected, icmpv4.checksum()); + icmp4.reconcile_all(); + assert_eq!(expected, icmp4.checksum()); } #[capsule::test] fn push_icmpv4_header_without_body() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv4 = ethernet.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); - assert!(ipv4.push::().is_err()); + assert!(ip4.push::().is_err()); } } diff --git a/core/src/packets/icmp/v4/redirect.rs b/core/src/packets/icmp/v4/redirect.rs index 8de0c154..43f936b4 100644 --- a/core/src/packets/icmp/v4/redirect.rs +++ b/core/src/packets/icmp/v4/redirect.rs @@ -17,9 +17,8 @@ */ 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::ip::v4::{Ipv4, Ipv4Packet, IPV4_MIN_MTU}; +use crate::packets::{Internal, Packet, SizeOf}; use anyhow::Result; use std::fmt; use std::net::Ipv4Addr; @@ -44,12 +43,12 @@ use std::ptr::NonNull; /// /// [IETF RFC 792]: https://tools.ietf.org/html/rfc792 #[derive(Icmpv4Packet)] -pub struct Redirect { - icmp: Icmpv4, +pub struct Redirect { + icmp: Icmpv4, body: NonNull, } -impl Redirect { +impl Redirect { #[inline] fn body(&self) -> &RedirectBody { unsafe { self.body.as_ref() } @@ -97,7 +96,7 @@ impl Redirect { } } -impl fmt::Debug for Redirect { +impl fmt::Debug for Redirect { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Redirect") .field("type", &format!("{}", self.msg_type())) @@ -111,24 +110,26 @@ impl fmt::Debug for Redirect { } } -impl Icmpv4Message for Redirect { +impl Icmpv4Message for Redirect { + type Envelope = E; + #[inline] fn msg_type() -> Icmpv4Type { Icmpv4Types::Redirect } #[inline] - fn icmp(&self) -> &Icmpv4 { + fn icmp(&self) -> &Icmpv4 { &self.icmp } #[inline] - fn icmp_mut(&mut self) -> &mut Icmpv4 { + fn icmp_mut(&mut self) -> &mut Icmpv4 { &mut self.icmp } #[inline] - fn into_icmp(self) -> Icmpv4 { + fn into_icmp(self) -> Icmpv4 { self.icmp } @@ -147,7 +148,7 @@ impl Icmpv4Message for Redirect { /// Returns an error if the payload does not have sufficient data for /// the redirect message body. #[inline] - fn try_parse(icmp: Icmpv4, _internal: Internal) -> Result { + fn try_parse(icmp: Icmpv4, _internal: Internal) -> Result { let mbuf = icmp.mbuf(); let offset = icmp.payload_offset(); let body = mbuf.read_data(offset)?; @@ -162,7 +163,7 @@ impl Icmpv4Message for Redirect { /// /// Returns an error if the buffer does not have enough free space. #[inline] - fn try_push(mut icmp: Icmpv4, _internal: Internal) -> Result { + fn try_push(mut icmp: Icmpv4, _internal: Internal) -> Result { let offset = icmp.payload_offset(); let mbuf = icmp.mbuf_mut(); @@ -216,10 +217,9 @@ impl Default for RedirectBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v4::Ipv4; - use crate::packets::Ethernet; - use crate::testils::byte_arrays::IPV4_TCP_PACKET; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::TCP4_PACKET; #[test] fn size_of_redirect_body() { @@ -228,12 +228,12 @@ mod tests { #[capsule::test] fn push_and_set_redirect() { - let packet = Mbuf::from_bytes(&IPV4_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let tcp_len = ipv4.payload_len(); + let ip4 = ethernet.parse::().unwrap(); + let tcp_len = ip4.payload_len(); - let mut redirect = ipv4.push::().unwrap(); + let mut redirect = ip4.push::().unwrap(); assert_eq!(4, redirect.header_len()); assert_eq!(RedirectBody::size_of() + tcp_len, redirect.payload_len()); @@ -257,8 +257,8 @@ mod tests { // starts with buffer larger than min MTU of 68 bytes. let packet = Mbuf::from_bytes(&[42; 100]).unwrap(); let ethernet = packet.push::().unwrap(); - let ipv4 = ethernet.push::().unwrap(); - let mut redirect = ipv4.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); + let mut redirect = ip4.push::().unwrap(); assert!(redirect.data_len() > IPV4_MIN_MTU); redirect.reconcile_all(); @@ -270,8 +270,8 @@ mod tests { // starts with buffer smaller than min MTU of 68 bytes. let packet = Mbuf::from_bytes(&[42; 50]).unwrap(); let ethernet = packet.push::().unwrap(); - let ipv4 = ethernet.push::().unwrap(); - let mut redirect = ipv4.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); + let mut redirect = ip4.push::().unwrap(); assert!(redirect.data_len() < IPV4_MIN_MTU); redirect.reconcile_all(); diff --git a/core/src/packets/icmp/v4/time_exceeded.rs b/core/src/packets/icmp/v4/time_exceeded.rs index affd0ad9..152e895d 100644 --- a/core/src/packets/icmp/v4/time_exceeded.rs +++ b/core/src/packets/icmp/v4/time_exceeded.rs @@ -17,10 +17,9 @@ */ use crate::packets::icmp::v4::{Icmpv4, Icmpv4Message, Icmpv4Packet, Icmpv4Type, Icmpv4Types}; -use crate::packets::ip::v4::IPV4_MIN_MTU; +use crate::packets::ip::v4::{Ipv4, Ipv4Packet, 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; @@ -40,12 +39,12 @@ use std::ptr::NonNull; /// /// [IETF RFC 792]: https://tools.ietf.org/html/rfc792 #[derive(Icmpv4Packet)] -pub struct TimeExceeded { - icmp: Icmpv4, +pub struct TimeExceeded { + icmp: Icmpv4, body: NonNull, } -impl TimeExceeded { +impl TimeExceeded { /// Returns the offset where the data field in the message body starts. #[inline] fn data_offset(&self) -> usize { @@ -73,7 +72,7 @@ impl TimeExceeded { } } -impl fmt::Debug for TimeExceeded { +impl fmt::Debug for TimeExceeded { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TimeExceeded") .field("type", &format!("{}", self.msg_type())) @@ -86,24 +85,26 @@ impl fmt::Debug for TimeExceeded { } } -impl Icmpv4Message for TimeExceeded { +impl Icmpv4Message for TimeExceeded { + type Envelope = E; + #[inline] fn msg_type() -> Icmpv4Type { Icmpv4Types::TimeExceeded } #[inline] - fn icmp(&self) -> &Icmpv4 { + fn icmp(&self) -> &Icmpv4 { &self.icmp } #[inline] - fn icmp_mut(&mut self) -> &mut Icmpv4 { + fn icmp_mut(&mut self) -> &mut Icmpv4 { &mut self.icmp } #[inline] - fn into_icmp(self) -> Icmpv4 { + fn into_icmp(self) -> Icmpv4 { self.icmp } @@ -122,7 +123,7 @@ impl Icmpv4Message for TimeExceeded { /// Returns an error if the payload does not have sufficient data for /// the time exceeded message body. #[inline] - fn try_parse(icmp: Icmpv4, _internal: Internal) -> Result { + fn try_parse(icmp: Icmpv4, _internal: Internal) -> Result { let mbuf = icmp.mbuf(); let offset = icmp.payload_offset(); let body = mbuf.read_data(offset)?; @@ -137,7 +138,7 @@ impl Icmpv4Message for TimeExceeded { /// /// Returns an error if the buffer does not have enough free space. #[inline] - fn try_push(mut icmp: Icmpv4, _internal: Internal) -> Result { + fn try_push(mut icmp: Icmpv4, _internal: Internal) -> Result { let offset = icmp.payload_offset(); let mbuf = icmp.mbuf_mut(); @@ -180,10 +181,9 @@ struct TimeExceededBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v4::Ipv4; - use crate::packets::Ethernet; - use crate::testils::byte_arrays::IPV4_TCP_PACKET; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::TCP4_PACKET; #[test] fn size_of_time_exceeded_body() { @@ -192,12 +192,12 @@ mod tests { #[capsule::test] fn push_and_set_time_exceeded() { - let packet = Mbuf::from_bytes(&IPV4_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let tcp_len = ipv4.payload_len(); + let ip4 = ethernet.parse::().unwrap(); + let tcp_len = ip4.payload_len(); - let mut exceeded = ipv4.push::().unwrap(); + let mut exceeded = ip4.push::().unwrap(); assert_eq!(4, exceeded.header_len()); assert_eq!( @@ -220,8 +220,8 @@ mod tests { // starts with a buffer with a message body larger than min MTU. let packet = Mbuf::from_bytes(&[42; 100]).unwrap(); let ethernet = packet.push::().unwrap(); - let ipv4 = ethernet.push::().unwrap(); - let mut exceeded = ipv4.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); + let mut exceeded = ip4.push::().unwrap(); assert!(exceeded.data_len() > IPV4_MIN_MTU); exceeded.reconcile_all(); @@ -233,8 +233,8 @@ mod tests { // starts with a buffer with a message body smaller than min MTU. let packet = Mbuf::from_bytes(&[42; 50]).unwrap(); let ethernet = packet.push::().unwrap(); - let ipv4 = ethernet.push::().unwrap(); - let mut exceeded = ipv4.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); + let mut exceeded = ip4.push::().unwrap(); assert!(exceeded.data_len() < IPV4_MIN_MTU); exceeded.reconcile_all(); diff --git a/core/src/packets/icmp/v6/echo_reply.rs b/core/src/packets/icmp/v6/echo_reply.rs index c2ab433d..de435ac1 100644 --- a/core/src/packets/icmp/v6/echo_reply.rs +++ b/core/src/packets/icmp/v6/echo_reply.rs @@ -17,10 +17,9 @@ */ use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; -use crate::packets::ip::v6::Ipv6Packet; +use crate::packets::ip::v6::{Ipv6, 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; @@ -48,7 +47,7 @@ use std::ptr::NonNull; /// /// [IETF RFC 4443]: https://tools.ietf.org/html/rfc4443#section-4.2 #[derive(Icmpv6Packet)] -pub struct EchoReply { +pub struct EchoReply { icmp: Icmpv6, body: NonNull, } @@ -218,9 +217,8 @@ struct EchoReplyBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; #[test] fn size_of_echo_reply_body() { @@ -231,8 +229,8 @@ mod tests { fn push_and_set_echo_reply() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut echo = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut echo = ip6.push::().unwrap(); assert_eq!(4, echo.header_len()); assert_eq!(EchoReplyBody::size_of(), echo.payload_len()); diff --git a/core/src/packets/icmp/v6/echo_request.rs b/core/src/packets/icmp/v6/echo_request.rs index 5e098131..fb11273f 100644 --- a/core/src/packets/icmp/v6/echo_request.rs +++ b/core/src/packets/icmp/v6/echo_request.rs @@ -17,10 +17,9 @@ */ use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; -use crate::packets::ip::v6::Ipv6Packet; +use crate::packets::ip::v6::{Ipv6, 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; @@ -49,7 +48,7 @@ use std::ptr::NonNull; /// /// [IETF RFC 4443]: https://tools.ietf.org/html/rfc4443#section-4.1 #[derive(Icmpv6Packet)] -pub struct EchoRequest { +pub struct EchoRequest { icmp: Icmpv6, body: NonNull, } @@ -219,9 +218,8 @@ struct EchoRequestBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; #[test] fn size_of_echo_request_body() { @@ -232,8 +230,8 @@ mod tests { fn push_and_set_echo_request() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut echo = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut echo = ip6.push::().unwrap(); assert_eq!(4, echo.header_len()); assert_eq!(EchoRequestBody::size_of(), echo.payload_len()); diff --git a/core/src/packets/icmp/v6/mod.rs b/core/src/packets/icmp/v6/mod.rs index 2fa68144..a2b7e963 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::packets::ip::v6::Ipv6Packet; +use crate::ensure; +use crate::packets::ip::v6::{Ipv6, 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; @@ -70,14 +70,14 @@ use std::ptr::NonNull; /// /// ``` /// if ipv6.next_header() == NextHeaders::Icmpv6 { -/// let icmpv6 = ipv6.parse::>().unwrap(); +/// let icmpv6 = ipv6.parse::().unwrap(); /// println!("{}", icmpv6.msg_type()); /// } /// ``` /// /// [IETF RFC 4443]: https://tools.ietf.org/html/rfc4443 /// [`Icmpv6Message`]: Icmpv6Message -pub struct Icmpv6 { +pub struct Icmpv6 { envelope: E, header: NonNull, offset: usize, @@ -377,8 +377,8 @@ pub struct Icmpv6Header { /// # Example /// /// ``` -/// let icmpv6 = ipv6.parse::>()?; -/// let reply = icmpv6.downcast::>()?; +/// let icmpv6 = ipv6.parse::()?; +/// let reply = icmpv6.downcast::()?; /// ``` /// /// [ICMPv6]: Icmpv6 @@ -498,11 +498,10 @@ 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::testils::byte_arrays::{ICMPV6_PACKET, IPV6_TCP_PACKET, ROUTER_ADVERT_PACKET}; - use crate::Mbuf; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{ICMPV6_PACKET, ROUTER_ADVERT_PACKET, TCP6_PACKET}; #[test] fn size_of_icmpv6_header() { @@ -513,16 +512,16 @@ mod tests { fn parse_icmpv6_packet() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let icmpv6 = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let icmp6 = ip6.parse::().unwrap(); // parses the generic header - assert_eq!(Icmpv6Types::RouterAdvertisement, icmpv6.msg_type()); - assert_eq!(0, icmpv6.code()); - assert_eq!(0xf50c, icmpv6.checksum()); + assert_eq!(Icmpv6Types::RouterAdvertisement, icmp6.msg_type()); + assert_eq!(0, icmp6.code()); + assert_eq!(0xf50c, icmp6.checksum()); // downcasts to specific message - let advert = icmpv6.downcast::>().unwrap(); + let advert = icmp6.downcast::().unwrap(); assert_eq!(Icmpv6Types::RouterAdvertisement, advert.msg_type()); assert_eq!(0, advert.code()); assert_eq!(0xf50c, advert.checksum()); @@ -534,48 +533,48 @@ mod tests { assert_eq!(0, advert.retrans_timer()); // also can one-step parse - let ipv6 = advert.deparse(); - assert!(ipv6.parse::>().is_ok()); + let ip6 = advert.deparse(); + assert!(ip6.parse::().is_ok()); } #[capsule::test] fn parse_wrong_icmpv6_type() { let packet = Mbuf::from_bytes(&ICMPV6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let icmpv6 = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let icmp6 = ip6.parse::().unwrap(); - assert!(icmpv6.downcast::>().is_err()); + assert!(icmp6.downcast::().is_err()); } #[capsule::test] fn parse_non_icmpv6_packet() { - let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); + let ip6 = ethernet.parse::().unwrap(); - assert!(ipv6.parse::>().is_err()); + assert!(ip6.parse::().is_err()); } #[capsule::test] fn compute_checksum() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut icmpv6 = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut icmp6 = ip6.parse::().unwrap(); - let expected = icmpv6.checksum(); + let expected = icmp6.checksum(); // no payload change but force a checksum recompute anyway - icmpv6.reconcile_all(); - assert_eq!(expected, icmpv6.checksum()); + icmp6.reconcile_all(); + assert_eq!(expected, icmp6.checksum()); } #[capsule::test] fn push_icmpv6_header_without_body() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); + let ip6 = ethernet.push::().unwrap(); - assert!(ipv6.push::>().is_err()); + assert!(ip6.push::().is_err()); } } diff --git a/core/src/packets/icmp/v6/ndp/mod.rs b/core/src/packets/icmp/v6/ndp/mod.rs index f9558210..2be9f432 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; @@ -64,7 +63,7 @@ pub trait NdpPacket: Packet { /// # Example /// /// ``` - /// let advert = ipv6.parse::>()?; + /// let advert = ipv6.parse::()?; /// let mut iter = advert.options_iter(); /// /// while let Some(option) = iter.next()? { @@ -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) @@ -222,7 +220,7 @@ impl<'a> ImmutableNdpOption<'a> { /// # Example /// /// ``` - /// let advert = ipv6.parse::>()?; + /// let advert = ipv6.parse::()?; /// let mut iter = advert.options(); /// /// while let Some(option) = iter.next()? { @@ -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) @@ -345,7 +342,7 @@ impl<'a> MutableNdpOption<'a> { /// # Example /// /// ``` - /// let advert = ipv6.parse::>()?; + /// let advert = ipv6.parse::()?; /// let mut iter = advert.options_mut().iter(); /// /// while let Some(option) = iter.next()? { @@ -419,7 +416,7 @@ impl NdpOptions<'_> { /// # Example /// /// ``` - /// let advert = ipv6.parse::>()?; + /// let advert = ipv6.parse::()?; /// let mut options = advert.options_mut(); /// let mut iter = options.iter(); /// @@ -440,7 +437,7 @@ impl NdpOptions<'_> { /// # Example /// /// ``` - /// let advert = ipv6.parse::>()?; + /// let advert = ipv6.parse::()?; /// let mut options = advert.options_mut(); /// let mut source = options.prepend::>()?; /// source.set_option_type_source(); @@ -455,7 +452,7 @@ impl NdpOptions<'_> { /// # Example /// /// ``` - /// let advert = ipv6.parse::>()?; + /// let advert = ipv6.parse::()?; /// let mut options = advert.options_mut(); /// let mut source = options.append::>()?; /// source.set_option_type_source(); @@ -474,7 +471,7 @@ impl NdpOptions<'_> { /// # Example /// /// ``` - /// let advert = ipv6.parse::>()?; + /// let advert = ipv6.parse::()?; /// let mut options = advert.options_mut(); /// let _ = options.retain(|option| option.option_type() == NdpOptionTypes::PrefixInformation); /// ``` @@ -554,16 +551,16 @@ 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] fn iterate_immutable_ndp_options() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let advert = ip6.parse::().unwrap(); let mut prefix = false; let mut mtu = false; @@ -591,8 +588,8 @@ mod tests { fn invalid_ndp_option_length() { let packet = Mbuf::from_bytes(&INVALID_OPTION_LENGTH).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let advert = ip6.parse::().unwrap(); assert!(advert.options_iter().next().is_err()); } @@ -601,8 +598,8 @@ mod tests { fn downcast_immutable_ndp_option() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.peek::().unwrap(); - let ipv6 = ethernet.peek::().unwrap(); - let advert = ipv6.peek::>().unwrap(); + let ip6 = ethernet.peek::().unwrap(); + let advert = ip6.peek::().unwrap(); let mut iter = advert.options_iter(); @@ -619,8 +616,8 @@ mod tests { fn iterate_mutable_ndp_options() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut advert = ip6.parse::().unwrap(); let mut prefix = false; let mut mtu = false; @@ -649,8 +646,8 @@ mod tests { fn downcast_mutable_ndp_option() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut advert = ip6.parse::().unwrap(); let mut options = advert.options_mut(); let mut iter = options.iter(); @@ -667,8 +664,8 @@ mod tests { fn modify_ndp_option() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut advert = ip6.parse::().unwrap(); let mut options = advert.options_mut(); let mut iter = options.iter(); @@ -686,8 +683,8 @@ mod tests { fn prepend_ndp_option() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut advert = ip6.parse::().unwrap(); let mut options = advert.options_mut(); let mut target = options.prepend::>().unwrap(); target.set_option_type_target(); @@ -702,8 +699,8 @@ mod tests { fn append_ndp_option() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut advert = ip6.parse::().unwrap(); let mut options = advert.options_mut(); let mut target = options.append::>().unwrap(); target.set_option_type_target(); @@ -726,8 +723,8 @@ mod tests { fn retain_ndp_options() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut advert = ip6.parse::().unwrap(); let mut options = advert.options_mut(); let _ = options.retain(|option| option.downcast::>().is_ok()); @@ -757,8 +754,8 @@ mod tests { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut advert = ip6.parse::().unwrap(); let mut iter = advert.options_iter(); advert.set_code(0); @@ -778,8 +775,8 @@ mod tests { fn cannot_mutate_immutable_option() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let advert = ip6.parse::().unwrap(); let mut iter = advert.options_iter(); let mut option = iter.next().unwrap().unwrap(); @@ -805,8 +802,8 @@ mod tests { fn cannot_mutate_options_while_iterating_options() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut advert = ip6.parse::().unwrap(); let mut options = advert.options_mut(); let mut iter = options.iter(); diff --git a/core/src/packets/icmp/v6/ndp/neighbor_advert.rs b/core/src/packets/icmp/v6/ndp/neighbor_advert.rs index c8c93655..2b03fa1a 100644 --- a/core/src/packets/icmp/v6/ndp/neighbor_advert.rs +++ b/core/src/packets/icmp/v6/ndp/neighbor_advert.rs @@ -18,10 +18,9 @@ use super::NdpPacket; use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; -use crate::packets::ip::v6::Ipv6Packet; +use crate::packets::ip::v6::{Ipv6, 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; @@ -79,7 +78,7 @@ const O_FLAG: u8 = 0b0010_0000; /// /// [IETF RFC 4861]: https://tools.ietf.org/html/rfc4861#section-4.4 #[derive(Icmpv6Packet)] -pub struct NeighborAdvertisement { +pub struct NeighborAdvertisement { icmp: Icmpv6, body: NonNull, } @@ -271,9 +270,8 @@ impl Default for NeighborAdvertisementBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; #[test] fn size_of_neighbor_advertisement_body() { @@ -284,8 +282,8 @@ mod tests { fn push_and_set_neighbor_advertisement() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut advert = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut advert = ip6.push::().unwrap(); assert_eq!(4, advert.header_len()); assert_eq!(NeighborAdvertisementBody::size_of(), advert.payload_len()); diff --git a/core/src/packets/icmp/v6/ndp/neighbor_solicit.rs b/core/src/packets/icmp/v6/ndp/neighbor_solicit.rs index bc96e6b9..f0327ca1 100644 --- a/core/src/packets/icmp/v6/ndp/neighbor_solicit.rs +++ b/core/src/packets/icmp/v6/ndp/neighbor_solicit.rs @@ -18,10 +18,9 @@ use super::NdpPacket; use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; -use crate::packets::ip::v6::Ipv6Packet; +use crate::packets::ip::v6::{Ipv6, 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; @@ -57,7 +56,7 @@ use std::ptr::NonNull; /// /// [IETF RFC 4861]: https://tools.ietf.org/html/rfc4861#section-4.3 #[derive(Icmpv6Packet)] -pub struct NeighborSolicitation { +pub struct NeighborSolicitation { icmp: Icmpv6, body: NonNull, } @@ -186,9 +185,8 @@ impl Default for NeighborSolicitationBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; #[test] fn size_of_neighbor_solicitation_body() { @@ -199,8 +197,8 @@ mod tests { fn push_and_set_neighbor_solicitation() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut solicit = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut solicit = ip6.push::().unwrap(); assert_eq!(4, solicit.header_len()); assert_eq!(NeighborSolicitationBody::size_of(), solicit.payload_len()); 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..ab5e8da0 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] @@ -204,8 +205,8 @@ mod tests { fn parse_link_layer_address() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut advert = ip6.parse::().unwrap(); let mut options = advert.options_mut(); let mut iter = options.iter(); @@ -228,8 +229,8 @@ mod tests { fn push_and_set_link_layer_address() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut advert = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut advert = ip6.push::().unwrap(); let mut options = advert.options_mut(); let mut lla = options.append::>().unwrap(); diff --git a/core/src/packets/icmp/v6/ndp/options/mtu.rs b/core/src/packets/icmp/v6/ndp/options/mtu.rs index 0d2f8e2b..4a463c89 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] @@ -184,8 +185,8 @@ mod tests { fn parse_mtu() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut advert = ip6.parse::().unwrap(); let mut options = advert.options_mut(); let mut iter = options.iter(); @@ -208,8 +209,8 @@ mod tests { fn push_and_set_mtu() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut advert = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut advert = ip6.push::().unwrap(); let mut options = advert.options_mut(); let mut mtu = options.append::>().unwrap(); 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..9cf5da1c 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] @@ -322,8 +323,8 @@ mod tests { fn parse_prefix_information() { let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut advert = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut advert = ip6.parse::().unwrap(); let mut options = advert.options_mut(); let mut iter = options.iter(); @@ -354,8 +355,8 @@ mod tests { fn push_and_set_prefix_information() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut advert = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut advert = ip6.push::().unwrap(); let mut options = advert.options_mut(); let mut prefix = options.append::>().unwrap(); diff --git a/core/src/packets/icmp/v6/ndp/options/redirected.rs b/core/src/packets/icmp/v6/ndp/options/redirected.rs index b16e094f..97c7525b 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; @@ -70,7 +70,7 @@ use std::ptr::NonNull; /// ``` /// let ethernet = orig_ipv6.deparse(); /// let ipv6 = ethernet.push::()?; -/// let mut redirect = ipv6.push::>()?; +/// let mut redirect = ipv6.push::()?; /// let mut options = redirect.options_mut(); /// let _ = options.prepend::>(); /// redirect.reconcile(); @@ -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() { @@ -248,8 +249,8 @@ mod tests { let packet = Mbuf::from_bytes(&data).unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut redirect = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut redirect = ip6.push::().unwrap(); let mut options = redirect.options_mut(); let mut header = options.prepend::>().unwrap(); diff --git a/core/src/packets/icmp/v6/ndp/redirect.rs b/core/src/packets/icmp/v6/ndp/redirect.rs index 3c253827..8ace688f 100644 --- a/core/src/packets/icmp/v6/ndp/redirect.rs +++ b/core/src/packets/icmp/v6/ndp/redirect.rs @@ -18,10 +18,9 @@ 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::ip::v6::{Ipv6, 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; @@ -69,7 +68,7 @@ use std::ptr::NonNull; /// /// [IETF RFC 4861]: https://tools.ietf.org/html/rfc2461#section-4.5 #[derive(Icmpv6Packet)] -pub struct Redirect { +pub struct Redirect { icmp: Icmpv6, body: NonNull, } @@ -242,9 +241,8 @@ impl Default for RedirectBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; #[test] fn size_of_redirect_body() { @@ -255,8 +253,8 @@ mod tests { fn push_and_set_redirect() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut redirect = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut redirect = ip6.push::().unwrap(); assert_eq!(4, redirect.header_len()); assert_eq!(RedirectBody::size_of(), redirect.payload_len()); @@ -277,8 +275,8 @@ mod tests { // starts with a buffer larger than min MTU. let packet = Mbuf::from_bytes(&[42; 1600]).unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut redirect = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut redirect = ip6.push::().unwrap(); let mut options = redirect.options_mut(); let _ = options.prepend::>().unwrap(); diff --git a/core/src/packets/icmp/v6/ndp/router_advert.rs b/core/src/packets/icmp/v6/ndp/router_advert.rs index 630af35e..45423d10 100644 --- a/core/src/packets/icmp/v6/ndp/router_advert.rs +++ b/core/src/packets/icmp/v6/ndp/router_advert.rs @@ -18,10 +18,9 @@ use super::NdpPacket; use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; -use crate::packets::ip::v6::Ipv6Packet; +use crate::packets::ip::v6::{Ipv6, 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; @@ -98,7 +97,7 @@ const O_FLAG: u8 = 0b0100_0000; /// /// [IETF RFC 4861]: https://tools.ietf.org/html/rfc4861#section-4.2 #[derive(Icmpv6Packet)] -pub struct RouterAdvertisement { +pub struct RouterAdvertisement { icmp: Icmpv6, body: NonNull, } @@ -302,9 +301,8 @@ struct RouterAdvertisementBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; #[test] fn size_of_router_advertisement_body() { @@ -315,8 +313,8 @@ mod tests { fn push_and_set_router_advertisement() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut advert = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut advert = ip6.push::().unwrap(); assert_eq!(4, advert.header_len()); assert_eq!(RouterAdvertisementBody::size_of(), advert.payload_len()); diff --git a/core/src/packets/icmp/v6/ndp/router_solicit.rs b/core/src/packets/icmp/v6/ndp/router_solicit.rs index adf9a03a..29dcf00d 100644 --- a/core/src/packets/icmp/v6/ndp/router_solicit.rs +++ b/core/src/packets/icmp/v6/ndp/router_solicit.rs @@ -18,10 +18,9 @@ use super::NdpPacket; use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; -use crate::packets::ip::v6::Ipv6Packet; +use crate::packets::ip::v6::{Ipv6, 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; @@ -50,7 +49,7 @@ use std::ptr::NonNull; /// /// [IETF RFC 4861]: https://tools.ietf.org/html/rfc4861#section-4.1 #[derive(Icmpv6Packet)] -pub struct RouterSolicitation { +pub struct RouterSolicitation { icmp: Icmpv6, body: NonNull, } @@ -144,9 +143,8 @@ struct RouterSolicitationBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; #[test] fn size_of_router_solicitation_body() { @@ -157,8 +155,8 @@ mod tests { fn push_and_set_router_solicitation() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut solicit = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut solicit = ip6.push::().unwrap(); assert_eq!(4, solicit.header_len()); assert_eq!(RouterSolicitationBody::size_of(), solicit.payload_len()); diff --git a/core/src/packets/icmp/v6/time_exceeded.rs b/core/src/packets/icmp/v6/time_exceeded.rs index e1c1e269..c80b6cce 100644 --- a/core/src/packets/icmp/v6/time_exceeded.rs +++ b/core/src/packets/icmp/v6/time_exceeded.rs @@ -17,10 +17,9 @@ */ use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; -use crate::packets::ip::v6::{Ipv6Packet, IPV6_MIN_MTU}; +use crate::packets::ip::v6::{Ipv6, 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; @@ -42,7 +41,7 @@ use std::ptr::NonNull; /// /// [IETF RFC 4443]: https://tools.ietf.org/html/rfc4443#section-3.3 #[derive(Icmpv6Packet)] -pub struct TimeExceeded { +pub struct TimeExceeded { icmp: Icmpv6, body: NonNull, } @@ -164,10 +163,9 @@ struct TimeExceededBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::testils::byte_arrays::IPV6_TCP_PACKET; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::TCP6_PACKET; #[test] fn size_of_time_exceeded_body() { @@ -176,12 +174,12 @@ mod tests { #[capsule::test] fn push_and_set_time_exceeded() { - let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let tcp_len = ipv6.payload_len(); + let ip6 = ethernet.parse::().unwrap(); + let tcp_len = ip6.payload_len(); - let mut exceeded = ipv6.push::>().unwrap(); + let mut exceeded = ip6.push::().unwrap(); assert_eq!(4, exceeded.header_len()); assert_eq!( @@ -204,12 +202,12 @@ mod tests { // starts with a buffer larger than min MTU. let packet = Mbuf::from_bytes(&[42; 1600]).unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); + let ip6 = ethernet.push::().unwrap(); // the max packet len is MTU + Ethernet header let max_len = IPV6_MIN_MTU + 14; - let mut exceeded = ipv6.push::>().unwrap(); + let mut exceeded = ip6.push::().unwrap(); assert!(exceeded.mbuf().data_len() > max_len); exceeded.reconcile_all(); diff --git a/core/src/packets/icmp/v6/too_big.rs b/core/src/packets/icmp/v6/too_big.rs index bc5c5cd3..a83c0e37 100644 --- a/core/src/packets/icmp/v6/too_big.rs +++ b/core/src/packets/icmp/v6/too_big.rs @@ -17,10 +17,9 @@ */ use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; -use crate::packets::ip::v6::{Ipv6Packet, IPV6_MIN_MTU}; +use crate::packets::ip::v6::{Ipv6, 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; @@ -44,7 +43,7 @@ use std::ptr::NonNull; /// /// [IETF RFC 4443]: https://tools.ietf.org/html/rfc4443#section-3.2 #[derive(Icmpv6Packet)] -pub struct PacketTooBig { +pub struct PacketTooBig { icmp: Icmpv6, body: NonNull, } @@ -189,10 +188,9 @@ struct PacketTooBigBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::testils::byte_arrays::IPV6_TCP_PACKET; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::TCP6_PACKET; #[test] fn size_of_packet_too_big() { @@ -201,12 +199,12 @@ mod tests { #[capsule::test] fn push_and_set_packet_too_big() { - let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let tcp_len = ipv6.payload_len(); + let ip6 = ethernet.parse::().unwrap(); + let tcp_len = ip6.payload_len(); - let mut too_big = ipv6.push::>().unwrap(); + let mut too_big = ip6.push::().unwrap(); assert_eq!(4, too_big.header_len()); assert_eq!(PacketTooBigBody::size_of() + tcp_len, too_big.payload_len()); @@ -226,12 +224,12 @@ mod tests { // starts with a buffer. let packet = Mbuf::from_bytes(&[42; 1600]).unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); + let ip6 = ethernet.push::().unwrap(); // the max packet len is MTU + Ethernet header let max_len = IPV6_MIN_MTU + 14; - let mut too_big = ipv6.push::>().unwrap(); + let mut too_big = ip6.push::().unwrap(); assert!(too_big.mbuf().data_len() > max_len); too_big.reconcile_all(); diff --git a/core/src/packets/icmp/v6/destination_unreachable.rs b/core/src/packets/icmp/v6/unreachable.rs similarity index 90% rename from core/src/packets/icmp/v6/destination_unreachable.rs rename to core/src/packets/icmp/v6/unreachable.rs index e23d29a1..a9afc87d 100644 --- a/core/src/packets/icmp/v6/destination_unreachable.rs +++ b/core/src/packets/icmp/v6/unreachable.rs @@ -17,10 +17,9 @@ */ use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; -use crate::packets::ip::v6::{Ipv6Packet, IPV6_MIN_MTU}; +use crate::packets::ip::v6::{Ipv6, 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; @@ -42,7 +41,7 @@ use std::ptr::NonNull; /// /// [IETF RFC 4443]: https://tools.ietf.org/html/rfc4443#section-3.1 #[derive(Icmpv6Packet)] -pub struct DestinationUnreachable { +pub struct DestinationUnreachable { icmp: Icmpv6, body: NonNull, } @@ -164,10 +163,9 @@ struct DestinationUnreachableBody { #[cfg(test)] mod tests { use super::*; - use crate::packets::ip::v6::Ipv6; - use crate::packets::Ethernet; - use crate::testils::byte_arrays::IPV6_TCP_PACKET; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::TCP6_PACKET; #[test] fn size_of_destination_unreachable_body() { @@ -176,12 +174,12 @@ mod tests { #[capsule::test] fn push_and_set_destination_unreachable() { - let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let tcp_len = ipv6.payload_len(); + let ip6 = ethernet.parse::().unwrap(); + let tcp_len = ip6.payload_len(); - let mut unreachable = ipv6.push::>().unwrap(); + let mut unreachable = ip6.push::().unwrap(); assert_eq!(4, unreachable.header_len()); assert_eq!( @@ -204,12 +202,12 @@ mod tests { // starts with a buffer larger than min MTU. let packet = Mbuf::from_bytes(&[42; 1600]).unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); + let ip6 = ethernet.push::().unwrap(); // the max packet len is MTU + Ethernet header let max_len = IPV6_MIN_MTU + 14; - let mut unreachable = ipv6.push::>().unwrap(); + let mut unreachable = ip6.push::().unwrap(); assert!(unreachable.mbuf().data_len() > max_len); unreachable.reconcile_all(); diff --git a/core/src/packets/ip/mod.rs b/core/src/packets/ip/mod.rs index d56cb58d..5c466d13 100644 --- a/core/src/packets/ip/mod.rs +++ b/core/src/packets/ip/mod.rs @@ -273,7 +273,9 @@ mod tests { assert_eq!("TCP", ProtocolNumbers::Tcp.to_string()); assert_eq!("UDP", ProtocolNumbers::Udp.to_string()); assert_eq!("IPv6 Route", ProtocolNumbers::Ipv6Route.to_string()); + assert_eq!("IPv6 Frag", ProtocolNumbers::Ipv6Frag.to_string()); assert_eq!("ICMPv6", ProtocolNumbers::Icmpv6.to_string()); + assert_eq!("ICMPv4", ProtocolNumbers::Icmpv4.to_string()); assert_eq!("0x00", ProtocolNumber::new(0).to_string()); } } diff --git a/core/src/packets/ip/v4.rs b/core/src/packets/ip/v4.rs index 3605e3bc..95f897a2 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::{Datalink, Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::net::{IpAddr, Ipv4Addr}; @@ -142,13 +143,13 @@ const FLAGS_MF: u16be = u16be(u16::to_be(0b0010_0000_0000_0000)); /// [IETF RFC 791]: https://tools.ietf.org/html/rfc791#section-3.1 /// [IETF RFC 2474]: https://tools.ietf.org/html/rfc2474 /// [IETF RFC 3168]: https://tools.ietf.org/html/rfc3168 -pub struct Ipv4 { - envelope: Ethernet, +pub struct Ipv4 { + envelope: E, header: NonNull, offset: usize, } -impl Ipv4 { +impl Ipv4 { #[inline] fn header(&self) -> &Ipv4Header { unsafe { self.header.as_ref() } @@ -357,7 +358,7 @@ impl Ipv4 { } } -impl fmt::Debug for Ipv4 { +impl fmt::Debug for Ipv4 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ipv4") .field("src", &format!("{}", self.src())) @@ -380,9 +381,8 @@ impl fmt::Debug for Ipv4 { } } -impl Packet for Ipv4 { - /// The preceding type for an IPv4 packet must be Ethernet. - type Envelope = Ethernet; +impl Packet for Ipv4 { + type Envelope = E; #[inline] fn envelope(&self) -> &Self::Envelope { @@ -426,7 +426,7 @@ impl Packet for Ipv4 { #[inline] fn try_parse(envelope: Self::Envelope, _internal: Internal) -> Result { ensure!( - envelope.ether_type() == EtherTypes::Ipv4, + envelope.protocol_type() == EtherTypes::Ipv4, anyhow!("not an IPv4 packet.") ); @@ -459,7 +459,7 @@ impl Packet for Ipv4 { mbuf.extend(offset, Ipv4Header::size_of())?; let header = mbuf.write_data(offset, &Ipv4Header::default())?; - envelope.set_ether_type(EtherTypes::Ipv4); + envelope.set_protocol_type(EtherTypes::Ipv4); Ok(Ipv4 { envelope, @@ -489,7 +489,7 @@ impl Packet for Ipv4 { } } -impl IpPacket for Ipv4 { +impl IpPacket for Ipv4 { #[inline] fn next_protocol(&self) -> ProtocolNumber { self.protocol() @@ -572,6 +572,13 @@ impl IpPacket for Ipv4 { } } +impl Ipv4Packet for Ipv4 {} + +/// A marker trait for Ipv4 packet. +/// +/// Used as a trait bound for packets succeding Ipv4. +pub trait Ipv4Packet: IpPacket {} + /// IPv4 header. /// /// The header only include the fixed portion of the IPv4 header. @@ -612,8 +619,8 @@ impl Default for Ipv4Header { mod tests { use super::*; use crate::packets::ip::ProtocolNumbers; - use crate::testils::byte_arrays::{IPV4_UDP_PACKET, IPV6_TCP_PACKET}; - use crate::Mbuf; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{TCP6_PACKET, UDP4_PACKET}; #[test] fn size_of_ipv4_header() { @@ -622,29 +629,29 @@ mod tests { #[capsule::test] fn parse_ipv4_packet() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - - assert_eq!(4, ipv4.version()); - assert_eq!(5, ipv4.ihl()); - assert_eq!(38, ipv4.total_length()); - assert_eq!(43849, ipv4.identification()); - assert_eq!(true, ipv4.dont_fragment()); - assert_eq!(false, ipv4.more_fragments()); - assert_eq!(0, ipv4.fragment_offset()); - assert_eq!(0, ipv4.dscp()); - assert_eq!(0, ipv4.ecn()); - assert_eq!(255, ipv4.ttl()); - assert_eq!(ProtocolNumbers::Udp, ipv4.protocol()); - assert_eq!(0xf700, ipv4.checksum()); - assert_eq!("139.133.217.110", ipv4.src().to_string()); - assert_eq!("139.133.233.2", ipv4.dst().to_string()); + let ip4 = ethernet.parse::().unwrap(); + + assert_eq!(4, ip4.version()); + assert_eq!(5, ip4.ihl()); + assert_eq!(38, ip4.total_length()); + assert_eq!(43849, ip4.identification()); + assert_eq!(true, ip4.dont_fragment()); + assert_eq!(false, ip4.more_fragments()); + assert_eq!(0, ip4.fragment_offset()); + assert_eq!(0, ip4.dscp()); + assert_eq!(0, ip4.ecn()); + assert_eq!(255, ip4.ttl()); + assert_eq!(ProtocolNumbers::Udp, ip4.protocol()); + assert_eq!(0xf700, ip4.checksum()); + assert_eq!("139.133.217.110", ip4.src().to_string()); + assert_eq!("139.133.233.2", ip4.dst().to_string()); } #[capsule::test] fn parse_non_ipv4_packet() { - let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); assert!(ethernet.parse::().is_err()); @@ -652,50 +659,50 @@ mod tests { #[capsule::test] fn parse_ipv4_setter_checks() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let mut ipv4 = ethernet.parse::().unwrap(); + let mut ip4 = ethernet.parse::().unwrap(); // Fields - ipv4.set_ihl(ipv4.ihl()); + ip4.set_ihl(ip4.ihl()); // Flags - assert_eq!(true, ipv4.dont_fragment()); - assert_eq!(false, ipv4.more_fragments()); + assert_eq!(true, ip4.dont_fragment()); + assert_eq!(false, ip4.more_fragments()); - ipv4.unset_dont_fragment(); - assert_eq!(false, ipv4.dont_fragment()); - ipv4.set_dont_fragment(); - assert_eq!(true, ipv4.dont_fragment()); + ip4.unset_dont_fragment(); + assert_eq!(false, ip4.dont_fragment()); + ip4.set_dont_fragment(); + assert_eq!(true, ip4.dont_fragment()); - ipv4.set_more_fragments(); - assert_eq!(true, ipv4.more_fragments()); - ipv4.unset_more_fragments(); - assert_eq!(false, ipv4.more_fragments()); + ip4.set_more_fragments(); + assert_eq!(true, ip4.more_fragments()); + ip4.unset_more_fragments(); + assert_eq!(false, ip4.more_fragments()); - ipv4.set_fragment_offset(5); - assert_eq!(5, ipv4.fragment_offset()); + ip4.set_fragment_offset(5); + assert_eq!(5, ip4.fragment_offset()); // DSCP & ECN - assert_eq!(0, ipv4.dscp()); - assert_eq!(0, ipv4.ecn()); - ipv4.set_dscp(10); - ipv4.set_ecn(3); - assert_eq!(10, ipv4.dscp()); - assert_eq!(3, ipv4.ecn()); + assert_eq!(0, ip4.dscp()); + assert_eq!(0, ip4.ecn()); + ip4.set_dscp(10); + ip4.set_ecn(3); + assert_eq!(10, ip4.dscp()); + assert_eq!(3, ip4.ecn()); } #[capsule::test] fn push_ipv4_packet() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv4 = ethernet.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); - assert_eq!(4, ipv4.version()); - assert_eq!(Ipv4Header::size_of(), ipv4.len()); + assert_eq!(4, ip4.version()); + assert_eq!(Ipv4Header::size_of(), ip4.len()); // make sure ether type is fixed - assert_eq!(EtherTypes::Ipv4, ipv4.envelope().ether_type()); + assert_eq!(EtherTypes::Ipv4, ip4.envelope().ether_type()); } #[capsule::test] @@ -705,25 +712,25 @@ mod tests { let _ = packet.extend(0, 2000); let ethernet = packet.push::().unwrap(); - let mut ipv4 = ethernet.push::().unwrap(); + let mut ip4 = ethernet.push::().unwrap(); // can't truncate to less than minimum MTU. - assert!(ipv4.truncate(60).is_err()); + assert!(ip4.truncate(60).is_err()); - assert!(ipv4.len() > 1000); - let _ = ipv4.truncate(1000); - assert_eq!(1000, ipv4.len()); + assert!(ip4.len() > 1000); + let _ = ip4.truncate(1000); + assert_eq!(1000, ip4.len()); } #[capsule::test] fn compute_checksum() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let mut ipv4 = ethernet.parse::().unwrap(); + let mut ip4 = ethernet.parse::().unwrap(); - let expected = ipv4.checksum(); + let expected = ip4.checksum(); // no payload change but force a checksum recompute anyway - ipv4.reconcile_all(); - assert_eq!(expected, ipv4.checksum()); + ip4.reconcile_all(); + assert_eq!(expected, ip4.checksum()); } } diff --git a/core/src/packets/ip/v6/fragment.rs b/core/src/packets/ip/v6/fragment.rs index 15e90ce7..eacb1ac0 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::testils::byte_arrays::{IPV6_FRAGMENT_PACKET, IPV6_TCP_PACKET}; - use crate::Mbuf; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{IPV6_FRAGMENT_PACKET, TCP6_PACKET}; #[test] fn size_of_fragment_header() { @@ -343,8 +343,8 @@ mod tests { fn parse_fragment_packet() { let packet = Mbuf::from_bytes(&IPV6_FRAGMENT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let frag = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let frag = ip6.parse::>().unwrap(); assert_eq!(ProtocolNumbers::Udp, frag.next_header()); assert_eq!(543, frag.fragment_offset()); @@ -354,19 +354,19 @@ mod tests { #[capsule::test] fn parse_non_fragment_packet() { - let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); + let ip6 = ethernet.parse::().unwrap(); - assert!(ipv6.parse::>().is_err()); + assert!(ip6.parse::>().is_err()); } #[capsule::test] fn push_and_set_fragment_packet() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); - let mut frag = ipv6.push::>().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut frag = ip6.push::>().unwrap(); assert_eq!(FragmentHeader::size_of(), frag.len()); assert_eq!(ProtocolNumbers::Ipv6Frag, frag.envelope().next_header()); @@ -391,13 +391,13 @@ mod tests { #[capsule::test] fn insert_fragment_packet() { - let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); + let ip6 = ethernet.parse::().unwrap(); - let next_header = ipv6.next_header(); - let payload_len = ipv6.payload_len(); - let frag = ipv6.push::>().unwrap(); + let next_header = ip6.next_header(); + let payload_len = ip6.payload_len(); + let frag = ip6.push::>().unwrap(); assert_eq!(ProtocolNumbers::Ipv6Frag, frag.envelope().next_header()); assert_eq!(next_header, frag.next_header()); @@ -408,14 +408,14 @@ mod tests { fn remove_fragment_packet() { let packet = Mbuf::from_bytes(&IPV6_FRAGMENT_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let frag = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let frag = ip6.parse::>().unwrap(); let next_header = frag.next_header(); let payload_len = frag.payload_len(); - let ipv6 = frag.remove().unwrap(); + let ip6 = frag.remove().unwrap(); - assert_eq!(next_header, ipv6.next_header()); - assert_eq!(payload_len, ipv6.payload_len()); + assert_eq!(next_header, ip6.next_header()); + assert_eq!(payload_len, ip6.payload_len()); } } diff --git a/core/src/packets/ip/v6/mod.rs b/core/src/packets/ip/v6/mod.rs index 5cf78f79..6b77931c 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::{Datalink, Internal, Packet, SizeOf}; use anyhow::{anyhow, Result}; use std::fmt; use std::net::{IpAddr, Ipv6Addr}; @@ -95,13 +96,13 @@ const FLOW: u32be = u32be(u32::to_be(0xfffff)); /// [IETF RFC 8200]: https://tools.ietf.org/html/rfc8200#section-3 /// [IETF RFC 2474]: https://tools.ietf.org/html/rfc2474 /// [IETF RFC 3168]: https://tools.ietf.org/html/rfc3168 -pub struct Ipv6 { - envelope: Ethernet, +pub struct Ipv6 { + envelope: E, header: NonNull, offset: usize, } -impl Ipv6 { +impl Ipv6 { #[inline] fn header(&self) -> &Ipv6Header { unsafe { self.header.as_ref() } @@ -209,7 +210,7 @@ impl Ipv6 { } } -impl fmt::Debug for Ipv6 { +impl fmt::Debug for Ipv6 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ipv6") .field("src", &format!("{}", self.src())) @@ -227,9 +228,8 @@ impl fmt::Debug for Ipv6 { } } -impl Packet for Ipv6 { - /// The preceding type for IPv6 packet must be Ethernet. - type Envelope = Ethernet; +impl Packet for Ipv6 { + type Envelope = E; #[inline] fn envelope(&self) -> &Self::Envelope { @@ -273,7 +273,7 @@ impl Packet for Ipv6 { #[inline] fn try_parse(envelope: Self::Envelope, _internal: Internal) -> Result { ensure!( - envelope.ether_type() == EtherTypes::Ipv6, + envelope.protocol_type() == EtherTypes::Ipv6, anyhow!("not an IPv6 packet.") ); @@ -306,7 +306,7 @@ impl Packet for Ipv6 { mbuf.extend(offset, Ipv6Header::size_of())?; let header = mbuf.write_data(offset, &Ipv6Header::default())?; - envelope.set_ether_type(EtherTypes::Ipv6); + envelope.set_protocol_type(EtherTypes::Ipv6); Ok(Ipv6 { envelope, @@ -334,7 +334,7 @@ impl Packet for Ipv6 { } } -impl IpPacket for Ipv6 { +impl IpPacket for Ipv6 { #[inline] fn next_protocol(&self) -> ProtocolNumber { self.next_header() @@ -417,7 +417,7 @@ impl IpPacket for Ipv6 { } } -impl Ipv6Packet for Ipv6 { +impl Ipv6Packet for Ipv6 { #[inline] fn next_header(&self) -> ProtocolNumber { ProtocolNumber::new(self.header().next_header) @@ -467,8 +467,8 @@ impl Default for Ipv6Header { mod tests { use super::*; use crate::packets::ip::ProtocolNumbers; - use crate::testils::byte_arrays::{IPV4_UDP_PACKET, IPV6_TCP_PACKET}; - use crate::Mbuf; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{TCP6_PACKET, UDP4_PACKET}; #[test] fn size_of_ipv6_header() { @@ -477,24 +477,24 @@ mod tests { #[capsule::test] fn parse_ipv6_packet() { - let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); + let ip6 = ethernet.parse::().unwrap(); - assert_eq!(6, ipv6.version()); - assert_eq!(0, ipv6.dscp()); - assert_eq!(0, ipv6.ecn()); - assert_eq!(0, ipv6.flow_label()); - assert_eq!(24, ipv6.payload_len()); - assert_eq!(ProtocolNumbers::Tcp, ipv6.next_header()); - assert_eq!(2, ipv6.hop_limit()); - assert_eq!("2001:db8:85a3::1", ipv6.src().to_string()); - assert_eq!("2001:db8:85a3::8a2e:370:7334", ipv6.dst().to_string()); + assert_eq!(6, ip6.version()); + assert_eq!(0, ip6.dscp()); + assert_eq!(0, ip6.ecn()); + assert_eq!(0, ip6.flow_label()); + assert_eq!(24, ip6.payload_len()); + assert_eq!(ProtocolNumbers::Tcp, ip6.next_header()); + assert_eq!(2, ip6.hop_limit()); + assert_eq!("2001:db8:85a3::1", ip6.src().to_string()); + assert_eq!("2001:db8:85a3::8a2e:370:7334", ip6.dst().to_string()); } #[capsule::test] fn parse_non_ipv6_packet() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); assert!(ethernet.parse::().is_err()); @@ -502,33 +502,33 @@ mod tests { #[capsule::test] fn parse_ipv6_setter_checks() { - let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let mut ipv6 = ethernet.parse::().unwrap(); + let mut ip6 = ethernet.parse::().unwrap(); - assert_eq!(6, ipv6.version()); - assert_eq!(0, ipv6.dscp()); - assert_eq!(0, ipv6.ecn()); - assert_eq!(0, ipv6.flow_label()); - ipv6.set_dscp(10); - ipv6.set_ecn(3); - assert_eq!(6, ipv6.version()); - assert_eq!(10, ipv6.dscp()); - assert_eq!(3, ipv6.ecn()); - assert_eq!(0, ipv6.flow_label()); + assert_eq!(6, ip6.version()); + assert_eq!(0, ip6.dscp()); + assert_eq!(0, ip6.ecn()); + assert_eq!(0, ip6.flow_label()); + ip6.set_dscp(10); + ip6.set_ecn(3); + assert_eq!(6, ip6.version()); + assert_eq!(10, ip6.dscp()); + assert_eq!(3, ip6.ecn()); + assert_eq!(0, ip6.flow_label()); } #[capsule::test] fn push_ipv6_packet() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv6 = ethernet.push::().unwrap(); + let ip6 = ethernet.push::().unwrap(); - assert_eq!(6, ipv6.version()); - assert_eq!(Ipv6Header::size_of(), ipv6.len()); + assert_eq!(6, ip6.version()); + assert_eq!(Ipv6Header::size_of(), ip6.len()); // make sure ether type is fixed - assert_eq!(EtherTypes::Ipv6, ipv6.envelope().ether_type()); + assert_eq!(EtherTypes::Ipv6, ip6.envelope().ether_type()); } #[capsule::test] @@ -538,13 +538,13 @@ mod tests { let _ = packet.extend(0, 1800); let ethernet = packet.push::().unwrap(); - let mut ipv6 = ethernet.push::().unwrap(); + let mut ip6 = ethernet.push::().unwrap(); // can't truncate to less than minimum MTU. - assert!(ipv6.truncate(1200).is_err()); + assert!(ip6.truncate(1200).is_err()); - assert!(ipv6.len() > 1500); - let _ = ipv6.truncate(1500); - assert_eq!(1500, ipv6.len()); + assert!(ip6.len() > 1500); + let _ = ip6.truncate(1500); + assert_eq!(1500, ip6.len()); } } diff --git a/core/src/packets/ip/v6/srh.rs b/core/src/packets/ip/v6/srh.rs index 9cefbb9d..3cce4c64 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::testils::byte_arrays::{IPV6_TCP_PACKET, SR_TCP_PACKET}; - use crate::Mbuf; + use crate::packets::tcp::{Tcp, Tcp6}; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{SR_TCP_PACKET, TCP6_PACKET}; #[test] fn size_of_segment_routing_header() { @@ -538,8 +539,8 @@ mod tests { fn parse_segment_routing_packet() { let packet = Mbuf::from_bytes(&SR_TCP_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let srh = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let srh = ip6.parse::>().unwrap(); assert_eq!(ProtocolNumbers::Tcp, srh.next_header()); assert_eq!(6, srh.hdr_ext_len()); @@ -557,19 +558,19 @@ mod tests { #[capsule::test] fn parse_non_segment_routing_packet() { - let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); + let ip6 = ethernet.parse::().unwrap(); - assert!(ipv6.parse::>().is_err()); + assert!(ip6.parse::>().is_err()); } #[capsule::test] fn set_segments() { let packet = Mbuf::from_bytes(&SR_TCP_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut srh = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut srh = ip6.parse::>().unwrap(); let segment1: Ipv6Addr = "::1".parse().unwrap(); @@ -604,8 +605,8 @@ mod tests { fn compute_checksum() { let packet = Mbuf::from_bytes(&SR_TCP_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let mut srh = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let mut srh = ip6.parse::>().unwrap(); let segment1: Ipv6Addr = "::1".parse().unwrap(); let segment2: Ipv6Addr = "::2".parse().unwrap(); @@ -653,11 +654,11 @@ mod tests { #[capsule::test] fn insert_segment_routing_packet() { - let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP6_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let ipv6_payload_len = ipv6.payload_len(); - let srh = ipv6.push::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let ip6_payload_len = ip6.payload_len(); + let srh = ip6.push::>().unwrap(); assert_eq!(2, srh.hdr_ext_len()); assert_eq!(1, srh.segments().len()); @@ -668,7 +669,7 @@ mod tests { assert_eq!(ProtocolNumbers::Ipv6Route, srh.envelope().next_header()); // ipv6 payload is srh payload after push - assert_eq!(ipv6_payload_len, srh.payload_len()); + assert_eq!(ip6_payload_len, srh.payload_len()); // make sure rest of the packet still valid let tcp = srh.parse::>>().unwrap(); assert_eq!(36869, tcp.src_port()); @@ -676,24 +677,24 @@ mod tests { let mut srh = tcp.deparse(); let srh_packet_len = srh.len(); srh.reconcile_all(); - let ipv6 = srh.deparse(); - assert_ne!(srh_packet_len, ipv6_payload_len as usize); - assert_eq!(srh_packet_len, ipv6.payload_length() as usize) + let ip6 = srh.deparse(); + assert_ne!(srh_packet_len, ip6_payload_len as usize); + assert_eq!(srh_packet_len, ip6.payload_length() as usize) } #[capsule::test] fn remove_segment_routing_packet() { let packet = Mbuf::from_bytes(&SR_TCP_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let srh = ipv6.parse::>().unwrap(); - let ipv6 = srh.remove().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let srh = ip6.parse::>().unwrap(); + let ip6 = srh.remove().unwrap(); // make sure next header is fixed - assert_eq!(ProtocolNumbers::Tcp, ipv6.next_header()); + assert_eq!(ProtocolNumbers::Tcp, ip6.next_header()); // make sure rest of the packet still valid - let tcp = ipv6.parse::().unwrap(); + let tcp = ip6.parse::().unwrap(); assert_eq!(3464, tcp.src_port()); } } diff --git a/core/src/dpdk/mbuf.rs b/core/src/packets/mbuf.rs similarity index 87% rename from core/src/dpdk/mbuf.rs rename to core/src/packets/mbuf.rs index 0bd3567b..79d76d0c 100644 --- a/core/src/dpdk/mbuf.rs +++ b/core/src/packets/mbuf.rs @@ -16,71 +16,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -use super::MEMPOOL; -use crate::dpdk::{DpdkError, MempoolError}; -use crate::ffi::{self, ToResult}; -use crate::packets::{Internal, Packet}; +use crate::ffi::dpdk::{self, MbufPtr}; +use crate::packets::{Internal, Packet, SizeOf}; +use crate::runtime::Mempool; use crate::{ensure, trace}; use anyhow::Result; +use capsule_ffi as cffi; use std::fmt; use std::mem; -use std::os::raw; 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 { @@ -113,21 +60,21 @@ enum MbufInner { /// Original version of the message buffer that should be freed when it goes /// out of scope, unless the ownership of the pointer is given back to /// DPDK on transmit. - Original(NonNull), + Original(NonNull), /// A clone version of the message buffer that should not be freed when /// it goes out of scope. - Clone(NonNull), + Clone(NonNull), } impl MbufInner { - fn ptr(&self) -> &NonNull { + fn ptr(&self) -> &NonNull { match self { MbufInner::Original(raw) => raw, MbufInner::Clone(raw) => raw, } } - fn ptr_mut(&mut self) -> &mut NonNull { + fn ptr_mut(&mut self) -> &mut NonNull { match self { MbufInner::Original(ref mut raw) => raw, MbufInner::Clone(ref mut raw) => raw, @@ -144,15 +91,15 @@ impl Mbuf { /// /// # Errors /// - /// Returns `MempoolError::Exhausted` if the allocation of mbuf fails. + /// Returns `MempoolPtrUnsetError` if invoked from a non lcore. + /// Returns `DpdkError` if the allocation of mbuf fails. #[inline] pub fn new() -> Result { - let mempool = MEMPOOL.with(|tls| tls.get()); - let raw = - unsafe { ffi::_rte_pktmbuf_alloc(mempool).into_result(|_| MempoolError::Exhausted)? }; + let mut mp = Mempool::thread_local_ptr()?; + let ptr = dpdk::pktmbuf_alloc(&mut mp)?; Ok(Mbuf { - inner: MbufInner::Original(raw), + inner: MbufInner::Original(ptr.into()), }) } @@ -160,7 +107,7 @@ impl Mbuf { /// /// # Errors /// - /// Returns `MempoolError::Exhausted` if the allocation of mbuf fails. + /// Returns `DpdkError` if the allocation of mbuf fails. /// Returns `BufferError::NotResized` if the byte array is larger than /// the maximum mbuf size. #[inline] @@ -173,21 +120,21 @@ impl Mbuf { /// Creates a new `Mbuf` from a raw pointer. #[inline] - pub(crate) unsafe fn from_ptr(ptr: *mut ffi::rte_mbuf) -> Self { + pub(crate) fn from_easyptr(ptr: MbufPtr) -> Self { Mbuf { - inner: MbufInner::Original(NonNull::new_unchecked(ptr)), + inner: MbufInner::Original(ptr.into()), } } /// Returns the raw struct needed for FFI calls. #[inline] - fn raw(&self) -> &ffi::rte_mbuf { + fn raw(&self) -> &cffi::rte_mbuf { unsafe { self.inner.ptr().as_ref() } } /// Returns the raw struct needed for FFI calls. #[inline] - fn raw_mut(&mut self) -> &mut ffi::rte_mbuf { + fn raw_mut(&mut self) -> &mut cffi::rte_mbuf { unsafe { self.inner.ptr_mut().as_mut() } } @@ -417,10 +364,10 @@ impl Mbuf { /// 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 ffi::rte_mbuf { - let ptr = self.inner.ptr().as_ptr(); + pub(crate) fn into_easyptr(self) -> MbufPtr { + let ptr = *self.inner.ptr(); mem::forget(self); - ptr + ptr.into() } /// Allocates a Vec of `Mbuf`s of `len` size. @@ -430,25 +377,21 @@ impl Mbuf { /// Returns `DpdkError` if the allocation of mbuf fails. pub fn alloc_bulk(len: usize) -> Result> { let mut ptrs = Vec::with_capacity(len); - let mempool = MEMPOOL.with(|tls| tls.get()); - - let mbufs = unsafe { - ffi::_rte_pktmbuf_alloc_bulk(mempool, ptrs.as_mut_ptr(), len as raw::c_uint) - .into_result(DpdkError::from_errno)?; + let mut mp = Mempool::thread_local_ptr()?; + dpdk::pktmbuf_alloc_bulk(&mut mp, &mut ptrs)?; - ptrs.set_len(len); - ptrs.into_iter() - .map(|ptr| Mbuf::from_ptr(ptr)) - .collect::>() - }; + let mbufs = ptrs.into_iter().map(Mbuf::from_easyptr).collect::>(); Ok(mbufs) } /// Frees the message buffers in bulk. pub(crate) fn free_bulk(mbufs: Vec) { - let ptrs = mbufs.into_iter().map(Mbuf::into_ptr).collect::>(); - super::mbuf_free_bulk(ptrs); + let mut ptrs = mbufs + .into_iter() + .map(Mbuf::into_easyptr) + .collect::>(); + dpdk::pktmbuf_free_bulk(&mut ptrs); } } @@ -469,9 +412,7 @@ impl Drop for Mbuf { match self.inner { MbufInner::Original(_) => { trace!("freeing mbuf@{:p}.", self.raw().buf_addr); - unsafe { - ffi::_rte_pktmbuf_free(self.raw_mut()); - } + dpdk::pktmbuf_free(self.inner.ptr().clone().into()); } MbufInner::Clone(_) => (), } diff --git a/core/src/packets/mod.rs b/core/src/packets/mod.rs index b4d5584c..86ea0fb8 100644 --- a/core/src/packets/mod.rs +++ b/core/src/packets/mod.rs @@ -20,18 +20,20 @@ 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 self::mbuf::*; +pub use self::size_of::*; +pub use capsule_macros::SizeOf; -use crate::Mbuf; +use crate::packets::ethernet::EtherType; use anyhow::{Context, Result}; use std::fmt; use std::marker::PhantomData; @@ -300,6 +302,29 @@ pub trait Packet { } } +/// A trait datalink layer protocol can implement. +/// +/// A datalink protocol must implement this trait if it needs to encapsulate +/// network protocols like IP or ARP. Otherwise, it should not implement this +/// trait. +pub trait Datalink: Packet { + /// Gets the encapsulated packet type. + /// + /// Returns the ethernet protocol type codes because ethernet is the most + /// ubiquitous datalink protocol. Other datalink like InfiniBand adopted + /// the ethernet type codes. When implementing a datalink with its own + /// type codes, a translation from ether type is needed. + fn protocol_type(&self) -> EtherType; + + /// Sets the protocol type of the encapsulated packet. + /// + /// Uses the ethernet protocol type codes because ethernet is the most + /// ubiquitous datalink protocol. Other datalink like InfiniBand adopted + /// the ethernet type codes. When implementing a datalink with its own + /// type codes, a translation from ether type is needed. + fn set_protocol_type(&mut self, ether_type: EtherType); +} + /// Immutable smart pointer to a struct. /// /// A smart pointer that prevents the struct from being modified. The main @@ -336,21 +361,37 @@ impl Deref for Immutable<'_, T> { } } +/// Mark of the packet as either `Emit` or `Drop`. +/// +/// Together, a `Result` represents all three possible outcome +/// of packet processing. A packet can either be emitted through port TX, +/// intentionally dropped, or aborted due to an error. +#[derive(Debug)] +pub enum Postmark { + /// Packet emitted through a port TX. + Emit, + + /// Packet intentionally dropped. + Drop(Mbuf), +} + #[cfg(test)] mod tests { use super::*; use crate::net::MacAddr; + use crate::packets::ethernet::Ethernet; use crate::packets::ip::v4::Ipv4; - use crate::testils::byte_arrays::IPV4_UDP_PACKET; + use crate::packets::udp::Udp4; + use crate::testils::byte_arrays::UDP4_PACKET; #[capsule::test] fn parse_and_reset_packet() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let len = packet.data_len(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let udp = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let udp = ip4.parse::().unwrap(); let reset = udp.reset(); assert_eq!(len, reset.data_len()); @@ -358,43 +399,44 @@ mod tests { #[capsule::test] fn peek_packet() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.peek::().unwrap(); assert_eq!(MacAddr::new(0, 0, 0, 0, 0, 2), ethernet.src()); - let v4 = ethernet.peek::().unwrap(); - assert_eq!(255, v4.ttl()); - let udp = v4.peek::().unwrap(); + let ip4 = ethernet.peek::().unwrap(); + assert_eq!(255, ip4.ttl()); + let udp = ip4.peek::().unwrap(); assert_eq!(39376, udp.src_port()); } #[capsule::test] - fn peek_back_via_envelope() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + fn parse_and_deparse_packet() { + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let v4 = ethernet.parse::().unwrap(); - let udp = v4.parse::().unwrap(); - let mut v4_2 = udp.deparse(); - v4_2.set_ttl(25); - let udp_2 = v4_2.parse::().unwrap(); - let v4_4 = udp_2.envelope(); - assert_eq!(v4_4.ttl(), 25); + let mut ip4 = ethernet.parse::().unwrap(); + ip4.set_ttl(25); + + let udp = ip4.parse::().unwrap(); + assert_eq!(udp.envelope().ttl(), 25); + + let ip4_2 = udp.deparse(); + assert_eq!(ip4_2.ttl(), 25); } #[capsule::test] fn remove_header_and_payload() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let v4 = ethernet.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); - let mut udp = v4.parse::().unwrap(); + let mut udp = ip4.parse::().unwrap(); assert!(udp.payload_len() > 0); let _ = udp.remove_payload(); assert_eq!(0, udp.payload_len()); - let v4 = udp.remove().unwrap(); - assert_eq!(0, v4.payload_len()); + let ip4 = udp.remove().unwrap(); + assert_eq!(0, ip4.payload_len()); } /// Demonstrates that `Packet::peek` behaves as an immutable borrow on @@ -403,21 +445,21 @@ mod tests { /// borrow through peek. /// /// ``` - /// | let ipv4 = ethernet.peek::().unwrap(); + /// | let ip4 = ethernet.peek::().unwrap(); /// | -------- immutable borrow occurs here /// | ethernet.set_src(MacAddr::UNSPECIFIED); /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here - /// | assert!(ipv4.payload_len() > 0); + /// | assert!(ip4.payload_len() > 0); /// | ---- immutable borrow later used here /// ``` #[test] #[cfg(feature = "compile_failure")] fn cannot_mutate_packet_while_peeking_into_payload() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let mut ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.peek::().unwrap(); + let ip4 = ethernet.peek::().unwrap(); ethernet.set_src(MacAddr::UNSPECIFIED); - assert!(ipv4.payload_len() > 0); + assert!(ip4.payload_len() > 0); } /// Demonstrates that `Packet::peek` returns an immutable packet wrapper. @@ -430,7 +472,7 @@ mod tests { #[test] #[cfg(feature = "compile_failure")] fn cannot_mutate_immutable_packet() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.peek::().unwrap(); ethernet.set_src(MacAddr::UNSPECIFIED); } 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..7391e56d 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; @@ -621,10 +623,10 @@ impl Packet for Tcp { } } -/// A type alias for an IPv4 TCP packet. +/// A type alias for an Ethernet IPv4 TCP packet. pub type Tcp4 = Tcp; -/// A type alias for an IPv6 TCP packet. +/// A type alias for an Ethernet IPv6 TCP packet. pub type Tcp6 = Tcp; /// TCP header. @@ -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::testils::byte_arrays::{IPV4_TCP_PACKET, IPV4_UDP_PACKET, SR_TCP_PACKET}; - use crate::Mbuf; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{SR_TCP_PACKET, TCP4_PACKET, UDP4_PACKET}; use std::net::{Ipv4Addr, Ipv6Addr}; #[test] @@ -677,10 +679,10 @@ mod tests { #[capsule::test] fn parse_tcp_packet() { - let packet = Mbuf::from_bytes(&IPV4_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let tcp = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let tcp = ip4.parse::().unwrap(); assert_eq!(36869, tcp.src_port()); assert_eq!(23, tcp.dst_port()); @@ -703,19 +705,19 @@ mod tests { #[capsule::test] fn parse_non_tcp_packet() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); - assert!(ipv4.parse::().is_err()); + assert!(ip4.parse::().is_err()); } #[capsule::test] fn tcp_flow_v4() { - let packet = Mbuf::from_bytes(&IPV4_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let tcp = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let tcp = ip4.parse::().unwrap(); let flow = tcp.flow(); assert_eq!("139.133.217.110", flow.src_ip().to_string()); @@ -729,8 +731,8 @@ mod tests { fn tcp_flow_v6() { let packet = Mbuf::from_bytes(&SR_TCP_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv6 = ethernet.parse::().unwrap(); - let srh = ipv6.parse::>().unwrap(); + let ip6 = ethernet.parse::().unwrap(); + let srh = ip6.parse::>().unwrap(); let tcp = srh.parse::>>().unwrap(); let flow = tcp.flow(); @@ -743,10 +745,10 @@ mod tests { #[capsule::test] fn set_src_dst_ip() { - let packet = Mbuf::from_bytes(&IPV4_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let mut tcp = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let mut tcp = ip4.parse::().unwrap(); let old_checksum = tcp.checksum(); let new_ip = Ipv4Addr::new(10, 0, 0, 0); @@ -766,10 +768,10 @@ mod tests { #[capsule::test] fn compute_checksum() { - let packet = Mbuf::from_bytes(&IPV4_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let mut tcp = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let mut tcp = ip4.parse::().unwrap(); let expected = tcp.checksum(); // no payload change but force a checksum recompute anyway @@ -781,8 +783,8 @@ mod tests { fn push_tcp_packet() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv4 = ethernet.push::().unwrap(); - let tcp = ipv4.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); + let tcp = ip4.push::().unwrap(); assert_eq!(TcpHeader::size_of(), tcp.len()); assert_eq!(5, tcp.data_offset()); diff --git a/core/src/packets/udp.rs b/core/src/packets/udp.rs index 62fae34d..42512e87 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; @@ -146,6 +148,19 @@ impl Udp { self.header_mut().checksum = u16be::default(); } + /// Returns the data as a `u8` slice. + #[inline] + pub fn data(&self) -> &[u8] { + if let Ok(data) = self + .mbuf() + .read_data_slice(self.payload_offset(), self.payload_len()) + { + unsafe { &*data.as_ptr() } + } else { + unreachable!() + } + } + /// Returns the 5-tuple that uniquely identifies a UDP connection. #[inline] pub fn flow(&self) -> Flow { @@ -354,10 +369,10 @@ impl Packet for Udp { } } -/// A type alias for an IPv4 UDP packet. +/// A type alias for an Ethernet IPv4 UDP packet. pub type Udp4 = Udp; -/// A type alias for an IPv6 UDP packet. +/// A type alias for an Ethernet IPv6 UDP packet. pub type Udp6 = Udp; /// UDP header. @@ -373,9 +388,9 @@ struct UdpHeader { #[cfg(test)] mod tests { use super::*; - use crate::packets::Ethernet; - use crate::testils::byte_arrays::{IPV4_TCP_PACKET, IPV4_UDP_PACKET}; - use crate::Mbuf; + use crate::packets::ethernet::Ethernet; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{TCP4_PACKET, UDP4_PACKET}; use std::net::{Ipv4Addr, Ipv6Addr}; #[test] @@ -385,32 +400,33 @@ mod tests { #[capsule::test] fn parse_udp_packet() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let udp = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let udp = ip4.parse::().unwrap(); assert_eq!(39376, udp.src_port()); assert_eq!(1087, udp.dst_port()); assert_eq!(18, udp.length()); assert_eq!(0x7228, udp.checksum()); + assert_eq!(10, udp.data().len()); } #[capsule::test] fn parse_non_udp_packet() { - let packet = Mbuf::from_bytes(&IPV4_TCP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&TCP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); - assert!(ipv4.parse::().is_err()); + assert!(ip4.parse::().is_err()); } #[capsule::test] fn udp_flow_v4() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let udp = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let udp = ip4.parse::().unwrap(); let flow = udp.flow(); assert_eq!("139.133.217.110", flow.src_ip().to_string()); @@ -422,10 +438,10 @@ mod tests { #[capsule::test] fn set_src_dst_ip() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let mut udp = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let mut udp = ip4.parse::().unwrap(); let old_checksum = udp.checksum(); let new_ip = Ipv4Addr::new(10, 0, 0, 0); @@ -445,10 +461,10 @@ mod tests { #[capsule::test] fn compute_checksum() { - let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap(); + let packet = Mbuf::from_bytes(&UDP4_PACKET).unwrap(); let ethernet = packet.parse::().unwrap(); - let ipv4 = ethernet.parse::().unwrap(); - let mut udp = ipv4.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let mut udp = ip4.parse::().unwrap(); let expected = udp.checksum(); // no payload change but force a checksum recompute anyway @@ -460,8 +476,8 @@ mod tests { fn push_udp_packet() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); - let ipv4 = ethernet.push::().unwrap(); - let udp = ipv4.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); + let udp = ip4.push::().unwrap(); assert_eq!(UdpHeader::size_of(), udp.len()); 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/config.rs b/core/src/runtime/config.rs similarity index 59% rename from core/src/config.rs rename to core/src/runtime/config.rs index 5ae8b14d..6aaf30c3 100644 --- a/core/src/config.rs +++ b/core/src/runtime/config.rs @@ -21,10 +21,11 @@ //! # Example //! //! A configuration from our [`pktdump`] example: +//! //! ``` //! app_name = "pktdump" -//! master_core = 0 -//! duration = 5 +//! main_core = 0 +//! worker_cores = [0] //! //! [mempool] //! capacity = 65535 @@ -34,100 +35,26 @@ //! name = "eth1" //! device = "net_pcap0" //! args = "rx_pcap=tcp4.pcap,tx_iface=lo" -//! cores = [0] +//! rx_core = [0] +//! tx_core = [0] //! //! [[ports]] //! name = "eth2" //! device = "net_pcap1" //! args = "rx_pcap=tcp6.pcap,tx_iface=lo" -//! cores = [0] +//! rx_core = [0] +//! tx_core = [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 capsule_ffi as cffi; use clap::{clap_app, crate_version}; use regex::Regex; -use serde::{de, Deserialize, Deserializer}; +use serde::Deserialize; 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)] @@ -151,16 +78,19 @@ pub struct RuntimeConfig { #[serde(default)] pub app_group: Option, - /// The identifier of the master core. This is the core the main thread + /// The identifier of the main core. This is the core the main thread /// will run on. - pub master_core: CoreId, + pub main_core: usize, - /// 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. + /// Worker cores used for packet processing and general async task + /// execution. + pub worker_cores: Vec, + + /// The root data directory the application writes to. + /// + /// If unset, the default is `/var/capsule/{app_name}`. #[serde(default)] - pub cores: Vec, + pub data_dir: Option, /// Per mempool settings. On a system with multiple sockets, aka NUMA /// nodes, one mempool will be allocated for each socket the apllication @@ -177,25 +107,23 @@ pub struct RuntimeConfig { /// [`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 { + fn other_cores(&self) -> Vec { let mut cores = vec![]; - cores.push(self.master_core); - cores.extend(self.cores.iter()); + cores.extend(&self.worker_cores); self.ports.iter().for_each(|port| { - cores.extend(port.cores.iter()); + if !port.rx_cores.is_empty() { + cores.extend(&port.rx_cores); + } + if !port.tx_cores.is_empty() { + cores.extend(&port.tx_cores); + } }); - cores.sort(); + cores.sort_unstable(); cores.dedup(); cores } @@ -204,29 +132,28 @@ impl RuntimeConfig { pub(crate) fn to_eal_args(&self) -> Vec { let mut eal_args = vec![]; - // adds the app name + // adds the app name. eal_args.push(self.app_name.clone()); + // adds the proc type. let proc_type = if self.secondary { - "secondary".to_owned() + "secondary" } else { - "primary".to_owned() + "primary" }; + eal_args.push("--proc-type".into()); + eal_args.push(proc_type.into()); - // adds the proc type - eal_args.push("--proc-type".to_owned()); - eal_args.push(proc_type); - - // adds the mem file prefix + // 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("--file-prefix".into()); eal_args.push(prefix.clone()); - // adds all the ports + // 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("--pci-whitelist".into()); eal_args.push(port.device.clone()); } else { let vdev = if let Some(args) = &port.args { @@ -234,53 +161,74 @@ impl RuntimeConfig { } else { port.device.clone() }; - eal_args.push("--vdev".to_owned()); + eal_args.push("--vdev".into()); eal_args.push(vdev); } }); - // adds the master core - eal_args.push("--master-lcore".to_owned()); - eal_args.push(self.master_core.raw().to_string()); + let mut main = self.main_core; + let others = self.other_cores(); - // 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()); + // if the main lcore is also used for other tasks, we will assign + // another lcore to be the main, and set the affinity to the same + // physical core/cpu. this is necessary because we need to be able + // to run an executor for other tasks without blocking the main + // application thread. + if others.contains(&main) { + main = cffi::RTE_MAX_LCORE as usize - 1; + } - // adds additional DPDK args + // adds the main core. + eal_args.push("--master-lcore".into()); + eal_args.push(main.to_string()); + + // adds all the lcores. + let mut cores = others + .iter() + .map(ToString::to_string) + .collect::>() + .join(","); + cores.push_str(&format!(",{}@{}", main, self.main_core)); + eal_args.push("--lcores".to_owned()); + eal_args.push(cores); + + // adds additional DPDK args. if let Some(args) = &self.dpdk_args { - eal_args.extend(args.split_ascii_whitespace().map(str::to_owned)); + eal_args.extend(args.split_ascii_whitespace().map(ToString::to_string)); } eal_args } - /// Returns the number of KNI enabled ports - pub(crate) fn num_knis(&self) -> usize { - self.ports.iter().filter(|p| p.kni).count() + /// Returns the data directory. + #[allow(dead_code)] + pub(crate) fn data_dir(&self) -> String { + self.data_dir.clone().unwrap_or_else(|| { + let base_dir = "/var/capsule"; + match &self.app_group { + Some(group) => format!("{}/{}/{}", base_dir, group, self.app_name), + None => format!("{}/{}", base_dir, self.app_name), + } + }) } } impl fmt::Debug for RuntimeConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut d = f.debug_struct("runtime"); + let mut d = f.debug_struct("RuntimeConfig"); 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("main_core", &self.main_core) + .field("worker_cores", &self.worker_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() } } @@ -321,7 +269,7 @@ impl Default for MempoolConfig { impl fmt::Debug for MempoolConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("mempool") + f.debug_struct("MempoolConfig") .field("capacity", &self.capacity) .field("cache_size", &self.cache_size) .finish() @@ -347,60 +295,68 @@ pub struct PortConfig { #[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 lcores to receive packets on. When no lcore specified, the port + /// will be TX only. + #[serde(default)] + pub rx_cores: Vec, - /// The receive queue capacity. Defaults to `128`. - #[serde(default = "default_port_rxd")] - pub rxd: usize, + /// The lcores to transmit packets on. When no lcore specified, the port + /// will be RX only. + #[serde(default)] + pub tx_cores: Vec, - /// The transmit queue capacity. Defaults to `128`. - #[serde(default = "default_port_txd")] - pub txd: usize, + /// The receive queue size. Defaults to `128`. + #[serde(default = "default_port_rxqs")] + pub rxqs: usize, - /// Whether promiscuous mode is enabled for this port. Defaults to `false`. - #[serde(default)] + /// The transmit queue size. Defaults to `128`. + #[serde(default = "default_port_txqs")] + pub txqs: usize, + + /// Whether promiscuous mode is enabled for this port. Defaults to `true`. + #[serde(default = "default_promiscuous_mode")] 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 { +fn default_port_rxqs() -> usize { 128 } -fn default_port_txd() -> usize { +fn default_port_txqs() -> usize { 128 } +fn default_promiscuous_mode() -> bool { + true +} + 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"); + let mut d = f.debug_struct("PortConfig"); 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) + if !self.rx_cores.is_empty() { + d.field("rx_cores", &self.rx_cores); + } + if !self.tx_cores.is_empty() { + d.field("tx_cores", &self.tx_cores); + } + d.field("rxqs", &self.rxqs) + .field("txqs", &self.txqs) .field("promiscuous", &self.promiscuous) .field("multicast", &self.multicast) - .field("kni", &self.kni) .finish() } } @@ -429,64 +385,66 @@ mod tests { use super::*; #[test] - fn config_defaults() { + fn config_defaults() -> Result<()> { const CONFIG: &str = r#" app_name = "myapp" - master_core = 0 - + main_core = 0 + worker_cores = [1, 2] [[ports]] name = "eth0" device = "0000:00:01.0" - cores = [2, 3] "#; - let config: RuntimeConfig = toml::from_str(CONFIG).unwrap(); + let config: RuntimeConfig = toml::from_str(CONFIG)?; assert_eq!(false, config.secondary); assert_eq!(None, config.app_group); - assert!(config.cores.is_empty()); + assert_eq!(None, config.data_dir); 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!(config.ports[0].rx_cores.is_empty()); + assert!(config.ports[0].tx_cores.is_empty()); + assert_eq!(default_port_rxqs(), config.ports[0].rxqs); + assert_eq!(default_port_txqs(), config.ports[0].txqs); + assert_eq!(default_promiscuous_mode(), config.ports[0].promiscuous); assert_eq!(default_multicast_mode(), config.ports[0].multicast); - assert_eq!(false, config.ports[0].kni); + + assert_eq!("/var/capsule/myapp", &config.data_dir()); + + Ok(()) } #[test] - fn config_to_eal_args() { + fn config_to_eal_args() -> Result<()> { const CONFIG: &str = r#" app_name = "myapp" secondary = false app_group = "mygroup" - master_core = 0 - cores = [1] + main_core = 0 + worker_cores = [1, 2] 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 - + rx_cores = [3] + tx_cores = [0] + rxqs = 32 + txqs = 32 [[ports]] name = "eth1" device = "net_pcap0" args = "rx=lo,tx=lo" - cores = [0, 4] - rxd = 32 - txd = 32 + tx_cores = [4] + rxqs = 32 + txqs = 32 "#; - let config: RuntimeConfig = toml::from_str(CONFIG).unwrap(); + let config: RuntimeConfig = toml::from_str(CONFIG)?; assert_eq!( &[ @@ -500,14 +458,16 @@ mod tests { "--vdev", "net_pcap0,rx=lo,tx=lo", "--master-lcore", - "0", - "-l", - "0", + "127", + "--lcores", + "0,1,2,3,4,127@0", "-v", "--log-level", "eal:8" ], config.to_eal_args().as_slice(), - ) + ); + + Ok(()) } } 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/runtime/lcore.rs b/core/src/runtime/lcore.rs new file mode 100644 index 00000000..7fb11595 --- /dev/null +++ b/core/src/runtime/lcore.rs @@ -0,0 +1,163 @@ +/* +* 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::ShutdownTrigger; +use crate::ffi::dpdk::{self, LcoreId}; +use crate::{debug, info}; +use anyhow::Result; +use async_executor::Executor; +use futures_lite::future; +use std::collections::HashMap; +use std::fmt; +use std::future::Future; +use std::sync::Arc; +use thiserror::Error; + +/// An async executor abstraction on top of a DPDK logical core. +pub struct Lcore { + id: LcoreId, + executor: Arc>, + shutdown: Option, +} + +impl Lcore { + /// Creates a new executor for the given lcore id. + /// + /// # Errors + /// + /// Returns `DpdkError` if the executor fails to run on the given lcore. + fn new(id: LcoreId) -> Result { + debug!(?id, "starting lcore."); + let trigger = ShutdownTrigger::new(); + let executor = Arc::new(Executor::new()); + + let handle = trigger.get_wait(); + let executor2 = Arc::clone(&executor); + dpdk::eal_remote_launch(id, move || { + info!(?id, "lcore started."); + let _ = future::block_on(executor2.run(handle.wait())); + info!(?id, "lcore stopped."); + })?; + + Ok(Lcore { + id, + executor, + shutdown: Some(trigger), + }) + } + + /// Returns the lcore id. + pub(crate) fn id(&self) -> LcoreId { + self.id + } + + /// Spawns an async task and waits for it to complete. + pub(crate) fn block_on( + &self, + future: impl Future + Send + 'static, + ) -> T { + let task = self.executor.spawn(future); + future::block_on(task) + } + + /// Spawns a background async task. + pub fn spawn(&self, future: impl Future + Send + 'static) { + self.executor.spawn(future).detach(); + } +} + +impl fmt::Debug for Lcore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Lcore").field("id", &self.id()).finish() + } +} + +impl Drop for Lcore { + fn drop(&mut self) { + if let Some(trigger) = self.shutdown.take() { + debug!(id = ?self.id, "stopping lcore."); + trigger.fire(); + } + } +} + +/// Lcore not found error. +#[derive(Debug, Error)] +#[error("lcore not found.")] +pub struct LcoreNotFound; + +/// Map to lookup the lcore by the assigned id. +#[derive(Debug)] +pub struct LcoreMap(HashMap); + +impl LcoreMap { + /// Returns the lcore with the assigned id. + pub fn get(&self, id: usize) -> Result<&Lcore> { + self.0.get(&id).ok_or_else(|| LcoreNotFound.into()) + } + + /// Returns a lcore iterator. + pub fn iter(&self) -> impl Iterator { + self.0.values() + } +} + +impl From> for LcoreMap { + fn from(lcores: Vec) -> Self { + let map = lcores + .into_iter() + .map(|lcore| (lcore.id.raw(), lcore)) + .collect::>(); + LcoreMap(map) + } +} + +/// Returns the enabled worker lcores. +pub(crate) fn lcore_pool() -> LcoreMap { + let mut lcores = Vec::new(); + let mut current = None; + + while let Some(id) = dpdk::get_next_lcore(current, true, false) { + lcores.push(Lcore::new(id).unwrap()); + current = Some(id); + } + + lcores.into() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::thread; + + #[capsule::test] + fn get_current_lcore_id_from_eal() { + let next_id = dpdk::get_next_lcore(None, true, false).expect("panic!"); + let lcore = Lcore::new(next_id).expect("panic!"); + let lcore_id = lcore.block_on(async { LcoreId::current() }); + + assert_eq!(next_id, lcore_id); + } + + #[capsule::test] + fn get_current_lcore_id_from_non_eal() { + let lcore_id = thread::spawn(LcoreId::current).join().expect("panic!"); + + assert_eq!(LcoreId::ANY, lcore_id); + } +} diff --git a/core/src/runtime/mempool.rs b/core/src/runtime/mempool.rs new file mode 100644 index 00000000..97d8265d --- /dev/null +++ b/core/src/runtime/mempool.rs @@ -0,0 +1,169 @@ +/* +* 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::ffi::dpdk::{self, LcoreId, MempoolPtr, SocketId}; +use crate::ffi::AsStr; +use crate::{debug, info}; +use anyhow::Result; +use std::cell::Cell; +use std::fmt; +use std::ptr::{self, NonNull}; +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 struct Mempool { + ptr: MempoolPtr, +} + +impl Mempool { + /// Creates a new `Mempool`. + /// + /// `capacity` is the maximum number of Mbufs in the pool. The optimum + /// size (in terms of memory usage) is when n is a power of two minus one. + /// + /// `cache_size` is the per core cache size. If `cache_size` is non-zero, + /// caching is enabled. New `Mbuf` will be retrieved first from cache, + /// subsequently from the common pool. The cache can be disabled if + /// `cache_size` is set to 0. + /// + /// # Errors + /// + /// Returns `DpdkError` if the mempool allocation fails. + pub(crate) fn new>( + name: S, + capacity: usize, + cache_size: usize, + socket_id: SocketId, + ) -> Result { + let name: String = name.into(); + let ptr = dpdk::pktmbuf_pool_create(&name, capacity, cache_size, socket_id)?; + + info!(?name, "pool created."); + + Ok(Self { ptr }) + } + + /// Returns the raw pointer. + #[inline] + pub(crate) fn ptr_mut(&mut self) -> &mut MempoolPtr { + &mut self.ptr + } + + /// Returns the pool name. + #[inline] + pub(crate) fn name(&self) -> &str { + self.ptr.name[..].as_str() + } + + /// Returns the maximum number of Mbufs in the pool. + #[inline] + pub fn capacity(&self) -> usize { + self.ptr.size as usize + } + + /// Returns the per core cache size. + #[inline] + pub fn cache_size(&self) -> usize { + self.ptr.cache_size as usize + } + + /// Returns the socket the pool is allocated from. + #[inline] + pub(crate) fn socket(&self) -> SocketId { + self.ptr.socket_id.into() + } + + /// Returns the thread local mempool pointer. + /// + /// # Errors + /// + /// Returns `MempoolPtrUnsetError` if the thread local pointer is not + /// set. For example when invoked from a non-EAL thread. + pub(crate) fn thread_local_ptr() -> Result { + let ptr = MEMPOOL.with(|tls| tls.get()); + NonNull::new(ptr) + .ok_or_else(|| MempoolPtrUnsetError(LcoreId::current()).into()) + .map(|ptr| ptr.into()) + } +} + +impl fmt::Debug for Mempool { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Mempool") + .field("name", &self.name()) + .field("capacity", &self.capacity()) + .field("cache_size", &self.cache_size()) + .field("socket", &self.socket()) + .finish() + } +} + +impl Drop for Mempool { + fn drop(&mut self) { + let name = self.name().to_string(); + debug!(?name, "freeing mempool."); + dpdk::mempool_free(&mut self.ptr); + info!(?name, "mempool freed."); + } +} + +/// The thread local mempool is not set. +#[derive(Debug, Error)] +#[error("thread local mempool pointer not set for {0:?}.")] +pub(crate) struct MempoolPtrUnsetError(LcoreId); + +thread_local! { + /// The `Mempool` assigned to the core when the core is initialized. + /// `Mbuf::new` uses this pool to allocate new buffers when executed on + /// the core. For best performance, the `Mempool` and the core should + /// share the same socket. + pub(crate) static MEMPOOL: Cell<*mut capsule_ffi::rte_mempool> = Cell::new(ptr::null_mut()); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[capsule::test] + fn create_mempool() -> Result<()> { + let pool = Mempool::new("pool1", 15, 1, SocketId::ANY)?; + + assert_eq!("pool1", pool.name()); + assert_eq!(15, pool.capacity()); + assert_eq!(1, pool.cache_size()); + + Ok(()) + } + + #[capsule::test] + fn drop_mempool() -> Result<()> { + let name = "pool2"; + let pool = Mempool::new(name, 7, 0, SocketId::ANY)?; + + let res = dpdk::mempool_lookup(name); + assert!(res.is_ok()); + + drop(pool); + + let res = dpdk::mempool_lookup(name); + assert!(res.is_err()); + + Ok(()) + } +} 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/runtime/pcap_dump.rs b/core/src/runtime/pcap_dump.rs new file mode 100644 index 00000000..87d8b503 --- /dev/null +++ b/core/src/runtime/pcap_dump.rs @@ -0,0 +1,221 @@ +/* +* 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::{LcoreMap, PortMap}; +use crate::ffi::dpdk::{self, MbufPtr, RxTxCallbackGuard}; +use crate::ffi::pcap::{self, DumperPtr, PcapPtr}; +use crate::{info, warn}; +use anyhow::{anyhow, Result}; +use capsule_ffi as cffi; +use std::fs; +use std::os::raw; +use std::path::Path; +use std::slice; +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Manages the lifecycle of a capture file. +struct CaptureFile { + path: String, + handle: PcapPtr, + dumper: DumperPtr, + guard: Option, +} + +impl CaptureFile { + /// Creates a new pcap file. + fn new(path: &str) -> Result { + let mut handle = pcap::open_dead()?; + let dumper = pcap::dump_open(&mut handle, path)?; + info!(file = ?path, "file opened."); + Ok(CaptureFile { + path: path.to_string(), + handle, + dumper, + guard: None, + }) + } + + /// Sets the RAII guard. + fn set_guard(&mut self, guard: RxTxCallbackGuard) { + self.guard = Some(guard); + } +} + +impl Drop for CaptureFile { + fn drop(&mut self) { + if let Some(guard) = self.guard.take() { + // unwires the rx/tx callback first. + drop(guard); + } + + pcap::dump_close(&mut self.dumper); + pcap::close(&mut self.handle); + info!(file = ?self.path, "file closed."); + } +} + +/// 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>, +} + +impl PcapDump { + /// Creates a new instance. + pub(crate) fn new(data_dir: &str) -> Result { + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + let path = Path::new(data_dir) + .join("pdump") + .join(timestamp.to_string()); + fs::create_dir_all(path.clone())?; + let output_dir = path + .to_str() + .ok_or_else(|| anyhow!("bad pdump output dir."))? + .to_string(); + + Ok(PcapDump { + output_dir, + captures: vec![], + }) + } + + /// Creates a new capture file. + fn new_capture(&mut self, filename: &str) -> Result<&mut Box> { + let path = format!("{}/{}", self.output_dir, filename); + let capture = CaptureFile::new(&path)?; + self.captures.push(Box::new(capture)); + Ok(self.captures.last_mut().unwrap()) + } +} + +/// Enables the pcap dump. +pub(crate) fn enable_pcap_dump( + data_dir: &str, + ports: &PortMap, + lcores: &LcoreMap, +) -> Result { + info!("enabling pcap dump ..."); + + let mut pcap_dump = PcapDump::new(data_dir)?; + + for port in ports.iter() { + for (index, lcore_id) in port.rx_lcores().iter().enumerate() { + let lcore = lcores.get(*lcore_id)?; + let filename = format!("{}-rx-{:?}.pcap", port.name(), lcore.id()); + let capture = pcap_dump.new_capture(&filename)?; + let guard = dpdk::eth_add_rx_callback( + port.port_id(), + index.into(), + Some(rx_callback_fn), + capture.as_mut(), + )?; + capture.set_guard(guard); + } + + for (index, lcore_id) in port.tx_lcores().iter().enumerate() { + let lcore = lcores.get(*lcore_id)?; + let filename = format!("{}-tx-{:?}.pcap", port.name(), lcore.id()); + let capture = pcap_dump.new_capture(&filename)?; + let guard = dpdk::eth_add_tx_callback( + port.port_id(), + index.into(), + Some(tx_callback_fn), + capture.as_mut(), + )?; + capture.set_guard(guard); + } + } + + Ok(pcap_dump) +} + +fn dump_mbufs(dumper: &mut DumperPtr, mbufs: &[MbufPtr]) { + for mbuf in mbufs { + pcap::dump(dumper, mbuf); + } + + if let Err(error) = pcap::dump_flush(dumper) { + warn!(?error); + } +} + +unsafe extern "C" fn rx_callback_fn( + _port_id: u16, + _queue_id: u16, + pkts: *mut *mut cffi::rte_mbuf, + num_pkts: u16, + _max_pkts: u16, + user_param: *mut raw::c_void, +) -> u16 { + let capture = Box::leak(Box::from_raw(user_param as *mut CaptureFile)); + let mbufs = slice::from_raw_parts_mut(pkts as *mut MbufPtr, num_pkts as usize); + dump_mbufs(&mut capture.dumper, &mbufs); + num_pkts +} + +unsafe extern "C" fn tx_callback_fn( + _port_id: u16, + _queue_id: u16, + pkts: *mut *mut cffi::rte_mbuf, + num_pkts: u16, + user_param: *mut raw::c_void, +) -> u16 { + let capture = Box::leak(Box::from_raw(user_param as *mut CaptureFile)); + let mbufs = slice::from_raw_parts_mut(pkts as *mut MbufPtr, num_pkts as usize); + dump_mbufs(&mut capture.dumper, &mbufs); + num_pkts +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{TCP4_PACKET, UDP4_PACKET}; + + #[capsule::test] + fn dump_mbufs_to_file() -> Result<()> { + let filename = "file.pcap"; + let mut capture = CaptureFile::new(filename)?; + + let tcp = Mbuf::from_bytes(&TCP4_PACKET)?; + let udp = Mbuf::from_bytes(&UDP4_PACKET)?; + + dump_mbufs( + &mut capture.dumper, + &[tcp.into_easyptr(), udp.into_easyptr()], + ); + + drop(capture); + + // reads the packets from file and assert they are the same. + let mut h2 = pcap::open_offline(filename)?; + let packet = pcap::next(&mut h2)?; + assert_eq!(&TCP4_PACKET, packet); + let packet = pcap::next(&mut h2)?; + assert_eq!(&UDP4_PACKET, packet); + + pcap::close(&mut h2); + + fs::remove_file(filename)?; + + Ok(()) + } +} diff --git a/core/src/runtime/port.rs b/core/src/runtime/port.rs new file mode 100644 index 00000000..0a941e5f --- /dev/null +++ b/core/src/runtime/port.rs @@ -0,0 +1,729 @@ +/* +* 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::{LcoreMap, Mempool, ShutdownTrigger}; +use crate::ffi::dpdk::{self, LcoreId, MbufPtr, PortId, PortRxQueueId, PortTxQueueId}; +use crate::net::MacAddr; +use crate::packets::{Mbuf, Packet, Postmark}; +use crate::{debug, ensure, info, warn}; +use anyhow::Result; +use async_channel::{self, Receiver, Sender}; +use capsule_ffi as cffi; +use futures_lite::future; +use std::collections::HashMap; +use std::fmt; +use thiserror::Error; + +/// A PMD device port. +pub struct Port { + name: String, + port_id: PortId, + rx_lcores: Vec, + tx_lcores: Vec, + outbox: Option>, + shutdown: Option, +} + +impl Port { + /// 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 fn name(&self) -> &str { + &self.name + } + + /// Returns the port ID. + pub(crate) fn port_id(&self) -> PortId { + self.port_id + } + + /// Returns the assigned RX lcores. + pub fn rx_lcores(&self) -> &Vec { + &self.rx_lcores + } + + /// Returns the assigned TX lcores. + pub fn tx_lcores(&self) -> &Vec { + &self.tx_lcores + } + + /// Returns the MAC address of the port. + /// + /// If fails to retrieve the MAC address, `MacAddr::default` is returned. + pub fn mac_addr(&self) -> MacAddr { + dpdk::eth_macaddr_get(self.port_id).unwrap_or_default() + } + + /// Returns whether the port has promiscuous mode enabled. + pub fn promiscuous(&self) -> bool { + dpdk::eth_promiscuous_get(self.port_id) + } + + /// Returns whether the port has multicast mode enabled. + pub fn multicast(&self) -> bool { + dpdk::eth_allmulticast_get(self.port_id) + } + + /// Returns the outbox queue. + /// + /// # Errors + /// + /// Returns `PortError::TxNotEnabled` if the port is not configured + /// to transmit packets. + pub fn outbox(&self) -> Result { + self.outbox + .as_ref() + .map(|s| Outbox(self.name.clone(), s.clone())) + .ok_or_else(|| PortError::TxNotEnabled.into()) + } + + /// Spawns the port receiving loop. + pub(crate) fn spawn_rx_loops(&self, f: F, lcores: &LcoreMap) -> Result<()> + where + F: Fn(Mbuf) -> Result + Clone + Send + Sync + 'static, + { + // port is built with the builder, this would not panic. + let shutdown = self.shutdown.as_ref().unwrap(); + + // can't run loop without assigned rx cores. + ensure!(!self.rx_lcores.is_empty(), PortError::RxNotEnabled); + // pipeline already set if the trigger is waited on. + ensure!(!shutdown.is_waited(), PortError::PipelineSet); + + for (index, lcore_id) in self.rx_lcores.iter().enumerate() { + let lcore = lcores.get(*lcore_id)?; + let port_name = self.name.clone(); + let handle = shutdown.get_wait(); + let f = f.clone(); + + debug!(port = ?self.name, lcore = ?lcore.id(), "spawning rx loop."); + + // the rx loop is endless, so we use a shutdown trigger to signal + // when the loop should stop executing. + lcore.spawn(future::or( + async move { + handle.wait().await; + debug!(port = ?port_name, lcore = ?LcoreId::current(), "rx loop exited."); + }, + rx_loop(self.name.clone(), self.port_id, index.into(), 32, f), + )); + } + + Ok(()) + } + + /// Spawns the port transmitting loop. + pub(crate) fn spawn_tx_loops(&mut self, lcores: &LcoreMap) -> Result<()> { + // though the channel is unbounded, in reality, it's bounded by the + // mempool size because that's the max number of mbufs the program + // has allocated. + let (sender, receiver) = async_channel::unbounded(); + + for (index, lcore_id) in self.tx_lcores.iter().enumerate() { + let lcore = lcores.get(*lcore_id)?; + let receiver = receiver.clone(); + + debug!(port = ?self.name, lcore = ?lcore.id(), "spawning tx loop."); + + lcore.spawn(tx_loop( + self.name.clone(), + self.port_id, + index.into(), + 32, + receiver, + )) + } + + self.outbox = Some(sender); + Ok(()) + } + + /// Starts the port. This is the final step before packets can be + /// received or transmitted on this port. + /// + /// # Errors + /// + /// Returns `DpdkError` if the port fails to start. + pub(crate) fn start(&self) -> Result<()> { + dpdk::eth_dev_start(self.port_id)?; + info!(port = ?self.name, "port started."); + Ok(()) + } + + /// Stops the port. + pub(crate) fn stop(&mut self) { + if let Some(trigger) = self.shutdown.take() { + debug!(port = ?self.name, "exiting rx loops."); + trigger.fire(); + } + + dpdk::eth_dev_stop(self.port_id); + info!(port = ?self.name, "port stopped."); + } +} + +impl fmt::Debug for Port { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Port") + .field("name", &self.name()) + .field("port_id", &self.port_id()) + .field("mac_addr", &format_args!("{}", self.mac_addr())) + .field("rx_lcores", &self.rx_lcores) + .field("tx_lcores", &self.tx_lcores) + .field("promiscuous", &self.promiscuous()) + .field("multicast", &self.multicast()) + .finish() + } +} + +/// An in-memory queue of packets waiting for transmission. +#[derive(Clone)] +pub struct Outbox(String, Sender); + +impl Outbox { + /// Pushes a new packet to the back of the queue. + pub fn push(&self, packet: P) -> std::result::Result<(), Mbuf> { + self.1 + .try_send(packet.reset().into_easyptr()) + .map_err(|err| Mbuf::from_easyptr(err.into_inner())) + } +} + +impl fmt::Debug for Outbox { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}#outbox", self.0) + } +} + +/// Port's receive queue. +pub(crate) struct PortRxQueue { + port_id: PortId, + queue_id: PortRxQueueId, +} + +impl PortRxQueue { + /// Receives a burst of packets, up to the `Vec`'s capacity. + pub(crate) fn receive(&self, mbufs: &mut Vec) { + dpdk::eth_rx_burst(self.port_id, self.queue_id, mbufs); + } +} + +async fn rx_loop( + port_name: String, + port_id: PortId, + queue_id: PortRxQueueId, + batch_size: usize, + f: F, +) where + F: Fn(Mbuf) -> Result + Send + Sync + 'static, +{ + debug!(port = ?port_name, lcore = ?LcoreId::current(), "executing rx loop."); + + let rxq = PortRxQueue { port_id, queue_id }; + let mut ptrs = Vec::with_capacity(batch_size); + + loop { + rxq.receive(&mut ptrs); + let mut drops = vec![]; + + for ptr in ptrs.drain(..) { + match f(Mbuf::from_easyptr(ptr)) { + Ok(Postmark::Emit) => (), + Ok(Postmark::Drop(ptr)) => drops.push(ptr), + Err(_) => (), + } + } + + if !drops.is_empty() { + Mbuf::free_bulk(drops); + } + + // cooperatively moves to the back of the execution queue, + // making room for other tasks before polling rx again. + future::yield_now().await; + } +} + +/// Port's transmit queue. +pub(crate) struct PortTxQueue { + port_id: PortId, + queue_id: PortTxQueueId, +} + +impl PortTxQueue { + /// Transmits a burst of packets. + /// + /// If the TX is full, the excess packets are dropped. + pub(crate) fn transmit(&self, mbufs: &mut Vec) { + dpdk::eth_tx_burst(self.port_id, self.queue_id, mbufs); + + if !mbufs.is_empty() { + // tx queue is full, we have to drop the excess. + dpdk::pktmbuf_free_bulk(mbufs); + } + } +} + +async fn tx_loop( + port_name: String, + port_id: PortId, + queue_id: PortTxQueueId, + batch_size: usize, + receiver: Receiver, +) { + debug!(port = ?port_name, lcore = ?LcoreId::current(), "executing tx loop."); + + let txq = PortTxQueue { port_id, queue_id }; + let mut ptrs = Vec::with_capacity(batch_size); + + 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); + + // 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."); +} + +/// Map to lookup the port by the port name. +#[derive(Debug)] +pub struct PortMap(HashMap); + +impl PortMap { + /// Returns the port with the assigned name. + /// + /// # Errors + /// + /// Returns `PortError::NotFound` if the port name is not found. + pub fn get(&self, name: &str) -> Result<&Port> { + self.0.get(name).ok_or_else(|| PortError::NotFound.into()) + } + + /// Returns a port iterator. + pub fn iter(&self) -> impl Iterator { + self.0.values() + } + + /// Returns a port iterator. + pub(crate) fn iter_mut(&mut self) -> impl Iterator { + self.0.values_mut() + } +} + +impl From> for PortMap { + fn from(ports: Vec) -> Self { + let ports = ports + .into_iter() + .map(|port| (port.name.clone(), port)) + .collect::>(); + PortMap(ports) + } +} + +/// Port related errors. +#[derive(Debug, Error)] +pub enum PortError { + /// The port is not found. + #[error("port not found.")] + NotFound, + + /// The maximum number of RX queues is less than the number of queues + /// requested. + #[error("insufficient number of receive queues. max is {0}.")] + InsufficientRxQueues(u16), + + /// The maximum number of TX queues is less than the number of queues + /// requested. + #[error("insufficient number of transmit queues. max is {0}.")] + InsufficientTxQueues(u16), + + /// The port does not have receive enabled. + #[error("receive not enabled on port.")] + RxNotEnabled, + + /// The port does not have transmit enabled. + #[error("transmit not enabled on port.")] + TxNotEnabled, + + /// The pipeline for the port is already set. + #[error("pipeline already set.")] + PipelineSet, +} + +/// Port builder. +pub(crate) struct Builder { + name: String, + port_id: PortId, + port_info: cffi::rte_eth_dev_info, + port_conf: cffi::rte_eth_conf, + rx_lcores: Vec, + tx_lcores: Vec, + rxqs: usize, + txqs: usize, +} + +impl Builder { + /// Creates a new port `Builder` with a logical name and device name. + /// + /// The device name can be the following + /// * PCIe address (domain:bus:device.function), for example `0000:02:00.0` + /// * DPDK virtual device name, for example `net_[pcap0|null0|tap0]` + /// + /// # Errors + /// + /// Returns `DpdkError` if the `device` is not found or failed to retrieve + /// the contextual information for the device. + pub(crate) fn for_device, S2: Into>( + name: S1, + device: S2, + ) -> Result { + let name: String = name.into(); + let device: String = device.into(); + + let port_id = dpdk::eth_dev_get_port_by_name(&device)?; + debug!(?name, id = ?port_id, ?device); + + let port_info = dpdk::eth_dev_info_get(port_id)?; + + Ok(Builder { + name, + port_id, + port_info, + port_conf: cffi::rte_eth_conf::default(), + rx_lcores: vec![], + tx_lcores: vec![], + rxqs: port_info.rx_desc_lim.nb_min as usize, + txqs: port_info.tx_desc_lim.nb_min as usize, + }) + } + + /// Sets the lcores to receive packets on. + /// + /// Enables receive side scaling if more than one lcore is used for RX or + /// packet processing is offloaded to the workers. + /// + /// # Errors + /// + /// Returns `PortError` if the maximum number of RX queues is less than + /// the number of lcores assigned. + pub(crate) fn set_rx_lcores(&mut self, lcores: Vec) -> Result<&mut Self> { + ensure!( + self.port_info.max_rx_queues >= lcores.len() as u16, + PortError::InsufficientRxQueues(self.port_info.max_rx_queues) + ); + + if lcores.len() > 1 { + const RSS_HF: u64 = + (cffi::ETH_RSS_IP | cffi::ETH_RSS_TCP | cffi::ETH_RSS_UDP | cffi::ETH_RSS_SCTP) + as u64; + + // enables receive side scaling. + self.port_conf.rxmode.mq_mode = cffi::rte_eth_rx_mq_mode::ETH_MQ_RX_RSS; + self.port_conf.rx_adv_conf.rss_conf.rss_hf = + self.port_info.flow_type_rss_offloads & RSS_HF; + + debug!( + port = ?self.name, + rss_hf = self.port_conf.rx_adv_conf.rss_conf.rss_hf, + "receive side scaling enabled." + ); + } + + self.rx_lcores = lcores; + Ok(self) + } + + /// Sets the lcores to transmit packets on. + /// + /// # Errors + /// + /// Returns `PortError` if the maximum number of TX queues is less than + /// the number of lcores assigned. + pub(crate) fn set_tx_lcores(&mut self, lcores: Vec) -> Result<&mut Self> { + ensure!( + self.port_info.max_tx_queues >= lcores.len() as u16, + PortError::InsufficientTxQueues(self.port_info.max_tx_queues) + ); + + self.tx_lcores = lcores; + Ok(self) + } + + /// Sets the capacity of each RX queue and TX queue. + /// + /// If the sizes are not within the limits of the device, they are adjusted + /// to the boundaries. + /// + /// # Errors + /// + /// Returns `DpdkError` if failed to set the queue capacity. + pub(crate) fn set_rxqs_txqs(&mut self, rxqs: usize, txqs: usize) -> Result<&mut Self> { + let (rxqs2, txqs2) = dpdk::eth_dev_adjust_nb_rx_tx_desc(self.port_id, rxqs, txqs)?; + + info!( + cond: rxqs2 != rxqs, + port = ?self.name, + before = rxqs, + after = rxqs2, + "rx ring size adjusted to limits.", + ); + info!( + cond: txqs2 != txqs, + port = ?self.name, + before = txqs, + after = txqs2, + "tx ring size adjusted to limits.", + ); + + self.rxqs = rxqs2; + self.txqs = txqs2; + Ok(self) + } + + /// Sets the promiscuous mode of the port. + /// + /// # Errors + /// + /// Returns `DpdkError` if the device does not support configurable mode. + pub(crate) fn set_promiscuous(&mut self, enable: bool) -> Result<&mut Self> { + if enable { + dpdk::eth_promiscuous_enable(self.port_id)?; + debug!(port = ?self.name, "promiscuous mode enabled."); + } else { + dpdk::eth_promiscuous_disable(self.port_id)?; + debug!(port = ?self.name, "promiscuous mode disabled."); + } + + Ok(self) + } + + /// Sets the multicast mode of the port. + /// + /// # Errors + /// + /// Returns `DpdkError` if the device does not support configurable mode. + pub(crate) fn set_multicast(&mut self, enable: bool) -> Result<&mut Self> { + if enable { + dpdk::eth_allmulticast_enable(self.port_id)?; + debug!(port = ?self.name, "multicast mode enabled."); + } else { + dpdk::eth_allmulticast_disable(self.port_id)?; + debug!(port = ?self.name, "multicast mode disabled."); + } + + Ok(self) + } + + /// Builds the port. + /// + /// # Errors + /// + /// Returns `DpdkError` if fails to configure the device or any of the + /// rx and tx queues. + pub(crate) fn build(&mut self, mempool: &mut Mempool) -> Result { + // turns on optimization for mbuf fast free. + if self.port_info.tx_offload_capa & cffi::DEV_TX_OFFLOAD_MBUF_FAST_FREE as u64 > 0 { + self.port_conf.txmode.offloads |= cffi::DEV_TX_OFFLOAD_MBUF_FAST_FREE as u64; + debug!(port = ?self.name, "mbuf fast free enabled."); + } + + // configures the device before everything else. + dpdk::eth_dev_configure( + self.port_id, + self.rx_lcores.len(), + self.tx_lcores.len(), + &self.port_conf, + )?; + + let socket = self.port_id.socket(); + warn!( + cond: mempool.socket() != socket, + message = "mempool socket does not match port socket.", + mempool = ?mempool.socket(), + port = ?socket + ); + + // configures the rx queues. + for index in 0..self.rx_lcores.len() { + dpdk::eth_rx_queue_setup( + self.port_id, + index.into(), + self.rxqs, + socket, + None, + mempool.ptr_mut(), + )?; + } + + // configures the tx queues. + for index in 0..self.tx_lcores.len() { + dpdk::eth_tx_queue_setup(self.port_id, index.into(), self.txqs, socket, None)?; + } + + Ok(Port { + name: self.name.clone(), + port_id: self.port_id, + rx_lcores: self.rx_lcores.clone(), + tx_lcores: self.tx_lcores.clone(), + outbox: None, + shutdown: Some(ShutdownTrigger::new()), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ffi::dpdk::SocketId; + + #[capsule::test] + fn port_not_found() { + assert!(Builder::for_device("test0", "notfound").is_err()); + } + + #[capsule::test] + fn set_rx_lcores() -> Result<()> { + let mut builder = Builder::for_device("test0", "net_ring0")?; + + // ring port has a max rxq of 16. + let lcores = (0..17).collect::>(); + assert!(builder.set_rx_lcores(lcores).is_err()); + + let lcores = (0..16).collect::>(); + assert!(builder.set_rx_lcores(lcores.clone()).is_ok()); + assert_eq!(lcores, builder.rx_lcores); + assert_eq!( + cffi::rte_eth_rx_mq_mode::ETH_MQ_RX_RSS, + builder.port_conf.rxmode.mq_mode + ); + + Ok(()) + } + + #[capsule::test] + fn set_tx_lcores() -> Result<()> { + let mut builder = Builder::for_device("test0", "net_ring0")?; + + // ring port has a max txq of 16. + let lcores = (0..17).collect::>(); + assert!(builder.set_tx_lcores(lcores).is_err()); + + let lcores = (0..16).collect::>(); + assert!(builder.set_tx_lcores(lcores.clone()).is_ok()); + assert_eq!(lcores, builder.tx_lcores); + + Ok(()) + } + + #[capsule::test] + fn set_rxqs_txqs() -> Result<()> { + let mut builder = Builder::for_device("test0", "net_ring0")?; + + // unfortunately can't test boundary adjustment + assert!(builder.set_rxqs_txqs(32, 32).is_ok()); + assert_eq!(32, builder.rxqs); + assert_eq!(32, builder.txqs); + + Ok(()) + } + + #[capsule::test] + fn set_promiscuous() -> Result<()> { + let mut builder = Builder::for_device("test0", "net_tap0")?; + + assert!(builder.set_promiscuous(true).is_ok()); + assert!(builder.set_promiscuous(false).is_ok()); + + Ok(()) + } + + #[capsule::test] + fn set_multicast() -> Result<()> { + let mut builder = Builder::for_device("test0", "net_tap0")?; + + assert!(builder.set_multicast(true).is_ok()); + assert!(builder.set_multicast(false).is_ok()); + + Ok(()) + } + + #[capsule::test] + fn build_port() -> Result<()> { + let rx_lcores = (0..2).collect::>(); + let tx_lcores = (3..6).collect::>(); + let mut pool = Mempool::new("mp_build_port", 15, 0, SocketId::ANY)?; + let port = Builder::for_device("test0", "net_ring0")? + .set_rx_lcores(rx_lcores.clone())? + .set_tx_lcores(tx_lcores.clone())? + .build(&mut pool)?; + + assert_eq!("test0", port.name()); + assert!(port.promiscuous()); + assert!(port.multicast()); + assert_eq!(rx_lcores, port.rx_lcores); + assert_eq!(tx_lcores, port.tx_lcores); + + Ok(()) + } + + #[capsule::test] + fn port_rx_tx() -> Result<()> { + let mut pool = Mempool::new("mp_port_rx", 15, 0, SocketId::ANY)?; + let port = Builder::for_device("test0", "net_null0")? + .set_rx_lcores(vec![0])? + .set_tx_lcores(vec![0])? + .build(&mut pool)?; + + let mut packets = Vec::with_capacity(4); + assert_eq!(0, packets.len()); + + let rxq = PortRxQueue { + port_id: port.port_id, + queue_id: 0.into(), + }; + + rxq.receive(&mut packets); + assert_eq!(4, packets.len()); + assert_eq!(4, dpdk::mempool_in_use_count(pool.ptr_mut())); + + let txq = PortTxQueue { + port_id: port.port_id, + queue_id: 0.into(), + }; + + txq.transmit(&mut packets); + assert_eq!(0, packets.len()); + assert_eq!(0, dpdk::mempool_in_use_count(pool.ptr_mut())); + + Ok(()) + } +} diff --git a/core/src/testils/byte_arrays.rs b/core/src/testils/byte_arrays.rs index 068732ee..f35818d2 100644 --- a/core/src/testils/byte_arrays.rs +++ b/core/src/testils/byte_arrays.rs @@ -64,7 +64,7 @@ pub const VLAN_QINQ_PACKET: [u8; 68] = [ /// An ARP packet. #[rustfmt::skip] -pub const ARP4_PACKET: [u8; 42] = [ +pub const ARP_PACKET: [u8; 42] = [ // Ethernet header 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, @@ -90,7 +90,7 @@ pub const ARP4_PACKET: [u8; 42] = [ /// An IPv4 TCP packet. #[rustfmt::skip] -pub const IPV4_TCP_PACKET: [u8; 58] = [ +pub const TCP4_PACKET: [u8; 58] = [ // Ethernet header 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, @@ -124,7 +124,7 @@ pub const IPV4_TCP_PACKET: [u8; 58] = [ /// An IPv4 UDP packet. #[rustfmt::skip] -pub const IPV4_UDP_PACKET: [u8; 52] = [ +pub const UDP4_PACKET: [u8; 52] = [ // Ethernet header 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, @@ -152,7 +152,7 @@ pub const IPV4_UDP_PACKET: [u8; 52] = [ /// An IPv6 TCP packet. #[rustfmt::skip] -pub const IPV6_TCP_PACKET: [u8; 78] = [ +pub const TCP6_PACKET: [u8; 78] = [ // Ethernet header 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 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 b5ed0c9f..da4393e4 100644 --- a/core/src/testils/mod.rs +++ b/core/src/testils/mod.rs @@ -27,10 +27,12 @@ mod rvg; pub use self::packet::*; pub use self::rvg::*; -use crate::dpdk::{self, Mempool, SocketId, MEMPOOL}; -use crate::metrics; +use crate::ffi::dpdk::{self, SocketId}; +use crate::runtime::{Mempool, MEMPOOL}; +use std::ops::DerefMut; use std::ptr; use std::sync::Once; +use std::thread; static TEST_INIT: Once = Once::new(); @@ -38,21 +40,35 @@ static TEST_INIT: Once = Once::new(); pub fn cargo_test_init() { TEST_INIT.call_once(|| { dpdk::eal_init(vec![ - "capsule_test".to_owned(), - "--no-huge".to_owned(), - "--iova-mode=va".to_owned(), + "capsule_test", + "--master-lcore", + "127", + "--lcores", + // 2 logical worker cores, pins master core to physical core 0 + "0,1,127@0", + // allows tests to run without hugepages + "--no-huge", + // allows tests to run without root privilege + "--iova-mode=va", + "--vdev", + // a null device for RX and TX tests + "net_null0", + "--vdev", + // a ring-based device that can be used with assertions + "net_ring0", + "--vdev", + // a TAP device for supported device feature tests + "net_tap0", ]) .unwrap(); - let _ = metrics::init(); }); } -/// A handle that keeps the mempool in scope for the duration of the test. It -/// will unset the thread-bound mempool on drop. +/// A RAII guard that keeps the mempool in scope for the duration of the +/// test. It will unset the thread-bound mempool on drop. #[derive(Debug)] pub struct MempoolGuard { - #[allow(dead_code)] - inner: Mempool, + _inner: Mempool, } impl Drop for MempoolGuard { @@ -64,7 +80,8 @@ impl Drop for MempoolGuard { /// Creates a new mempool for test that automatically cleans up after the /// test completes. pub fn new_mempool(capacity: usize, cache_size: usize) -> MempoolGuard { - let mut mempool = Mempool::new(capacity, cache_size, SocketId::ANY).unwrap(); - MEMPOOL.with(|tls| tls.set(mempool.raw_mut())); - MempoolGuard { inner: mempool } + let name = format!("test-mp-{:?}", thread::current().id()); + let mut mempool = Mempool::new(name, capacity, cache_size, SocketId::ANY).unwrap(); + MEMPOOL.with(|tls| tls.set(mempool.ptr_mut().deref_mut())); + MempoolGuard { _inner: mempool } } diff --git a/core/src/testils/packet.rs b/core/src/testils/packet.rs index fbdb710e..ec049282 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. /// @@ -34,38 +37,38 @@ pub trait PacketExt: Packet + Sized { } /// Converts the packet into an IPv4 packet. - fn into_v4(self) -> Ipv4 { + fn into_ip4(self) -> Ipv4 { self.into_eth().parse::().unwrap() } /// Converts the packet into a TCP packet inside IPv4. - fn into_v4_tcp(self) -> Tcp4 { - self.into_v4().parse::().unwrap() + fn into_tcp4(self) -> Tcp4 { + self.into_ip4().parse::().unwrap() } /// Converts the packet into a UDP packet inside IPv4. - fn into_v4_udp(self) -> Udp4 { - self.into_v4().parse::().unwrap() + fn into_udp4(self) -> Udp4 { + self.into_ip4().parse::().unwrap() } /// Converts the packet into an IPv6 packet. - fn into_v6(self) -> Ipv6 { + fn into_ip6(self) -> Ipv6 { self.into_eth().parse::().unwrap() } /// Converts the packet into a TCP packet inside IPv6. - fn into_v6_tcp(self) -> Tcp6 { - self.into_v6().parse::().unwrap() + fn into_tcp6(self) -> Tcp6 { + self.into_ip6().parse::().unwrap() } /// Converts the packet into a UDP packet inside IPv6. - fn into_v6_udp(self) -> Udp6 { - self.into_v6().parse::().unwrap() + fn into_udp6(self) -> Udp6 { + self.into_ip6().parse::().unwrap() } /// Converts the packet into an IPv6 packet with a SRH extension. fn into_sr(self) -> SegmentRouting { - self.into_v6().parse::>().unwrap() + self.into_ip6().parse::>().unwrap() } /// Converts the packet into a TCP packet inside IPv6 with a SRH extension. 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..5e08e0a3 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; @@ -217,7 +219,7 @@ fn ethernet(ether_type: EtherType, map: &StrategyMap) -> impl Strategy impl Strategy { +fn ip4(protocol: ProtocolNumber, map: &StrategyMap) -> impl Strategy { ( ethernet(EtherTypes::Ipv4, map), map.ipv4_addr(&field::ipv4_src), @@ -263,7 +265,7 @@ fn ipv4(protocol: ProtocolNumber, map: &StrategyMap) -> impl Strategy impl Strategy { +fn ip6(next_header: ProtocolNumber, map: &StrategyMap) -> impl Strategy { ( ethernet(EtherTypes::Ipv6, map), map.ipv6_addr(&field::ipv6_src), @@ -288,7 +290,7 @@ fn ipv6(next_header: ProtocolNumber, map: &StrategyMap) -> impl Strategy( +fn sr( envelope: impl Strategy, next_header: ProtocolNumber, map: &StrategyMap, @@ -405,13 +407,13 @@ fn udp( /// in order for the packet to be internally consistent. For example, /// `ether_type` is always `EtherTypes::Ipv4` and `next_header` is always /// `ProtocolNumbers::Tcp`. -pub fn v4_tcp() -> impl Strategy { - v4_tcp_with(fieldmap! {}) +pub fn tcp4() -> impl Strategy { + tcp4_with(fieldmap! {}) } /// Returns a strategy to generate IPv4 TCP packets. /// -/// Similar to `v4_tcp`. Some fields can be explicitly set through `fieldmap!`. +/// Similar to `tcp4`. Some fields can be explicitly set through `fieldmap!`. /// All other fields are randomly generated. See the `field` enum for a list /// of fields that can be set explicitly. /// @@ -419,21 +421,21 @@ pub fn v4_tcp() -> impl Strategy { /// /// ``` /// #[capsule::test] -/// fn v4_tcp_packet() { -/// proptest!(|(packet in v4_tcp_with(fieldmap! { +/// fn tcp4_packet() { +/// proptest!(|(packet in tcp4_with(fieldmap! { /// field::ipv4_src => "127.0.0.1".parse(), /// field::tcp_dst_port => 80 /// }))| { /// let packet = packet.parse::().unwrap(); -/// let v4 = packet.parse::().unwrap(); -/// assert_eq!("127.0.0.1".parse(), v4.src()); -/// let tcp = v4.parse::().unwrap(); +/// let ip4 = packet.parse::().unwrap(); +/// assert_eq!("127.0.0.1".parse(), ip4.src()); +/// let tcp = ip4.parse::().unwrap(); /// assert_eq!(80, tcp.dst_port()); /// }); /// } /// ``` -pub fn v4_tcp_with(map: StrategyMap) -> impl Strategy { - let envelope = ipv4(ProtocolNumbers::Tcp, &map); +pub fn tcp4_with(map: StrategyMap) -> impl Strategy { + let envelope = ip4(ProtocolNumbers::Tcp, &map); tcp(envelope, &map) } @@ -443,13 +445,13 @@ pub fn v4_tcp_with(map: StrategyMap) -> impl Strategy { /// in order for the packet to be internally consistent. For example, /// `ether_type` is always `EtherTypes::Ipv4` and `next_header` is always /// `ProtocolNumbers::Udp`. -pub fn v4_udp() -> impl Strategy { - v4_udp_with(fieldmap! {}) +pub fn udp4() -> impl Strategy { + udp4_with(fieldmap! {}) } /// Returns a strategy to generate IPv4 UDP packets. /// -/// Similar to `v4_udp`. Some fields can be explicitly set through `fieldmap!`. +/// Similar to `udp4`. Some fields can be explicitly set through `fieldmap!`. /// All other fields are randomly generated. See the `field` enum for a list /// of fields that can be set explicitly. /// @@ -457,21 +459,21 @@ pub fn v4_udp() -> impl Strategy { /// /// ``` /// #[capsule::test] -/// fn v4_udp_packet() { -/// proptest!(|(packet in v4_udp_with(fieldmap! { +/// fn udp4_packet() { +/// proptest!(|(packet in udp4_with(fieldmap! { /// field::ipv4_src => "127.0.0.1".parse(), /// field::udp_dst_port => 53, /// }))| { /// let packet = packet.parse::().unwrap(); -/// let v4 = packet.parse::().unwrap(); -/// prop_assert_eq!("127.0.0.1".parse(), v4.src()); -/// let udp = v4.parse::().unwrap(); +/// let ip4 = packet.parse::().unwrap(); +/// prop_assert_eq!("127.0.0.1".parse(), ip4.src()); +/// let udp = ip4.parse::().unwrap(); /// prop_assert_eq!(53, udp.dst_port()); /// }); /// } /// ``` -pub fn v4_udp_with(map: StrategyMap) -> impl Strategy { - let envelope = ipv4(ProtocolNumbers::Udp, &map); +pub fn udp4_with(map: StrategyMap) -> impl Strategy { + let envelope = ip4(ProtocolNumbers::Udp, &map); udp(envelope, &map) } @@ -481,13 +483,13 @@ pub fn v4_udp_with(map: StrategyMap) -> impl Strategy { /// in order for the packet to be internally consistent. For example, /// `ether_type` is always `EtherTypes::Ipv6` and `next_header` is always /// `ProtocolNumbers::Tcp`. -pub fn v6_tcp() -> impl Strategy { - v6_tcp_with(fieldmap! {}) +pub fn tcp6() -> impl Strategy { + tcp6_with(fieldmap! {}) } /// Returns a strategy to generate IPv6 TCP packets. /// -/// Similar to `v6_tcp`. Some fields can be explicitly set through `fieldmap!`. +/// Similar to `tcp6`. Some fields can be explicitly set through `fieldmap!`. /// All other fields are randomly generated. See the `field` enum for a list /// of fields that can be set explicitly. /// @@ -495,21 +497,21 @@ pub fn v6_tcp() -> impl Strategy { /// /// ``` /// #[capsule::test] -/// fn v6_tcp_packet() { -/// proptest!(|(packet in v6_tcp_with(fieldmap! { +/// fn tcp6_packet() { +/// proptest!(|(packet in tcp6_with(fieldmap! { /// field::ipv6_src => "::1".parse(), /// field::tcp_dst_port => 80, /// }))| { /// let packet = packet.parse::().unwrap(); -/// let v6 = packet.parse::().unwrap(); -/// prop_assert_eq!("::1".parse(), v6.src()); -/// let tcp = v6.parse::().unwrap(); +/// let ip6 = packet.parse::().unwrap(); +/// prop_assert_eq!("::1".parse(), ip6.src()); +/// let tcp = ip6.parse::().unwrap(); /// prop_assert_eq!(80, tcp.dst_port()); /// }); /// } /// ``` -pub fn v6_tcp_with(map: StrategyMap) -> impl Strategy { - let envelope = ipv6(ProtocolNumbers::Tcp, &map); +pub fn tcp6_with(map: StrategyMap) -> impl Strategy { + let envelope = ip6(ProtocolNumbers::Tcp, &map); tcp(envelope, &map) } @@ -519,13 +521,13 @@ pub fn v6_tcp_with(map: StrategyMap) -> impl Strategy { /// in order for the packet to be internally consistent. For example, /// `ether_type` is always `EtherTypes::Ipv6` and `next_header` is always /// `ProtocolNumbers::Udp`. -pub fn v6_udp() -> impl Strategy { - v6_udp_with(fieldmap! {}) +pub fn udp6() -> impl Strategy { + udp6_with(fieldmap! {}) } /// Returns a strategy to generate IPv6 UDP packets. /// -/// Similar to `v6_udp`. Some fields can be explicitly set through `fieldmap!`. +/// Similar to `udp6`. Some fields can be explicitly set through `fieldmap!`. /// All other fields are randomly generated. See the `field` enum for a list /// of fields that can be set explicitly. /// @@ -533,21 +535,21 @@ pub fn v6_udp() -> impl Strategy { /// /// ``` /// #[capsule::test] -/// fn v6_udp_packet() { -/// proptest!(|(packet in v6_udp_with(fieldmap! { +/// fn udp6_packet() { +/// proptest!(|(packet in udp6_with(fieldmap! { /// field::ipv6_src => "::1".parse(), /// field::udp_dst_port => 53, /// }))| { /// let packet = packet.parse::().unwrap(); -/// let v6 = packet.parse::().unwrap(); -/// prop_assert_eq!("::1".parse(), v6.src()); -/// let udp = v6.parse::().unwrap(); +/// let ip6 = packet.parse::().unwrap(); +/// prop_assert_eq!("::1".parse(), ip6.src()); +/// let udp = ip6.parse::().unwrap(); /// prop_assert_eq!(53, udp.dst_port()); /// }); /// } /// ``` -pub fn v6_udp_with(map: StrategyMap) -> impl Strategy { - let envelope = ipv6(ProtocolNumbers::Udp, &map); +pub fn udp6_with(map: StrategyMap) -> impl Strategy { + let envelope = ip6(ProtocolNumbers::Udp, &map); udp(envelope, &map) } @@ -578,18 +580,18 @@ pub fn sr_tcp() -> impl Strategy { /// field::tcp_dst_port => 80, /// }))| { /// let packet = packet.parse::().unwrap(); -/// let v6 = packet.parse::().unwrap(); -/// prop_assert_eq!("::1".parse(), v6.src()); -/// let srh = v6.parse::>().unwrap(); -/// prop_assert_eq!(2, srh.segments().len()); -/// let tcp = srh.parse::>>().unwrap(); +/// let ip6 = packet.parse::().unwrap(); +/// prop_assert_eq!("::1".parse(), ip6.src()); +/// let sr = ip6.parse::>().unwrap(); +/// prop_assert_eq!(2, sr.segments().len()); +/// let tcp = sr.parse::>>().unwrap(); /// prop_assert_eq!(80, tcp.dst_port()); /// }); /// } /// ``` pub fn sr_tcp_with(map: StrategyMap) -> impl Strategy { - let envelope = ipv6(ProtocolNumbers::Ipv6Route, &map); - let envelope = srh(envelope, ProtocolNumbers::Tcp, &map); + let envelope = ip6(ProtocolNumbers::Ipv6Route, &map); + let envelope = sr(envelope, ProtocolNumbers::Tcp, &map); tcp(envelope, &map) } @@ -597,7 +599,7 @@ pub fn sr_tcp_with(map: StrategyMap) -> impl Strategy { /// /// The IP addresses and ports are random. The protocol can be /// either TCP, UDP or ICMP. -pub fn v4_flow() -> impl Strategy { +pub fn ip4_flow() -> impl Strategy { ( any::(), any::(), @@ -624,7 +626,7 @@ pub fn v4_flow() -> impl Strategy { /// /// The IP addresses and ports are random. The protocol can be /// either TCP, UDP or ICMP. -pub fn v6_flow() -> impl Strategy { +pub fn ip6_flow() -> impl Strategy { ( any::(), any::(), diff --git a/core/src/testils/rvg.rs b/core/src/testils/rvg.rs index 6bbfbe06..3cc86160 100644 --- a/core/src/testils/rvg.rs +++ b/core/src/testils/rvg.rs @@ -84,11 +84,11 @@ mod tests { use std::net::Ipv6Addr; #[capsule::test] - fn gen_v4_packet() { + fn gen_ip4_packet() { let mut gen = Rvg::new(); - let packet = gen.generate(&v4_udp()); - let v4 = packet.into_v4(); - assert_eq!(ProtocolNumbers::Udp, v4.protocol()); + let packet = gen.generate(&udp4()); + let ip4 = packet.into_ip4(); + assert_eq!(ProtocolNumbers::Udp, ip4.protocol()); } #[capsule::test] diff --git a/examples/kni/Cargo.toml b/examples/kni/Cargo.toml index c5735dba..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,8 +17,8 @@ doctest = false [dependencies] anyhow = "1.0" -capsule = { version = "0.1", path = "../../core" } -metrics-core = "0.5" -metrics-observer-yaml = "0.1" +capsule = { version = "0.2", path = "../../core" } +colored = "2.0" +signal-hook = "0.3" tracing = "0.1" tracing-subscriber = "0.2" diff --git a/examples/kni/README.md b/examples/kni/README.md index 0929ad23..22a5f4bf 100644 --- a/examples/kni/README.md +++ b/examples/kni/README.md @@ -1,19 +1,21 @@ # Kernel NIC interface example -The Kernel NIC Interface (KNI) is a DPDK control plane solution that allows userspace applications to exchange packets with the Linux kernel networking stack. See DPDK's [KNI documentation](https://doc.dpdk.org/guides/prog_guide/kernel_nic_interface.html) for more information. This example is a minimum program that can forward packets to and from the Linux kernel. +The Kernel NIC Interface (KNI) is a DPDK control plane solution that allows userspace applications to exchange packets with the Linux kernel networking stack. See DPDK's [KNI documentation](https://doc.dpdk.org/guides/prog_guide/kernel_nic_interface.html) for more information. ## Overview -KNI is useful for applications that want to conceptually share the port with the Linux kernel. For example, the application may want to leverage the kernel's built-in ability to handle [ARP](https://tools.ietf.org/html/rfc826) traffic instead of implementing the protocol natively. By enabling KNI for a port, a virtual device with the same name and MAC address as the port is exposed to the Linux kernel. The kernel will be able to receive all packets that are forwarded to this virtual device and the application will receive all packets the kernel sends to it. +KNI is useful for Capsule applications that want to delegate the processing of various network control plane protocols to either the Linux kernel or other implementations that run on Linux. For example, [Address Resolution Protocol](https://tools.ietf.org/html/rfc826) is the mechanism for hosts to discover each other's link layer address on an IPv4 network. Typically, the Linux kernel handles the ARP discovery for all the network interfaces on the host. However, because Capsule-bound network devices are not visible to the kernel, each Capsule application needs its own ARP implementation, otherwise the network won't be able to route packets to it. Or alternatively, an easier approach is for the application to simply leverage the kernel stack implementation by delegating and forwarding ARP packets via KNI. + +This example demonstrates said approach by delegating the processing of [Neighbor Discovery Procotol](https://tools.ietf.org/html/rfc4861), the IPv6 equivalent of ARP, to the Linux kernel. ## Prerequisite -This application requires the kernel module `rte_kni`. Kernel modules are version specific. If you are using our `Vagrant` with `Docker` setup, the module is already preloaded. Otherwise, you will have to compile it by installing the kernel headers or sources required to build kernel modules on your system, then [build `DPDK` from source](https://doc.dpdk.org/guides/linux_gsg/build_dpdk.html). +This application requires the kernel module `rte_kni`. Kernel modules are version specific. If you are using our Vagrant with Docker setup, the module is already preloaded. Otherwise, you will have to compile it by installing the kernel headers or sources required to build kernel modules on your system, then [build `DPDK` from source](https://doc.dpdk.org/guides/linux_gsg/build_dpdk.html). Once the build is complete, load the module with command: -``` -$ sudo insmod /lib/modules/`uname -r`/extra/dpdk/rte_kni.ko +```bash +$ sudo insmod /lib/modules/`uname -r`/extra/dpdk/rte_kni.ko carrier=on ``` We may provide precompiled modules for different kernel versions and Linux distributions in the future. @@ -22,26 +24,65 @@ We may provide precompiled modules for different kernel versions and Linux distr The example is located in the `examples/kni` sub-directory. To run the application, -``` +```bash /examples/kni$ cargo run -- -f kni.toml ``` -While the application is running, the new virtual device is exposed to the kernel, +While the application is running, in a seperate Vagrant VM terminal, check that a new virtual device `kni0` is exposed to the kernel, +```bash +vagrant$ ip link show dev kni0 + +14: kni0: mtu 2034 qdisc noop state DOWN mode DEFAULT group default qlen 1000 + link/ether 6a:80:63:c3:01:42 brd ff:ff:ff:ff:ff:ff ``` -$ ip link -254: kni0: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 - link/ether ba:dc:af:eb:ee:f1 brd ff:ff:ff:ff:ff:ff +Change the MAC address of `kni0` to match the MAC address of the physical interface first; then bring up the link, + +```bash +vagrant$ sudo ip link set dev kni0 address 02:00:00:ff:ff:00 +vagrant$ sudo ip link set dev kni0 up ``` -The kernel can assign an IP address to the device and bring the link up, at which point the kernel and the application can forward each other packets. +Once `kni0` is up, it should be automatically assigned an IPv6 address, we will need this address for the next step, +```bash +vagrant$ ip addr show dev kni0 + +14: kni0: mtu 2034 qdisc pfifo_fast state UP group default qlen 1000 + link/ether 02:00:00:ff:ff:00 brd ff:ff:ff:ff:ff:ff + inet6 fe80::ff:feff:ff00/64 scope link + valid_lft forever preferred_lft forever ``` -$ sudo ip addr add dev kni0 10.0.2.16/24 -$ sudo ip link set up dev kni0 + +Finally use `socat` to read from `stdin`, and send the input messages as UDP packets to this IPv6 address. These packets will be routed to the running Capsule application, + +```bash +vagrant$ socat -d -d -u - udp6:[fe80::ff:feff:ff00%eth3]:6667 + +Hello? +Is there anybody in there? +``` + +The running application should print out, + +``` +Mar 28 19:59:34.805 INFO kni: to kni0: Neighbor Solicitation +Mar 28 19:59:34.810 INFO kni: from kni0: Neighbor Advertisement +Mar 28 19:59:34.811 INFO kni: you said: Hello? + +Mar 28 19:59:39.475 INFO kni: you said: Is there anybody in there? + ``` # Explanation -The assigned port `0000:00:08.0` has KNI support turned on by setting the `kni` flag to `true`. To forward packets received on the port to the kernel, the application adds a simple forwarding pipeline by calling `add_pipeline_to_port`. To forward packets received from the kernel through the port, the application adds another forwarding pipeline by calling `add_kni_rx_pipeline_to_port`. +Capsule leverages the KNI poll mode driver instead of the `librte_kni` API directly. This lets the application to interact with KNI the same way as any other physical or virtual port device. + +The example is configured with one PCI port, `cap0`, and one KNI port, `kni0`. As new packets arrive through `cap0`'s rx, the application will forward all ICMPv6 packets to the `kni0`'s tx. For the sake of simplicity, it is assumed that all ICMPv6 packets received in this example will be NDP messages, and the application is delegating this link layer address discovery process to the kernel stack. In the reverse direction, kernel stack's NDP responses will come in through `kni0`'s rx, and immediately forwarded out through `cap0`'s tx without modifications. Because we assigned `cap0`'s link layer MAC address, `02:00:00:ff:ff:00`, to `kni0` with the `ip link set` command, the NDP responses already contain the correct link layer information. + +When `socat` sends out an UDP packet via `eth3` to `kni0`'s IPv6 address `fe80::ff:feff:ff00`, a lookup is performed trying to find the link layer address of the destination. On the very first attempt, that link layer address is not found through the lookup. A neighbor solicitation message is broadcasted instead to initiate the discovery process. + +`cap0` receives the broadcasted neighbor solicitation message and forwards it to the kernel stack via `kni0`. Kernel responds with a neighbor advertisement message because the IPv6 address matches the address of the `kni0` interface. This response is sent back to `eth3` through `kni0`'s rx then `cap0`'s tx, completing the discovery. + +The link layer address from the response is cached, all UDP packets are routed to `cap0` with this lookup until the cached entry expires. The Capsule application will receive the UDP packets from `socat`. It parses and prints out the data payload. diff --git a/examples/kni/kni.toml b/examples/kni/kni.toml index 0cd8494a..15752191 100644 --- a/examples/kni/kni.toml +++ b/examples/kni/kni.toml @@ -1,12 +1,19 @@ app_name = "kni" -master_core = 0 +main_core = 0 +worker_cores = [] [mempool] capacity = 65535 cache_size = 256 [[ports]] - name = "kni0" + name = "cap0" device = "0000:00:08.0" - cores = [1] - kni = true + rx_cores = [1] + tx_cores = [2] + +[[ports]] + name = "kni0" + device = "net_kni0" + rx_cores = [1] + tx_cores = [2] diff --git a/examples/kni/main.rs b/examples/kni/main.rs index dc2fbedb..d64fcc1c 100644 --- a/examples/kni/main.rs +++ b/examples/kni/main.rs @@ -17,19 +17,58 @@ */ use anyhow::Result; -use capsule::config::load_config; -use capsule::metrics; -use capsule::{batch, Runtime}; -use metrics_core::{Builder, Drain, Observe}; -use metrics_observer_yaml::YamlBuilder; -use std::time::Duration; -use tracing::{debug, Level}; +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::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; +use std::str; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use tracing::{info, Level}; use tracing_subscriber::fmt; -fn print_stats() { - let mut observer = YamlBuilder::new().build(); - metrics::global().controller().observe(&mut observer); - println!("{}", observer.drain()); +fn route_pkt(packet: Mbuf, kni0: &Outbox) -> Result { + let ip6 = packet.parse::()?.parse::()?; + + match ip6.next_header() { + ProtocolNumbers::Icmpv6 => { + let icmp = ip6.parse::()?; + let fmt = format!("to kni0: {}", icmp.msg_type()).cyan(); + info!("{}", fmt); + let _ = kni0.push(icmp); + Ok(Postmark::Emit) + } + ProtocolNumbers::Udp => { + let udp = ip6.parse::()?; + let fmt = format!("you said: {}", str::from_utf8(udp.data())?).bright_blue(); + info!("{}", fmt); + Ok(Postmark::Drop(udp.reset())) + } + _ => { + let fmt = format!("not supported: {}", ip6.next_header()).red(); + info!("{}", fmt); + Ok(Postmark::Drop(ip6.reset())) + } + } +} + +fn from_kni(packet: Mbuf, cap0: &Outbox) -> Result { + let icmp = packet + .parse::()? + .parse::()? + .parse::()?; + + let fmt = format!("from kni0: {}", icmp.msg_type()).green(); + info!("{}", fmt); + + let _ = cap0.push(icmp); + Ok(Postmark::Emit) } fn main() -> Result<()> { @@ -38,14 +77,21 @@ fn main() -> Result<()> { .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = load_config()?; - debug!(?config); + let config = runtime::load_config()?; + let runtime = Runtime::from_config(config)?; + + let kni0 = runtime.ports().get("kni0")?.outbox()?; + runtime.set_port_pipeline("cap0", move |packet| route_pkt(packet, &kni0))?; + + let cap0 = runtime.ports().get("cap0")?.outbox()?; + runtime.set_port_pipeline("kni0", move |packet| from_kni(packet, &cap0))?; + + let _guard = runtime.execute()?; + + let term = Arc::new(AtomicBool::new(false)); + flag::register(consts::SIGINT, Arc::clone(&term))?; + info!("ctrl-c to quit ..."); + while !term.load(Ordering::Relaxed) {} - Runtime::build(config)? - .add_pipeline_to_port("kni0", |q| { - batch::splice(q.clone(), q.kni().unwrap().clone()) - })? - .add_kni_rx_pipeline_to_port("kni0", batch::splice)? - .add_periodic_task_to_core(0, print_stats, Duration::from_secs(1))? - .execute() + Ok(()) } diff --git a/examples/nat64/Cargo.toml b/examples/nat64/Cargo.toml index 319e30d5..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" @@ -17,8 +17,10 @@ doctest = false [dependencies] anyhow = "1.0" -capsule = { version = "0.1", path = "../../core" } -chashmap = "2.2" +bimap = "0.6" +capsule = { version = "0.2", path = "../../core" } +colored = "2.0" once_cell = "1.7" +signal-hook = "0.3" tracing = "0.1" tracing-subscriber = "0.2" diff --git a/examples/nat64/README.md b/examples/nat64/README.md index 9534f8c4..84c7b4e5 100644 --- a/examples/nat64/README.md +++ b/examples/nat64/README.md @@ -1,6 +1,6 @@ # IPv6 to IPv4 network address translation example -**NAT64** is a network address translation gateway that facilitates communitcation between a host on an IPv6 network to another host on an IPv4 network. This example is a simplified implementation of such gateway that can forward TCP traffic between the two networks. Non-TCP or fragmented TCP packets are dropped by the gateway. +**NAT64** is a network address translation gateway that facilitates communitcation between a client on an IPv6 network to a server on an IPv4 network. This example is a simplified implementation of such gateway that can forward TCP traffic between the two networks. ## Overview @@ -21,48 +21,95 @@ A simple network topology may consist of a gateway with two interfaces connected +---------------------+ +---------------+ ``` -We will use the well-known prefix `64:ff9b::/96` as defined in [IETF RFC 6052](https://tools.ietf.org/html/rfc6052#section-2.1) to represent IPv4 addresses to the IPv6 network. Packets from the IPv6 network with a destination address within this prefix are routed to the **NAT64** gateway. +The example will not replicate the above network topology. Instead it will send to and receive from the same dual-stacked network interface `eth3`, and perform translations between the two stacks. Conceptually it will work the same way as a gateway for two single-stacked networks. Also, non-TCP or fragmented TCP packets are dropped. -The gateway also has the address `203.0.113.1` assigned to it on the IPv4 network. +To represent IPv4 addresses to the IPv6 network, the example uses the well-known prefix `64:ff9b::/96` as defined in [IETF RFC 6052](https://tools.ietf.org/html/rfc6052#section-2.1). For example, for the server listening on the address `10.100.1.254`, it's mapped IPv6 address is `64:ff9b::a64:1fe`. + +The gateway also has the address `10.100.1.11` assigned to it on the IPv4 network. ## Running the application The example is located in the `examples/nat64` sub-directory. To run the application, -``` +```bash /examples/nat64$ cargo run -- -f nat64.toml ``` -## Explanation +In a separate Vagrant VM terminal, add a static entry to map the gateway's IPv4 address, `10.100.1.11`, to its link layer address on the IPv4 network, + +```bash +vagrant$ sudo ip neigh add 10.100.1.11 lladdr 02:00:00:ff:ff:01 dev eth3 nud permanent +``` + +Also add a routing rule for the `64:ff9b::/96` subnet, and a static entry to map the server's IPv6 representation, `64:ff9b::a64:1fe`, to the gateway's link layer address on the IPv6 network. -The **NAT64** gateway is configured with two ports. `eth1` is the port connected to the IPv6 network and `eth2` is the port connected to the IPv4 network. Also both ports are assigned the same core, core `1`. +``` +vagrant$ sudo ip route add 64:ff9b::/96 dev eth3 +vagrant$ sudo ip neigh add 64:ff9b::a64:1fe lladdr 02:00:00:ff:ff:00 dev eth3 nud permanent +``` + +Finally start a HTTP server on the IPv4 network, binding to its IP address `10.100.1.254`, +```bash +vagrant$ python3 -m http.server 8080 --bind 10.100.1.254 + +Serving HTTP on 10.100.1.254 port 8080 (http://10.100.1.254:8080/) ... ``` -[[ports]] - name = "eth1" - device = "0000:00:08.0" - cores = [1] - rxd = 512 - txd = 512 - -[[ports]] - name = "eth2" - device = "0000:00:09.0" - cores = [1] - rxd = 512 - txd = 512 + +To test the gateway, `curl` the IPv6 representation of the server address, + +```bash +vagrant$ curl -g -6 -v 'http://[64:ff9b::a64:1fe]:8080' + +... +> GET / HTTP/1.1 +> Host: [64:ff9b::a64:1fe]:8080 +> User-Agent: curl/7.64.0 +> Accept: */* +> +* HTTP 1.0, assume close after body +< HTTP/1.0 200 OK +< Server: SimpleHTTP/0.6 Python/3.7.3 +< Date: Sun, 28 Mar 2021 17:47:08 GMT +< Content-type: text/html; charset=utf-8 +< Content-Length: 956 +... ``` -Because they are assigned the same core, we can install pipelines that forward packets received on `eth1` through `eth2` and `eth2` to `eth1` by using `add_pipeline_to_core`. +## Explanation + +The gateway example is configured with two ports. Conceptually, `cap0` is the port on the IPv6 network receiving packets intended for the `64:ff9b::/96` subnet. `cap1` is the port on the IPv4 network with the address `10.100.1.11`. ### 6-to-4 translation -When the gateway receives an unfragmented IPv6 TCP packet, it will translate the destination address to the IPv4 counterpart by stripping away the `64:ff9b::/96` prefix. The source address will be replaced by the gateway's assigned IPv4 address and the source port will be replaced by a free port on the gateway. This address and port mapping is stored in the global `PORT_MAP`. +The interaction starts with a client, the `curl` program, on the IPv6 network tries to connect to a python HTTP server on the IPv4 network. When `cap0` receives the TCP packet, it will translate the destination address to the IPv4 counterpart by stripping away the `64:ff9b::/96` prefix. The source address will be replaced by the gateway's IPv4 address `10.100.1.11`, and the source port will be replaced by a free port on the gateway. The original source address and port are saved and will be used later to translate the response packets. + +The IPv6 header is removed and replaced by an IPv4 header using the steps outlined in [IETF RFC 6145](https://tools.ietf.org/html/rfc6145#section-5.1). -The IPv6 header is removed and replaced by an IPv4 header using the model outlined in [IETF RFC 6145](https://tools.ietf.org/html/rfc6145#section-5.1). +Once the translation is complete, the packet is transmitted through `cap1` and routed to the python HTTP server. ### 4-to-6 translation -When the gateway receives an unfragmented IPv4 TCP packet, it will translate the source address to the IPv6 counterpart by adding the `64:ff9b::/96` prefix. The TCP destination port is used as the lookup key to find the mapped destination address and port of the IPv6 host. This mapping is stored in the global `ADDR_MAP`. +The response from the python HTTP server is routed to the gateway's IPv4 address because from the server's perspective, the request was originated from `10.100.1.11`, as shown in the access log. + +``` +10.100.1.11 - - [28/Mar/2021 17:47:08] "GET / HTTP/1.1" 200 - +``` + +The response TCP packets are received by `cap1`. It will translate the source address to the IPv6 counterpart by adding the `64:ff9b::/96` prefix. A lookup is performed with the TCP destination port to retrieve the source address and port of the original client on the IPv6 network. -The IPv4 header is removed and replaced by an IPv6 header using the model outlined in [IETF RFC 6145](https://tools.ietf.org/html/rfc6145#section-4.1). +The IPv4 header is removed and replaced by an IPv6 header using the steps outlined in [IETF RFC 6145](https://tools.ietf.org/html/rfc6145#section-4.1). + +Once the translation is complete, the packet is transmitted through `cap0` and routed to the client. + +A HTTP request-response cycle consists of multiple TCP packets, from connection establishment to termination. The entire TCP lifecycle is logged by the example application. `curl` writes out the response text after the process completes. + +## Cleaning up + +To clean up the static routes, + +```bash +vagrant$ sudo ip neigh del 10.100.1.11 dev eth3 +vagrant$ sudo ip neigh del 64:ff9b::a64:1fe dev eth3 +vagrant$ sudo ip route del 64:ff9b::/96 dev eth3 +``` diff --git a/examples/nat64/main.rs b/examples/nat64/main.rs index a0a8f275..132b132e 100644 --- a/examples/nat64/main.rs +++ b/examples/nat64/main.rs @@ -17,49 +17,102 @@ */ use anyhow::Result; -use capsule::batch::{Batch, Either, Pipeline, Poll}; -use capsule::config::load_config; +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, Packet, Tcp4, Tcp6}; -use capsule::{Mbuf, PortQueue, Runtime}; -use chashmap::CHashMap; +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; +use signal_hook::flag; use std::collections::HashMap; use std::net::{Ipv4Addr, Ipv6Addr}; -use std::sync::atomic::{AtomicU16, Ordering}; -use tracing::{debug, Level}; +use std::sync::atomic::{AtomicBool, AtomicU16, Ordering}; +use std::sync::{Arc, Mutex}; +use tracing::{info, Level}; use tracing_subscriber::fmt; -const V4_ADDR: Ipv4Addr = Ipv4Addr::new(203, 0, 113, 1); +static PORTS: Lazy>> = Lazy::new(|| Mutex::new(BiMap::new())); +static MACS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); -static PORT_MAP: Lazy> = Lazy::new(CHashMap::new); -static ADDR_MAP: Lazy> = Lazy::new(CHashMap::new); +/// Maps the destination IPv6 address to its IPv4 counterpart by stripping +/// off the 96-bit prefix. +fn map_6to4(addr: Ipv6Addr) -> Ipv4Addr { + let segments = addr.segments(); + let mapped = (segments[6] as u32) << 16 | (segments[7] as u32); + mapped.into() +} -/// Looks up the assigned port for a source IPv6 address and port tuple. -fn assigned_port(addr: Ipv6Addr, port: u16) -> u16 { +/// Looks up the assigned port for an IPv6 source. +fn get_ip4_port(mac: MacAddr, ip: Ipv6Addr, port: u16) -> u16 { static NEXT_PORT: AtomicU16 = AtomicU16::new(1025); - let key = (addr, port); - if let Some(value) = PORT_MAP.get(&key) { + let key = (ip, port); + let mut ports = PORTS.lock().unwrap(); + + if let Some(value) = ports.get_by_left(&key) { *value } else { let port = NEXT_PORT.fetch_add(1, Ordering::Relaxed); - PORT_MAP.insert_new(key, port); - ADDR_MAP.insert_new(port, key); + MACS.lock().unwrap().insert(ip, mac); + ports.insert(key, port); port } } -/// Looks up the IPv6 address and port the gateway port is assigned to. -fn assigned_addr(port: u16) -> Option<(Ipv6Addr, u16)> { - ADDR_MAP.get(&port).map(|v| *v) +fn nat_6to4(packet: Mbuf, cap1: &Outbox) -> Result { + const SRC_IP: Ipv4Addr = Ipv4Addr::new(10, 100, 1, 11); + const DST_MAC: MacAddr = MacAddr::new(0x02, 0x00, 0x00, 0xff, 0xff, 0xff); + + let ethernet = packet.parse::()?; + let ip6 = ethernet.parse::()?; + + if ip6.next_header() == ProtocolNumbers::Tcp { + let dscp = ip6.dscp(); + let ecn = ip6.ecn(); + let ttl = ip6.hop_limit() - 1; + let protocol = ip6.next_header(); + let src_ip = ip6.src(); + let dst_ip = map_6to4(ip6.dst()); + let src_mac = ip6.envelope().src(); + + let mut ethernet = ip6.remove()?; + ethernet.swap_addresses(); + ethernet.set_dst(DST_MAC); + + let mut ip4 = ethernet.push::()?; + ip4.set_dscp(dscp); + ip4.set_ecn(ecn); + ip4.set_ttl(ttl); + ip4.set_protocol(protocol); + ip4.set_src(SRC_IP); + ip4.set_dst(dst_ip); + + let mut tcp = ip4.parse::()?; + let port = tcp.src_port(); + tcp.set_src_port(get_ip4_port(src_mac, src_ip, port)); + tcp.reconcile_all(); + + let fmt = format!("{:?}", tcp.envelope()).magenta(); + info!("{}", fmt); + let fmt = format!("{:?}", tcp).bright_blue(); + info!("{}", fmt); + + let _ = cap1.push(tcp); + Ok(Postmark::Emit) + } else { + Ok(Postmark::Drop(ip6.reset())) + } } /// Maps the source IPv4 address to its IPv6 counterpart with well-known /// prefix `64:ff9b::/96`. -fn map4to6(addr: Ipv4Addr) -> Ipv6Addr { +fn map_4to6(addr: Ipv4Addr) -> Ipv6Addr { let octets = addr.octets(); Ipv6Addr::new( 0x64, @@ -73,105 +126,82 @@ fn map4to6(addr: Ipv4Addr) -> Ipv6Addr { ) } -/// Maps the destination IPv6 address to its IPv4 counterpart by stripping -/// off the 96-bit prefix. -#[inline] -fn map6to4(addr: Ipv6Addr) -> Ipv4Addr { - let segments = addr.segments(); - let mapped = (segments[6] as u32) << 16 | (segments[7] as u32); - mapped.into() +/// Looks up the IPv6 destination +fn get_ip6_dst(port: u16) -> Option<(MacAddr, Ipv6Addr, u16)> { + PORTS + .lock() + .unwrap() + .get_by_right(&port) + .and_then(|(ip, port)| MACS.lock().unwrap().get(ip).map(|mac| (*mac, *ip, *port))) } -#[inline] -fn nat_4to6(packet: Mbuf) -> Result> { +fn nat_4to6(packet: Mbuf, cap0: &Outbox) -> Result { let ethernet = packet.parse::()?; - let v4 = ethernet.parse::()?; - if v4.protocol() == ProtocolNumbers::Tcp && v4.fragment_offset() == 0 && !v4.more_fragments() { - let tcp = v4.peek::()?; - if let Some((dst, port)) = assigned_addr(tcp.dst_port()) { - let dscp = v4.dscp(); - let ecn = v4.ecn(); - let next_header = v4.protocol(); - let hop_limit = v4.ttl() - 1; - let src = map4to6(v4.src()); - - let ethernet = v4.remove()?; - let mut v6 = ethernet.push::()?; - v6.set_dscp(dscp); - v6.set_ecn(ecn); - v6.set_next_header(next_header); - v6.set_hop_limit(hop_limit); - v6.set_src(src); - v6.set_dst(dst); - - let mut tcp = v6.parse::()?; + let ip4 = ethernet.parse::()?; + + if ip4.protocol() == ProtocolNumbers::Tcp && ip4.fragment_offset() == 0 && !ip4.more_fragments() + { + let tcp = ip4.peek::()?; + + if let Some((dst_mac, dst_ip, port)) = get_ip6_dst(tcp.dst_port()) { + let dscp = ip4.dscp(); + let ecn = ip4.ecn(); + let next_header = ip4.protocol(); + let hop_limit = ip4.ttl() - 1; + let src_ip = map_4to6(ip4.src()); + + let mut ethernet = ip4.remove()?; + ethernet.swap_addresses(); + ethernet.set_dst(dst_mac); + + let mut ip6 = ethernet.push::()?; + ip6.set_dscp(dscp); + ip6.set_ecn(ecn); + ip6.set_next_header(next_header); + ip6.set_hop_limit(hop_limit); + ip6.set_src(src_ip); + ip6.set_dst(dst_ip); + + let mut tcp = ip6.parse::()?; tcp.set_dst_port(port); tcp.reconcile_all(); - Ok(Either::Keep(tcp.reset())) + let fmt = format!("{:?}", tcp.envelope()).cyan(); + info!("{}", fmt); + let fmt = format!("{:?}", tcp).bright_blue(); + info!("{}", fmt); + + let _ = cap0.push(tcp); + Ok(Postmark::Emit) } else { - Ok(Either::Drop(v4.reset())) + Ok(Postmark::Drop(ip4.reset())) } } else { - Ok(Either::Drop(v4.reset())) - } -} - -#[inline] -fn nat_6to4(packet: Mbuf) -> Result> { - let ethernet = packet.parse::()?; - let v6 = ethernet.parse::()?; - if v6.next_header() == ProtocolNumbers::Tcp { - let dscp = v6.dscp(); - let ecn = v6.ecn(); - let ttl = v6.hop_limit() - 1; - let protocol = v6.next_header(); - let src = v6.src(); - let dst = map6to4(v6.dst()); - - let ethernet = v6.remove()?; - let mut v4 = ethernet.push::()?; - v4.set_dscp(dscp); - v4.set_ecn(ecn); - v4.set_ttl(ttl); - v4.set_protocol(protocol); - v4.set_src(V4_ADDR); - v4.set_dst(dst); - - let mut tcp = v4.parse::()?; - let port = tcp.src_port(); - tcp.set_src_port(assigned_port(src, port)); - tcp.reconcile_all(); - - Ok(Either::Keep(tcp.reset())) - } else { - Ok(Either::Drop(v6.reset())) + Ok(Postmark::Drop(ip4.reset())) } } -fn install_6to4(qs: HashMap) -> impl Pipeline { - Poll::new(qs["eth1"].clone()) - .filter_map(nat_6to4) - .send(qs["eth2"].clone()) -} - -fn install_4to6(qs: HashMap) -> impl Pipeline { - Poll::new(qs["eth2"].clone()) - .filter_map(nat_4to6) - .send(qs["eth1"].clone()) -} - fn main() -> Result<()> { let subscriber = fmt::Subscriber::builder() .with_max_level(Level::INFO) .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = load_config()?; - debug!(?config); + let config = runtime::load_config()?; + let runtime = Runtime::from_config(config)?; + + let cap1 = runtime.ports().get("cap1")?.outbox()?; + runtime.set_port_pipeline("cap0", move |packet| nat_6to4(packet, &cap1))?; + + let cap0 = runtime.ports().get("cap0")?.outbox()?; + runtime.set_port_pipeline("cap1", move |packet| nat_4to6(packet, &cap0))?; + + let _guard = runtime.execute()?; + + let term = Arc::new(AtomicBool::new(false)); + flag::register(consts::SIGINT, Arc::clone(&term))?; + info!("ctrl-c to quit ..."); + while !term.load(Ordering::Relaxed) {} - Runtime::build(config)? - .add_pipeline_to_core(1, install_6to4)? - .add_pipeline_to_core(1, install_4to6)? - .execute() + Ok(()) } diff --git a/examples/nat64/nat64.toml b/examples/nat64/nat64.toml index 8db5992b..e0ba3586 100644 --- a/examples/nat64/nat64.toml +++ b/examples/nat64/nat64.toml @@ -1,16 +1,20 @@ app_name = "nat64" -master_core = 0 +main_core = 0 +worker_cores = [] [mempool] capacity = 65535 cache_size = 256 [[ports]] - name = "eth1" + name = "cap0" device = "0000:00:08.0" - cores = [1] + promiscuous = false + rx_cores = [1] + tx_cores = [0] [[ports]] - name = "eth2" + name = "cap1" device = "0000:00:09.0" - cores = [1] + rx_cores = [1] + tx_cores = [0] diff --git a/examples/ping4d/Cargo.toml b/examples/ping4d/Cargo.toml index c36466f7..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,6 +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/README.md b/examples/ping4d/README.md index 1f02bff6..e2c109e4 100644 --- a/examples/ping4d/README.md +++ b/examples/ping4d/README.md @@ -6,10 +6,48 @@ The example is located in the `examples/ping4d` sub-directory. To run the application, -``` +```bash /examples/ping4d$ cargo run -- -f ping4d.toml ``` +In a separate Vagrant VM terminal, first add a static entry to the ARP table so packets are routed to the interface `0000:00:08.0` for address `10.100.1.10`, + +```bash +vagrant$ sudo ip neigh add 10.100.1.10 lladdr 02:00:00:ff:ff:00 dev eth3 nud permanent +``` + +Check the ARP table to verify that the new entry is added, + +```bash +vagrant$ ip neigh show dev eth3 + +10.100.1.10 lladdr 02:00:00:ff:ff:00 PERMANENT +``` + +Now while the application is still running, ping `10.100.1.10`, + +```bash +vagrant$ ping -I eth3 10.100.1.10 + +PING 10.100.1.10 (10.100.1.10) from 10.100.1.254 eth3: 56(84) bytes of data. +64 bytes from 10.100.1.10: icmp_seq=1 ttl=255 time=3.96 ms +64 bytes from 10.100.1.10: icmp_seq=2 ttl=255 time=0.799 ms +64 bytes from 10.100.1.10: icmp_seq=3 ttl=255 time=0.525 ms +... +``` + ## Explanation -Ping operates by sending an ICMP echo request packet to the target host and waiting for an ICMP echo reply. The pipeline uses the `replace` combinator to create a new reply packet for each received request packet. The request packet is immutable. +`cap0` is configured to receive packets on lcore `0` and transmit packets on lcore `1`. + +The `ping` utility sends out ICMPv4 echo request packets to address `10.100.1.10`. With the static ARP entry, the packets are routed to `cap0`. For each echo request, the application generates and sends out an ICMPv4 echo reply packet in response and drops the original echo request. + +The `ping` utility calculates the latency as it receives each echo reply. + +## Cleaning up + +To clean up the ARP table, + +```bash +vagrant$ sudo ip neigh del 10.100.1.10 dev eth3 +``` diff --git a/examples/ping4d/echo.pcap b/examples/ping4d/echo.pcap deleted file mode 100644 index 4848b66f..00000000 Binary files a/examples/ping4d/echo.pcap and /dev/null differ diff --git a/examples/ping4d/main.rs b/examples/ping4d/main.rs index 99f78f19..1a1e5ace 100644 --- a/examples/ping4d/main.rs +++ b/examples/ping4d/main.rs @@ -17,16 +17,19 @@ */ use anyhow::Result; -use capsule::batch::{Batch, Pipeline, Poll}; -use capsule::config::load_config; +use capsule::packets::ethernet::Ethernet; use capsule::packets::icmp::v4::{EchoReply, EchoRequest}; use capsule::packets::ip::v4::Ipv4; -use capsule::packets::{Ethernet, Packet}; -use capsule::{Mbuf, PortQueue, Runtime}; -use tracing::{debug, Level}; +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}; +use std::sync::Arc; +use tracing::{info, Level}; use tracing_subscriber::fmt; -fn reply_echo(packet: &Mbuf) -> Result { +fn reply_echo(packet: Mbuf, cap0: &Outbox) -> Result { let reply = Mbuf::new()?; let ethernet = packet.peek::()?; @@ -34,39 +37,45 @@ fn reply_echo(packet: &Mbuf) -> Result { reply.set_src(ethernet.dst()); reply.set_dst(ethernet.src()); - let ipv4 = ethernet.peek::()?; + let ip4 = ethernet.peek::()?; let mut reply = reply.push::()?; - reply.set_src(ipv4.dst()); - reply.set_dst(ipv4.src()); + reply.set_src(ip4.dst()); + reply.set_dst(ip4.src()); reply.set_ttl(255); - let request = ipv4.peek::()?; + let request = ip4.peek::()?; let mut reply = reply.push::()?; reply.set_identifier(request.identifier()); reply.set_seq_no(request.seq_no()); reply.set_data(request.data())?; reply.reconcile_all(); - debug!(?request); - debug!(?reply); + info!(?request); + info!(?reply); - Ok(reply) -} + let _ = cap0.push(reply); -fn install(q: PortQueue) -> impl Pipeline { - Poll::new(q.clone()).replace(reply_echo).send(q) + Ok(Postmark::Drop(packet)) } fn main() -> Result<()> { let subscriber = fmt::Subscriber::builder() - .with_max_level(Level::DEBUG) + .with_max_level(Level::INFO) .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = load_config()?; - debug!(?config); + let config = runtime::load_config()?; + let runtime = Runtime::from_config(config)?; + + let outbox = runtime.ports().get("cap0")?.outbox()?; + runtime.set_port_pipeline("cap0", move |packet| reply_echo(packet, &outbox))?; + + let _guard = runtime.execute()?; + + let term = Arc::new(AtomicBool::new(false)); + flag::register(consts::SIGINT, Arc::clone(&term))?; + info!("ctrl-c to quit ..."); + while !term.load(Ordering::Relaxed) {} - Runtime::build(config)? - .add_pipeline_to_port("eth1", install)? - .execute() + Ok(()) } diff --git a/examples/ping4d/ping4d.toml b/examples/ping4d/ping4d.toml index 62ff3ab7..c90724db 100644 --- a/examples/ping4d/ping4d.toml +++ b/examples/ping4d/ping4d.toml @@ -1,13 +1,13 @@ app_name = "ping4d" -master_core = 0 -duration = 5 +main_core = 0 +worker_cores = [] [mempool] capacity = 65535 cache_size = 256 [[ports]] - name = "eth1" - device = "net_pcap0" - args = "rx_pcap=echo.pcap,tx_iface=lo" - cores = [0] + name = "cap0" + device = "0000:00:08.0" + rx_cores = [0] + tx_cores = [1] diff --git a/examples/pktdump/Cargo.toml b/examples/pktdump/Cargo.toml index 1556e133..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,8 @@ 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" tracing-subscriber = "0.2" diff --git a/examples/pktdump/README.md b/examples/pktdump/README.md index c3896254..ed030d44 100644 --- a/examples/pktdump/README.md +++ b/examples/pktdump/README.md @@ -1,15 +1,21 @@ # Packet dump example -An example demonstrating pipeline combinators and packet peeking. +An example demonstrating basic packet processing. ## Running the application The example is located in the `examples/pktdump` sub-directory. To run the application, -``` +```bash /examples/pktdump$ cargo run -- -f pktdump.toml ``` ## Explanation -Packet captures of IPv4 and IPv6 packets are played back with libpcap based virtual devices. The pipeline uses the `group_by` combinator to separate the packets by the L3 protocol and processes them with either `dump_v4` and `dump_v6`. Both functions use `peek` instead of `parse` to read the packets immutability. +Packet captures, or pcaps, of IPv4 and IPv6 TCP packets are played back with ports using `libpcap` based virtual devices. `cap0` replays the IPv4 pcap and `cap1` replays the IPv6 pcap. Both ports are receiving on the same worker lcore, and have transmit disabled. + +The parse functions showcase the packet type system. Both IPv4 and IPv6 packet types are preceded by the Ethernet packet type. TCP packet type can succeed either IPv4 or IPv6 packet types. + +Packets are dropped at the end of `dump_pkt`. + +`ctrl-c` terminates the application. diff --git a/examples/pktdump/main.rs b/examples/pktdump/main.rs index 38e6d963..dcb946af 100644 --- a/examples/pktdump/main.rs +++ b/examples/pktdump/main.rs @@ -16,91 +16,83 @@ * SPDX-License-Identifier: Apache-2.0 */ -use anyhow::Result; -use capsule::batch::{Batch, Pipeline, Poll}; -use capsule::config::load_config; +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, Packet, Tcp, Tcp4, Tcp6}; -use capsule::{compose, Mbuf, PortQueue, Runtime}; -use colored::*; -use tracing::{debug, Level}; +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; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use tracing::{info, Level}; use tracing_subscriber::fmt; -#[inline] -fn dump_eth(packet: Mbuf) -> Result { +fn dump_pkt(packet: Mbuf) -> Result { let ethernet = packet.parse::()?; - let info_fmt = format!("{:?}", ethernet).magenta().bold(); - println!("{}", info_fmt); + let fmt = format!("{:?}", ethernet).magenta().bold(); + info!("{}", fmt); - Ok(ethernet) + match ethernet.ether_type() { + EtherTypes::Ipv4 => dump_ip4(ðernet), + EtherTypes::Ipv6 => dump_ip6(ðernet), + _ => Err(anyhow!("not v4 or v6.")), + }?; + + Ok(Postmark::Drop(ethernet.reset())) } -#[inline] -fn dump_v4(ethernet: &Ethernet) -> Result<()> { - let v4 = ethernet.peek::()?; - let info_fmt = format!("{:?}", v4).yellow(); - println!("{}", info_fmt); +fn dump_ip4(ethernet: &Ethernet) -> Result<()> { + let ip4 = ethernet.peek::()?; + let fmt = format!("{:?}", ip4).yellow(); + info!("{}", fmt); - let tcp = v4.peek::()?; + let tcp = ip4.peek::()?; dump_tcp(&tcp); Ok(()) } -#[inline] -fn dump_v6(ethernet: &Ethernet) -> Result<()> { - let v6 = ethernet.peek::()?; - let info_fmt = format!("{:?}", v6).cyan(); - println!("{}", info_fmt); +fn dump_ip6(ethernet: &Ethernet) -> Result<()> { + let ip6 = ethernet.peek::()?; + let fmt = format!("{:?}", ip6).cyan(); + info!("{}", fmt); - let tcp = v6.peek::()?; + let tcp = ip6.peek::()?; dump_tcp(&tcp); Ok(()) } -#[inline] fn dump_tcp(tcp: &Tcp) { - let tcp_fmt = format!("{:?}", tcp).green(); - println!("{}", tcp_fmt); - - let flow_fmt = format!("{:?}", tcp.flow()).bright_blue(); - println!("{}", flow_fmt); -} + let fmt = format!("{:?}", tcp).green(); + info!("{}", fmt); -fn install(q: PortQueue) -> impl Pipeline { - Poll::new(q.clone()) - .map(dump_eth) - .group_by( - |ethernet| ethernet.ether_type(), - |groups| { - compose!( groups { - EtherTypes::Ipv4 => |group| { - group.for_each(dump_v4) - } - EtherTypes::Ipv6 => |group| { - group.for_each(dump_v6) - } - }); - }, - ) - .send(q) + let fmt = format!("{:?}", tcp.flow()).bright_blue(); + info!("{}", fmt); } fn main() -> Result<()> { let subscriber = fmt::Subscriber::builder() - .with_max_level(Level::DEBUG) + .with_max_level(Level::INFO) .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = load_config()?; - debug!(?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)?; + let _guard = runtime.execute()?; - Runtime::build(config)? - .add_pipeline_to_port("eth1", install)? - .add_pipeline_to_port("eth2", install)? - .execute() + let term = Arc::new(AtomicBool::new(false)); + flag::register(consts::SIGINT, Arc::clone(&term))?; + info!("ctrl-c to quit ..."); + while !term.load(Ordering::Relaxed) {} + + Ok(()) } diff --git a/examples/pktdump/pktdump.toml b/examples/pktdump/pktdump.toml index 1ee29b14..066bd2c0 100644 --- a/examples/pktdump/pktdump.toml +++ b/examples/pktdump/pktdump.toml @@ -1,19 +1,19 @@ app_name = "pktdump" -master_core = 0 -duration = 5 +main_core = 0 +worker_cores = [] [mempool] capacity = 65535 cache_size = 256 [[ports]] - name = "eth1" + name = "cap0" device = "net_pcap0" - args = "rx_pcap=tcp4.pcap,tx_iface=lo" - cores = [0] + args = "rx_pcap=tcp4.pcap" + rx_cores = [0] [[ports]] - name = "eth2" + name = "cap1" device = "net_pcap1" - args = "rx_pcap=tcp6.pcap,tx_iface=lo" - cores = [0] + args = "rx_pcap=tcp6.pcap" + rx_cores = [0] diff --git a/examples/signals/Cargo.toml b/examples/signals/Cargo.toml deleted file mode 100644 index 41f7eec9..00000000 --- a/examples/signals/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "signals" -version = "0.1.0" -authors = ["Capsule Developers "] -license = "Apache-2.0" -edition = "2018" -publish = false -readme = "README.md" -description = """ -Linux signal handling example. -""" - -[[bin]] -name = "signals" -path = "main.rs" -doctest = false - -[dependencies] -anyhow = "1.0" -capsule = { version = "0.1", path = "../../core" } -tracing = "0.1" -tracing-subscriber = "0.2" diff --git a/examples/signals/README.md b/examples/signals/README.md deleted file mode 100644 index 9fb10473..00000000 --- a/examples/signals/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Linux signal handling example - -An example demonstrating how to handle linux signals in a Capsule application. - -## Running the application - -The example is located in the `examples/signals` sub-directory. To run the application, - -``` -/examples/signals$ cargo run -- -f signals.toml -``` - -To send signals to the application, execute these commands in a separate terminal, - -``` -$ kill -s SIGHUP $(pidof signals) -$ kill -s SIGTERM $(pidof signals) -``` - -## Explanation - -The `Runtime` exposes `SIGHUP`, `SIGINT`, and `SIGTERM` to the application. By default, any signal received will terminate the running application. To customize the signal handling, use `set_on_signal` to set a custom handler. A return of `false` will continue the runtime execution and a return of `true` will stop the application. This example will ignore `SIGHUP` and terminate on `SIGINT` (`Ctrl-C`) or `SIGTERM`. diff --git a/examples/signals/main.rs b/examples/signals/main.rs deleted file mode 100644 index b0d7b922..00000000 --- a/examples/signals/main.rs +++ /dev/null @@ -1,46 +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::config::load_config; -use capsule::Runtime; -use capsule::UnixSignal::{self, *}; -use tracing::{info, Level}; -use tracing_subscriber::fmt; - -fn on_signal(signal: UnixSignal) -> bool { - info!(?signal); - match signal { - SIGHUP => false, - SIGINT | SIGTERM => true, - } -} - -fn main() -> Result<()> { - let subscriber = fmt::Subscriber::builder() - .with_max_level(Level::INFO) - .finish(); - tracing::subscriber::set_global_default(subscriber)?; - - let config = load_config()?; - let mut runtime = Runtime::build(config)?; - runtime.set_on_signal(on_signal); - - println!("Ctrl-C to stop..."); - runtime.execute() -} diff --git a/examples/signals/signals.toml b/examples/signals/signals.toml deleted file mode 100644 index 1398b962..00000000 --- a/examples/signals/signals.toml +++ /dev/null @@ -1,3 +0,0 @@ -app_name = "signals" -master_core = 0 -ports = [] 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/README.md b/examples/skeleton/README.md index 39e85183..e8ae0b19 100644 --- a/examples/skeleton/README.md +++ b/examples/skeleton/README.md @@ -6,12 +6,18 @@ The base skeleton example is the simplest Capsule application that can be writte The example is located in the `examples/skeleton` sub-directory. To run the application, -``` +```bash /examples/skeleton$ cargo run -- -f skeleton.toml ``` ## Explanation -`skeleton.toml` demonstrates the configuration file structure. The application must specify an `app_name`, `master_core`, `mempool`, and at least one `port`. For the skeleton example, the main application thread runs on CPU core `0`. It has a mempool with capacity of `65535` mbufs preallocated. It has one port configured to also run on CPU core `0`, using an in-memory ring-based virtual device. +`skeleton.toml` demonstrates the configuration file structure. The application must specify an `app_name`, `main_core`, `worker_cores`, `mempool`, and at least one `port`. + +For the skeleton example, the main application thread, referred to as _lcore_ per DPDK terminology, runs on CPU physical core `0`. This main lcore executes the application bootstrapping logic, such as initializing the Capsule runtime. This example does not have any worker tasks, so `worker_cores` is set to empty. + +The global mempool has a preallocated capacity of `65535` mbufs, or message buffers, with a `256` per lcore cache. The mempool's capacity places an upper bound on the total amount of memory used for storing network packets, and is constant for the lifetime of the Capsule application. + +The example has one port configured, named `cap0`, using an in-memory ring-based virtual device. The port's receive loop, aka `rx`, is assigned to run on worker lcore `0`; and it's transmit loop, aka `tx`, is also assigned to run on worker lcore `0`. Both `rx` and `tx` are continuous loops constantly trying to receive and transmit network packets respectively. In practice, especially for heavy work load, they should be executed on separate, dedicated worker lcores for maximum throughput. Sharing the same worker lcore, like in this example, will have negative impact on performance because they are competing with each other for CPU time. -The `main` function first sets up the [`tracing`](https://github.com/tokio-rs/tracing) framework to record log output to the console at `TRACE` level. Then it builds a `Runtime` with the settings from `skeleton.toml` and executes that runtime. Because there are no pipelines or tasks scheduled with the runtime, the application doesn't do anything. It simply waits for the timeout duration of 5 seconds and terminates. +The `main` function first sets up the [`tracing`](https://github.com/tokio-rs/tracing) framework to record log output to the console at `DEBUG` level. Then it builds a `Runtime` with the settings from `skeleton.toml` and executes that runtime. Because there are no tasks scheduled with the runtime, the application doesn't do anything. It terminates immediately. The console output shows the lifecycle of the Capsule runtime from initialization to termination. diff --git a/examples/skeleton/main.rs b/examples/skeleton/main.rs index a8ce0996..ccb0de88 100644 --- a/examples/skeleton/main.rs +++ b/examples/skeleton/main.rs @@ -17,19 +17,21 @@ */ use anyhow::Result; -use capsule::config::load_config; -use capsule::Runtime; +use capsule::runtime::{self, Runtime}; use tracing::{debug, Level}; use tracing_subscriber::fmt; fn main() -> Result<()> { let subscriber = fmt::Subscriber::builder() - .with_max_level(Level::TRACE) + .with_max_level(Level::DEBUG) .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = load_config()?; + let config = runtime::load_config()?; debug!(?config); - Runtime::build(config)?.execute() + let runtime = Runtime::from_config(config)?; + let _guard = runtime.execute()?; + + Ok(()) } diff --git a/examples/skeleton/skeleton.toml b/examples/skeleton/skeleton.toml index fbc9ca06..b44d1307 100644 --- a/examples/skeleton/skeleton.toml +++ b/examples/skeleton/skeleton.toml @@ -1,9 +1,14 @@ app_name = "skeleton" -master_core = 0 +main_core = 0 +worker_cores = [] dpdk_args = "-v --log-level eal:8" -duration = 5 + +[mempool] + capacity = 65535 + cache_size = 256 [[ports]] - name = "eth1" + name = "cap0" device = "net_ring0" - cores = [0] + rx_cores = [0] + tx_cores = [0] diff --git a/examples/syn-flood/Cargo.toml b/examples/syn-flood/Cargo.toml index 1dbe9331..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" @@ -17,8 +17,10 @@ doctest = false [dependencies] anyhow = "1.0" -capsule = { version = "0.1", path = "../../core" } -metrics-core = "0.5" -metrics-observer-yaml = "0.1" +async-io = "1.3" +capsule = { version = "0.2", path = "../../core" } +futures-lite = "1.11" +rand = "0.8" +signal-hook = "0.3" tracing = "0.1" tracing-subscriber = "0.2" diff --git a/examples/syn-flood/README.md b/examples/syn-flood/README.md index c1c10e36..3c71bc97 100644 --- a/examples/syn-flood/README.md +++ b/examples/syn-flood/README.md @@ -1,37 +1,47 @@ # TCP SYN flood example -SYN flood is a form of denial-of-service attack in which the sender attempts to consume resources on the target host by sending large amount of SYN packets. This example demonstrates how to generate new packets as the start of a pipeline. +SYN flood is a form of denial-of-service attack in which the sender attempts to consume resources on the target host by sending large amount of SYN packets. ## Overview SYN flood exploits the [TCP three-way handshake](https://tools.ietf.org/html/rfc793#section-3.4) by sending large amount of SYNs to the target with spoofed source IP addresses. The target will send SYN-ACK to these falsified IP addresses. They will either be unreachable or not respond. +This example demonstrates how to generate new packets instead of receiving packets from a port. + ## Running the application The example is located in the `examples/syn-flood` sub-directory. To run the application, -``` +```bash /examples/syn-flood$ cargo run -- -f syn-flood.toml ``` To observe the `SYN` flood traffic, in the vagrant VM, run `tcpdump` to capture packets sent to the destination IP address and port, -``` -$ sudo tcpdump -nn host 10.100.1.255 and port 80 +```bash +vagrant$ sudo tcpdump -i eth3 -nn host 10.100.1.254 and port 80 + +tcpdump: verbose output suppressed, use -v or -vv for full protocol decode +listening on eth3, link-type EN10MB (Ethernet), capture size 262144 bytes +18:59:27.140269 IP 136.178.185.105.0 > 10.100.1.254.80: Flags [S], seq 1, win 10, length 0 +18:59:27.140275 IP 225.67.11.246.0 > 10.100.1.254.80: Flags [S], seq 1, win 10, length 0 +18:59:27.140279 IP 12.164.180.121.0 > 10.100.1.254.80: Flags [S], seq 1, win 10, length 0 +... ``` ## Explanation -The application schedules a periodic pipeline on port `eth1`'s assigned core `1`. The pipeline will repeat every 10 milliseconds. Instead of receiving packets from the port, the pipeline uses `batch::poll_fn` to generate a batch of new SYN packets each iteration and sends them to the interface `eth3` with assigned IP `10.100.1.255` on port `80`. Every packet is assigned a different spoofed source IP address. +`cap0` is configured to transmit on lcore `0` with queue depth set at `2048`. -On the main core `0`, a scheduled task prints out the port metrics once every second. +The example spawns a separate worker task on lcore `1` that will at 50ms interval generate a batch of 128 TCP SYN packets and send them through `cap0`. Each generated TCP SYN will have a random source IP address. The destination is set to `10.100.1.254` on port `80`, which is the address of the `eth3` interface on the host. (On a side note, the 50ms delay is necessary because emulated `virtio` driver is too slow on tx. Without a delay, the mempool is exhausted.) +```bash +vagrant$ ip addr show dev eth3 + +5: eth3: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 + link/ether 02:00:00:ff:ff:ff brd ff:ff:ff:ff:ff:ff + inet 10.100.1.254/24 brd 10.100.1.255 scope global eth3 + valid_lft forever preferred_lft forever ``` ---- -capsule: - port: - "dropped{port=\"eth1\",dir=\"tx\"}": 87545 - "errors{port=\"eth1\",dir=\"tx\"}": 0 - "octets{port=\"eth1\",dir=\"tx\",core=\"1\"}": 16008570 - "packets{port=\"eth1\",dir=\"tx\",core=\"1\"}": 296455 -``` + +`ctrl-c` to stop the worker task and quit the application. diff --git a/examples/syn-flood/main.rs b/examples/syn-flood/main.rs index ee61243e..fce4154c 100644 --- a/examples/syn-flood/main.rs +++ b/examples/syn-flood/main.rs @@ -17,63 +17,62 @@ */ use anyhow::Result; -use capsule::batch::{Batch, Pipeline}; -use capsule::config::load_config; -use capsule::metrics; +use async_io::Timer; use capsule::net::MacAddr; +use capsule::packets::ethernet::Ethernet; use capsule::packets::ip::v4::Ipv4; -use capsule::packets::{Ethernet, Packet, Tcp4}; -use capsule::{batch, Mbuf, PortQueue, Runtime}; -use metrics_core::{Builder, Drain, Observe}; -use metrics_observer_yaml::YamlBuilder; -use std::collections::HashMap; +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; use std::net::Ipv4Addr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::time::Duration; -use tracing::{debug, error, Level}; +use tracing::{error, info, Level}; use tracing_subscriber::fmt; -fn install(qs: HashMap) -> impl Pipeline { - let src_mac = qs["eth1"].mac_addr(); - let dst_ip = Ipv4Addr::new(10, 100, 1, 255); +async fn syn_flood(src_mac: MacAddr, cap0: Outbox, term: Arc) { + let dst_ip = Ipv4Addr::new(10, 100, 1, 254); let dst_mac = MacAddr::new(0x02, 0x00, 0x00, 0xff, 0xff, 0xff); - // starts the src ip at 10.0.0.0 - let mut next_ip = 10u32 << 24; - - batch::poll_fn(|| { - Mbuf::alloc_bulk(128).unwrap_or_else(|err| { - error!(?err); - vec![] - }) - }) - .map(move |packet| { - let mut ethernet = packet.push::()?; - ethernet.set_src(src_mac); - ethernet.set_dst(dst_mac); - - // +1 to gen the next ip - next_ip += 1; - - let mut v4 = ethernet.push::()?; - v4.set_src(next_ip.into()); - v4.set_dst(dst_ip); - - let mut tcp = v4.push::()?; - tcp.set_syn(); - tcp.set_seq_no(1); - tcp.set_window(10); - tcp.set_dst_port(80); - tcp.reconcile_all(); - - Ok(tcp) - }) - .send(qs["eth1"].clone()) -} + // 50ms delay between batches. + let mut timer = Timer::interval(Duration::from_millis(50)); + + while !term.load(Ordering::Relaxed) { + let _ = timer.next().await; + info!("generating 128 SYN packets."); + + match Mbuf::alloc_bulk(128) { + Ok(mbufs) => mbufs + .into_iter() + .map(|mbuf| -> Result { + let mut ethernet = mbuf.push::()?; + ethernet.set_src(src_mac); + ethernet.set_dst(dst_mac); + + let mut ip4 = ethernet.push::()?; + ip4.set_src(rand::random::().into()); + ip4.set_dst(dst_ip); + + let mut tcp = ip4.push::()?; + tcp.set_syn(); + tcp.set_seq_no(1); + tcp.set_window(10); + tcp.set_dst_port(80); + tcp.reconcile_all(); -fn print_stats() { - let mut observer = YamlBuilder::new().build(); - metrics::global().controller().observe(&mut observer); - println!("{}", observer.drain()); + Ok(tcp.reset()) + }) + .filter_map(|res| res.ok()) + .for_each(|mbuf| { + let _ = cap0.push(mbuf); + }), + Err(err) => error!(?err), + } + } } fn main() -> Result<()> { @@ -82,11 +81,25 @@ fn main() -> Result<()> { .finish(); tracing::subscriber::set_global_default(subscriber)?; - let config = load_config()?; - debug!(?config); + let config = runtime::load_config()?; + let runtime = Runtime::from_config(config)?; + + let term = Arc::new(AtomicBool::new(false)); + + let cap0 = runtime.ports().get("cap0")?; + let outbox = cap0.outbox()?; + let src_mac = cap0.mac_addr(); + + runtime + .lcores() + .get(1)? + .spawn(syn_flood(src_mac, outbox, term.clone())); + + let _guard = runtime.execute()?; + + flag::register(consts::SIGINT, Arc::clone(&term))?; + info!("ctrl-c to quit ..."); + while !term.load(Ordering::Relaxed) {} - Runtime::build(config)? - .add_periodic_pipeline_to_core(1, install, Duration::from_millis(10))? - .add_periodic_task_to_core(0, print_stats, Duration::from_secs(1))? - .execute() + Ok(()) } diff --git a/examples/syn-flood/syn-flood.toml b/examples/syn-flood/syn-flood.toml index 1570e046..1d9c010a 100644 --- a/examples/syn-flood/syn-flood.toml +++ b/examples/syn-flood/syn-flood.toml @@ -1,13 +1,13 @@ app_name = "syn-flood" -master_core = 0 +main_core = 0 +worker_cores = [1] [mempool] capacity = 65535 cache_size = 256 [[ports]] - name = "eth1" + name = "cap0" device = "0000:00:08.0" - cores = [1] - rxd = 512 - txd = 512 + tx_cores = [0] + txd = 2048 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/ffi/src/bindings.h b/ffi/src/bindings.h index 4f096325..fbfd33f2 100644 --- a/ffi/src/bindings.h +++ b/ffi/src/bindings.h @@ -24,7 +24,11 @@ #include #include #include +#include +#include #include +#include +#include // libnuma functions and types #include @@ -41,6 +45,11 @@ */ int _rte_errno(void); +/** + * Return the Application thread ID of the execution unit. + */ +unsigned _rte_lcore_id(void); + /** * Allocate a new mbuf from a mempool. */ diff --git a/ffi/src/bindings_rustdoc.rs b/ffi/src/bindings_rustdoc.rs index 4f766588..1c97bdb3 100644 --- a/ffi/src/bindings_rustdoc.rs +++ b/ffi/src/bindings_rustdoc.rs @@ -26077,6 +26077,10 @@ extern "C" { #[doc = " calls to certain functions to determine why those functions failed."] pub fn _rte_errno() -> ::std::os::raw::c_int; } +extern "C" { + #[doc = " Return the Application thread ID of the execution unit."] + pub fn _rte_lcore_id() -> ::std::os::raw::c_uint; +} extern "C" { #[doc = " Allocate a new mbuf from a mempool."] pub fn _rte_pktmbuf_alloc(mp: *mut rte_mempool) -> *mut rte_mbuf; diff --git a/ffi/src/shim.c b/ffi/src/shim.c index 421fdd94..b334cebb 100644 --- a/ffi/src/shim.c +++ b/ffi/src/shim.c @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -25,6 +26,10 @@ int _rte_errno(void) { return rte_errno; } +unsigned _rte_lcore_id(void) { + return rte_lcore_id(); +} + struct rte_mbuf *_rte_pktmbuf_alloc(struct rte_mempool *mp) { return rte_pktmbuf_alloc(mp); } 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..7b6829ba 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(); @@ -121,7 +122,7 @@ pub fn gen_icmpv4(input: syn::DeriveInput) -> TokenStream { let name = input.ident; let expanded = quote! { - impl ::capsule::packets::icmp::v4::Icmpv4Packet for #name { + impl ::capsule::packets::icmp::v4::Icmpv4Packet for #name { #[inline] fn msg_type(&self) -> ::capsule::packets::icmp::v4::Icmpv4Type { self.icmp().msg_type() @@ -143,8 +144,8 @@ pub fn gen_icmpv4(input: syn::DeriveInput) -> TokenStream { } } - impl ::capsule::packets::Packet for #name { - type Envelope = ::capsule::packets::ip::v4::Ipv4; + impl ::capsule::packets::Packet for #name { + type Envelope = E; #[inline] fn envelope(&self) -> &Self::Envelope { @@ -173,13 +174,14 @@ pub fn gen_icmpv4(input: syn::DeriveInput) -> TokenStream { #[inline] fn try_parse(envelope: Self::Envelope, _internal: ::capsule::packets::Internal) -> ::anyhow::Result { - envelope.parse::<::capsule::packets::icmp::v4::Icmpv4>()?.downcast::<#name>() + envelope.parse::<::capsule::packets::icmp::v4::Icmpv4>()?.downcast::<#name>() } #[inline] 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(); @@ -193,11 +195,11 @@ pub fn gen_icmpv4(input: syn::DeriveInput) -> TokenStream { offset, }; - icmp.header_mut().msg_type = <#name as Icmpv4Message>::msg_type().0; + icmp.header_mut().msg_type = <#name as Icmpv4Message>::msg_type().0; icmp.envelope_mut() .set_next_protocol(ProtocolNumbers::Icmpv4); - <#name as Icmpv4Message>::try_push(icmp, internal) + <#name as Icmpv4Message>::try_push(icmp, internal) } #[inline] 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::() }