├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── config.yml │ └── feature-request.md ├── pull_request_template.md └── workflows │ └── main.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── bench ├── Cargo.toml ├── combinators.rs ├── mbuf.rs └── packets.rs ├── codecov.yml ├── core ├── Cargo.toml └── src │ ├── batch │ ├── emit.rs │ ├── filter.rs │ ├── filter_map.rs │ ├── for_each.rs │ ├── group_by.rs │ ├── inspect.rs │ ├── map.rs │ ├── mod.rs │ ├── poll.rs │ ├── replace.rs │ ├── rxtx.rs │ └── send.rs │ ├── config.rs │ ├── dpdk │ ├── kni.rs │ ├── mbuf.rs │ ├── mempool.rs │ ├── mod.rs │ ├── port.rs │ └── stats.rs │ ├── ffi.rs │ ├── lib.rs │ ├── macros.rs │ ├── metrics.rs │ ├── net │ ├── cidr │ │ ├── mod.rs │ │ ├── v4.rs │ │ └── v6.rs │ ├── mac.rs │ └── mod.rs │ ├── packets │ ├── arp.rs │ ├── checksum.rs │ ├── ethernet.rs │ ├── icmp │ │ ├── mod.rs │ │ ├── v4 │ │ │ ├── echo_reply.rs │ │ │ ├── echo_request.rs │ │ │ ├── mod.rs │ │ │ ├── redirect.rs │ │ │ └── time_exceeded.rs │ │ └── v6 │ │ │ ├── destination_unreachable.rs │ │ │ ├── echo_reply.rs │ │ │ ├── echo_request.rs │ │ │ ├── mod.rs │ │ │ ├── ndp │ │ │ ├── mod.rs │ │ │ ├── neighbor_advert.rs │ │ │ ├── neighbor_solicit.rs │ │ │ ├── options │ │ │ │ ├── link_layer_addr.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── mtu.rs │ │ │ │ ├── prefix_info.rs │ │ │ │ └── redirected.rs │ │ │ ├── redirect.rs │ │ │ ├── router_advert.rs │ │ │ └── router_solicit.rs │ │ │ ├── time_exceeded.rs │ │ │ └── too_big.rs │ ├── ip │ │ ├── mod.rs │ │ ├── v4.rs │ │ └── v6 │ │ │ ├── fragment.rs │ │ │ ├── mod.rs │ │ │ └── srh.rs │ ├── mod.rs │ ├── tcp.rs │ ├── types.rs │ └── udp.rs │ ├── pcap.rs │ ├── runtime │ ├── core_map.rs │ └── mod.rs │ └── testils │ ├── byte_arrays.rs │ ├── criterion.rs │ ├── mod.rs │ ├── packet.rs │ ├── proptest │ ├── arbitrary.rs │ ├── mod.rs │ └── strategy.rs │ └── rvg.rs ├── examples ├── kni │ ├── Cargo.toml │ ├── README.md │ ├── kni.toml │ └── main.rs ├── nat64 │ ├── Cargo.toml │ ├── README.md │ ├── main.rs │ └── nat64.toml ├── ping4d │ ├── Cargo.toml │ ├── README.md │ ├── echo.pcap │ ├── main.rs │ └── ping4d.toml ├── pktdump │ ├── Cargo.toml │ ├── README.md │ ├── main.rs │ ├── pktdump.toml │ ├── suppressions.txt │ ├── tcp4.pcap │ └── tcp6.pcap ├── signals │ ├── Cargo.toml │ ├── README.md │ ├── main.rs │ └── signals.toml ├── skeleton │ ├── Cargo.toml │ ├── README.md │ ├── main.rs │ └── skeleton.toml └── syn-flood │ ├── Cargo.toml │ ├── README.md │ ├── main.rs │ └── syn-flood.toml ├── ffi ├── Cargo.toml ├── build.rs └── src │ ├── bindings.h │ ├── bindings_rustdoc.rs │ ├── lib.rs │ └── shim.c └── macros ├── Cargo.toml └── src ├── derive_packet.rs └── lib.rs /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug in Capsule 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug? 11 | 12 | ## Steps to reproduce? 13 | 1. ... 14 | 2. ... 15 | 16 | ## Expected behavior? 17 | 18 | ### Capsule version? 19 | 20 | ### OS? 21 | 22 | ### Docker / VM / Bare? 23 | 24 | ### Stack trace or error log output 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Capsule Discord 4 | url: https://discord.gg/sAgzNV27sA 5 | about: Please ask and answer questions about Capsule in our Discord. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a feature for Capsule 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe what you'd like to see/use? 11 | 12 | ## Describe alternatives you've considered? 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Fixes # (issue) - if applicable 4 | 5 | ## Type of change 6 | 7 | - [ ] Bug fix 8 | - [ ] New feature 9 | - [ ] New protocol 10 | - [ ] Breaking change 11 | - [ ] Documentation update 12 | 13 | ## Checklist 14 | 15 | - [ ] Associated tests 16 | - [ ] Associated documentation 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | **/target 4 | **/Cargo.lock 5 | **/*.pcap 6 | 7 | # coverage 8 | *.xml 9 | 10 | # other 11 | *log* 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Capsule Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to making participation in our project and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, sex characteristics, gender identity and expression, 10 | level of experience, education, socio-economic status, nationality, personal 11 | appearance, race, religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or 42 | reject comments, commits, code, wiki edits, issues, and other contributions 43 | that are not aligned to this Code of Conduct, or to ban temporarily or 44 | permanently any contributor for other behaviors that they deem inappropriate, 45 | threatening, offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies both within project spaces and in public spaces 50 | when an individual is representing the project or its community. Examples of 51 | representing a project or community include using an official project e-mail 52 | address, discussion on the official [Discord](https://discord.gg/sAgzNV27sA), 53 | posting via an official social media account, or acting as an appointed representative 54 | at an online or offline event. Representation of a project may be further defined and 55 | clarified by project maintainers. 56 | 57 | ## Enforcement 58 | 59 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 60 | reported by contacting the project team at [capsule-dev@googlegroups.com](mailto:capsule-dev@googlegroups.com). 61 | All complaints will be reviewed and investigated and will result in a response that 62 | is deemed necessary and appropriate to the circumstances. The project team is 63 | obligated to maintain confidentiality with regard to the reporter of an incident. 64 | Further details of specific enforcement policies may be posted separately. 65 | 66 | Project maintainers who do not follow or enforce the Code of Conduct in good 67 | faith may face temporary or permanent repercussions as determined by other 68 | members of the project's leadership. 69 | 70 | ## Attribution 71 | 72 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 73 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 74 | 75 | [homepage]: https://www.contributor-covenant.org 76 | 77 | For answers to common questions about this code of conduct, see 78 | https://www.contributor-covenant.org/faq 79 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Capsule 2 | 3 | Thank you for your help improving the project! No contribution is too small and all contributions are valued. 4 | 5 | When contributing to this repository, please first discuss the change you wish to make via issue, [Discord](https://discord.gg/sAgzNV27sA) or [email](mailto:capsule-dev@googlegroups.com) with the `Capsule` [maintainers](https://github.com/orgs/capsule-rs/teams/maintainers/members) before making a change. 6 | 7 | This guide will help you get started. Please note that we have a [Code of Conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project. 8 | 9 | ## Table of Contents 10 | 11 | - [Contributing to Capsule](#contributing-to-capsule) 12 | - [Table of Contents](#table-of-contents) 13 | - [Code of Conduct](#code-of-conduct) 14 | - [Contributing in Issues](#contributing-in-issues) 15 | - [Asking for General Help](#asking-for-general-help) 16 | - [Submitting a Bug Report](#submitting-a-bug-report) 17 | - [Requesting a New Feature](#requesting-a-new-feature) 18 | - [Pull Requests](#pull-requests) 19 | - [Development Notes](#development-notes) 20 | - [Commits](#commits) 21 | - [Opening the Pull Request](#opening-the-pull-request) 22 | - [Contributor License Agreement](#contributor-license-agreement) 23 | - [Pull Request Approval](#pull-request-approval) 24 | 25 | ## Code of Conduct 26 | 27 | This project and everyone participating in it are governed by the [Capsule Code Of Conduct](CODE_OF_CONDUCT.md). By 28 | participating, you agree to this Code. Please report any violations of the code of conduct to capsule-dev@googlegroups.com. 29 | 30 | ## Contributing in Issues 31 | 32 | Work on `Capsule` is tracked by Github Issues. Anybody can participate in any stage of contribution. We encourage you to participate in the discussion around bugs and PRs. 33 | 34 | ### Asking for General Help 35 | 36 | If you have reviewed existing documentation and still have questions or are having problems, you can join our [Discord](https://discord.gg/sAgzNV27sA) to ask for help. When applicable, we would appretiate it if you can contribute back a documentation PR that helps others avoid the problems that you encountered. 37 | 38 | ### Submitting a Bug Report 39 | 40 | If you believe that you have uncovered a bug, please fill out the [bug report](.github/ISSUE_TEMPLATE/bug-report.md) form, following the template to the best of your ability. Do not worry if you cannot answer every detail, just fill in what you can. 41 | 42 | The two most important pieces of information we need in order to properly evaluate the report is a description of the behavior you are seeing and a simple test case we can use to recreate the problem on our own. 43 | 44 | ### Requesting a New Feature 45 | 46 | If you want a new feature added to `Capsule`, please fill out the [feature request](.github/ISSUE_TEMPLATE/feature-request.md) form. 47 | 48 | There are no hard rules as to what features will or will not be accepted. It can be a network protocol, integrating with a feature in DPDK, or something new entirely. Ultimately, it depends on what the expected benefit is relative to the expected maintenance burden. 49 | 50 | ## Pull Requests 51 | 52 | Pull requests which fix bugs, add features or improve documentation are welcome and greatly appretiated. Before making a large change, it is usually a good idea to first open an issue describing the change to solicit feedback and guidance. This will increase the likelihood of the PR getting merged. 53 | 54 | ### Development Notes 55 | 56 | `Capsule` requires extra arguments to many common `cargo` commands typically use. We've simplified these commands with a set of [make targets](Makefile) so you don't have to remember them. 57 | 58 | To check compilation of the workspace with all features enabled, use 59 | 60 | ``` 61 | make check 62 | ``` 63 | 64 | To run the unit tests, including those that are feature-gated, use 65 | 66 | ``` 67 | make test 68 | ``` 69 | 70 | We use [rustfmt](https://github.com/rust-lang/rustfmt) to maintain a consistent coding style. To automatically format your source code, use 71 | 72 | ``` 73 | make fmt 74 | ``` 75 | 76 | We use [Clippy](https://github.com/rust-lang/rust-clippy) to catch common Rust mistakes and follow best practices like [Rust 2018](https://doc.rust-lang.org/edition-guide/rust-2018/index.html) idioms. To lint the source code, use 77 | 78 | ``` 79 | make lint 80 | ``` 81 | 82 | When generating documentation normally, the markers that list the features required for various parts of `Capsule` are missing. To build the documentation correctly, install nightly Rust (`rustup install nightly`) and use 83 | 84 | ``` 85 | make docs 86 | ``` 87 | 88 | ### Commits 89 | 90 | It is a recommended best practice to keep your changes as logically grouped as possible within individual commits. There is no limit to the number of commits any single Pull Request may have. That said, if you have a number of commits that are "checkpoints" and don't represent a single logical change, please squash those together. 91 | 92 | The first line of the commit message should 93 | * contain a short description of the change no more than 72 characters 94 | * start with an imperative verb in the present tense 95 | * be entirely in lowercase with the exception of proper nouns and acronyms 96 | 97 | When necessary to include a longer commit message, keep the second line blank. You can use markdown syntax for the rest of the commit message. 98 | 99 | Note that multiple commits may get squashed when they are merged. 100 | 101 | ### Opening the Pull Request 102 | 103 | Opening a new Pull Request will present you with a [template](.github/pull_request_template.md) that should be filled out. Please try to do your best at filling out the details. 104 | 105 | You will get feedback or requests for changes to your Pull Request. This is a big part of the submission process so don't be discouraged! This is a necessary part of the process in order to evaluate whether the changes are correct and necessary. 106 | 107 | Any community member can review a PR and you might get conflicting feedback. Keep an eye out for comments from `Capsule` [maintainers](https://github.com/orgs/capsule-rs/teams/maintainers/members) to provide guidance on conflicting feedback. 108 | 109 | ### Contributor License Agreement 110 | 111 | Before Comcast merges your code into the project you must sign the [Comcast Contributor License Agreement (CLA)](https://gist.github.com/ComcastOSS/a7b8933dd8e368535378cda25c92d19a). 112 | 113 | If you haven't previously signed a Comcast CLA, you'll automatically be asked to when you open a pull request. Alternatively, we can send you a PDF that you can sign and scan back to us. Please create a new GitHub issue to request a PDF version of the CLA. 114 | 115 | ### Pull Request Approval 116 | 117 | A Pull Request must be approved by at least one maintainer of `Capsule`. Once approved, a maintainer will merge it. If you are a maintainer, you can merge your Pull Request once you have the approval of another maintainer. 118 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "bench", 4 | "core", 5 | "examples/kni", 6 | "examples/nat64", 7 | "examples/ping4d", 8 | "examples/pktdump", 9 | "examples/signals", 10 | "examples/skeleton", 11 | "examples/syn-flood", 12 | "ffi", 13 | "macros" 14 | ] 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | CLIPPY_ARGS = -- -D clippy::wildcard_dependencies -D rust-2018-idioms -D warnings 4 | CRITERION_PLOTS_DIR = bench/target/criterion 5 | NIGHTLY := $(shell rustup show|grep nightly 2> /dev/null) 6 | 7 | TO_DEVNULL = &>/dev/null 8 | 9 | .PHONY: bench check clean clean-plots docs fmt lint find-plots test watch watch-lint 10 | 11 | bench: 12 | @cargo bench 13 | 14 | check: 15 | @pushd core $(TO_DEVNULL) && cargo check --all-targets --features full && popd $(TO_DEVNULL) 16 | @cargo check --all-targets --workspace --exclude capsule 17 | 18 | clean: 19 | @cargo clean 20 | 21 | clean-plots: 22 | @rm -rf $(CRITERION_PLOTS_DIR) 23 | 24 | docs: 25 | ifdef NIGHTLY 26 | @RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --lib -p capsule \ 27 | -p capsule-ffi -p capsule-macros --no-deps --all-features 28 | else 29 | @cargo doc --lib -p capsule -p capsule-ffi -p capsule-macros --no-deps \ 30 | --all-features 31 | endif 32 | 33 | find-plots: 34 | @ls $(CRITERION_PLOTS_DIR)/report/index.html 35 | 36 | fmt: 37 | @cargo fmt --all 38 | 39 | lint: 40 | @pushd core $(TO_DEVNULL) && cargo clippy --all-targets --features full $(CLIPPY_ARGS) && popd $(TO_DEVNULL) 41 | @cargo clippy --all-targets --workspace --exclude capsule $(CLIPPY_ARGS) 42 | 43 | test: 44 | @pushd core $(TO_DEVNULL) && cargo test --all-targets --features full && popd $(TO_DEVNULL) 45 | @cargo test --all-targets --workspace --exclude capsule 46 | 47 | compile-failure: 48 | @pushd core $(TO_DEVNULL) && cargo test --features compile_failure && popd $(TO_DEVNULL) 49 | 50 | watch: 51 | ifdef WATCH 52 | @cargo watch --poll -x build -w $(WATCH) 53 | else 54 | @cargo watch --poll -x build --all 55 | endif 56 | 57 | watch-lint: 58 | ifdef WATCH 59 | @cargo watch --poll -s "make lint" -w $(WATCH) 60 | else 61 | @cargo watch --poll -s "make lint" --all 62 | endif 63 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Capsule 2 | 3 | Copyright 2019 Comcast Cable Communications Management, LLC 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | SPDX-License-Identifier: Apache-2.0 18 | 19 | This product includes software developed at Comcast (http://www.comcast.com/). 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crates.io][crates-badge]][crates-url] 2 | [![Documentation][docs-badge]][docs-url] 3 | [![Apache-2.0 licensed][apache-badge]][apache-url] 4 | [![CI-Github Actions][gh-actions-badge]][gh-actions-url] 5 | [![Codecov][codecov-badge]][codecov-url] 6 | [![Code of Conduct][code-of-conduct-badge]][code-of-conduct-url] 7 | [![Discord][discord-badge]][discord-url] 8 | 9 | [crates-badge]: https://img.shields.io/crates/v/capsule.svg 10 | [crates-url]: https://crates.io/crates/capsule 11 | [docs-badge]: https://docs.rs/capsule/badge.svg 12 | [docs-url]: https://docs.rs/capsule 13 | [apache-badge]: https://img.shields.io/github/license/capsule-rs/capsule 14 | [apache-url]: LICENSE 15 | [gh-actions-badge]: https://github.com/capsule-rs/capsule/workflows/build/badge.svg 16 | [gh-actions-url]: https://github.com/capsule-rs/capsule/actions 17 | [codecov-badge]: https://codecov.io/gh/capsule-rs/capsule/branch/master/graph/badge.svg 18 | [codecov-url]: https://codecov.io/gh/capsule-rs/capsule 19 | [code-of-conduct-badge]: https://img.shields.io/badge/%E2%9D%A4-code%20of%20conduct-ff69b4 20 | [code-of-conduct-url]: CODE_OF_CONDUCT.md 21 | [discord-badge]: https://img.shields.io/discord/690406128567320597.svg?logo=discord 22 | [discord-url]: https://discord.gg/sAgzNV27sA 23 | 24 | # Capsule 25 | 26 | A framework for network function development. Written in Rust, inspired by [NetBricks](https://www.usenix.org/system/files/conference/osdi16/osdi16-panda.pdf) and built on Intel's [Data Plane Development Kit](https://www.dpdk.org/). 27 | 28 | ## Table of Contents 29 | 30 | - [Overview](#overview) 31 | - [Quick Start](#quick-start) 32 | - [Contributing](#contributing) 33 | - [Code of Conduct](#code-of-conduct) 34 | - [Contact](#contact) 35 | - [Maintainers](#maintainers) 36 | - [License](#license) 37 | 38 | ## Overview 39 | 40 | The goal of `Capsule` is to offer an ergonomic framework for network function development that traditionally has high barriers of entry for developers. We've created a tool to efficiently manipulate network packets while being type-safe, memory-safe, and thread-safe. Building on `DPDK` and `Rust`, `Capsule` offers 41 | 42 | - a fast packet processor that uses minimum number of CPU cycles. 43 | - a rich packet type system that guarantees memory-safety and thread-safety. 44 | - a declarative programming model that emphasizes simplicity. 45 | - an extendable and testable framework that is easy to develop and maintain. 46 | 47 | ## Quick Start 48 | 49 | The easiest way to start developing `Capsule` applications is to use the `Vagrant` [virtual machine](https://github.com/capsule-rs/sandbox/blob/master/Vagrantfile) and the `Docker` [sandbox](https://hub.docker.com/repository/docker/getcapsule/sandbox) provided by our team. The sandbox is preconfigured with all the necessary tools and libraries for `Capsule` development, including: 50 | 51 | - [`DPDK` 19.11](https://doc.dpdk.org/guides-19.11/rel_notes/release_19_11.html) 52 | - [`Clang`](https://clang.llvm.org/) and [`LLVM`](https://www.llvm.org/) 53 | - [`Rust 1.50`](https://blog.rust-lang.org/2021/02/11/Rust-1.50.0.html) 54 | - [`rr`](https://rr-project.org/) 5.3 55 | 56 | First install [`Vagrant`](https://www.vagrantup.com/) and [`VirtualBox`](https://www.virtualbox.org/) on your system. Also install the following `Vagrant` plugins, 57 | 58 | ``` 59 | host$ vagrant plugin install vagrant-reload vagrant-disksize vagrant-vbguest 60 | ``` 61 | 62 | Then clone our sandbox repository, start and ssh into the Vagrant VM, 63 | 64 | ``` 65 | host$ git clone https://github.com/capsule-rs/sandbox.git 66 | host$ cd sandbox 67 | host$ vagrant up 68 | host$ vagrant ssh 69 | ``` 70 | 71 | Once inside the created `Debian` VM with `Docker` installed, run the sandbox with the command, 72 | 73 | ``` 74 | vagrant$ docker run -it --rm \ 75 | --privileged \ 76 | --network=host \ 77 | --name sandbox \ 78 | --cap-add=SYS_PTRACE \ 79 | --security-opt seccomp=unconfined \ 80 | -v /lib/modules:/lib/modules \ 81 | -v /dev/hugepages:/dev/hugepages \ 82 | getcapsule/sandbox:19.11.6-1.50 /bin/bash 83 | ``` 84 | 85 | Remember to also mount the working directory of your project as a volume for the sandbox. Then you can use `Cargo` commands inside the container as normal. 86 | 87 | Add `Capsule` as a dependency to your `Cargo.toml` and start writing your application, 88 | 89 | ```toml 90 | [dependencies] 91 | capsule = "0.1" 92 | ``` 93 | 94 | If you want to develop `Capsule` without using `Docker` in `Vagrant`, please check out our [sandbox repo](https://github.com/capsule-rs/sandbox/blob/master/README.md) for instructions on running our Vagrant VM environment, as well as others options not using either `Vagrant` or `Docker`. 95 | 96 | ## Contributing 97 | 98 | Thanks for your help improving the project! We have a [contributing guide](CONTRIBUTING.md) to help you get involved with the `Capsule` project. 99 | 100 | ## Code of Conduct 101 | 102 | This project and everyone participating in it are governed by the [Capsule Code Of Conduct](CODE_OF_CONDUCT.md). By participating, you agree to this Code. Please report any violations to the code of conduct to capsule-dev@googlegroups.com. 103 | 104 | ## Contact 105 | 106 | You can contact us either through [`Discord`](https://discord.gg/sAgzNV27sA) or [email](mailto:capsule-dev@googlegroups.com). 107 | 108 | ## Maintainers 109 | 110 | The current maintainers with roles to merge PRs are: 111 | 112 | - [Daniel Jin](https://github.com/drunkirishcoder) 113 | - [Zeeshan Lakhani](https://github.com/zeeshanlakhani) 114 | - [Andrew Wang](https://github.com/awangc) 115 | - [Peter Cline](https://github.com/clinedome) 116 | 117 | ## Read More About Capsule 118 | 119 | - [Modernize network function development with this Rust-based framework](https://opensource.com/article/20/8/capsule-networking) 120 | 121 | ## License 122 | 123 | This project is licensed under the [Apache-2.0 license](LICENSE). 124 | -------------------------------------------------------------------------------- /bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "capsule-bench" 3 | version = "0.1.0" 4 | authors = ["Capsule Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | publish = false 8 | description = """ 9 | Benchmarks for Capsule. 10 | """ 11 | 12 | [dev-dependencies] 13 | anyhow = "1.0" 14 | capsule = { version = "0.1", path = "../core", features = ["testils"] } 15 | criterion = "0.3" 16 | proptest = "1.0" 17 | 18 | [[bench]] 19 | name = "packets" 20 | path = "packets.rs" 21 | harness = false 22 | 23 | [[bench]] 24 | name = "combinators" 25 | path = "combinators.rs" 26 | harness = false 27 | 28 | [[bench]] 29 | name = "mbuf" 30 | path = "mbuf.rs" 31 | harness = false 32 | -------------------------------------------------------------------------------- /bench/combinators.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use anyhow::Result; 20 | use capsule::batch::{Batch, Either}; 21 | use capsule::packets::ip::v4::Ipv4; 22 | use capsule::packets::{Ethernet, Packet}; 23 | use capsule::testils::criterion::BencherExt; 24 | use capsule::testils::proptest::*; 25 | use capsule::{compose, Mbuf}; 26 | use criterion::{criterion_group, criterion_main, Criterion}; 27 | use proptest::prelude::*; 28 | use proptest::strategy; 29 | 30 | const BATCH_SIZE: usize = 500; 31 | 32 | fn filter_true(batch: impl Batch) -> impl Batch { 33 | batch.filter(|_p| true) 34 | } 35 | 36 | fn filter_false(batch: impl Batch) -> impl Batch { 37 | batch.filter(|_p| false) 38 | } 39 | 40 | #[capsule::bench(mempool_capacity = 511)] 41 | fn filters_batch(c: &mut Criterion) { 42 | let mut group = c.benchmark_group("combinators::filter"); 43 | 44 | group.bench_function("combinators::filter_true", |b| { 45 | let s = any::(); 46 | b.iter_proptest_combinators(s, filter_true, BATCH_SIZE) 47 | }); 48 | 49 | group.bench_function("combinators::filter_false", |b| { 50 | let s = any::(); 51 | b.iter_proptest_combinators(s, filter_false, BATCH_SIZE) 52 | }); 53 | 54 | group.finish() 55 | } 56 | 57 | fn filter_map(batch: impl Batch) -> impl Batch { 58 | batch.filter_map(|p| { 59 | let ethernet = p.parse::()?; 60 | Ok(Either::Keep(ethernet)) 61 | }) 62 | } 63 | 64 | fn map_then_filter(batch: impl Batch) -> impl Batch { 65 | batch.map(|p| p.parse::()).filter(|_p| true) 66 | } 67 | 68 | #[capsule::bench(mempool_capacity = 511)] 69 | fn filter_map_vs_map_then_filter_batch(c: &mut Criterion) { 70 | let mut group = c.benchmark_group("combinators::filter_map_vs_map_then_filter"); 71 | 72 | group.bench_function("combinators::filter_map", |b| { 73 | let s = v4_udp(); 74 | b.iter_proptest_combinators(s, filter_map, BATCH_SIZE) 75 | }); 76 | 77 | group.bench_function("combinators::map_then_filter", |b| { 78 | let s = v4_udp(); 79 | b.iter_proptest_combinators(s, map_then_filter, BATCH_SIZE) 80 | }); 81 | 82 | group.finish() 83 | } 84 | 85 | fn map(batch: impl Batch) -> impl Batch { 86 | batch.map(|p| p.parse::()) 87 | } 88 | 89 | fn no_batch_map(mbuf: Mbuf) -> Result { 90 | mbuf.parse::() 91 | } 92 | 93 | #[capsule::bench(mempool_capacity = 511)] 94 | fn map_batch_vs_parse(c: &mut Criterion) { 95 | let mut group = c.benchmark_group("combinators::map_batch_vs_parse"); 96 | 97 | group.bench_function("combinators::map", |b| { 98 | let s = v4_udp(); 99 | b.iter_proptest_combinators(s, map, BATCH_SIZE) 100 | }); 101 | 102 | group.bench_function("combinators::no_batch_map", |b| { 103 | let s = v4_udp(); 104 | b.iter_proptest_batched(s, no_batch_map, BATCH_SIZE) 105 | }); 106 | } 107 | 108 | #[capsule::bench(mempool_capacity = 1023)] 109 | fn map_batches(c: &mut Criterion) { 110 | let mut group = c.benchmark_group("combinators::map_on_diff_batch_sizes"); 111 | 112 | group.bench_function("combinators::map_10", |b| { 113 | let s = v4_udp(); 114 | b.iter_proptest_combinators(s, map, 10) 115 | }); 116 | 117 | group.bench_function("combinators::map_50", |b| { 118 | let s = v4_udp(); 119 | b.iter_proptest_combinators(s, map, 50) 120 | }); 121 | 122 | group.bench_function("combinators::map_150", |b| { 123 | let s = v4_udp(); 124 | b.iter_proptest_combinators(s, map, 150) 125 | }); 126 | 127 | group.bench_function("combinators::map_500", |b| { 128 | let s = v4_udp(); 129 | b.iter_proptest_combinators(s, map, 500) 130 | }); 131 | 132 | group.bench_function("combinators::map_1000", |b| { 133 | let s = v4_udp(); 134 | b.iter_proptest_combinators(s, map, 1000) 135 | }); 136 | 137 | group.finish() 138 | } 139 | 140 | fn map_parse_errors(batch: impl Batch) -> impl Batch { 141 | batch.map(|p| p.parse::()?.parse::()) 142 | } 143 | 144 | #[capsule::bench(mempool_capacity = 511)] 145 | fn map_errors(c: &mut Criterion) { 146 | let mut group = c.benchmark_group("combinators::map_errors_vs_no_errors"); 147 | 148 | group.bench_function("combinators::map_no_errors", |b| { 149 | let s = v4_udp(); 150 | b.iter_proptest_combinators(s, map_parse_errors, BATCH_SIZE) 151 | }); 152 | 153 | group.bench_function("combinators::map_with_errors", |b| { 154 | let s = strategy::Union::new_weighted(vec![(8, v4_udp().boxed()), (2, v6_udp().boxed())]); 155 | b.iter_proptest_combinators(s, map_parse_errors, BATCH_SIZE) 156 | }); 157 | } 158 | 159 | static mut COUNTER: u32 = 0; 160 | fn group_by(batch: impl Batch) -> impl Batch { 161 | unsafe { COUNTER += 1 }; 162 | 163 | unsafe { 164 | batch.group_by( 165 | |_p| COUNTER % 2, 166 | |groups| { 167 | compose!(groups { 168 | 0 => |group| { 169 | group 170 | } 171 | _ => |group| { 172 | group 173 | } 174 | }) 175 | }, 176 | ) 177 | } 178 | } 179 | 180 | #[capsule::bench(mempool_capacity = 511)] 181 | fn group_by_batch(c: &mut Criterion) { 182 | c.bench_function("combinators::group_by", |b| { 183 | let s = any::(); 184 | b.iter_proptest_combinators(s, group_by, BATCH_SIZE) 185 | }); 186 | } 187 | 188 | fn replace(batch: impl Batch) -> impl Batch { 189 | batch.replace(|_p| Mbuf::new()) 190 | } 191 | 192 | fn no_batch_replace(_mbuf: Mbuf) -> Result { 193 | Mbuf::new() 194 | } 195 | 196 | #[capsule::bench(mempool_capacity = 511)] 197 | fn replace_batch(c: &mut Criterion) { 198 | let mut group = c.benchmark_group("combinators::replace_with_new_mbuf_vs_create_new_mbuf"); 199 | 200 | group.bench_function("combinators::replace", |b| { 201 | let s = any::(); 202 | b.iter_proptest_combinators(s, replace, BATCH_SIZE) 203 | }); 204 | 205 | group.bench_function("combinators::no_batch_replace", |b| { 206 | let s = any::(); 207 | b.iter_proptest_batched(s, no_batch_replace, BATCH_SIZE) 208 | }); 209 | 210 | group.finish() 211 | } 212 | 213 | fn bench_config() -> Criterion { 214 | Criterion::default().with_plots() 215 | } 216 | 217 | criterion_group! { 218 | name = benches; 219 | config=bench_config(); 220 | targets=filters_batch, 221 | filter_map_vs_map_then_filter_batch, 222 | map_batch_vs_parse, 223 | group_by_batch, 224 | replace_batch, 225 | map_batches, 226 | map_errors, 227 | } 228 | 229 | criterion_main!(benches); 230 | -------------------------------------------------------------------------------- /bench/mbuf.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use anyhow::Result; 20 | use capsule::Mbuf; 21 | use criterion::{criterion_group, criterion_main, Criterion}; 22 | 23 | const BATCH_SIZE: usize = 100; 24 | 25 | fn alloc() -> Result> { 26 | (0..BATCH_SIZE) 27 | .map(|_| Mbuf::new()) 28 | .collect::>>() 29 | } 30 | 31 | fn alloc_bulk() -> Result> { 32 | Mbuf::alloc_bulk(BATCH_SIZE) 33 | } 34 | 35 | #[capsule::bench(mempool_capacity = 511)] 36 | fn alloc_batch(c: &mut Criterion) { 37 | let mut group = c.benchmark_group("mbuf::alloc_vs_alloc_bulk"); 38 | group.bench_function("mbuf::alloc", |b| b.iter_with_large_drop(alloc)); 39 | group.bench_function("mbuf::alloc_bulk", |b| b.iter_with_large_drop(alloc_bulk)); 40 | 41 | group.finish() 42 | } 43 | 44 | fn bench_config() -> Criterion { 45 | Criterion::default().with_plots() 46 | } 47 | 48 | criterion_group! { 49 | name = benches; 50 | config=bench_config(); 51 | targets=alloc_batch, 52 | } 53 | 54 | criterion_main!(benches); 55 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | 3 | status: 4 | project: 5 | default: 6 | threshold: 5% 7 | patch: false 8 | changes: false 9 | 10 | precision: 2 11 | range: 50...85 12 | round: down 13 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "capsule" 3 | version = "0.1.5" 4 | authors = ["Capsule Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | readme = "../README.md" 8 | repository = "https://github.com/capsule-rs/capsule" 9 | keywords = ["nfv", "network-functions", "packet-processing", "packet-parsing", "dpdk"] 10 | categories = ["network-programming", "development-tools::ffi"] 11 | documentation = "https://docs.rs/capsule/0.1.4/capsule/" 12 | description = """ 13 | A framework for network function development. Written in Rust, inspired by 14 | NetBricks and built on Intel's Data Plane Development Kit. 15 | """ 16 | 17 | [lib] 18 | name = "capsule" 19 | path = "src/lib.rs" 20 | doctest = false 21 | 22 | [dependencies] 23 | anyhow = "1.0" 24 | capsule-ffi = { version = "0.1.5", path = "../ffi" } 25 | capsule-macros = { version = "0.1.5", path = "../macros" } 26 | clap = "2.33" 27 | criterion = { version = "0.3", optional = true } 28 | futures-preview = "=0.3.0-alpha.19" 29 | libc = "0.2" 30 | metrics-core = { version = "0.5", optional = true } 31 | metrics-runtime = { version = "0.13", optional = true, default-features = false } 32 | once_cell = "1.7" 33 | proptest = { version = "1.0", optional = true } 34 | regex = "1" 35 | serde = { version = "1.0", features = ["derive"] } 36 | thiserror = "1.0" 37 | tokio = "=0.2.0-alpha.6" 38 | tokio-executor = { version = "=0.2.0-alpha.6", features = ["current-thread", "threadpool"] } 39 | tokio-net = { version = "=0.2.0-alpha.6", features = ["signal"] } 40 | tokio-timer = "=0.3.0-alpha.6" 41 | toml = "0.5" 42 | tracing = "0.1" 43 | 44 | [dev-dependencies] 45 | criterion = "0.3" 46 | proptest = { version = "1.0", default-features = false, features = ["default-code-coverage"] } 47 | 48 | [features] 49 | default = ["metrics"] 50 | compile_failure = [] # compiler tests to check mutability rules are followed 51 | full = ["metrics", "pcap-dump", "testils"] 52 | metrics = ["metrics-core", "metrics-runtime"] 53 | pcap-dump = [] 54 | testils = ["criterion", "proptest"] 55 | 56 | [package.metadata.docs.rs] 57 | features = ["capsule-ffi/rustdoc", "full"] 58 | rustdoc-args = ["--cfg", "docsrs"] 59 | -------------------------------------------------------------------------------- /core/src/batch/emit.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::{Batch, Disposition, PacketTx}; 20 | use crate::packets::Packet; 21 | 22 | /// A batch that transmits the packets through the specified [`PacketTx`]. 23 | /// 24 | /// [`PacketTx`]: crate::batch::PacketTx 25 | #[allow(missing_debug_implementations)] 26 | pub struct Emit { 27 | batch: B, 28 | tx: Tx, 29 | } 30 | 31 | impl Emit { 32 | /// Creates a new `Emit` batch. 33 | #[inline] 34 | pub fn new(batch: B, tx: Tx) -> Self { 35 | Emit { batch, tx } 36 | } 37 | } 38 | 39 | impl Batch for Emit { 40 | type Item = B::Item; 41 | 42 | #[inline] 43 | fn replenish(&mut self) { 44 | self.batch.replenish(); 45 | } 46 | 47 | #[inline] 48 | fn next(&mut self) -> Option> { 49 | self.batch.next().map(|disp| { 50 | disp.map(|pkt| { 51 | self.tx.transmit(vec![pkt.reset()]); 52 | Disposition::Emit 53 | }) 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /core/src/batch/filter.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::{Batch, Disposition}; 20 | use crate::packets::Packet; 21 | 22 | /// A batch that filters the packets of the underlying batch. 23 | /// 24 | /// If the predicate evaluates to `false`, the packet is marked as dropped 25 | /// and will short-circuit the remainder of the pipeline. 26 | #[allow(missing_debug_implementations)] 27 | pub struct Filter 28 | where 29 | P: FnMut(&B::Item) -> bool, 30 | { 31 | batch: B, 32 | predicate: P, 33 | } 34 | 35 | impl Filter 36 | where 37 | P: FnMut(&B::Item) -> bool, 38 | { 39 | /// Creates a new `Filter` batch. 40 | #[inline] 41 | pub fn new(batch: B, predicate: P) -> Self { 42 | Filter { batch, predicate } 43 | } 44 | } 45 | 46 | impl Batch for Filter 47 | where 48 | P: FnMut(&B::Item) -> bool, 49 | { 50 | type Item = B::Item; 51 | 52 | #[inline] 53 | fn replenish(&mut self) { 54 | self.batch.replenish(); 55 | } 56 | 57 | #[inline] 58 | fn next(&mut self) -> Option> { 59 | self.batch.next().map(|disp| { 60 | disp.map(|pkt| { 61 | if (self.predicate)(&pkt) { 62 | Disposition::Act(pkt) 63 | } else { 64 | Disposition::Drop(pkt.reset()) 65 | } 66 | }) 67 | }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /core/src/batch/filter_map.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::{Batch, Disposition}; 20 | use crate::packets::Packet; 21 | use crate::Mbuf; 22 | use anyhow::Result; 23 | 24 | /// The result of a [`filter_map`]. 25 | /// 26 | /// [`filter_map`]: crate::batch::Batch::filter_map 27 | #[allow(missing_debug_implementations)] 28 | pub enum Either { 29 | /// Keeps the packet as mapped result. 30 | Keep(T), 31 | 32 | /// Drops the packet. 33 | Drop(Mbuf), 34 | } 35 | 36 | /// A batch that both filters and maps the packets of the underlying batch. 37 | /// 38 | /// If the closure returns `Drop`, the packet is marked as dropped. On 39 | /// error, the packet is marked as aborted. In both scenarios, it will 40 | /// short-circuit the remainder of the pipeline. 41 | #[allow(missing_debug_implementations)] 42 | pub struct FilterMap 43 | where 44 | F: FnMut(B::Item) -> Result>, 45 | { 46 | batch: B, 47 | f: F, 48 | } 49 | 50 | impl FilterMap 51 | where 52 | F: FnMut(B::Item) -> Result>, 53 | { 54 | /// Creates a new `FilterMap` batch. 55 | #[inline] 56 | pub fn new(batch: B, f: F) -> Self { 57 | FilterMap { batch, f } 58 | } 59 | } 60 | 61 | impl Batch for FilterMap 62 | where 63 | F: FnMut(B::Item) -> Result>, 64 | { 65 | type Item = T; 66 | 67 | #[inline] 68 | fn replenish(&mut self) { 69 | self.batch.replenish(); 70 | } 71 | 72 | #[inline] 73 | fn next(&mut self) -> Option> { 74 | self.batch.next().map(|disp| { 75 | disp.map(|orig| match (self.f)(orig) { 76 | Ok(Either::Keep(new)) => Disposition::Act(new), 77 | Ok(Either::Drop(mbuf)) => Disposition::Drop(mbuf), 78 | Err(e) => Disposition::Abort(e), 79 | }) 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /core/src/batch/for_each.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::{Batch, Disposition}; 20 | use anyhow::Result; 21 | 22 | /// A batch that calls a closure on packets in the underlying batch. 23 | #[allow(missing_debug_implementations)] 24 | pub struct ForEach 25 | where 26 | F: FnMut(&B::Item) -> Result<()>, 27 | { 28 | batch: B, 29 | f: F, 30 | } 31 | 32 | impl ForEach 33 | where 34 | F: FnMut(&B::Item) -> Result<()>, 35 | { 36 | /// Creates a new `ForEach` batch. 37 | #[inline] 38 | pub fn new(batch: B, f: F) -> Self { 39 | ForEach { batch, f } 40 | } 41 | } 42 | 43 | impl Batch for ForEach 44 | where 45 | F: FnMut(&B::Item) -> Result<()>, 46 | { 47 | type Item = B::Item; 48 | 49 | #[inline] 50 | fn replenish(&mut self) { 51 | self.batch.replenish(); 52 | } 53 | 54 | #[inline] 55 | fn next(&mut self) -> Option> { 56 | self.batch.next().map(|disp| { 57 | disp.map(|pkt| match (self.f)(&pkt) { 58 | Ok(_) => Disposition::Act(pkt), 59 | Err(e) => Disposition::Abort(e), 60 | }) 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/src/batch/group_by.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::{Batch, Disposition}; 20 | use crate::packets::Packet; 21 | use std::cell::Cell; 22 | use std::collections::{HashMap, VecDeque}; 23 | use std::hash::Hash; 24 | use std::rc::Rc; 25 | 26 | /// A bridge between the main batch pipeline and the branch pipelines 27 | /// created by the [`GroupBy`] combinator. Packets can be fed one at a time 28 | /// through the bridge. Because the pipeline execution is depth first, 29 | /// this is the most efficient way storage wise. 30 | #[allow(missing_debug_implementations)] 31 | #[derive(Default)] 32 | pub struct Bridge(Rc>>); 33 | 34 | impl Bridge { 35 | /// Creates a new, empty bridge. 36 | pub fn new() -> Self { 37 | Bridge(Rc::new(Cell::new(None))) 38 | } 39 | 40 | /// Feeds a packet into the bridge container. 41 | pub fn set(&self, pkt: T) { 42 | self.0.set(Some(pkt)); 43 | } 44 | } 45 | 46 | impl Clone for Bridge { 47 | fn clone(&self) -> Self { 48 | Bridge(Rc::clone(&self.0)) 49 | } 50 | } 51 | 52 | impl Batch for Bridge { 53 | type Item = T; 54 | 55 | fn replenish(&mut self) { 56 | // nothing to do 57 | } 58 | 59 | fn next(&mut self) -> Option> { 60 | self.0.take().map(Disposition::Act) 61 | } 62 | } 63 | 64 | /// Builder closure for a sub batch from a bridge. 65 | pub type GroupByBatchBuilder = dyn FnOnce(Bridge) -> Box>; 66 | 67 | /// A batch that splits the underlying batch into multiple sub batches. 68 | /// 69 | /// A closure is used to extract the discriminator used to determine how to 70 | /// split the packets in the batch. If a packet is unmatched, it will be 71 | /// marked as dropped. On error, the packet is marked as aborted. 72 | /// 73 | /// All the sub batches must have the same packet type as the underlying 74 | /// batch. 75 | #[allow(missing_debug_implementations)] 76 | pub struct GroupBy 77 | where 78 | D: Eq + Clone + Hash, 79 | S: Fn(&B::Item) -> D, 80 | { 81 | batch: B, 82 | selector: S, 83 | bridge: Bridge, 84 | groups: HashMap>>, 85 | catchall: Box>, 86 | fanouts: VecDeque>, 87 | } 88 | 89 | impl GroupBy 90 | where 91 | D: Eq + Clone + Hash, 92 | S: Fn(&B::Item) -> D, 93 | { 94 | /// Creates a new `GroupBy` batch. 95 | #[inline] 96 | pub fn new(batch: B, selector: S, composer: C) -> Self 97 | where 98 | C: FnOnce(&mut HashMap, Box>>), 99 | { 100 | // get the builders for the sub batches 101 | let mut builders = HashMap::new(); 102 | composer(&mut builders); 103 | 104 | let bridge = Bridge::new(); 105 | 106 | // build the catchall batch pipeline 107 | let catchall = builders.remove(&None).unwrap()(bridge.clone()); 108 | 109 | // build the rest of the batch pipelines 110 | let groups = builders 111 | .into_iter() 112 | .map(|(key, build)| { 113 | let key = key.unwrap(); 114 | let group = build(bridge.clone()); 115 | (key, group) 116 | }) 117 | .collect::>(); 118 | 119 | GroupBy { 120 | batch, 121 | selector, 122 | bridge, 123 | groups, 124 | catchall, 125 | fanouts: VecDeque::new(), 126 | } 127 | } 128 | } 129 | 130 | impl Batch for GroupBy 131 | where 132 | D: Eq + Clone + Hash, 133 | S: Fn(&B::Item) -> D, 134 | { 135 | type Item = B::Item; 136 | 137 | #[inline] 138 | fn replenish(&mut self) { 139 | self.batch.replenish(); 140 | } 141 | 142 | #[inline] 143 | fn next(&mut self) -> Option> { 144 | if let Some(disp) = self.fanouts.pop_front() { 145 | Some(disp) 146 | } else { 147 | self.batch.next().map(|disp| { 148 | disp.map(|pkt| { 149 | // gets the discriminator key 150 | let key = (self.selector)(&pkt); 151 | 152 | // feeds this packet through the bridge 153 | self.bridge.set(pkt); 154 | 155 | // runs the packet through. the sub-batch could be a fanout 156 | // that produces multiple packets from one input. they are 157 | // temporarily stored in a queue and returned in the subsequent 158 | // iterations. 159 | let batch = match self.groups.get_mut(&key) { 160 | Some(group) => group, 161 | None => &mut self.catchall, 162 | }; 163 | 164 | while let Some(next) = batch.next() { 165 | self.fanouts.push_back(next) 166 | } 167 | 168 | self.fanouts.pop_front().unwrap() 169 | }) 170 | }) 171 | } 172 | } 173 | } 174 | 175 | #[doc(hidden)] 176 | #[macro_export] 177 | macro_rules! __compose { 178 | ($map:ident, $($key:expr => |$arg:tt| $body:block),*) => {{ 179 | $( 180 | $map.insert(Some($key), Box::new(|$arg| Box::new($body))); 181 | )* 182 | }}; 183 | } 184 | 185 | /// Composes the batch builders for the [`group_by`] combinator. 186 | /// 187 | /// [`group_by`]: crate::batch::Batch::group_by 188 | #[macro_export] 189 | macro_rules! compose { 190 | ($map:ident { $($key:expr => |$arg:tt| $body:block)+ }) => {{ 191 | $crate::__compose!($map, $($key => |$arg| $body),*); 192 | $map.insert(None, Box::new(|group| Box::new(group))); 193 | }}; 194 | ($map:ident { $($key:expr => |$arg:tt| $body:block)+ _ => |$_arg:tt| $_body:block }) => {{ 195 | $crate::__compose!($map, $($key => |$arg| $body),*); 196 | $map.insert(None, Box::new(|$_arg| Box::new($_body))); 197 | }}; 198 | ($map:ident { $($key:expr),+ => |$arg:tt| $body:block }) => {{ 199 | $crate::compose!($map { $($key => |$arg| $body)+ }); 200 | }}; 201 | ($map:ident { $($key:expr),+ => |$arg:tt| $body:block _ => |$_arg:tt| $_body:block }) => {{ 202 | $crate::compose!($map { $($key => |$arg| $body)+ _ => |$_arg| $_body }); 203 | }}; 204 | ($map:ident { _ => |$_arg:tt| $_body:block }) => {{ 205 | $map.insert(None, Box::new(|$_arg| Box::new($_body))); 206 | }}; 207 | } 208 | -------------------------------------------------------------------------------- /core/src/batch/inspect.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::{Batch, Disposition}; 20 | 21 | /// A batch that calls a closure on packets in the underlying batch, including 22 | /// ones that are already dropped, emitted or aborted. 23 | #[allow(missing_debug_implementations)] 24 | pub struct Inspect 25 | where 26 | F: FnMut(&Disposition), 27 | { 28 | batch: B, 29 | f: F, 30 | } 31 | 32 | impl Inspect 33 | where 34 | F: FnMut(&Disposition), 35 | { 36 | /// Creates a new `Inspect` batch. 37 | #[inline] 38 | pub fn new(batch: B, f: F) -> Self { 39 | Inspect { batch, f } 40 | } 41 | } 42 | 43 | impl Batch for Inspect 44 | where 45 | F: FnMut(&Disposition), 46 | { 47 | type Item = B::Item; 48 | 49 | #[inline] 50 | fn replenish(&mut self) { 51 | self.batch.replenish(); 52 | } 53 | 54 | #[inline] 55 | fn next(&mut self) -> Option> { 56 | self.batch.next().map(|disp| { 57 | (self.f)(&disp); 58 | disp 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/batch/map.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::{Batch, Disposition}; 20 | use crate::packets::Packet; 21 | use anyhow::Result; 22 | 23 | /// A batch that maps the packets of the underlying batch. 24 | /// 25 | /// On error, the packet is marked as `aborted` and will short-circuit the 26 | /// remainder of the pipeline. 27 | #[allow(missing_debug_implementations)] 28 | pub struct Map 29 | where 30 | F: FnMut(B::Item) -> Result, 31 | { 32 | batch: B, 33 | f: F, 34 | } 35 | 36 | impl Map 37 | where 38 | F: FnMut(B::Item) -> Result, 39 | { 40 | /// Creates a new `Map` batch. 41 | #[inline] 42 | pub fn new(batch: B, f: F) -> Self { 43 | Map { batch, f } 44 | } 45 | } 46 | 47 | impl Batch for Map 48 | where 49 | F: FnMut(B::Item) -> Result, 50 | { 51 | type Item = T; 52 | 53 | #[inline] 54 | fn replenish(&mut self) { 55 | self.batch.replenish(); 56 | } 57 | 58 | #[inline] 59 | fn next(&mut self) -> Option> { 60 | self.batch.next().map(|disp| { 61 | disp.map(|orig| match (self.f)(orig) { 62 | Ok(new) => Disposition::Act(new), 63 | Err(e) => Disposition::Abort(e), 64 | }) 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /core/src/batch/poll.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::{Batch, Disposition, PacketRx, PollRx}; 20 | use crate::Mbuf; 21 | use std::collections::VecDeque; 22 | 23 | /// A batch that polls a receiving source for new packets. 24 | /// 25 | /// This marks the beginning of the pipeline. 26 | #[allow(missing_debug_implementations)] 27 | pub struct Poll { 28 | rx: Rx, 29 | packets: Option>, 30 | } 31 | 32 | impl Poll { 33 | /// Creates a new `Poll` batch. 34 | #[inline] 35 | pub fn new(rx: Rx) -> Self { 36 | Poll { rx, packets: None } 37 | } 38 | } 39 | 40 | impl Batch for Poll { 41 | type Item = Mbuf; 42 | 43 | /// Replenishes the batch with new packets from the RX source. 44 | /// 45 | /// If there are still packets left in the current queue, they are lost. 46 | #[inline] 47 | fn replenish(&mut self) { 48 | // `VecDeque` is not the ideal structure here. We are relying on the 49 | // conversion from `Vec` to `VecDeque` to be allocation-free. but 50 | // unfortunately that's not always the case. We need an efficient and 51 | // allocation-free data structure with pop semantic. 52 | self.packets = Some(self.rx.receive().into()); 53 | } 54 | 55 | #[inline] 56 | fn next(&mut self) -> Option> { 57 | if let Some(q) = self.packets.as_mut() { 58 | q.pop_front().map(Disposition::Act) 59 | } else { 60 | None 61 | } 62 | } 63 | } 64 | 65 | /// Creates a new poll batch from a closure. 66 | pub fn poll_fn(f: F) -> Poll> 67 | where 68 | F: Fn() -> Vec, 69 | { 70 | Poll::new(PollRx { f }) 71 | } 72 | -------------------------------------------------------------------------------- /core/src/batch/replace.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::{Batch, Disposition}; 20 | use crate::packets::Packet; 21 | use anyhow::Result; 22 | 23 | /// A batch that replaces each packet of the batch with another packet. 24 | /// 25 | /// The original packet is dropped from the batch with the new packet in its 26 | /// place. On error, the packet is `aborted` and will short-circuit the 27 | /// remainder of the pipeline. 28 | #[allow(missing_debug_implementations)] 29 | pub struct Replace 30 | where 31 | F: FnMut(&B::Item) -> Result, 32 | { 33 | batch: B, 34 | f: F, 35 | slot: Option, 36 | } 37 | 38 | impl Replace 39 | where 40 | F: FnMut(&B::Item) -> Result, 41 | { 42 | /// Creates a new `Replace` batch. 43 | #[inline] 44 | pub fn new(batch: B, f: F) -> Self { 45 | Replace { 46 | batch, 47 | f, 48 | slot: None, 49 | } 50 | } 51 | } 52 | 53 | impl Batch for Replace 54 | where 55 | F: FnMut(&B::Item) -> Result, 56 | { 57 | type Item = T; 58 | 59 | #[inline] 60 | fn replenish(&mut self) { 61 | self.batch.replenish(); 62 | } 63 | 64 | #[inline] 65 | fn next(&mut self) -> Option> { 66 | // internally the replace combinator will add a new packet to the 67 | // batch and mark the original as dropped. the iteration grows to 68 | // 2x in length because each item becomes 2 items. 69 | if let Some(pkt) = self.slot.take() { 70 | // has a packet in the temp slot. marks it as dropped. 71 | Some(Disposition::Drop(pkt.reset())) 72 | } else { 73 | // nothing in the slot, fetches a new packet from source. 74 | self.batch.next().map(|disp| { 75 | disp.map(|orig| { 76 | match (self.f)(&orig) { 77 | Ok(new) => { 78 | // keeps the original in the temp slot, we will mark it dropped 79 | // in the iteration that immediately follows. 80 | self.slot.replace(orig); 81 | Disposition::Act(new) 82 | } 83 | Err(e) => Disposition::Abort(e), 84 | } 85 | }) 86 | }) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /core/src/batch/rxtx.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! Implementations of `PacketRx` and `PacketTx`. 20 | //! 21 | //! Implemented for `PortQueue`. 22 | //! 23 | //! `PacketRx` implemented for `KniRx`. 24 | //! 25 | //! `PacketTx` implemented for `KniTxQueue`. 26 | //! 27 | //! Implemented for the MPSC channel so it can be used as a batch source 28 | //! mostly in tests. 29 | 30 | use super::{PacketRx, PacketTx}; 31 | use crate::{KniRx, KniTxQueue, Mbuf, PortQueue}; 32 | use std::iter; 33 | use std::sync::mpsc::{Receiver, Sender}; 34 | 35 | impl PacketRx for PortQueue { 36 | fn receive(&mut self) -> Vec { 37 | PortQueue::receive(self) 38 | } 39 | } 40 | 41 | impl PacketTx for PortQueue { 42 | fn transmit(&mut self, packets: Vec) { 43 | PortQueue::transmit(self, packets) 44 | } 45 | } 46 | 47 | impl PacketRx for KniRx { 48 | fn receive(&mut self) -> Vec { 49 | KniRx::receive(self) 50 | } 51 | } 52 | 53 | impl PacketTx for KniTxQueue { 54 | fn transmit(&mut self, packets: Vec) { 55 | KniTxQueue::transmit(self, packets) 56 | } 57 | } 58 | 59 | impl PacketRx for Receiver { 60 | fn receive(&mut self) -> Vec { 61 | iter::from_fn(|| self.try_recv().ok()).collect::>() 62 | } 63 | } 64 | 65 | impl PacketTx for Sender { 66 | fn transmit(&mut self, packets: Vec) { 67 | packets.into_iter().for_each(|packet| { 68 | let _ = self.send(packet); 69 | }); 70 | } 71 | } 72 | 73 | /// A batch that polls a closure for packets. 74 | #[allow(missing_debug_implementations)] 75 | pub struct PollRx 76 | where 77 | F: Fn() -> Vec, 78 | { 79 | pub(crate) f: F, 80 | } 81 | 82 | impl PacketRx for PollRx 83 | where 84 | F: Fn() -> Vec, 85 | { 86 | fn receive(&mut self) -> Vec { 87 | (self.f)() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /core/src/batch/send.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::{Batch, Disposition, PacketTx, Pipeline}; 20 | use crate::dpdk::CoreId; 21 | #[cfg(feature = "metrics")] 22 | use crate::metrics::{labels, Counter, SINK}; 23 | use crate::packets::Packet; 24 | use crate::Mbuf; 25 | use futures::{future, Future}; 26 | use std::pin::Pin; 27 | use std::task::{Context, Poll}; 28 | use tokio_executor::current_thread; 29 | 30 | /// Creates a new pipeline counter. 31 | #[cfg(feature = "metrics")] 32 | fn new_counter(name: &'static str, pipeline: &str) -> Counter { 33 | SINK.scoped("pipeline").counter_with_labels( 34 | name, 35 | labels!( 36 | "pipeline" => pipeline.to_owned(), 37 | "core" => CoreId::current().raw().to_string(), 38 | ), 39 | ) 40 | } 41 | 42 | /// A batch that can be executed as a runtime task. 43 | #[allow(missing_debug_implementations)] 44 | pub struct Send { 45 | name: String, 46 | batch: B, 47 | tx: Tx, 48 | #[cfg(feature = "metrics")] 49 | runs: Counter, 50 | #[cfg(feature = "metrics")] 51 | processed: Counter, 52 | #[cfg(feature = "metrics")] 53 | dropped: Counter, 54 | #[cfg(feature = "metrics")] 55 | errors: Counter, 56 | } 57 | 58 | impl Send { 59 | /// Creates a new `Send` batch. 60 | #[cfg(not(feature = "metrics"))] 61 | #[inline] 62 | pub fn new(name: String, batch: B, tx: Tx) -> Self { 63 | Send { name, batch, tx } 64 | } 65 | 66 | /// Creates a new `Send` batch. 67 | #[cfg(feature = "metrics")] 68 | #[inline] 69 | pub fn new(name: String, batch: B, tx: Tx) -> Self { 70 | let runs = new_counter("runs", &name); 71 | let processed = new_counter("processed", &name); 72 | let dropped = new_counter("dropped", &name); 73 | let errors = new_counter("errors", &name); 74 | Send { 75 | name, 76 | batch, 77 | tx, 78 | runs, 79 | processed, 80 | dropped, 81 | errors, 82 | } 83 | } 84 | 85 | fn run(&mut self) { 86 | // let's get a new batch 87 | self.batch.replenish(); 88 | 89 | let mut transmit_q = Vec::with_capacity(64); 90 | let mut drop_q = Vec::with_capacity(64); 91 | let mut emitted = 0u64; 92 | let mut aborted = 0u64; 93 | 94 | // consume the whole batch to completion 95 | while let Some(disp) = self.batch.next() { 96 | match disp { 97 | Disposition::Act(packet) => transmit_q.push(packet.reset()), 98 | Disposition::Drop(mbuf) => drop_q.push(mbuf), 99 | Disposition::Emit => emitted += 1, 100 | Disposition::Abort(_) => aborted += 1, 101 | } 102 | } 103 | 104 | #[cfg(feature = "metrics")] 105 | { 106 | self.runs.record(1); 107 | self.processed.record(transmit_q.len() as u64 + emitted); 108 | self.dropped.record(drop_q.len() as u64); 109 | self.errors.record(aborted); 110 | } 111 | 112 | if !transmit_q.is_empty() { 113 | self.tx.transmit(transmit_q); 114 | } 115 | 116 | if !drop_q.is_empty() { 117 | Mbuf::free_bulk(drop_q); 118 | } 119 | } 120 | } 121 | 122 | /// By implementing the `Future` trait, `Send` can be spawned onto the tokio 123 | /// executor. Each time the future is polled, it processes one batch of 124 | /// packets before returning the `Poll::Pending` status and yields. 125 | impl Future for Send { 126 | type Output = (); 127 | 128 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 129 | // executes a batch of packets. 130 | self.get_mut().run(); 131 | 132 | // now schedules the waker as a future and yields the core so other 133 | // futures have a chance to run. 134 | let waker = cx.waker().clone(); 135 | current_thread::spawn(future::lazy(|_| waker.wake())); 136 | 137 | Poll::Pending 138 | } 139 | } 140 | 141 | impl Pipeline for Send { 142 | #[inline] 143 | fn name(&self) -> &str { 144 | &self.name 145 | } 146 | 147 | #[inline] 148 | fn run_once(&mut self) { 149 | self.run() 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /core/src/dpdk/mempool.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::SocketId; 20 | use crate::dpdk::DpdkError; 21 | use crate::ffi::{self, AsStr, ToCString, ToResult}; 22 | use crate::{debug, info}; 23 | use anyhow::Result; 24 | use std::cell::Cell; 25 | use std::collections::HashMap; 26 | use std::fmt; 27 | use std::os::raw; 28 | use std::ptr::{self, NonNull}; 29 | use std::sync::atomic::{AtomicUsize, Ordering}; 30 | use thiserror::Error; 31 | 32 | /// A memory pool is an allocator of message buffers, or `Mbuf`. For best 33 | /// performance, each socket should have a dedicated `Mempool`. 34 | pub(crate) struct Mempool { 35 | raw: NonNull, 36 | } 37 | 38 | impl Mempool { 39 | /// Creates a new `Mempool` for `Mbuf`. 40 | /// 41 | /// `capacity` is the maximum number of `Mbuf` the `Mempool` can hold. 42 | /// The optimum size (in terms of memory usage) is when n is a power 43 | /// of two minus one. 44 | /// 45 | /// `cache_size` is the per core object cache. If cache_size is non-zero, 46 | /// the library will try to limit the accesses to the common lockless 47 | /// pool. The cache can be disabled if the argument is set to 0. 48 | /// 49 | /// `socket_id` is the socket where the memory should be allocated. The 50 | /// value can be `SocketId::ANY` if there is no constraint. 51 | /// 52 | /// # Errors 53 | /// 54 | /// If allocation fails, then `DpdkError` is returned. 55 | pub(crate) fn new(capacity: usize, cache_size: usize, socket_id: SocketId) -> Result { 56 | static MEMPOOL_COUNT: AtomicUsize = AtomicUsize::new(0); 57 | let n = MEMPOOL_COUNT.fetch_add(1, Ordering::Relaxed); 58 | let name = format!("mempool{}", n); 59 | 60 | let raw = unsafe { 61 | ffi::rte_pktmbuf_pool_create( 62 | name.clone().into_cstring().as_ptr(), 63 | capacity as raw::c_uint, 64 | cache_size as raw::c_uint, 65 | 0, 66 | ffi::RTE_MBUF_DEFAULT_BUF_SIZE as u16, 67 | socket_id.raw(), 68 | ) 69 | .into_result(|_| DpdkError::new())? 70 | }; 71 | 72 | info!("created {}.", name); 73 | Ok(Self { raw }) 74 | } 75 | 76 | /// Returns the raw struct needed for FFI calls. 77 | #[inline] 78 | pub(crate) fn raw(&self) -> &ffi::rte_mempool { 79 | unsafe { self.raw.as_ref() } 80 | } 81 | 82 | /// Returns the raw struct needed for FFI calls. 83 | #[inline] 84 | pub(crate) fn raw_mut(&mut self) -> &mut ffi::rte_mempool { 85 | unsafe { self.raw.as_mut() } 86 | } 87 | 88 | /// Returns the name of the `Mempool`. 89 | #[inline] 90 | pub(crate) fn name(&self) -> &str { 91 | self.raw().name[..].as_str() 92 | } 93 | 94 | #[cfg(feature = "metrics")] 95 | pub(crate) fn stats(&self) -> super::MempoolStats { 96 | super::MempoolStats::build(self) 97 | } 98 | } 99 | 100 | impl fmt::Debug for Mempool { 101 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 102 | let raw = self.raw(); 103 | f.debug_struct(self.name()) 104 | .field("capacity", &raw.size) 105 | .field("cache_size", &raw.cache_size) 106 | .field("flags", &format_args!("{:#x}", raw.flags)) 107 | .field("socket", &raw.socket_id) 108 | .finish() 109 | } 110 | } 111 | 112 | impl Drop for Mempool { 113 | fn drop(&mut self) { 114 | debug!("freeing {}.", self.name()); 115 | 116 | unsafe { 117 | ffi::rte_mempool_free(self.raw_mut()); 118 | } 119 | } 120 | } 121 | 122 | thread_local! { 123 | /// `Mempool` on the same socket as the current core. 124 | /// 125 | /// It's set when the core is first initialized. New `Mbuf` is allocated 126 | /// from this `Mempool` when executed on this core. 127 | pub static MEMPOOL: Cell<*mut ffi::rte_mempool> = Cell::new(ptr::null_mut()); 128 | } 129 | 130 | /// Error indicating the `Mempool` is not found or is exhaused. 131 | #[derive(Debug, Error)] 132 | pub(crate) enum MempoolError { 133 | #[error("Cannot allocate a new mbuf from mempool")] 134 | Exhausted, 135 | 136 | #[error("Mempool for {0:?} not found.")] 137 | NotFound(SocketId), 138 | } 139 | 140 | /// A specialized hash map of `SocketId` to `&mut Mempool`. 141 | #[derive(Debug)] 142 | pub(crate) struct MempoolMap<'a> { 143 | inner: HashMap, 144 | } 145 | 146 | impl<'a> MempoolMap<'a> { 147 | /// Creates a new map from a mutable slice. 148 | pub(crate) fn new(mempools: &'a mut [Mempool]) -> Self { 149 | let map = mempools 150 | .iter_mut() 151 | .map(|pool| { 152 | let socket = SocketId(pool.raw().socket_id); 153 | (socket, pool) 154 | }) 155 | .collect::>(); 156 | 157 | Self { inner: map } 158 | } 159 | 160 | /// Returns a mutable reference to the raw mempool corresponding to the 161 | /// socket id. 162 | /// 163 | /// # Errors 164 | /// 165 | /// If the value is not found, `MempoolError::NotFound` is returned. 166 | pub(crate) fn get_raw(&mut self, socket_id: SocketId) -> Result<&mut ffi::rte_mempool> { 167 | self.inner 168 | .get_mut(&socket_id) 169 | .ok_or_else(|| MempoolError::NotFound(socket_id).into()) 170 | .map(|pool| pool.raw_mut()) 171 | } 172 | } 173 | 174 | impl<'a> Default for MempoolMap<'a> { 175 | fn default() -> MempoolMap<'a> { 176 | MempoolMap { 177 | inner: HashMap::new(), 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /core/src/dpdk/stats.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::{Mempool, Port, PortId}; 20 | use crate::dpdk::DpdkError; 21 | use crate::ffi::{self, AsStr, ToResult}; 22 | use crate::metrics::{labels, Key, Measurement}; 23 | use anyhow::Result; 24 | use std::ptr::NonNull; 25 | 26 | /// Port stats collector. 27 | pub(crate) struct PortStats { 28 | id: PortId, 29 | name: String, 30 | } 31 | 32 | impl PortStats { 33 | /// Builds a collector from the port. 34 | pub(crate) fn build(port: &Port) -> Self { 35 | PortStats { 36 | id: port.id(), 37 | name: port.name().to_owned(), 38 | } 39 | } 40 | 41 | /// Returns the port name. 42 | pub(crate) fn name(&self) -> &str { 43 | self.name.as_str() 44 | } 45 | 46 | /// Returns a counter with port and direction labels. 47 | fn new_counter(&self, name: &'static str, value: u64, dir: &'static str) -> (Key, Measurement) { 48 | ( 49 | Key::from_name_and_labels( 50 | name, 51 | labels!( 52 | "port" => self.name.clone(), 53 | "dir" => dir, 54 | ), 55 | ), 56 | Measurement::Counter(value), 57 | ) 58 | } 59 | 60 | /// Collects the port stats tracked by DPDK. 61 | pub(crate) fn collect(&self) -> Result> { 62 | let mut stats = ffi::rte_eth_stats::default(); 63 | unsafe { 64 | ffi::rte_eth_stats_get(self.id.raw(), &mut stats).into_result(DpdkError::from_errno)?; 65 | } 66 | 67 | let mut values = Vec::new(); 68 | 69 | values.push(self.new_counter("octets", stats.ibytes, "rx")); 70 | values.push(self.new_counter("octets", stats.obytes, "tx")); 71 | values.push(self.new_counter("dropped", stats.imissed, "rx")); 72 | values.push(self.new_counter("errors", stats.ierrors, "rx")); 73 | values.push(self.new_counter("errors", stats.oerrors, "tx")); 74 | values.push(self.new_counter("no_mbuf", stats.rx_nombuf, "rx")); 75 | 76 | Ok(values) 77 | } 78 | } 79 | 80 | /// Mempool stats collector. 81 | pub(crate) struct MempoolStats { 82 | raw: NonNull, 83 | } 84 | 85 | impl MempoolStats { 86 | /// Builds a collector from the port. 87 | pub(crate) fn build(mempool: &Mempool) -> Self { 88 | MempoolStats { 89 | raw: unsafe { 90 | NonNull::new_unchecked( 91 | mempool.raw() as *const ffi::rte_mempool as *mut ffi::rte_mempool 92 | ) 93 | }, 94 | } 95 | } 96 | 97 | fn raw(&self) -> &ffi::rte_mempool { 98 | unsafe { self.raw.as_ref() } 99 | } 100 | 101 | /// Returns the name of the `Mempool`. 102 | fn name(&self) -> &str { 103 | self.raw().name[..].as_str() 104 | } 105 | 106 | /// Returns a gauge. 107 | fn new_gauge(&self, name: &'static str, value: i64) -> (Key, Measurement) { 108 | ( 109 | Key::from_name_and_labels( 110 | name, 111 | labels!( 112 | "pool" => self.name().to_string(), 113 | ), 114 | ), 115 | Measurement::Gauge(value), 116 | ) 117 | } 118 | 119 | /// Collects the mempool stats. 120 | pub(crate) fn collect(&self) -> Vec<(Key, Measurement)> { 121 | let used = unsafe { ffi::rte_mempool_in_use_count(self.raw()) as i64 }; 122 | let free = self.raw().size as i64 - used; 123 | 124 | vec![self.new_gauge("used", used), self.new_gauge("free", free)] 125 | } 126 | } 127 | 128 | /// Send mempool stats across threads. 129 | unsafe impl Send for MempoolStats {} 130 | unsafe impl Sync for MempoolStats {} 131 | -------------------------------------------------------------------------------- /core/src/ffi.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | pub(crate) use capsule_ffi::*; 20 | 21 | use crate::warn; 22 | use anyhow::Result; 23 | use std::error::Error; 24 | use std::ffi::{CStr, CString}; 25 | use std::os::raw; 26 | use std::ptr::NonNull; 27 | 28 | /// Simplify `*const c_char` or [c_char] to `&str` conversion. 29 | pub(crate) trait AsStr { 30 | fn as_str(&self) -> &str; 31 | } 32 | 33 | impl AsStr for *const raw::c_char { 34 | #[inline] 35 | fn as_str(&self) -> &str { 36 | unsafe { 37 | CStr::from_ptr(*self).to_str().unwrap_or_else(|_| { 38 | warn!("invalid UTF8 data"); 39 | Default::default() 40 | }) 41 | } 42 | } 43 | } 44 | 45 | impl AsStr for [raw::c_char] { 46 | #[inline] 47 | fn as_str(&self) -> &str { 48 | unsafe { 49 | CStr::from_ptr(self.as_ptr()).to_str().unwrap_or_else(|_| { 50 | warn!("invalid UTF8 data"); 51 | Default::default() 52 | }) 53 | } 54 | } 55 | } 56 | 57 | /// Simplify `String` and `&str` to `CString` conversion. 58 | pub(crate) trait ToCString { 59 | fn into_cstring(self) -> CString; 60 | } 61 | 62 | impl ToCString for String { 63 | #[inline] 64 | fn into_cstring(self) -> CString { 65 | CString::new(self).unwrap() 66 | } 67 | } 68 | 69 | impl ToCString for &str { 70 | #[inline] 71 | fn into_cstring(self) -> CString { 72 | CString::new(self).unwrap() 73 | } 74 | } 75 | 76 | /// Simplify dpdk FFI binding's return to a `Result` type. 77 | /// 78 | /// # Example 79 | /// 80 | /// ``` 81 | /// ffi::rte_eth_add_tx_callback(..., ..., ..., ...) 82 | /// .into_result(|_| { 83 | /// DpdkError::new() 84 | /// })?; 85 | /// ``` 86 | pub(crate) trait ToResult { 87 | type Ok; 88 | 89 | fn into_result(self, f: F) -> Result 90 | where 91 | E: Error + Send + Sync + 'static, 92 | F: FnOnce(Self) -> E, 93 | Self: Sized; 94 | } 95 | 96 | impl ToResult for *mut T { 97 | type Ok = NonNull; 98 | 99 | #[inline] 100 | fn into_result(self, f: F) -> Result 101 | where 102 | E: Error + Send + Sync + 'static, 103 | F: FnOnce(Self) -> E, 104 | { 105 | NonNull::new(self).ok_or_else(|| f(self).into()) 106 | } 107 | } 108 | 109 | impl ToResult for *const T { 110 | type Ok = *const T; 111 | 112 | #[inline] 113 | fn into_result(self, f: F) -> Result 114 | where 115 | E: Error + Send + Sync + 'static, 116 | F: FnOnce(Self) -> E, 117 | { 118 | if self.is_null() { 119 | Err(f(self).into()) 120 | } else { 121 | Ok(self) 122 | } 123 | } 124 | } 125 | 126 | impl ToResult for raw::c_int { 127 | type Ok = u32; 128 | 129 | #[inline] 130 | fn into_result(self, f: F) -> Result 131 | where 132 | E: Error + Send + Sync + 'static, 133 | F: FnOnce(Self) -> E, 134 | { 135 | if self >= 0 { 136 | Ok(self as u32) 137 | } else { 138 | Err(f(self).into()) 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | #![warn( 20 | missing_debug_implementations, 21 | missing_docs, 22 | rust_2018_idioms, 23 | unreachable_pub 24 | )] 25 | #![deny(broken_intra_doc_links)] 26 | #![cfg_attr(docsrs, feature(doc_cfg))] 27 | #![doc(html_root_url = "https://docs.rs/capsule/0.1.5")] 28 | 29 | //! A framework for network function development. Written in Rust, inspired by 30 | //! [NetBricks] and built on Intel's [Data Plane Development Kit]. 31 | //! 32 | //! The goal of Capsule is to offer an ergonomic framework for network function 33 | //! development that traditionally has high barriers of entry for developers. 34 | //! We've created a tool to efficiently manipulate network packets while being 35 | //! type-safe, memory-safe, and thread-safe. Building on DPDK and Rust, Capsule 36 | //! offers: 37 | //! 38 | //! * a fast packet processor that uses minimum number of CPU cycles. 39 | //! * a rich packet type system that guarantees memory-safety and thread-safety. 40 | //! * a declarative programming model that emphasizes simplicity. 41 | //! * an extensible and testable framework that is easy to develop and maintain. 42 | //! 43 | //! ## Getting started 44 | //! 45 | //! The easiest way to start developing Capsule applications is to use the 46 | //! `Vagrant` [virtual machine] and the `Docker` [sandbox] provided by the 47 | //! Capsule team. The sandbox is preconfigured with all the necessary tools and 48 | //! libraries for Capsule development, including: 49 | //! 50 | //! * [DPDK 19.11] 51 | //! * [Clang] and [LLVM] 52 | //! * [Rust 1.50] 53 | //! * [rr] 5.3 54 | //! 55 | //! For more information on getting started, please check out Capsule's 56 | //! [README], as well as our [sandbox repo] for developer environments. 57 | //! 58 | //! ### Adding Capsule as a Cargo dependency 59 | //! 60 | //! ```toml 61 | //! [dependencies] 62 | //! capsule = "0.1" 63 | //! ``` 64 | //! 65 | //! ### Using features 66 | //! 67 | //! To enable test/bench features for example, you can include Capsule in your 68 | //! Cargo dependencies with the `testils` feature flag: 69 | //! 70 | //! ```toml 71 | //! [dev-dependencies] 72 | //! capsule = { version = "0.1", features = ["testils"] } 73 | //! ``` 74 | //! 75 | //! Or, to enable the capturing of port traffic to `pcap` files 76 | //! automatically per-port, per-core, you can run a Capsule application with the 77 | //! `pcap-dump` feature flag turned on: 78 | //! 79 | //! ```shell 80 | //! cargo run --features capsule/pcap-dump -- -f capsule-app.toml 81 | //! ``` 82 | //! 83 | //! ## Feature flags 84 | //! 85 | //! - `default`: Enables metrics by default. 86 | //! - `metrics`: Enables automatic [`metrics`] collection. 87 | //! - `pcap-dump`: Enables capturing port traffic to `pcap` files. 88 | //! - `testils`: Enables utilities for unit testing and benchmarking. 89 | //! - `full`: Enables all features. 90 | //! 91 | //! ### Examples 92 | //! 93 | //! - [kni]: Kernel NIC interface example. 94 | //! - [nat64]: IPv6 to IPv4 NAT gateway example. 95 | //! - [ping4d]: Ping4 daemon example. 96 | //! - [pktdump]: Packet dump example. 97 | //! - [signals]: Linux signal handling example. 98 | //! - [skeleton]: Base skeleton example. 99 | //! - [syn-flood]: TCP SYN flood example. 100 | //! 101 | //! [NetBricks]: https://www.usenix.org/system/files/conference/osdi16/osdi16-panda.pdf 102 | //! [Data Plane Development Kit]: https://www.dpdk.org/ 103 | //! [virtual machine]: https://github.com/capsule-rs/sandbox/blob/master/Vagrantfile 104 | //! [sandbox]: https://hub.docker.com/repository/docker/getcapsule/sandbox 105 | //! [DPDK 19.11]: https://doc.dpdk.org/guides-19.11/rel_notes/release_19_11.html 106 | //! [Clang]: https://clang.llvm.org/ 107 | //! [LLVM]: https://www.llvm.org/ 108 | //! [Rust 1.50]: https://blog.rust-lang.org/2021/02/11/Rust-1.50.0.html 109 | //! [rr]: https://rr-project.org/ 110 | //! [README]: https://github.com/capsule-rs/capsule/blob/master/README.md 111 | //! [sandbox repo]: https://github.com/capsule-rs/sandbox 112 | //! [`metrics`]: crate::metrics 113 | //! [kni]: https://github.com/capsule-rs/capsule/tree/master/examples/kni 114 | //! [nat64]: https://github.com/capsule-rs/capsule/tree/master/examples/nat64 115 | //! [ping4d]: https://github.com/capsule-rs/capsule/tree/master/examples/ping4d 116 | //! [pktdump]: https://github.com/capsule-rs/capsule/tree/master/examples/pktdump 117 | //! [signals]: https://github.com/capsule-rs/capsule/tree/master/examples/signals 118 | //! [skeleton]: https://github.com/capsule-rs/capsule/tree/master/examples/skeleton 119 | //! [syn-flood]: https://github.com/capsule-rs/capsule/tree/master/examples/syn-flood 120 | 121 | // alias for the macros 122 | extern crate self as capsule; 123 | 124 | pub mod batch; 125 | pub mod config; 126 | mod dpdk; 127 | mod ffi; 128 | mod macros; 129 | #[cfg(feature = "metrics")] 130 | #[cfg_attr(docsrs, doc(cfg(all(feature = "default", feature = "metrics"))))] 131 | pub mod metrics; 132 | pub mod net; 133 | pub mod packets; 134 | #[cfg(feature = "pcap-dump")] 135 | #[cfg_attr(docsrs, doc(cfg(feature = "pcap-dump")))] 136 | mod pcap; 137 | mod runtime; 138 | #[cfg(any(test, feature = "testils"))] 139 | #[cfg_attr(docsrs, doc(cfg(feature = "testils")))] 140 | pub mod testils; 141 | 142 | pub use self::dpdk::{KniRx, KniTxQueue, Mbuf, PortQueue, SizeOf}; 143 | pub use self::runtime::{Runtime, UnixSignal}; 144 | pub use capsule_macros::SizeOf; 145 | #[cfg(any(test, feature = "testils"))] 146 | #[cfg_attr(docsrs, doc(cfg(feature = "testils")))] 147 | pub use capsule_macros::{bench, test}; 148 | -------------------------------------------------------------------------------- /core/src/macros.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | /// Exits a function early with an `Error` if the condition is not satisfied. 20 | /// 21 | /// # Example 22 | /// 23 | /// ``` 24 | /// ensure!(vec.len() > 0, EmptyVecError::new()); 25 | /// ``` 26 | /// 27 | /// is equivalent to 28 | /// 29 | /// ``` 30 | /// if !(vec.len() > 0) { 31 | /// return Err(EmptyVecError::new().into()); 32 | /// } 33 | /// ``` 34 | #[macro_export] 35 | macro_rules! ensure { 36 | ($cond:expr, $e:expr) => { 37 | if !($cond) { 38 | return Err($e.into()); 39 | } 40 | }; 41 | } 42 | 43 | #[doc(hidden)] 44 | #[macro_export] 45 | macro_rules! error { 46 | (cond: $cond:expr, $($arg:tt)+) => ( 47 | if $cond { 48 | error!($($arg)+); 49 | } 50 | ); 51 | ($($arg:tt)+) => ( 52 | ::tracing::error!($($arg)+); 53 | ) 54 | } 55 | 56 | #[doc(hidden)] 57 | #[macro_export] 58 | macro_rules! warn { 59 | (cond: $cond:expr, $($arg:tt)+) => ( 60 | if $cond { 61 | warn!($($arg)+); 62 | } 63 | ); 64 | ($($arg:tt)+) => ( 65 | ::tracing::warn!($($arg)+); 66 | ) 67 | } 68 | 69 | #[doc(hidden)] 70 | #[macro_export] 71 | macro_rules! info { 72 | (cond: $cond:expr, $($arg:tt)+) => ( 73 | if $cond { 74 | info!($($arg)+); 75 | } 76 | ); 77 | ($($arg:tt)+) => ( 78 | ::tracing::info!($($arg)+); 79 | ) 80 | } 81 | 82 | #[doc(hidden)] 83 | #[macro_export] 84 | macro_rules! debug { 85 | (cond: $cond:expr, $($arg:tt)+) => ( 86 | if $cond { 87 | debug!($($arg)+); 88 | } 89 | ); 90 | ($($arg:tt)+) => ( 91 | ::tracing::debug!($($arg)+); 92 | ) 93 | } 94 | 95 | #[doc(hidden)] 96 | #[macro_export] 97 | macro_rules! trace { 98 | (cond: $cond:expr, $($arg:tt)+) => ( 99 | if $cond { 100 | trace!($($arg)+); 101 | } 102 | ); 103 | ($($arg:tt)+) => ( 104 | ::tracing::trace!($($arg)+); 105 | ) 106 | } 107 | -------------------------------------------------------------------------------- /core/src/metrics.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! Exposes framework metrics, including port, kni, mempool, and pipeline 20 | //! metrics. 21 | //! 22 | //! # Port Metrics 23 | //! 24 | //! * `port.packets`, total number of successfully received or transmitted 25 | //! packets. 26 | //! * `port.octets`, total number of successfully received or transmitted 27 | //! bytes. 28 | //! * `port.dropped`, total number of packets dropped because the receive 29 | //! or transmit queues are full. 30 | //! * `port.errors`, total number of erroneous received packets or packets 31 | //! failed to transmit. 32 | //! * `port.no_mbuf`, total number of packets dropped due to mbuf allocation 33 | //! failures. 34 | //! 35 | //! Each metric is labeled with the port name and a direction, which can be 36 | //! either RX or TX. `port.packets` and `port.dropped` are tracked per core 37 | //! and labeled with the core id. The others are tracked by only the overall 38 | //! metrics. 39 | //! 40 | //! 41 | //! # KNI Metrics 42 | //! 43 | //! * `kni.packets`, total number of successfully received or transmitted 44 | //! packets. 45 | //! * `kni.octets`, total number of successfully received or transmitted bytes. 46 | //! * `kni.dropped`, total number of packets dropped because the transmit 47 | //! queue is full. 48 | //! 49 | //! Each metric is labeled with the KNI interface name and a direction, which 50 | //! can be either RX or TX. 51 | //! 52 | //! 53 | //! # Mempool Metrics 54 | //! 55 | //! * `mempool.used`, total number of mbufs which have been allocated from 56 | //! the mempool. 57 | //! * `mempool.free`, total number of mbufs available for allocation. 58 | //! 59 | //! Each metric is labeled with the mempool name. 60 | //! 61 | //! 62 | //! # Pipeline Metrics 63 | //! 64 | //! * `pipeline.runs`, total number of times the pipeline executes. 65 | //! * `pipeline.processed`, total number of successfully processed packets. 66 | //! * `pipeline.dropped`, total number of packets intentionally dropped. 67 | //! * `pipeline.errors`, total number of packet dropped due to processing 68 | //! errors. 69 | //! 70 | //! Each metric is tracked per core and labeled with the core id and the 71 | //! pipeline name. If the pipeline doesn't have a name, it will be labeled 72 | //! as "default". 73 | 74 | // re-export some metrics types to make feature gated imports easier. 75 | pub(crate) use metrics_core::{labels, Key}; 76 | pub(crate) use metrics_runtime::data::Counter; 77 | pub(crate) use metrics_runtime::Measurement; 78 | 79 | use crate::dpdk::{Mempool, MempoolStats, Port}; 80 | use crate::warn; 81 | use anyhow::{anyhow, Result}; 82 | use metrics_runtime::{Receiver, Sink}; 83 | use once_cell::sync::{Lazy, OnceCell}; 84 | 85 | /// The metrics store. 86 | static RECEIVER: OnceCell = OnceCell::new(); 87 | 88 | /// Safely initializes the metrics store. Because the receiver builder could 89 | /// potentially fail, the `Lazy` convenience type is not safe. 90 | /// 91 | /// Also very important that `init` is not called twice. 92 | pub(crate) fn init() -> Result<()> { 93 | let receiver = Receiver::builder().build()?; 94 | 95 | RECEIVER 96 | .set(receiver) 97 | .map_err(|_| anyhow!("already initialized."))?; 98 | Ok(()) 99 | } 100 | 101 | /// Registers DPDK collected port stats with the metrics store. 102 | pub(crate) fn register_port_stats(ports: &[Port]) { 103 | let stats = ports.iter().map(Port::stats).collect::>(); 104 | SINK.clone().proxy("port", move || { 105 | stats 106 | .iter() 107 | .flat_map(|s| { 108 | s.collect().unwrap_or_else(|err| { 109 | warn!(message = "failed to collect stats.", port = s.name(), ?err); 110 | Vec::new() 111 | }) 112 | }) 113 | .collect() 114 | }); 115 | } 116 | 117 | /// Registers collected mempool stats with the metrics store. 118 | pub(crate) fn register_mempool_stats(mempools: &[Mempool]) { 119 | let stats = mempools.iter().map(Mempool::stats).collect::>(); 120 | SINK.clone().proxy("mempool", move || { 121 | stats.iter().flat_map(MempoolStats::collect).collect() 122 | }); 123 | } 124 | 125 | /// Returns the global metrics store. 126 | /// 127 | /// Metrics are managed using [metrics-rs]. The application can use this to 128 | /// access framework metrics or to add new application metrics. 129 | /// 130 | /// # Panics 131 | /// 132 | /// Panics if `Runtime::build` is not called first. 133 | /// 134 | /// [metrics-rs]: https://github.com/metrics-rs 135 | pub fn global() -> &'static Receiver { 136 | unsafe { RECEIVER.get_unchecked() } 137 | } 138 | 139 | /// The root sink for all framework metrics. 140 | pub(crate) static SINK: Lazy = Lazy::new(|| global().sink().scoped("capsule")); 141 | -------------------------------------------------------------------------------- /core/src/net/cidr/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | mod v4; 20 | mod v6; 21 | 22 | #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 23 | pub use self::v4::Ipv4Cidr; 24 | #[allow(unreachable_pub)] 25 | pub use self::v6::Ipv6Cidr; 26 | 27 | use thiserror::Error; 28 | 29 | /// Error indicating that a CIDR range cannot be parsed or is handled with an invalid prefix length. 30 | #[derive(Debug, Error)] 31 | pub enum CidrError { 32 | /// Error returned when parsing a malformed CIDR range. 33 | #[error("Failed to parse CIDR: {0}")] 34 | Malformed(String), 35 | 36 | /// Error returned when converting from v4/v6 address mask to a prefix length. 37 | #[error("Invalid prefix length")] 38 | InvalidPrefixLength, 39 | } 40 | 41 | /// Common behaviors for interacting with CIDR ranges. 42 | pub trait Cidr: Sized { 43 | /// Type of address, i.e. IPv4 or IPv6, associated with the CIDR. 44 | type Addr; 45 | 46 | /// Returns the IP address prefix. 47 | fn address(&self) -> Self::Addr; 48 | 49 | /// Returns the broadcast address in a CIDR range. 50 | fn broadcast(&self) -> Self::Addr; 51 | 52 | /// Checks whether an address is contained within the CIDR range. 53 | fn contains(&self, address: Self::Addr) -> bool; 54 | 55 | /// Returns the CIDR hostmask address. 56 | fn hostmask(&self) -> Self::Addr; 57 | 58 | /// Returns the CIDR prefix length. 59 | fn length(&self) -> usize; 60 | 61 | /// Returns the CIDR netmask. 62 | fn netmask(&self) -> Self::Addr; 63 | 64 | /// Returns the network address in a CIDR range. 65 | fn network(&self) -> Self::Addr; 66 | 67 | /// Returns a new CIDR range from a prefix length. 68 | fn new(address: Self::Addr, length: usize) -> Result; 69 | 70 | /// Returns the number of possible addresses within the CIDR range. 71 | fn size(&self) -> usize; 72 | 73 | /// Returns a new CIDR range from a netmask. 74 | fn with_netmask(address: Self::Addr, netmask: Self::Addr) -> Result; 75 | } 76 | -------------------------------------------------------------------------------- /core/src/net/mac.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use std::convert::From; 20 | use std::fmt; 21 | use std::str::FromStr; 22 | use thiserror::Error; 23 | 24 | /// Ethernet MAC address. 25 | #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] 26 | #[repr(C, packed)] 27 | pub struct MacAddr([u8; 6]); 28 | 29 | impl MacAddr { 30 | /// A MAC address representing an unspecified address: 00:00:00:00:00:00. 31 | pub const UNSPECIFIED: Self = MacAddr([0, 0, 0, 0, 0, 0]); 32 | 33 | /// Creates a MAC address from 6 octets. 34 | #[allow(clippy::many_single_char_names)] 35 | pub fn new(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8) -> Self { 36 | MacAddr([a, b, c, d, e, f]) 37 | } 38 | 39 | /// Returns the six bytes the MAC address consists of. 40 | #[allow(clippy::trivially_copy_pass_by_ref)] 41 | pub fn octets(&self) -> [u8; 6] { 42 | self.0 43 | } 44 | } 45 | 46 | impl fmt::Display for MacAddr { 47 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 48 | write!( 49 | f, 50 | "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", 51 | self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5] 52 | ) 53 | } 54 | } 55 | 56 | impl From<[u8; 6]> for MacAddr { 57 | fn from(octets: [u8; 6]) -> MacAddr { 58 | MacAddr(octets) 59 | } 60 | } 61 | 62 | /// Error returned when parsing a malformed MAC address. 63 | #[derive(Debug, Error)] 64 | #[error("Failed to parse '{0}' as MAC address.")] 65 | pub struct MacParseError(String); 66 | 67 | impl FromStr for MacAddr { 68 | type Err = MacParseError; 69 | 70 | fn from_str(s: &str) -> Result { 71 | let u8s = s 72 | .split(|c| c == ':' || c == '-') 73 | .map(|s| u8::from_str_radix(s, 16)) 74 | .filter_map(Result::ok) 75 | .collect::>(); 76 | 77 | if u8s.len() == 6 { 78 | let mut octets = [0; 6]; 79 | octets.copy_from_slice(u8s.as_slice()); 80 | Ok(octets.into()) 81 | } else { 82 | Err(MacParseError(s.to_owned())) 83 | } 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | use super::*; 90 | 91 | #[test] 92 | fn mac_addr_to_string() { 93 | assert_eq!( 94 | "00:00:00:00:00:00", 95 | MacAddr::new(0, 0, 0, 0, 0, 0).to_string() 96 | ); 97 | assert_eq!( 98 | "ff:ff:ff:ff:ff:ff", 99 | MacAddr::new(255, 255, 255, 255, 255, 255).to_string() 100 | ); 101 | assert_eq!( 102 | "12:34:56:ab:cd:ef", 103 | MacAddr::new(0x12, 0x34, 0x56, 0xAB, 0xCD, 0xEF).to_string() 104 | ); 105 | } 106 | 107 | #[test] 108 | fn string_to_mac_addr() { 109 | assert_eq!( 110 | MacAddr::new(0, 0, 0, 0, 0, 0), 111 | "00:00:00:00:00:00".parse().unwrap() 112 | ); 113 | assert_eq!( 114 | MacAddr::new(255, 255, 255, 255, 255, 255), 115 | "ff:ff:ff:ff:ff:ff".parse().unwrap() 116 | ); 117 | assert_eq!( 118 | MacAddr::new(0x12, 0x34, 0x56, 0xAB, 0xCD, 0xEF), 119 | "12:34:56:ab:cd:ef".parse().unwrap() 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /core/src/net/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! Common network utilities. 20 | 21 | mod cidr; 22 | mod mac; 23 | 24 | pub use self::cidr::{Cidr, CidrError, Ipv4Cidr, Ipv6Cidr}; 25 | pub use self::mac::{MacAddr, MacParseError}; 26 | -------------------------------------------------------------------------------- /core/src/packets/icmp/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! Internet Control Message Protocol for IPv4 and IPv6. 20 | 21 | pub mod v4; 22 | pub mod v6; 23 | -------------------------------------------------------------------------------- /core/src/packets/icmp/v6/ndp/neighbor_solicit.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::NdpPacket; 20 | use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; 21 | use crate::packets::ip::v6::Ipv6Packet; 22 | use crate::packets::types::u32be; 23 | use crate::packets::{Internal, Packet}; 24 | use crate::SizeOf; 25 | use anyhow::Result; 26 | use std::fmt; 27 | use std::net::Ipv6Addr; 28 | use std::ptr::NonNull; 29 | 30 | /// Neighbor Solicitation Message defined in [IETF RFC 4861]. 31 | /// 32 | /// ``` 33 | /// 0 1 2 3 34 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 35 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 36 | /// | Type | Code | Checksum | 37 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 38 | /// | Reserved | 39 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 40 | /// | Target Address (128 bits IPv6 address) | 41 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 42 | /// | Options ... 43 | /// +-+-+-+-+-+-+-+-+-+-+-+- 44 | /// ``` 45 | /// 46 | /// - *Reserved*: This field is unused. It MUST be initialized to 47 | /// zero by the sender and MUST be ignored by the 48 | /// receiver. 49 | /// 50 | /// - *Target Address*: The IP address of the target of the solicitation. 51 | /// It MUST NOT be a multicast address. 52 | /// 53 | /// Possible options: 54 | /// 55 | /// - *Source link-layer address*: 56 | /// The link-layer address for the sender. 57 | /// 58 | /// [IETF RFC 4861]: https://tools.ietf.org/html/rfc4861#section-4.3 59 | #[derive(Icmpv6Packet)] 60 | pub struct NeighborSolicitation { 61 | icmp: Icmpv6, 62 | body: NonNull, 63 | } 64 | 65 | impl NeighborSolicitation { 66 | #[inline] 67 | fn body(&self) -> &NeighborSolicitationBody { 68 | unsafe { self.body.as_ref() } 69 | } 70 | 71 | #[inline] 72 | fn body_mut(&mut self) -> &mut NeighborSolicitationBody { 73 | unsafe { self.body.as_mut() } 74 | } 75 | 76 | /// Returns the target address. 77 | #[inline] 78 | pub fn target(&self) -> Ipv6Addr { 79 | self.body().target 80 | } 81 | 82 | /// Sets the target address. 83 | #[inline] 84 | pub fn set_target(&mut self, target: Ipv6Addr) { 85 | self.body_mut().target = target 86 | } 87 | } 88 | 89 | impl fmt::Debug for NeighborSolicitation { 90 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 91 | f.debug_struct("NeighborSolicitation") 92 | .field("type", &format!("{}", self.msg_type())) 93 | .field("code", &self.code()) 94 | .field("checksum", &format!("0x{:04x}", self.checksum())) 95 | .field("target", &self.target()) 96 | .finish() 97 | } 98 | } 99 | 100 | impl Icmpv6Message for NeighborSolicitation { 101 | type Envelope = E; 102 | 103 | #[inline] 104 | fn msg_type() -> Icmpv6Type { 105 | Icmpv6Types::NeighborSolicitation 106 | } 107 | 108 | #[inline] 109 | fn icmp(&self) -> &Icmpv6 { 110 | &self.icmp 111 | } 112 | 113 | #[inline] 114 | fn icmp_mut(&mut self) -> &mut Icmpv6 { 115 | &mut self.icmp 116 | } 117 | 118 | #[inline] 119 | fn into_icmp(self) -> Icmpv6 { 120 | self.icmp 121 | } 122 | 123 | #[inline] 124 | unsafe fn clone(&self, internal: Internal) -> Self { 125 | NeighborSolicitation { 126 | icmp: self.icmp.clone(internal), 127 | body: self.body, 128 | } 129 | } 130 | 131 | /// Parses the ICMPv6 packet's payload as neighbor solicitation. 132 | /// 133 | /// # Errors 134 | /// 135 | /// Returns an error if the payload does not have sufficient data for 136 | /// the neighbor solicitation message body. 137 | #[inline] 138 | fn try_parse(icmp: Icmpv6, _internal: Internal) -> Result { 139 | let mbuf = icmp.mbuf(); 140 | let offset = icmp.payload_offset(); 141 | let body = mbuf.read_data(offset)?; 142 | 143 | Ok(NeighborSolicitation { icmp, body }) 144 | } 145 | 146 | /// Prepends a new neighbor solicitation message to the beginning of 147 | /// the ICMPv6's payload. 148 | /// 149 | /// # Errors 150 | /// 151 | /// Returns an error if the buffer does not have enough free space. 152 | #[inline] 153 | fn try_push(mut icmp: Icmpv6, _internal: Internal) -> Result { 154 | let offset = icmp.payload_offset(); 155 | let mbuf = icmp.mbuf_mut(); 156 | 157 | mbuf.extend(offset, NeighborSolicitationBody::size_of())?; 158 | let body = mbuf.write_data(offset, &NeighborSolicitationBody::default())?; 159 | 160 | Ok(NeighborSolicitation { icmp, body }) 161 | } 162 | } 163 | 164 | impl NdpPacket for NeighborSolicitation { 165 | fn options_offset(&self) -> usize { 166 | self.payload_offset() + NeighborSolicitationBody::size_of() 167 | } 168 | } 169 | 170 | #[derive(Clone, Copy, Debug, SizeOf)] 171 | #[repr(C)] 172 | struct NeighborSolicitationBody { 173 | reserved: u32be, 174 | target: Ipv6Addr, 175 | } 176 | 177 | impl Default for NeighborSolicitationBody { 178 | fn default() -> Self { 179 | NeighborSolicitationBody { 180 | reserved: u32be::default(), 181 | target: Ipv6Addr::UNSPECIFIED, 182 | } 183 | } 184 | } 185 | 186 | #[cfg(test)] 187 | mod tests { 188 | use super::*; 189 | use crate::packets::ip::v6::Ipv6; 190 | use crate::packets::Ethernet; 191 | use crate::Mbuf; 192 | 193 | #[test] 194 | fn size_of_neighbor_solicitation_body() { 195 | assert_eq!(20, NeighborSolicitationBody::size_of()); 196 | } 197 | 198 | #[capsule::test] 199 | fn push_and_set_neighbor_solicitation() { 200 | let packet = Mbuf::new().unwrap(); 201 | let ethernet = packet.push::().unwrap(); 202 | let ipv6 = ethernet.push::().unwrap(); 203 | let mut solicit = ipv6.push::>().unwrap(); 204 | 205 | assert_eq!(4, solicit.header_len()); 206 | assert_eq!(NeighborSolicitationBody::size_of(), solicit.payload_len()); 207 | assert_eq!(Icmpv6Types::NeighborSolicitation, solicit.msg_type()); 208 | assert_eq!(0, solicit.code()); 209 | 210 | solicit.set_target(Ipv6Addr::LOCALHOST); 211 | assert_eq!(Ipv6Addr::LOCALHOST, solicit.target()); 212 | 213 | solicit.reconcile_all(); 214 | assert!(solicit.checksum() != 0); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /core/src/packets/icmp/v6/ndp/options/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | // https://github.com/rust-lang/rust/issues/57411 20 | #![allow(unreachable_pub)] 21 | 22 | mod link_layer_addr; 23 | mod mtu; 24 | mod prefix_info; 25 | mod redirected; 26 | 27 | pub use self::link_layer_addr::*; 28 | pub use self::mtu::*; 29 | pub use self::prefix_info::*; 30 | pub use self::redirected::*; 31 | -------------------------------------------------------------------------------- /core/src/packets/icmp/v6/ndp/options/mtu.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use crate::packets::icmp::v6::ndp::{NdpOption, NdpOptionType, NdpOptionTypes}; 20 | use crate::packets::types::{u16be, u32be}; 21 | use crate::packets::Internal; 22 | use crate::{ensure, Mbuf, SizeOf}; 23 | use anyhow::{anyhow, Result}; 24 | use std::fmt; 25 | use std::ptr::NonNull; 26 | 27 | /// MTU option defined in [IETF RFC 4861]. 28 | /// 29 | /// ``` 30 | /// 0 1 2 3 31 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 32 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 33 | /// | Type | Length | Reserved | 34 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 35 | /// | MTU | 36 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 37 | /// ``` 38 | /// 39 | /// - *Type*: 5 40 | /// 41 | /// - *Length*: 1 42 | /// 43 | /// - *Reserved*: This field is unused. It MUST be initialized to 44 | /// zero by the sender and MUST be ignored by the 45 | /// receiver. 46 | /// 47 | /// - *MTU*: 32-bit unsigned integer. The recommended MTU for 48 | /// the link. 49 | /// 50 | /// [IETF RFC 4861]: https://tools.ietf.org/html/rfc4861#section-4.6.4 51 | pub struct Mtu<'a> { 52 | _mbuf: &'a mut Mbuf, 53 | fields: NonNull, 54 | offset: usize, 55 | } 56 | 57 | impl Mtu<'_> { 58 | #[inline] 59 | fn fields(&self) -> &MtuFields { 60 | unsafe { self.fields.as_ref() } 61 | } 62 | 63 | #[inline] 64 | fn fields_mut(&mut self) -> &mut MtuFields { 65 | unsafe { self.fields.as_mut() } 66 | } 67 | 68 | /// Returns the recommended MTU for the link. 69 | pub fn mtu(&self) -> u32 { 70 | self.fields().mtu.into() 71 | } 72 | 73 | /// Sets the recommended MTU for the link. 74 | pub fn set_mtu(&mut self, mtu: u32) { 75 | self.fields_mut().mtu = mtu.into(); 76 | } 77 | } 78 | 79 | impl fmt::Debug for Mtu<'_> { 80 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 81 | f.debug_struct("Mtu") 82 | .field("type", &self.option_type()) 83 | .field("length", &self.length()) 84 | .field("mtu", &self.mtu()) 85 | .field("$offset", &self.offset) 86 | .finish() 87 | } 88 | } 89 | 90 | impl<'a> NdpOption<'a> for Mtu<'a> { 91 | /// Returns the option type. Should always be `5`. 92 | #[inline] 93 | fn option_type(&self) -> NdpOptionType { 94 | NdpOptionType(self.fields().option_type) 95 | } 96 | 97 | /// Returns the length of the option measured in units of 8 octets. 98 | /// Should always be `1`. 99 | #[inline] 100 | fn length(&self) -> u8 { 101 | self.fields().length 102 | } 103 | 104 | /// Parses the buffer at offset as MTU option. 105 | /// 106 | /// # Errors 107 | /// 108 | /// Returns an error if the `option_type` is not set to `MTU`. Returns 109 | /// an error if the option length is incorrect. 110 | #[inline] 111 | fn try_parse(mbuf: &'a mut Mbuf, offset: usize, _internal: Internal) -> Result> { 112 | let fields = mbuf.read_data::(offset)?; 113 | let option = Mtu { 114 | _mbuf: mbuf, 115 | fields, 116 | offset, 117 | }; 118 | 119 | ensure!( 120 | option.option_type() == NdpOptionTypes::Mtu, 121 | anyhow!("not MTU.") 122 | ); 123 | 124 | ensure!( 125 | option.length() * 8 == MtuFields::size_of() as u8, 126 | anyhow!("invalid MTU option length.") 127 | ); 128 | 129 | Ok(option) 130 | } 131 | 132 | /// Pushes a new MTU option to the buffer at offset. 133 | /// 134 | /// # Errors 135 | /// 136 | /// Returns an error if the buffer does not have enough free space. 137 | #[inline] 138 | fn try_push(mbuf: &'a mut Mbuf, offset: usize, _internal: Internal) -> Result> { 139 | mbuf.extend(offset, MtuFields::size_of())?; 140 | let fields = mbuf.write_data(offset, &MtuFields::default())?; 141 | Ok(Mtu { 142 | _mbuf: mbuf, 143 | fields, 144 | offset, 145 | }) 146 | } 147 | } 148 | 149 | /// MTU option fields. 150 | #[derive(Clone, Copy, Debug, SizeOf)] 151 | #[repr(C, packed)] 152 | struct MtuFields { 153 | option_type: u8, 154 | length: u8, 155 | reserved: u16be, 156 | mtu: u32be, 157 | } 158 | 159 | impl Default for MtuFields { 160 | fn default() -> MtuFields { 161 | MtuFields { 162 | option_type: NdpOptionTypes::Mtu.0, 163 | length: 1, 164 | reserved: u16be::default(), 165 | mtu: u32be::default(), 166 | } 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | mod tests { 172 | use super::*; 173 | use crate::packets::icmp::v6::ndp::{NdpPacket, RouterAdvertisement}; 174 | use crate::packets::ip::v6::Ipv6; 175 | use crate::packets::{Ethernet, Packet}; 176 | use crate::testils::byte_arrays::ROUTER_ADVERT_PACKET; 177 | 178 | #[test] 179 | fn size_of_mtu_fields() { 180 | assert_eq!(8, MtuFields::size_of()); 181 | } 182 | 183 | #[capsule::test] 184 | fn parse_mtu() { 185 | let packet = Mbuf::from_bytes(&ROUTER_ADVERT_PACKET).unwrap(); 186 | let ethernet = packet.parse::().unwrap(); 187 | let ipv6 = ethernet.parse::().unwrap(); 188 | let mut advert = ipv6.parse::>().unwrap(); 189 | let mut options = advert.options_mut(); 190 | let mut iter = options.iter(); 191 | 192 | let mut pass = false; 193 | while let Some(mut option) = iter.next().unwrap() { 194 | if let Ok(mtu) = option.downcast::>() { 195 | assert_eq!(NdpOptionTypes::Mtu, mtu.option_type()); 196 | assert_eq!(1, mtu.length()); 197 | assert_eq!(1500, mtu.mtu()); 198 | 199 | pass = true; 200 | break; 201 | } 202 | } 203 | 204 | assert!(pass); 205 | } 206 | 207 | #[capsule::test] 208 | fn push_and_set_mtu() { 209 | let packet = Mbuf::new().unwrap(); 210 | let ethernet = packet.push::().unwrap(); 211 | let ipv6 = ethernet.push::().unwrap(); 212 | let mut advert = ipv6.push::>().unwrap(); 213 | let mut options = advert.options_mut(); 214 | let mut mtu = options.append::>().unwrap(); 215 | 216 | assert_eq!(NdpOptionTypes::Mtu, mtu.option_type()); 217 | assert_eq!(1, mtu.length()); 218 | assert_eq!(0, mtu.mtu()); 219 | 220 | mtu.set_mtu(1280); 221 | assert_eq!(1280, mtu.mtu()); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /core/src/packets/icmp/v6/ndp/router_solicit.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use super::NdpPacket; 20 | use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; 21 | use crate::packets::ip::v6::Ipv6Packet; 22 | use crate::packets::types::u32be; 23 | use crate::packets::{Internal, Packet}; 24 | use crate::SizeOf; 25 | use anyhow::Result; 26 | use std::fmt; 27 | use std::ptr::NonNull; 28 | 29 | /// Router Solicitation Message defined in [IETF RFC 4861]. 30 | /// 31 | /// ``` 32 | /// 0 1 2 3 33 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 34 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 35 | /// | Type | Code | Checksum | 36 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 37 | /// | Reserved | 38 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 39 | /// | Options ... 40 | /// +-+-+-+-+-+-+-+-+-+-+-+- 41 | /// ``` 42 | /// 43 | /// - *Reserved*: This field is unused. It MUST be initialized to 44 | /// zero by the sender and MUST be ignored by the 45 | /// receiver. 46 | /// Valid Options: 47 | /// 48 | /// - *Source link-layer address*: 49 | /// The link-layer address of the sender, if known. 50 | /// 51 | /// [IETF RFC 4861]: https://tools.ietf.org/html/rfc4861#section-4.1 52 | #[derive(Icmpv6Packet)] 53 | pub struct RouterSolicitation { 54 | icmp: Icmpv6, 55 | body: NonNull, 56 | } 57 | 58 | impl fmt::Debug for RouterSolicitation { 59 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 60 | f.debug_struct("RouterSolicitation") 61 | .field("type", &format!("{}", self.msg_type())) 62 | .field("code", &self.code()) 63 | .field("checksum", &format!("0x{:04x}", self.checksum())) 64 | .finish() 65 | } 66 | } 67 | 68 | impl Icmpv6Message for RouterSolicitation { 69 | type Envelope = E; 70 | 71 | #[inline] 72 | fn msg_type() -> Icmpv6Type { 73 | Icmpv6Types::RouterSolicitation 74 | } 75 | 76 | #[inline] 77 | fn icmp(&self) -> &Icmpv6 { 78 | &self.icmp 79 | } 80 | 81 | #[inline] 82 | fn icmp_mut(&mut self) -> &mut Icmpv6 { 83 | &mut self.icmp 84 | } 85 | 86 | #[inline] 87 | fn into_icmp(self) -> Icmpv6 { 88 | self.icmp 89 | } 90 | 91 | #[inline] 92 | unsafe fn clone(&self, internal: Internal) -> Self { 93 | RouterSolicitation { 94 | icmp: self.icmp.clone(internal), 95 | body: self.body, 96 | } 97 | } 98 | 99 | /// Parses the ICMPv6 packet's payload as router solicitation. 100 | /// 101 | /// # Errors 102 | /// 103 | /// Returns an error if the payload does not have sufficient data for 104 | /// the router solicitation message body. 105 | #[inline] 106 | fn try_parse(icmp: Icmpv6, _internal: Internal) -> Result { 107 | let mbuf = icmp.mbuf(); 108 | let offset = icmp.payload_offset(); 109 | let body = mbuf.read_data(offset)?; 110 | 111 | Ok(RouterSolicitation { icmp, body }) 112 | } 113 | 114 | /// Prepends a new router solicitation message to the beginning of 115 | /// the ICMPv6's payload. 116 | /// 117 | /// # Errors 118 | /// 119 | /// Returns an error if the buffer does not have enough free space. 120 | #[inline] 121 | fn try_push(mut icmp: Icmpv6, _internal: Internal) -> Result { 122 | let offset = icmp.payload_offset(); 123 | let mbuf = icmp.mbuf_mut(); 124 | 125 | mbuf.extend(offset, RouterSolicitationBody::size_of())?; 126 | let body = mbuf.write_data(offset, &RouterSolicitationBody::default())?; 127 | 128 | Ok(RouterSolicitation { icmp, body }) 129 | } 130 | } 131 | 132 | impl NdpPacket for RouterSolicitation { 133 | fn options_offset(&self) -> usize { 134 | self.payload_offset() + RouterSolicitationBody::size_of() 135 | } 136 | } 137 | 138 | #[derive(Clone, Copy, Debug, Default, SizeOf)] 139 | #[repr(C, packed)] 140 | struct RouterSolicitationBody { 141 | _reserved: u32be, 142 | } 143 | 144 | #[cfg(test)] 145 | mod tests { 146 | use super::*; 147 | use crate::packets::ip::v6::Ipv6; 148 | use crate::packets::Ethernet; 149 | use crate::Mbuf; 150 | 151 | #[test] 152 | fn size_of_router_solicitation_body() { 153 | assert_eq!(4, RouterSolicitationBody::size_of()); 154 | } 155 | 156 | #[capsule::test] 157 | fn push_and_set_router_solicitation() { 158 | let packet = Mbuf::new().unwrap(); 159 | let ethernet = packet.push::().unwrap(); 160 | let ipv6 = ethernet.push::().unwrap(); 161 | let mut solicit = ipv6.push::>().unwrap(); 162 | 163 | assert_eq!(4, solicit.header_len()); 164 | assert_eq!(RouterSolicitationBody::size_of(), solicit.payload_len()); 165 | assert_eq!(Icmpv6Types::RouterSolicitation, solicit.msg_type()); 166 | assert_eq!(0, solicit.code()); 167 | 168 | solicit.reconcile_all(); 169 | assert!(solicit.checksum() != 0); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /core/src/packets/icmp/v6/time_exceeded.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use crate::packets::icmp::v6::{Icmpv6, Icmpv6Message, Icmpv6Packet, Icmpv6Type, Icmpv6Types}; 20 | use crate::packets::ip::v6::{Ipv6Packet, IPV6_MIN_MTU}; 21 | use crate::packets::types::u32be; 22 | use crate::packets::{Internal, Packet}; 23 | use crate::SizeOf; 24 | use anyhow::Result; 25 | use std::fmt; 26 | use std::ptr::NonNull; 27 | 28 | /// Time Exceeded Message defined in [IETF RFC 4443]. 29 | /// 30 | /// ``` 31 | /// 0 1 2 3 32 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 33 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 34 | /// | Type | Code | Checksum | 35 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 36 | /// | Unused | 37 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 38 | /// | As much of invoking packet | 39 | /// + as possible without the ICMPv6 packet + 40 | /// | exceeding the minimum IPv6 MTU [IPv6] | 41 | /// ``` 42 | /// 43 | /// [IETF RFC 4443]: https://tools.ietf.org/html/rfc4443#section-3.3 44 | #[derive(Icmpv6Packet)] 45 | pub struct TimeExceeded { 46 | icmp: Icmpv6, 47 | body: NonNull, 48 | } 49 | 50 | impl TimeExceeded { 51 | /// Returns the invoking packet as a `u8` slice. 52 | #[inline] 53 | pub fn data(&self) -> &[u8] { 54 | let offset = self.payload_offset() + TimeExceededBody::size_of(); 55 | let len = self.payload_len() - TimeExceededBody::size_of(); 56 | 57 | if let Ok(data) = self.icmp().mbuf().read_data_slice(offset, len) { 58 | unsafe { &*data.as_ptr() } 59 | } else { 60 | &[] 61 | } 62 | } 63 | } 64 | 65 | impl fmt::Debug for TimeExceeded { 66 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 67 | f.debug_struct("TimeExceeded") 68 | .field("type", &format!("{}", self.msg_type())) 69 | .field("code", &self.code()) 70 | .field("checksum", &format!("0x{:04x}", self.checksum())) 71 | .field("$offset", &self.offset()) 72 | .field("$len", &self.len()) 73 | .field("$header_len", &self.header_len()) 74 | .finish() 75 | } 76 | } 77 | 78 | impl Icmpv6Message for TimeExceeded { 79 | type Envelope = E; 80 | 81 | #[inline] 82 | fn msg_type() -> Icmpv6Type { 83 | Icmpv6Types::TimeExceeded 84 | } 85 | 86 | #[inline] 87 | fn icmp(&self) -> &Icmpv6 { 88 | &self.icmp 89 | } 90 | 91 | #[inline] 92 | fn icmp_mut(&mut self) -> &mut Icmpv6 { 93 | &mut self.icmp 94 | } 95 | 96 | #[inline] 97 | fn into_icmp(self) -> Icmpv6 { 98 | self.icmp 99 | } 100 | 101 | #[inline] 102 | unsafe fn clone(&self, internal: Internal) -> Self { 103 | TimeExceeded { 104 | icmp: self.icmp.clone(internal), 105 | body: self.body, 106 | } 107 | } 108 | 109 | /// Parses the ICMPv6 packet's payload as time exceeded. 110 | /// 111 | /// # Errors 112 | /// 113 | /// Returns an error if the payload does not have sufficient data for 114 | /// the time exceeded message body. 115 | #[inline] 116 | fn try_parse(icmp: Icmpv6, _internal: Internal) -> Result { 117 | let mbuf = icmp.mbuf(); 118 | let offset = icmp.payload_offset(); 119 | let body = mbuf.read_data(offset)?; 120 | 121 | Ok(TimeExceeded { icmp, body }) 122 | } 123 | 124 | /// Prepends a new time exceeded message to the beginning of the ICMPv6's 125 | /// payload. 126 | /// 127 | /// # Errors 128 | /// 129 | /// Returns an error if the buffer does not have enough free space. 130 | #[inline] 131 | fn try_push(mut icmp: Icmpv6, _internal: Internal) -> Result { 132 | let offset = icmp.payload_offset(); 133 | let mbuf = icmp.mbuf_mut(); 134 | 135 | mbuf.extend(offset, TimeExceededBody::size_of())?; 136 | let body = mbuf.write_data(offset, &TimeExceededBody::default())?; 137 | 138 | Ok(TimeExceeded { icmp, body }) 139 | } 140 | 141 | /// Reconciles the derivable header fields against the changes made to 142 | /// the packet. 143 | /// 144 | /// * the whole packet is truncated so it doesn't exceed the [minimum 145 | /// IPv6 MTU]. 146 | /// * [`checksum`] is computed based on the pseudo-header and the 147 | /// `TimeExceeded` message. 148 | /// 149 | /// [minimum IPv6 MTU]: IPV6_MIN_MTU 150 | /// [`checksum`]: Icmpv6::checksum 151 | #[inline] 152 | fn reconcile(&mut self) { 153 | let _ = self.envelope_mut().truncate(IPV6_MIN_MTU); 154 | self.icmp_mut().compute_checksum(); 155 | } 156 | } 157 | 158 | #[derive(Clone, Copy, Debug, Default, SizeOf)] 159 | #[repr(C, packed)] 160 | struct TimeExceededBody { 161 | _unused: u32be, 162 | } 163 | 164 | #[cfg(test)] 165 | mod tests { 166 | use super::*; 167 | use crate::packets::ip::v6::Ipv6; 168 | use crate::packets::Ethernet; 169 | use crate::testils::byte_arrays::IPV6_TCP_PACKET; 170 | use crate::Mbuf; 171 | 172 | #[test] 173 | fn size_of_time_exceeded_body() { 174 | assert_eq!(4, TimeExceededBody::size_of()); 175 | } 176 | 177 | #[capsule::test] 178 | fn push_and_set_time_exceeded() { 179 | let packet = Mbuf::from_bytes(&IPV6_TCP_PACKET).unwrap(); 180 | let ethernet = packet.parse::().unwrap(); 181 | let ipv6 = ethernet.parse::().unwrap(); 182 | let tcp_len = ipv6.payload_len(); 183 | 184 | let mut exceeded = ipv6.push::>().unwrap(); 185 | 186 | assert_eq!(4, exceeded.header_len()); 187 | assert_eq!( 188 | TimeExceededBody::size_of() + tcp_len, 189 | exceeded.payload_len() 190 | ); 191 | assert_eq!(Icmpv6Types::TimeExceeded, exceeded.msg_type()); 192 | assert_eq!(0, exceeded.code()); 193 | assert_eq!(tcp_len, exceeded.data().len()); 194 | 195 | exceeded.set_code(1); 196 | assert_eq!(1, exceeded.code()); 197 | 198 | exceeded.reconcile_all(); 199 | assert!(exceeded.checksum() != 0); 200 | } 201 | 202 | #[capsule::test] 203 | fn truncate_to_ipv6_min_mtu() { 204 | // starts with a buffer larger than min MTU. 205 | let packet = Mbuf::from_bytes(&[42; 1600]).unwrap(); 206 | let ethernet = packet.push::().unwrap(); 207 | let ipv6 = ethernet.push::().unwrap(); 208 | 209 | // the max packet len is MTU + Ethernet header 210 | let max_len = IPV6_MIN_MTU + 14; 211 | 212 | let mut exceeded = ipv6.push::>().unwrap(); 213 | assert!(exceeded.mbuf().data_len() > max_len); 214 | 215 | exceeded.reconcile_all(); 216 | assert_eq!(max_len, exceeded.mbuf().data_len()); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /core/src/packets/types.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! Custom primitive wrapper types for converting data to/from network byte 20 | //! order. 21 | 22 | use std::convert::From; 23 | use std::fmt; 24 | use std::ops; 25 | 26 | /// The 16-bit unsigned integer in big-endian order. 27 | /// 28 | /// Used to convert packet fields to host byte order on get and network byte 29 | /// order on set. 30 | #[allow(non_camel_case_types)] 31 | #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] 32 | #[repr(C, packed)] 33 | pub struct u16be(pub u16); 34 | 35 | impl u16be { 36 | /// The smallest value that can be represented by this integer type. 37 | pub const MIN: u16be = u16be(0); 38 | } 39 | 40 | impl From for u16be { 41 | fn from(item: u16) -> Self { 42 | u16be(u16::to_be(item)) 43 | } 44 | } 45 | 46 | impl From for u16 { 47 | fn from(item: u16be) -> Self { 48 | u16::from_be(item.0) 49 | } 50 | } 51 | 52 | impl ops::BitAnd for u16be { 53 | type Output = Self; 54 | 55 | fn bitand(self, rhs: Self) -> Self::Output { 56 | u16be(self.0 & rhs.0) 57 | } 58 | } 59 | 60 | impl ops::BitAndAssign for u16be { 61 | fn bitand_assign(&mut self, rhs: Self) { 62 | *self = Self(self.0 & rhs.0) 63 | } 64 | } 65 | 66 | impl ops::BitOr for u16be { 67 | type Output = Self; 68 | 69 | fn bitor(self, rhs: Self) -> Self { 70 | Self(self.0 | rhs.0) 71 | } 72 | } 73 | 74 | impl ops::BitOrAssign for u16be { 75 | fn bitor_assign(&mut self, rhs: Self) { 76 | *self = Self(self.0 | rhs.0) 77 | } 78 | } 79 | 80 | impl ops::BitXor for u16be { 81 | type Output = Self; 82 | 83 | fn bitxor(self, rhs: Self) -> Self { 84 | Self(self.0 ^ rhs.0) 85 | } 86 | } 87 | 88 | impl ops::BitXorAssign for u16be { 89 | fn bitxor_assign(&mut self, rhs: Self) { 90 | *self = Self(self.0 ^ rhs.0) 91 | } 92 | } 93 | 94 | impl ops::Not for u16be { 95 | type Output = Self; 96 | 97 | fn not(self) -> Self::Output { 98 | u16be(!self.0) 99 | } 100 | } 101 | 102 | impl fmt::Display for u16be { 103 | #[inline] 104 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 105 | let item = self.0; 106 | item.fmt(f) 107 | } 108 | } 109 | 110 | /// The 32-bit unsigned integer in big-endian order. 111 | /// 112 | /// Used to convert packet fields to host byte order on get and network byte 113 | /// order on set. 114 | #[allow(non_camel_case_types)] 115 | #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] 116 | #[repr(C, packed)] 117 | pub struct u32be(pub u32); 118 | 119 | impl u32be { 120 | /// The smallest value that can be represented by this integer type. 121 | pub const MIN: u32be = u32be(0); 122 | } 123 | 124 | impl From for u32be { 125 | fn from(item: u32) -> Self { 126 | u32be(u32::to_be(item)) 127 | } 128 | } 129 | 130 | impl From for u32 { 131 | fn from(item: u32be) -> Self { 132 | u32::from_be(item.0) 133 | } 134 | } 135 | 136 | impl ops::BitAnd for u32be { 137 | type Output = Self; 138 | 139 | fn bitand(self, rhs: Self) -> Self::Output { 140 | u32be(self.0 & rhs.0) 141 | } 142 | } 143 | 144 | impl ops::BitAndAssign for u32be { 145 | fn bitand_assign(&mut self, rhs: Self) { 146 | *self = Self(self.0 & rhs.0) 147 | } 148 | } 149 | 150 | impl ops::BitOr for u32be { 151 | type Output = Self; 152 | 153 | fn bitor(self, rhs: Self) -> Self { 154 | Self(self.0 | rhs.0) 155 | } 156 | } 157 | 158 | impl ops::BitOrAssign for u32be { 159 | fn bitor_assign(&mut self, rhs: Self) { 160 | *self = Self(self.0 | rhs.0) 161 | } 162 | } 163 | 164 | impl ops::BitXor for u32be { 165 | type Output = Self; 166 | 167 | fn bitxor(self, rhs: Self) -> Self { 168 | Self(self.0 ^ rhs.0) 169 | } 170 | } 171 | 172 | impl ops::BitXorAssign for u32be { 173 | fn bitxor_assign(&mut self, rhs: Self) { 174 | *self = Self(self.0 ^ rhs.0) 175 | } 176 | } 177 | 178 | impl ops::Not for u32be { 179 | type Output = Self; 180 | 181 | fn not(self) -> Self::Output { 182 | u32be(!self.0) 183 | } 184 | } 185 | 186 | impl fmt::Display for u32be { 187 | #[inline] 188 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 189 | let item = self.0; 190 | item.fmt(f) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /core/src/testils/criterion.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! Iterator extensions to [criterion], leveraging proptest strategy 20 | //! generators. 21 | //! 22 | //! [criterion]: https://crates.io/crates/criterion 23 | 24 | use super::Rvg; 25 | use crate::batch::{Batch, PacketTx, Poll}; 26 | use crate::Mbuf; 27 | use criterion::{black_box, Bencher}; 28 | use proptest::strategy::Strategy; 29 | use std::cmp; 30 | use std::sync::mpsc::{self, Receiver}; 31 | use std::time::{Duration, Instant}; 32 | 33 | /// Criterion `Bencher` extension trait. 34 | pub trait BencherExt { 35 | /// Times a `routine` with an input generated via a `proptest strategy` 36 | /// batch of input, and then times the iteration of the benchmark over the 37 | /// input. See [`BatchSize`] for details on choosing the batch size. The 38 | /// routine consumes its input. 39 | /// 40 | /// [`BatchSize`]: https://docs.rs/criterion/latest/criterion/enum.BatchSize.html 41 | fn iter_proptest_batched(&mut self, strategy: S, routine: R, batch_size: usize) 42 | where 43 | R: FnMut(S::Value) -> O, 44 | S: Strategy; 45 | 46 | /// Times a `routine` with an input generated via a `proptest strategy` 47 | /// batch of input that can be polled for benchmarking pipeline combinators 48 | /// in [`Batch`] and then times the iteration of the benchmark 49 | /// over the input. See [`BatchSize`] for details on choosing the batch size. 50 | /// 51 | /// [`BatchSize`]: https://docs.rs/criterion/latest/criterion/enum.BatchSize.html 52 | /// [`Batch`]: crate::batch::Batch 53 | fn iter_proptest_combinators(&mut self, strategy: S, routine: R, batch_size: usize) 54 | where 55 | R: FnMut(Poll>) -> O, 56 | S: Strategy, 57 | O: Batch; 58 | } 59 | 60 | impl BencherExt for Bencher<'_> { 61 | fn iter_proptest_batched( 62 | &mut self, 63 | strategy: S, 64 | mut routine: R, 65 | batch_size: usize, 66 | ) where 67 | R: FnMut(S::Value) -> O, 68 | { 69 | self.iter_custom(|mut iters| { 70 | let mut total_elapsed = Duration::from_secs(0); 71 | let mut gen = Rvg::deterministic(); 72 | while iters > 0 { 73 | let batch_size = cmp::min(batch_size, iters as usize); 74 | let inputs = black_box(gen.generate_vec(&strategy, batch_size)); 75 | let mut outputs = Vec::with_capacity(batch_size); 76 | let start = Instant::now(); 77 | outputs.extend(inputs.into_iter().map(&mut routine)); 78 | total_elapsed += start.elapsed(); 79 | 80 | black_box(outputs); 81 | 82 | iters -= batch_size as u64; 83 | } 84 | total_elapsed 85 | }) 86 | } 87 | 88 | fn iter_proptest_combinators, O: Batch>( 89 | &mut self, 90 | strategy: S, 91 | mut routine: R, 92 | batch_size: usize, 93 | ) where 94 | R: FnMut(Poll>) -> O, 95 | { 96 | self.iter_custom(|mut iters| { 97 | let mut total_elapsed = Duration::from_secs(0); 98 | let mut gen = Rvg::deterministic(); 99 | while iters > 0 { 100 | let batch_size = cmp::min(batch_size, iters as usize); 101 | let inputs = black_box(gen.generate_vec(&strategy, batch_size)); 102 | let mut outputs = Vec::with_capacity(batch_size); 103 | 104 | let (mut tx, rx) = mpsc::channel(); 105 | tx.transmit(inputs.into_iter().collect::>()); 106 | let mut new_batch = Poll::new(rx); 107 | new_batch.replenish(); 108 | 109 | let start = Instant::now(); 110 | let mut batch = routine(new_batch); 111 | 112 | while let Some(disp) = batch.next() { 113 | outputs.push(disp) 114 | } 115 | 116 | total_elapsed += start.elapsed(); 117 | 118 | black_box(batch); 119 | black_box(outputs); 120 | 121 | iters -= batch_size as u64; 122 | } 123 | total_elapsed 124 | }) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /core/src/testils/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! Utilities for unit tests and benchmarks. 20 | 21 | pub mod byte_arrays; 22 | pub mod criterion; 23 | mod packet; 24 | pub mod proptest; 25 | mod rvg; 26 | 27 | pub use self::packet::*; 28 | pub use self::rvg::*; 29 | 30 | use crate::dpdk::{self, Mempool, SocketId, MEMPOOL}; 31 | use crate::metrics; 32 | use std::ptr; 33 | use std::sync::Once; 34 | 35 | static TEST_INIT: Once = Once::new(); 36 | 37 | /// Run once initialization of EAL for `cargo test`. 38 | pub fn cargo_test_init() { 39 | TEST_INIT.call_once(|| { 40 | dpdk::eal_init(vec![ 41 | "capsule_test".to_owned(), 42 | "--no-huge".to_owned(), 43 | "--iova-mode=va".to_owned(), 44 | ]) 45 | .unwrap(); 46 | let _ = metrics::init(); 47 | }); 48 | } 49 | 50 | /// A handle that keeps the mempool in scope for the duration of the test. It 51 | /// will unset the thread-bound mempool on drop. 52 | #[derive(Debug)] 53 | pub struct MempoolGuard { 54 | #[allow(dead_code)] 55 | inner: Mempool, 56 | } 57 | 58 | impl Drop for MempoolGuard { 59 | fn drop(&mut self) { 60 | MEMPOOL.with(|tls| tls.replace(ptr::null_mut())); 61 | } 62 | } 63 | 64 | /// Creates a new mempool for test that automatically cleans up after the 65 | /// test completes. 66 | pub fn new_mempool(capacity: usize, cache_size: usize) -> MempoolGuard { 67 | let mut mempool = Mempool::new(capacity, cache_size, SocketId::ANY).unwrap(); 68 | MEMPOOL.with(|tls| tls.set(mempool.raw_mut())); 69 | MempoolGuard { inner: mempool } 70 | } 71 | -------------------------------------------------------------------------------- /core/src/testils/packet.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use crate::packets::ip::v4::Ipv4; 20 | use crate::packets::ip::v6::{Ipv6, SegmentRouting}; 21 | use crate::packets::{Ethernet, Packet, Tcp, Tcp4, Tcp6, Udp4, Udp6}; 22 | 23 | /// [`Packet`] extension trait. 24 | /// 25 | /// Helper methods for packet conversion that make testing less verbose. Does 26 | /// not guarantee that the result of the conversion will be a valid packet, 27 | /// and will panic if the conversion fails. 28 | /// 29 | /// [`Packet`]: crate::packets::Packet 30 | pub trait PacketExt: Packet + Sized { 31 | /// Converts the packet into an Ethernet packet. 32 | fn into_eth(self) -> Ethernet { 33 | self.reset().parse::().unwrap() 34 | } 35 | 36 | /// Converts the packet into an IPv4 packet. 37 | fn into_v4(self) -> Ipv4 { 38 | self.into_eth().parse::().unwrap() 39 | } 40 | 41 | /// Converts the packet into a TCP packet inside IPv4. 42 | fn into_v4_tcp(self) -> Tcp4 { 43 | self.into_v4().parse::().unwrap() 44 | } 45 | 46 | /// Converts the packet into a UDP packet inside IPv4. 47 | fn into_v4_udp(self) -> Udp4 { 48 | self.into_v4().parse::().unwrap() 49 | } 50 | 51 | /// Converts the packet into an IPv6 packet. 52 | fn into_v6(self) -> Ipv6 { 53 | self.into_eth().parse::().unwrap() 54 | } 55 | 56 | /// Converts the packet into a TCP packet inside IPv6. 57 | fn into_v6_tcp(self) -> Tcp6 { 58 | self.into_v6().parse::().unwrap() 59 | } 60 | 61 | /// Converts the packet into a UDP packet inside IPv6. 62 | fn into_v6_udp(self) -> Udp6 { 63 | self.into_v6().parse::().unwrap() 64 | } 65 | 66 | /// Converts the packet into an IPv6 packet with a SRH extension. 67 | fn into_sr(self) -> SegmentRouting { 68 | self.into_v6().parse::>().unwrap() 69 | } 70 | 71 | /// Converts the packet into a TCP packet inside IPv6 with a SRH extension. 72 | fn into_sr_tcp(self) -> Tcp> { 73 | self.into_sr().parse::>>().unwrap() 74 | } 75 | } 76 | 77 | impl PacketExt for T where T: Packet + Sized {} 78 | -------------------------------------------------------------------------------- /core/src/testils/proptest/arbitrary.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! Implementations of `proptest.arbitrary.Arbitrary` trait for 20 | //! various types. 21 | 22 | use crate::dpdk::Mbuf; 23 | use crate::net::MacAddr; 24 | use proptest::arbitrary::{any, Arbitrary, StrategyFor}; 25 | use proptest::strategy::{MapInto, Strategy}; 26 | 27 | impl Arbitrary for MacAddr { 28 | type Parameters = (); 29 | type Strategy = MapInto, Self>; 30 | 31 | fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { 32 | any::<[u8; 6]>().prop_map_into() 33 | } 34 | } 35 | 36 | impl Arbitrary for Mbuf { 37 | type Parameters = (); 38 | type Strategy = fn() -> Self; 39 | 40 | fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { 41 | || Mbuf::new().unwrap() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/src/testils/proptest/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! Defines extended [proptest] [`Arbitrary`] and [`Strategy`] traits for 20 | //! packet generation and manipulation for testing and benchmarking purposes. 21 | //! 22 | //! [proptest]: https://github.com/altsysrq/proptest 23 | //! [`Arbitrary`]: https://docs.rs/proptest/latest/proptest/arbitrary/trait.Arbitrary.html 24 | //! [`Strategy`]: https://docs.rs/proptest/latest/proptest/strategy/trait.Strategy.html 25 | 26 | mod arbitrary; 27 | mod strategy; 28 | 29 | pub use self::strategy::*; 30 | -------------------------------------------------------------------------------- /core/src/testils/rvg.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use proptest::collection::vec; 20 | use proptest::strategy::{Strategy, ValueTree}; 21 | use proptest::test_runner::{Config, TestRunner}; 22 | 23 | /// A random value generator (RVG), which, given proptest strategies, will 24 | /// generate random values based on those strategies. 25 | #[derive(Debug, Default)] 26 | pub struct Rvg { 27 | runner: TestRunner, 28 | } 29 | 30 | impl Rvg { 31 | /// Creates a new RVG with the default random number generator. 32 | pub fn new() -> Self { 33 | Rvg { 34 | runner: TestRunner::new(Config::default()), 35 | } 36 | } 37 | 38 | /// Creates a new RVG with a deterministic random number generator, 39 | /// using the same seed across test runs. 40 | pub fn deterministic() -> Self { 41 | Rvg { 42 | runner: TestRunner::deterministic(), 43 | } 44 | } 45 | 46 | /// Generates a value for the strategy. 47 | /// 48 | /// # Example 49 | /// 50 | /// ``` 51 | /// let mut gen = Rvg::new(); 52 | /// let udp = gen.generate(v4_udp()); 53 | /// ``` 54 | pub fn generate(&mut self, strategy: &S) -> S::Value { 55 | strategy 56 | .new_tree(&mut self.runner) 57 | .expect("No value can be generated") 58 | .current() 59 | } 60 | 61 | /// Generates a vec of some length with a value for the strategy. 62 | /// 63 | /// # Example 64 | /// 65 | /// ``` 66 | /// let mut gen = Rvg::new(); 67 | /// let udps = gen.generate_vec(v4_udp(), 10); 68 | /// ``` 69 | pub fn generate_vec(&mut self, strategy: &S, len: usize) -> Vec { 70 | vec(strategy, len..=len) 71 | .new_tree(&mut self.runner) 72 | .expect("No value can be generated") 73 | .current() 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | use crate::fieldmap; 81 | use crate::packets::ip::ProtocolNumbers; 82 | use crate::testils::packet::PacketExt; 83 | use crate::testils::proptest::*; 84 | use std::net::Ipv6Addr; 85 | 86 | #[capsule::test] 87 | fn gen_v4_packet() { 88 | let mut gen = Rvg::new(); 89 | let packet = gen.generate(&v4_udp()); 90 | let v4 = packet.into_v4(); 91 | assert_eq!(ProtocolNumbers::Udp, v4.protocol()); 92 | } 93 | 94 | #[capsule::test] 95 | fn gen_sr_packets() { 96 | let mut gen = Rvg::new(); 97 | let srhs = gen.generate_vec(&sr_tcp().prop_map(|v| v.into_sr_tcp()), 10); 98 | assert_eq!(10, srhs.len()); 99 | } 100 | 101 | #[capsule::test] 102 | fn gen_sr_packets_with_fieldmap() { 103 | let mut gen = Rvg::new(); 104 | 105 | let segments = vec![ 106 | "::2".parse::().unwrap(), 107 | "::3".parse::().unwrap(), 108 | "::4".parse::().unwrap(), 109 | ]; 110 | let srhs = gen.generate_vec( 111 | &sr_tcp_with(fieldmap! {field::sr_segments => segments}).prop_map(|v| v.into_sr()), 112 | 10, 113 | ); 114 | assert_eq!(10, srhs.len()); 115 | let _ = srhs.iter().map(|srh| assert_eq!(3, srh.segments().len())); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /examples/kni/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kni" 3 | version = "0.1.0" 4 | authors = ["Capsule Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | publish = false 8 | readme = "README.md" 9 | description = """ 10 | Kernel NIC interface example. 11 | """ 12 | 13 | [[bin]] 14 | name = "kni" 15 | path = "main.rs" 16 | doctest = false 17 | 18 | [dependencies] 19 | anyhow = "1.0" 20 | capsule = { version = "0.1", path = "../../core" } 21 | metrics-core = "0.5" 22 | metrics-observer-yaml = "0.1" 23 | tracing = "0.1" 24 | tracing-subscriber = "0.2" 25 | -------------------------------------------------------------------------------- /examples/kni/README.md: -------------------------------------------------------------------------------- 1 | # Kernel NIC interface example 2 | 3 | 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. 4 | 5 | ## Overview 6 | 7 | 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. 8 | 9 | ## Prerequisite 10 | 11 | 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). 12 | 13 | Once the build is complete, load the module with command: 14 | 15 | ``` 16 | $ sudo insmod /lib/modules/`uname -r`/extra/dpdk/rte_kni.ko 17 | ``` 18 | 19 | We may provide precompiled modules for different kernel versions and Linux distributions in the future. 20 | 21 | ## Running the application 22 | 23 | The example is located in the `examples/kni` sub-directory. To run the application, 24 | 25 | ``` 26 | /examples/kni$ cargo run -- -f kni.toml 27 | ``` 28 | 29 | While the application is running, the new virtual device is exposed to the kernel, 30 | 31 | ``` 32 | $ ip link 33 | 34 | 254: kni0: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 35 | link/ether ba:dc:af:eb:ee:f1 brd ff:ff:ff:ff:ff:ff 36 | ``` 37 | 38 | 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. 39 | 40 | ``` 41 | $ sudo ip addr add dev kni0 10.0.2.16/24 42 | $ sudo ip link set up dev kni0 43 | ``` 44 | 45 | # Explanation 46 | 47 | 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`. 48 | -------------------------------------------------------------------------------- /examples/kni/kni.toml: -------------------------------------------------------------------------------- 1 | app_name = "kni" 2 | master_core = 0 3 | 4 | [mempool] 5 | capacity = 65535 6 | cache_size = 256 7 | 8 | [[ports]] 9 | name = "kni0" 10 | device = "0000:00:08.0" 11 | cores = [1] 12 | kni = true 13 | -------------------------------------------------------------------------------- /examples/kni/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use anyhow::Result; 20 | use capsule::config::load_config; 21 | use capsule::metrics; 22 | use capsule::{batch, Runtime}; 23 | use metrics_core::{Builder, Drain, Observe}; 24 | use metrics_observer_yaml::YamlBuilder; 25 | use std::time::Duration; 26 | use tracing::{debug, Level}; 27 | use tracing_subscriber::fmt; 28 | 29 | fn print_stats() { 30 | let mut observer = YamlBuilder::new().build(); 31 | metrics::global().controller().observe(&mut observer); 32 | println!("{}", observer.drain()); 33 | } 34 | 35 | fn main() -> Result<()> { 36 | let subscriber = fmt::Subscriber::builder() 37 | .with_max_level(Level::INFO) 38 | .finish(); 39 | tracing::subscriber::set_global_default(subscriber)?; 40 | 41 | let config = load_config()?; 42 | debug!(?config); 43 | 44 | Runtime::build(config)? 45 | .add_pipeline_to_port("kni0", |q| { 46 | batch::splice(q.clone(), q.kni().unwrap().clone()) 47 | })? 48 | .add_kni_rx_pipeline_to_port("kni0", batch::splice)? 49 | .add_periodic_task_to_core(0, print_stats, Duration::from_secs(1))? 50 | .execute() 51 | } 52 | -------------------------------------------------------------------------------- /examples/nat64/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nat64" 3 | version = "0.1.0" 4 | authors = ["Capsule Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | publish = false 8 | readme = "README.md" 9 | description = """ 10 | IPv6 to IPv4 NAT gateway example. 11 | """ 12 | 13 | [[bin]] 14 | name = "nat64" 15 | path = "main.rs" 16 | doctest = false 17 | 18 | [dependencies] 19 | anyhow = "1.0" 20 | capsule = { version = "0.1", path = "../../core" } 21 | chashmap = "2.2" 22 | once_cell = "1.7" 23 | tracing = "0.1" 24 | tracing-subscriber = "0.2" 25 | -------------------------------------------------------------------------------- /examples/nat64/README.md: -------------------------------------------------------------------------------- 1 | # IPv6 to IPv4 network address translation example 2 | 3 | **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. 4 | 5 | ## Overview 6 | 7 | A simple network topology may consist of a gateway with two interfaces connected to an IPv6 only network and an IPv4 only network, as illustrated by the following figure from [IETF RFC 6146](https://tools.ietf.org/html/rfc6146#section-1.2.2). 8 | 9 | ``` 10 | +---------------------+ +---------------+ 11 | |IPv6 network | | IPv4 | 12 | | | +-------------+ | network | 13 | | |--| Name server |--| | 14 | | | | with DNS64 | | +----+ | 15 | | +----+ | +-------------+ | | H2 | | 16 | | | H1 |---| | | +----+ | 17 | | +----+ | +-------+ | 192.0.2.1 | 18 | |2001:db8::1|------| NAT64 |----| | 19 | | | +-------+ | | 20 | | | | | | 21 | +---------------------+ +---------------+ 22 | ``` 23 | 24 | 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. 25 | 26 | The gateway also has the address `203.0.113.1` assigned to it on the IPv4 network. 27 | 28 | ## Running the application 29 | 30 | The example is located in the `examples/nat64` sub-directory. To run the application, 31 | 32 | ``` 33 | /examples/nat64$ cargo run -- -f nat64.toml 34 | ``` 35 | 36 | ## Explanation 37 | 38 | 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`. 39 | 40 | ``` 41 | [[ports]] 42 | name = "eth1" 43 | device = "0000:00:08.0" 44 | cores = [1] 45 | rxd = 512 46 | txd = 512 47 | 48 | [[ports]] 49 | name = "eth2" 50 | device = "0000:00:09.0" 51 | cores = [1] 52 | rxd = 512 53 | txd = 512 54 | ``` 55 | 56 | 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`. 57 | 58 | ### 6-to-4 translation 59 | 60 | 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`. 61 | 62 | 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). 63 | 64 | ### 4-to-6 translation 65 | 66 | 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`. 67 | 68 | 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). 69 | -------------------------------------------------------------------------------- /examples/nat64/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use anyhow::Result; 20 | use capsule::batch::{Batch, Either, Pipeline, Poll}; 21 | use capsule::config::load_config; 22 | use capsule::packets::ip::v4::Ipv4; 23 | use capsule::packets::ip::v6::{Ipv6, Ipv6Packet}; 24 | use capsule::packets::ip::ProtocolNumbers; 25 | use capsule::packets::{Ethernet, Packet, Tcp4, Tcp6}; 26 | use capsule::{Mbuf, PortQueue, Runtime}; 27 | use chashmap::CHashMap; 28 | use once_cell::sync::Lazy; 29 | use std::collections::HashMap; 30 | use std::net::{Ipv4Addr, Ipv6Addr}; 31 | use std::sync::atomic::{AtomicU16, Ordering}; 32 | use tracing::{debug, Level}; 33 | use tracing_subscriber::fmt; 34 | 35 | const V4_ADDR: Ipv4Addr = Ipv4Addr::new(203, 0, 113, 1); 36 | 37 | static PORT_MAP: Lazy> = Lazy::new(CHashMap::new); 38 | static ADDR_MAP: Lazy> = Lazy::new(CHashMap::new); 39 | 40 | /// Looks up the assigned port for a source IPv6 address and port tuple. 41 | fn assigned_port(addr: Ipv6Addr, port: u16) -> u16 { 42 | static NEXT_PORT: AtomicU16 = AtomicU16::new(1025); 43 | 44 | let key = (addr, port); 45 | if let Some(value) = PORT_MAP.get(&key) { 46 | *value 47 | } else { 48 | let port = NEXT_PORT.fetch_add(1, Ordering::Relaxed); 49 | PORT_MAP.insert_new(key, port); 50 | ADDR_MAP.insert_new(port, key); 51 | port 52 | } 53 | } 54 | 55 | /// Looks up the IPv6 address and port the gateway port is assigned to. 56 | fn assigned_addr(port: u16) -> Option<(Ipv6Addr, u16)> { 57 | ADDR_MAP.get(&port).map(|v| *v) 58 | } 59 | 60 | /// Maps the source IPv4 address to its IPv6 counterpart with well-known 61 | /// prefix `64:ff9b::/96`. 62 | fn map4to6(addr: Ipv4Addr) -> Ipv6Addr { 63 | let octets = addr.octets(); 64 | Ipv6Addr::new( 65 | 0x64, 66 | 0xff9b, 67 | 0x0, 68 | 0x0, 69 | 0x0, 70 | 0x0, 71 | (octets[0] as u16) << 8 | (octets[1] as u16), 72 | (octets[2] as u16) << 8 | (octets[3] as u16), 73 | ) 74 | } 75 | 76 | /// Maps the destination IPv6 address to its IPv4 counterpart by stripping 77 | /// off the 96-bit prefix. 78 | #[inline] 79 | fn map6to4(addr: Ipv6Addr) -> Ipv4Addr { 80 | let segments = addr.segments(); 81 | let mapped = (segments[6] as u32) << 16 | (segments[7] as u32); 82 | mapped.into() 83 | } 84 | 85 | #[inline] 86 | fn nat_4to6(packet: Mbuf) -> Result> { 87 | let ethernet = packet.parse::()?; 88 | let v4 = ethernet.parse::()?; 89 | if v4.protocol() == ProtocolNumbers::Tcp && v4.fragment_offset() == 0 && !v4.more_fragments() { 90 | let tcp = v4.peek::()?; 91 | if let Some((dst, port)) = assigned_addr(tcp.dst_port()) { 92 | let dscp = v4.dscp(); 93 | let ecn = v4.ecn(); 94 | let next_header = v4.protocol(); 95 | let hop_limit = v4.ttl() - 1; 96 | let src = map4to6(v4.src()); 97 | 98 | let ethernet = v4.remove()?; 99 | let mut v6 = ethernet.push::()?; 100 | v6.set_dscp(dscp); 101 | v6.set_ecn(ecn); 102 | v6.set_next_header(next_header); 103 | v6.set_hop_limit(hop_limit); 104 | v6.set_src(src); 105 | v6.set_dst(dst); 106 | 107 | let mut tcp = v6.parse::()?; 108 | tcp.set_dst_port(port); 109 | tcp.reconcile_all(); 110 | 111 | Ok(Either::Keep(tcp.reset())) 112 | } else { 113 | Ok(Either::Drop(v4.reset())) 114 | } 115 | } else { 116 | Ok(Either::Drop(v4.reset())) 117 | } 118 | } 119 | 120 | #[inline] 121 | fn nat_6to4(packet: Mbuf) -> Result> { 122 | let ethernet = packet.parse::()?; 123 | let v6 = ethernet.parse::()?; 124 | if v6.next_header() == ProtocolNumbers::Tcp { 125 | let dscp = v6.dscp(); 126 | let ecn = v6.ecn(); 127 | let ttl = v6.hop_limit() - 1; 128 | let protocol = v6.next_header(); 129 | let src = v6.src(); 130 | let dst = map6to4(v6.dst()); 131 | 132 | let ethernet = v6.remove()?; 133 | let mut v4 = ethernet.push::()?; 134 | v4.set_dscp(dscp); 135 | v4.set_ecn(ecn); 136 | v4.set_ttl(ttl); 137 | v4.set_protocol(protocol); 138 | v4.set_src(V4_ADDR); 139 | v4.set_dst(dst); 140 | 141 | let mut tcp = v4.parse::()?; 142 | let port = tcp.src_port(); 143 | tcp.set_src_port(assigned_port(src, port)); 144 | tcp.reconcile_all(); 145 | 146 | Ok(Either::Keep(tcp.reset())) 147 | } else { 148 | Ok(Either::Drop(v6.reset())) 149 | } 150 | } 151 | 152 | fn install_6to4(qs: HashMap) -> impl Pipeline { 153 | Poll::new(qs["eth1"].clone()) 154 | .filter_map(nat_6to4) 155 | .send(qs["eth2"].clone()) 156 | } 157 | 158 | fn install_4to6(qs: HashMap) -> impl Pipeline { 159 | Poll::new(qs["eth2"].clone()) 160 | .filter_map(nat_4to6) 161 | .send(qs["eth1"].clone()) 162 | } 163 | 164 | fn main() -> Result<()> { 165 | let subscriber = fmt::Subscriber::builder() 166 | .with_max_level(Level::INFO) 167 | .finish(); 168 | tracing::subscriber::set_global_default(subscriber)?; 169 | 170 | let config = load_config()?; 171 | debug!(?config); 172 | 173 | Runtime::build(config)? 174 | .add_pipeline_to_core(1, install_6to4)? 175 | .add_pipeline_to_core(1, install_4to6)? 176 | .execute() 177 | } 178 | -------------------------------------------------------------------------------- /examples/nat64/nat64.toml: -------------------------------------------------------------------------------- 1 | app_name = "nat64" 2 | master_core = 0 3 | 4 | [mempool] 5 | capacity = 65535 6 | cache_size = 256 7 | 8 | [[ports]] 9 | name = "eth1" 10 | device = "0000:00:08.0" 11 | cores = [1] 12 | 13 | [[ports]] 14 | name = "eth2" 15 | device = "0000:00:09.0" 16 | cores = [1] 17 | -------------------------------------------------------------------------------- /examples/ping4d/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ping4d" 3 | version = "0.1.0" 4 | authors = ["Capsule Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | publish = false 8 | readme = "README.md" 9 | description = """ 10 | Ping4 daemon example. 11 | """ 12 | 13 | [[bin]] 14 | name = "ping4d" 15 | path = "main.rs" 16 | doctest = false 17 | 18 | [dependencies] 19 | anyhow = "1.0" 20 | capsule = { version = "0.1", path = "../../core" } 21 | tracing = "0.1" 22 | tracing-subscriber = "0.2" 23 | -------------------------------------------------------------------------------- /examples/ping4d/README.md: -------------------------------------------------------------------------------- 1 | # Ping4 daemon example 2 | 3 | [Ping](http://manpages.ubuntu.com/manpages/bionic/man1/ping.1.html) is a utility used to test the reachability of a host. Ping4 daemon example is a small application that answers the ping requests. 4 | 5 | ## Running the application 6 | 7 | The example is located in the `examples/ping4d` sub-directory. To run the application, 8 | 9 | ``` 10 | /examples/ping4d$ cargo run -- -f ping4d.toml 11 | ``` 12 | 13 | ## Explanation 14 | 15 | 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. 16 | -------------------------------------------------------------------------------- /examples/ping4d/echo.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capsule-rs/capsule/63b7155e4427decd77ef96edd68a93e418eaba7b/examples/ping4d/echo.pcap -------------------------------------------------------------------------------- /examples/ping4d/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use anyhow::Result; 20 | use capsule::batch::{Batch, Pipeline, Poll}; 21 | use capsule::config::load_config; 22 | use capsule::packets::icmp::v4::{EchoReply, EchoRequest}; 23 | use capsule::packets::ip::v4::Ipv4; 24 | use capsule::packets::{Ethernet, Packet}; 25 | use capsule::{Mbuf, PortQueue, Runtime}; 26 | use tracing::{debug, Level}; 27 | use tracing_subscriber::fmt; 28 | 29 | fn reply_echo(packet: &Mbuf) -> Result { 30 | let reply = Mbuf::new()?; 31 | 32 | let ethernet = packet.peek::()?; 33 | let mut reply = reply.push::()?; 34 | reply.set_src(ethernet.dst()); 35 | reply.set_dst(ethernet.src()); 36 | 37 | let ipv4 = ethernet.peek::()?; 38 | let mut reply = reply.push::()?; 39 | reply.set_src(ipv4.dst()); 40 | reply.set_dst(ipv4.src()); 41 | reply.set_ttl(255); 42 | 43 | let request = ipv4.peek::()?; 44 | let mut reply = reply.push::()?; 45 | reply.set_identifier(request.identifier()); 46 | reply.set_seq_no(request.seq_no()); 47 | reply.set_data(request.data())?; 48 | reply.reconcile_all(); 49 | 50 | debug!(?request); 51 | debug!(?reply); 52 | 53 | Ok(reply) 54 | } 55 | 56 | fn install(q: PortQueue) -> impl Pipeline { 57 | Poll::new(q.clone()).replace(reply_echo).send(q) 58 | } 59 | 60 | fn main() -> Result<()> { 61 | let subscriber = fmt::Subscriber::builder() 62 | .with_max_level(Level::DEBUG) 63 | .finish(); 64 | tracing::subscriber::set_global_default(subscriber)?; 65 | 66 | let config = load_config()?; 67 | debug!(?config); 68 | 69 | Runtime::build(config)? 70 | .add_pipeline_to_port("eth1", install)? 71 | .execute() 72 | } 73 | -------------------------------------------------------------------------------- /examples/ping4d/ping4d.toml: -------------------------------------------------------------------------------- 1 | app_name = "ping4d" 2 | master_core = 0 3 | duration = 5 4 | 5 | [mempool] 6 | capacity = 65535 7 | cache_size = 256 8 | 9 | [[ports]] 10 | name = "eth1" 11 | device = "net_pcap0" 12 | args = "rx_pcap=echo.pcap,tx_iface=lo" 13 | cores = [0] 14 | -------------------------------------------------------------------------------- /examples/pktdump/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pktdump" 3 | version = "0.1.0" 4 | authors = ["Capsule Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | publish = false 8 | readme = "README.md" 9 | description = """ 10 | Packet dump example. 11 | """ 12 | 13 | [[bin]] 14 | name = "pktdump" 15 | path = "main.rs" 16 | doctest = false 17 | 18 | [dependencies] 19 | anyhow = "1.0" 20 | capsule = { version = "0.1", path = "../../core" } 21 | colored = "2.0" 22 | tracing = "0.1" 23 | tracing-subscriber = "0.2" 24 | -------------------------------------------------------------------------------- /examples/pktdump/README.md: -------------------------------------------------------------------------------- 1 | # Packet dump example 2 | 3 | An example demonstrating pipeline combinators and packet peeking. 4 | 5 | ## Running the application 6 | 7 | The example is located in the `examples/pktdump` sub-directory. To run the application, 8 | 9 | ``` 10 | /examples/pktdump$ cargo run -- -f pktdump.toml 11 | ``` 12 | 13 | ## Explanation 14 | 15 | 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. 16 | -------------------------------------------------------------------------------- /examples/pktdump/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use anyhow::Result; 20 | use capsule::batch::{Batch, Pipeline, Poll}; 21 | use capsule::config::load_config; 22 | use capsule::packets::ip::v4::Ipv4; 23 | use capsule::packets::ip::v6::Ipv6; 24 | use capsule::packets::ip::IpPacket; 25 | use capsule::packets::{EtherTypes, Ethernet, Packet, Tcp, Tcp4, Tcp6}; 26 | use capsule::{compose, Mbuf, PortQueue, Runtime}; 27 | use colored::*; 28 | use tracing::{debug, Level}; 29 | use tracing_subscriber::fmt; 30 | 31 | #[inline] 32 | fn dump_eth(packet: Mbuf) -> Result { 33 | let ethernet = packet.parse::()?; 34 | 35 | let info_fmt = format!("{:?}", ethernet).magenta().bold(); 36 | println!("{}", info_fmt); 37 | 38 | Ok(ethernet) 39 | } 40 | 41 | #[inline] 42 | fn dump_v4(ethernet: &Ethernet) -> Result<()> { 43 | let v4 = ethernet.peek::()?; 44 | let info_fmt = format!("{:?}", v4).yellow(); 45 | println!("{}", info_fmt); 46 | 47 | let tcp = v4.peek::()?; 48 | dump_tcp(&tcp); 49 | 50 | Ok(()) 51 | } 52 | 53 | #[inline] 54 | fn dump_v6(ethernet: &Ethernet) -> Result<()> { 55 | let v6 = ethernet.peek::()?; 56 | let info_fmt = format!("{:?}", v6).cyan(); 57 | println!("{}", info_fmt); 58 | 59 | let tcp = v6.peek::()?; 60 | dump_tcp(&tcp); 61 | 62 | Ok(()) 63 | } 64 | 65 | #[inline] 66 | fn dump_tcp(tcp: &Tcp) { 67 | let tcp_fmt = format!("{:?}", tcp).green(); 68 | println!("{}", tcp_fmt); 69 | 70 | let flow_fmt = format!("{:?}", tcp.flow()).bright_blue(); 71 | println!("{}", flow_fmt); 72 | } 73 | 74 | fn install(q: PortQueue) -> impl Pipeline { 75 | Poll::new(q.clone()) 76 | .map(dump_eth) 77 | .group_by( 78 | |ethernet| ethernet.ether_type(), 79 | |groups| { 80 | compose!( groups { 81 | EtherTypes::Ipv4 => |group| { 82 | group.for_each(dump_v4) 83 | } 84 | EtherTypes::Ipv6 => |group| { 85 | group.for_each(dump_v6) 86 | } 87 | }); 88 | }, 89 | ) 90 | .send(q) 91 | } 92 | 93 | fn main() -> Result<()> { 94 | let subscriber = fmt::Subscriber::builder() 95 | .with_max_level(Level::DEBUG) 96 | .finish(); 97 | tracing::subscriber::set_global_default(subscriber)?; 98 | 99 | let config = load_config()?; 100 | debug!(?config); 101 | 102 | Runtime::build(config)? 103 | .add_pipeline_to_port("eth1", install)? 104 | .add_pipeline_to_port("eth2", install)? 105 | .execute() 106 | } 107 | -------------------------------------------------------------------------------- /examples/pktdump/pktdump.toml: -------------------------------------------------------------------------------- 1 | app_name = "pktdump" 2 | master_core = 0 3 | duration = 5 4 | 5 | [mempool] 6 | capacity = 65535 7 | cache_size = 256 8 | 9 | [[ports]] 10 | name = "eth1" 11 | device = "net_pcap0" 12 | args = "rx_pcap=tcp4.pcap,tx_iface=lo" 13 | cores = [0] 14 | 15 | [[ports]] 16 | name = "eth2" 17 | device = "net_pcap1" 18 | args = "rx_pcap=tcp6.pcap,tx_iface=lo" 19 | cores = [0] 20 | -------------------------------------------------------------------------------- /examples/pktdump/suppressions.txt: -------------------------------------------------------------------------------- 1 | # This file is used to suppress certain known leak-checks when running this 2 | # example as part of our CI build. More info can be found here: 3 | # https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer 4 | leak:vmbus_scan_one 5 | -------------------------------------------------------------------------------- /examples/pktdump/tcp4.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capsule-rs/capsule/63b7155e4427decd77ef96edd68a93e418eaba7b/examples/pktdump/tcp4.pcap -------------------------------------------------------------------------------- /examples/pktdump/tcp6.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capsule-rs/capsule/63b7155e4427decd77ef96edd68a93e418eaba7b/examples/pktdump/tcp6.pcap -------------------------------------------------------------------------------- /examples/signals/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signals" 3 | version = "0.1.0" 4 | authors = ["Capsule Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | publish = false 8 | readme = "README.md" 9 | description = """ 10 | Linux signal handling example. 11 | """ 12 | 13 | [[bin]] 14 | name = "signals" 15 | path = "main.rs" 16 | doctest = false 17 | 18 | [dependencies] 19 | anyhow = "1.0" 20 | capsule = { version = "0.1", path = "../../core" } 21 | tracing = "0.1" 22 | tracing-subscriber = "0.2" 23 | -------------------------------------------------------------------------------- /examples/signals/README.md: -------------------------------------------------------------------------------- 1 | # Linux signal handling example 2 | 3 | An example demonstrating how to handle linux signals in a Capsule application. 4 | 5 | ## Running the application 6 | 7 | The example is located in the `examples/signals` sub-directory. To run the application, 8 | 9 | ``` 10 | /examples/signals$ cargo run -- -f signals.toml 11 | ``` 12 | 13 | To send signals to the application, execute these commands in a separate terminal, 14 | 15 | ``` 16 | $ kill -s SIGHUP $(pidof signals) 17 | $ kill -s SIGTERM $(pidof signals) 18 | ``` 19 | 20 | ## Explanation 21 | 22 | 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`. 23 | -------------------------------------------------------------------------------- /examples/signals/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use anyhow::Result; 20 | use capsule::config::load_config; 21 | use capsule::Runtime; 22 | use capsule::UnixSignal::{self, *}; 23 | use tracing::{info, Level}; 24 | use tracing_subscriber::fmt; 25 | 26 | fn on_signal(signal: UnixSignal) -> bool { 27 | info!(?signal); 28 | match signal { 29 | SIGHUP => false, 30 | SIGINT | SIGTERM => true, 31 | } 32 | } 33 | 34 | fn main() -> Result<()> { 35 | let subscriber = fmt::Subscriber::builder() 36 | .with_max_level(Level::INFO) 37 | .finish(); 38 | tracing::subscriber::set_global_default(subscriber)?; 39 | 40 | let config = load_config()?; 41 | let mut runtime = Runtime::build(config)?; 42 | runtime.set_on_signal(on_signal); 43 | 44 | println!("Ctrl-C to stop..."); 45 | runtime.execute() 46 | } 47 | -------------------------------------------------------------------------------- /examples/signals/signals.toml: -------------------------------------------------------------------------------- 1 | app_name = "signals" 2 | master_core = 0 3 | ports = [] 4 | -------------------------------------------------------------------------------- /examples/skeleton/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skeleton" 3 | version = "0.1.0" 4 | authors = ["Capsule Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | publish = false 8 | readme = "README.md" 9 | description = """ 10 | Base skeleton example. 11 | """ 12 | 13 | [[bin]] 14 | name = "skeleton" 15 | path = "main.rs" 16 | doctest = false 17 | 18 | [dependencies] 19 | anyhow = "1.0" 20 | capsule = { version = "0.1", path = "../../core" } 21 | tracing = "0.1" 22 | tracing-subscriber = "0.2" 23 | -------------------------------------------------------------------------------- /examples/skeleton/README.md: -------------------------------------------------------------------------------- 1 | # Base skeleton example 2 | 3 | The base skeleton example is the simplest Capsule application that can be written. 4 | 5 | ## Running the application 6 | 7 | The example is located in the `examples/skeleton` sub-directory. To run the application, 8 | 9 | ``` 10 | /examples/skeleton$ cargo run -- -f skeleton.toml 11 | ``` 12 | 13 | ## Explanation 14 | 15 | `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. 16 | 17 | 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. 18 | -------------------------------------------------------------------------------- /examples/skeleton/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use anyhow::Result; 20 | use capsule::config::load_config; 21 | use capsule::Runtime; 22 | use tracing::{debug, Level}; 23 | use tracing_subscriber::fmt; 24 | 25 | fn main() -> Result<()> { 26 | let subscriber = fmt::Subscriber::builder() 27 | .with_max_level(Level::TRACE) 28 | .finish(); 29 | tracing::subscriber::set_global_default(subscriber)?; 30 | 31 | let config = load_config()?; 32 | debug!(?config); 33 | 34 | Runtime::build(config)?.execute() 35 | } 36 | -------------------------------------------------------------------------------- /examples/skeleton/skeleton.toml: -------------------------------------------------------------------------------- 1 | app_name = "skeleton" 2 | master_core = 0 3 | dpdk_args = "-v --log-level eal:8" 4 | duration = 5 5 | 6 | [[ports]] 7 | name = "eth1" 8 | device = "net_ring0" 9 | cores = [0] 10 | -------------------------------------------------------------------------------- /examples/syn-flood/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "syn-flood" 3 | version = "0.1.0" 4 | authors = ["Capsule Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | publish = false 8 | readme = "README.md" 9 | description = """ 10 | TCP SYN flood example. 11 | """ 12 | 13 | [[bin]] 14 | name = "synflood" 15 | path = "main.rs" 16 | doctest = false 17 | 18 | [dependencies] 19 | anyhow = "1.0" 20 | capsule = { version = "0.1", path = "../../core" } 21 | metrics-core = "0.5" 22 | metrics-observer-yaml = "0.1" 23 | tracing = "0.1" 24 | tracing-subscriber = "0.2" 25 | -------------------------------------------------------------------------------- /examples/syn-flood/README.md: -------------------------------------------------------------------------------- 1 | # TCP SYN flood example 2 | 3 | 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. 4 | 5 | ## Overview 6 | 7 | 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. 8 | 9 | ## Running the application 10 | 11 | The example is located in the `examples/syn-flood` sub-directory. To run the application, 12 | 13 | ``` 14 | /examples/syn-flood$ cargo run -- -f syn-flood.toml 15 | ``` 16 | 17 | To observe the `SYN` flood traffic, in the vagrant VM, run `tcpdump` to capture packets sent to the destination IP address and port, 18 | 19 | ``` 20 | $ sudo tcpdump -nn host 10.100.1.255 and port 80 21 | ``` 22 | 23 | ## Explanation 24 | 25 | 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. 26 | 27 | On the main core `0`, a scheduled task prints out the port metrics once every second. 28 | 29 | ``` 30 | --- 31 | capsule: 32 | port: 33 | "dropped{port=\"eth1\",dir=\"tx\"}": 87545 34 | "errors{port=\"eth1\",dir=\"tx\"}": 0 35 | "octets{port=\"eth1\",dir=\"tx\",core=\"1\"}": 16008570 36 | "packets{port=\"eth1\",dir=\"tx\",core=\"1\"}": 296455 37 | ``` 38 | -------------------------------------------------------------------------------- /examples/syn-flood/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use anyhow::Result; 20 | use capsule::batch::{Batch, Pipeline}; 21 | use capsule::config::load_config; 22 | use capsule::metrics; 23 | use capsule::net::MacAddr; 24 | use capsule::packets::ip::v4::Ipv4; 25 | use capsule::packets::{Ethernet, Packet, Tcp4}; 26 | use capsule::{batch, Mbuf, PortQueue, Runtime}; 27 | use metrics_core::{Builder, Drain, Observe}; 28 | use metrics_observer_yaml::YamlBuilder; 29 | use std::collections::HashMap; 30 | use std::net::Ipv4Addr; 31 | use std::time::Duration; 32 | use tracing::{debug, error, Level}; 33 | use tracing_subscriber::fmt; 34 | 35 | fn install(qs: HashMap) -> impl Pipeline { 36 | let src_mac = qs["eth1"].mac_addr(); 37 | let dst_ip = Ipv4Addr::new(10, 100, 1, 255); 38 | let dst_mac = MacAddr::new(0x02, 0x00, 0x00, 0xff, 0xff, 0xff); 39 | 40 | // starts the src ip at 10.0.0.0 41 | let mut next_ip = 10u32 << 24; 42 | 43 | batch::poll_fn(|| { 44 | Mbuf::alloc_bulk(128).unwrap_or_else(|err| { 45 | error!(?err); 46 | vec![] 47 | }) 48 | }) 49 | .map(move |packet| { 50 | let mut ethernet = packet.push::()?; 51 | ethernet.set_src(src_mac); 52 | ethernet.set_dst(dst_mac); 53 | 54 | // +1 to gen the next ip 55 | next_ip += 1; 56 | 57 | let mut v4 = ethernet.push::()?; 58 | v4.set_src(next_ip.into()); 59 | v4.set_dst(dst_ip); 60 | 61 | let mut tcp = v4.push::()?; 62 | tcp.set_syn(); 63 | tcp.set_seq_no(1); 64 | tcp.set_window(10); 65 | tcp.set_dst_port(80); 66 | tcp.reconcile_all(); 67 | 68 | Ok(tcp) 69 | }) 70 | .send(qs["eth1"].clone()) 71 | } 72 | 73 | fn print_stats() { 74 | let mut observer = YamlBuilder::new().build(); 75 | metrics::global().controller().observe(&mut observer); 76 | println!("{}", observer.drain()); 77 | } 78 | 79 | fn main() -> Result<()> { 80 | let subscriber = fmt::Subscriber::builder() 81 | .with_max_level(Level::INFO) 82 | .finish(); 83 | tracing::subscriber::set_global_default(subscriber)?; 84 | 85 | let config = load_config()?; 86 | debug!(?config); 87 | 88 | Runtime::build(config)? 89 | .add_periodic_pipeline_to_core(1, install, Duration::from_millis(10))? 90 | .add_periodic_task_to_core(0, print_stats, Duration::from_secs(1))? 91 | .execute() 92 | } 93 | -------------------------------------------------------------------------------- /examples/syn-flood/syn-flood.toml: -------------------------------------------------------------------------------- 1 | app_name = "syn-flood" 2 | master_core = 0 3 | 4 | [mempool] 5 | capacity = 65535 6 | cache_size = 256 7 | 8 | [[ports]] 9 | name = "eth1" 10 | device = "0000:00:08.0" 11 | cores = [1] 12 | rxd = 512 13 | txd = 512 14 | -------------------------------------------------------------------------------- /ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "capsule-ffi" 3 | version = "0.1.5" 4 | authors = ["Capsule Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | repository = "https://github.com/capsule-rs/capsule" 8 | build = "build.rs" 9 | links = "dpdk" 10 | description = """ 11 | Capsule FFI bindings for native libraries. 12 | """ 13 | 14 | [lib] 15 | name = "capsule_ffi" 16 | doctest = false 17 | 18 | [build-dependencies] 19 | bindgen = "0.57" 20 | cc = "1.0" 21 | libc = "0.2" 22 | 23 | [features] 24 | rustdoc = [] 25 | 26 | [package.metadata.docs.rs] 27 | features = ["rustdoc"] 28 | -------------------------------------------------------------------------------- /ffi/build.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | #[cfg(not(feature = "rustdoc"))] 20 | use std::env; 21 | #[cfg(not(feature = "rustdoc"))] 22 | use std::path::{Path, PathBuf}; 23 | 24 | #[cfg(not(feature = "rustdoc"))] 25 | const RTE_CORE_LIBS: &[&str] = &[ 26 | "rte_acl", 27 | "rte_bbdev", 28 | "rte_bitratestats", 29 | "rte_bpf", 30 | "rte_bus_dpaa", 31 | "rte_bus_fslmc", 32 | "rte_bus_ifpga", 33 | "rte_bus_pci", 34 | "rte_bus_vdev", 35 | "rte_bus_vmbus", 36 | "rte_cfgfile", 37 | "rte_cmdline", 38 | "rte_common_cpt", 39 | "rte_common_dpaax", 40 | "rte_common_octeontx", 41 | "rte_common_octeontx2", 42 | "rte_compressdev", 43 | "rte_cryptodev", 44 | "rte_distributor", 45 | "rte_eal", 46 | "rte_efd", 47 | "rte_ethdev", 48 | "rte_eventdev", 49 | "rte_fib", 50 | "rte_flow_classify", 51 | "rte_gro", 52 | "rte_gso", 53 | "rte_hash", 54 | "rte_ip_frag", 55 | "rte_ipsec", 56 | "rte_jobstats", 57 | "rte_kni", 58 | "rte_kvargs", 59 | "rte_latencystats", 60 | "rte_lpm", 61 | "rte_mbuf", 62 | "rte_member", 63 | "rte_mempool", 64 | "rte_mempool_bucket", 65 | "rte_mempool_dpaa", 66 | "rte_mempool_dpaa2", 67 | "rte_mempool_octeontx", 68 | "rte_mempool_octeontx2", 69 | "rte_mempool_ring", 70 | "rte_mempool_stack", 71 | "rte_meter", 72 | "rte_metrics", 73 | "rte_net", 74 | "rte_pci", 75 | "rte_pdump", 76 | "rte_pipeline", 77 | "rte_port", 78 | "rte_power", 79 | "rte_rawdev", 80 | "rte_rawdev_dpaa2_cmdif", 81 | "rte_rawdev_dpaa2_qdma", 82 | "rte_rawdev_ioat", 83 | "rte_rawdev_ntb", 84 | "rte_rawdev_octeontx2_dma", 85 | "rte_rawdev_skeleton", 86 | "rte_rcu", 87 | "rte_reorder", 88 | "rte_rib", 89 | "rte_ring", 90 | "rte_sched", 91 | "rte_security", 92 | "rte_stack", 93 | "rte_table", 94 | "rte_timer", 95 | "rte_vhost", 96 | ]; 97 | 98 | #[cfg(not(feature = "rustdoc"))] 99 | static RTE_PMD_LIBS: &[&str] = &[ 100 | "rte_pmd_af_packet", 101 | "rte_pmd_ark", 102 | "rte_pmd_atlantic", 103 | "rte_pmd_avp", 104 | "rte_pmd_axgbe", 105 | "rte_pmd_bbdev_fpga_lte_fec", 106 | "rte_pmd_bbdev_null", 107 | "rte_pmd_bbdev_turbo_sw", 108 | "rte_pmd_bnxt", 109 | "rte_pmd_bond", 110 | "rte_pmd_caam_jr", 111 | "rte_pmd_crypto_scheduler", 112 | "rte_pmd_cxgbe", 113 | "rte_pmd_dpaa", 114 | "rte_pmd_dpaa2", 115 | "rte_pmd_dpaa2_event", 116 | "rte_pmd_dpaa2_sec", 117 | "rte_pmd_dpaa_event", 118 | "rte_pmd_dpaa_sec", 119 | "rte_pmd_dsw_event", 120 | "rte_pmd_e1000", 121 | "rte_pmd_ena", 122 | "rte_pmd_enetc", 123 | "rte_pmd_enic", 124 | "rte_pmd_failsafe", 125 | "rte_pmd_fm10k", 126 | "rte_pmd_hinic", 127 | "rte_pmd_hns3", 128 | "rte_pmd_i40e", 129 | "rte_pmd_iavf", 130 | "rte_pmd_ice", 131 | "rte_pmd_ifc", 132 | "rte_pmd_ixgbe", 133 | "rte_pmd_kni", 134 | "rte_pmd_liquidio", 135 | "rte_pmd_memif", 136 | "rte_pmd_netvsc", 137 | "rte_pmd_nfp", 138 | "rte_pmd_nitrox", 139 | "rte_pmd_null", 140 | "rte_pmd_null_crypto", 141 | "rte_pmd_octeontx", 142 | "rte_pmd_octeontx2", 143 | "rte_pmd_octeontx2_crypto", 144 | "rte_pmd_octeontx2_event", 145 | "rte_pmd_octeontx_compress", 146 | "rte_pmd_octeontx_crypto", 147 | "rte_pmd_octeontx_event", 148 | "rte_pmd_opdl_event", 149 | "rte_pmd_pcap", 150 | "rte_pmd_pfe", 151 | "rte_pmd_qat", 152 | "rte_pmd_qede", 153 | "rte_pmd_ring", 154 | "rte_pmd_sfc", 155 | "rte_pmd_skeleton_event", 156 | "rte_pmd_softnic", 157 | "rte_pmd_sw_event", 158 | "rte_pmd_tap", 159 | "rte_pmd_thunderx", 160 | "rte_pmd_vdev_netvsc", 161 | "rte_pmd_vhost", 162 | "rte_pmd_virtio", 163 | "rte_pmd_virtio_crypto", 164 | "rte_pmd_vmxnet3", 165 | ]; 166 | 167 | #[cfg(not(feature = "rustdoc"))] 168 | const RTE_DEPS_LIBS: &[&str] = &["numa", "pcap"]; 169 | 170 | #[cfg(not(feature = "rustdoc"))] 171 | fn bind(path: &Path) { 172 | cc::Build::new() 173 | .file("src/shim.c") 174 | .flag("-march=corei7-avx") 175 | .compile("rte_shim"); 176 | 177 | bindgen::Builder::default() 178 | .header("src/bindings.h") 179 | .generate_comments(true) 180 | .generate_inline_functions(true) 181 | // treat as opaque as per issue w/ combining align/packed: 182 | // https://github.com/rust-lang/rust-bindgen/issues/1538 183 | .opaque_type(r"rte_arp_ipv4|rte_arp_hdr") 184 | .whitelist_type(r"(rte|eth|pcap)_.*") 185 | .whitelist_function(r"(_rte|rte|eth|numa|pcap)_.*") 186 | .whitelist_var(r"(RTE|DEV|ETH|MEMPOOL|PKT|rte)_.*") 187 | .derive_copy(true) 188 | .derive_debug(true) 189 | .derive_default(true) 190 | .derive_partialeq(true) 191 | .default_enum_style(bindgen::EnumVariation::ModuleConsts) 192 | .clang_arg("-finline-functions") 193 | .clang_arg("-march=corei7-avx") 194 | .rustfmt_bindings(true) 195 | .generate() 196 | .expect("Unable to generate bindings") 197 | .write_to_file(path.join("bindings.rs")) 198 | .expect("Couldn't write bindings!"); 199 | } 200 | 201 | #[cfg(not(feature = "rustdoc"))] 202 | fn main() { 203 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 204 | 205 | bind(&out_path); 206 | 207 | RTE_CORE_LIBS 208 | .iter() 209 | .chain(RTE_PMD_LIBS) 210 | .chain(RTE_DEPS_LIBS) 211 | .for_each(|lib| println!("cargo:rustc-link-lib=dylib={}", lib)); 212 | 213 | // re-run build.rs upon changes 214 | println!("cargo:rerun-if-changed=build.rs"); 215 | println!("cargo:rerun-if-changed=src/"); 216 | } 217 | 218 | // Skip the build script on docs.rs 219 | #[cfg(feature = "rustdoc")] 220 | fn main() {} 221 | -------------------------------------------------------------------------------- /ffi/src/bindings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | /// known issues: 20 | // 1. https://github.com/rust-lang/rust/issues/54341 21 | 22 | // all the necessary DPDK functions, types and constants are defined 23 | // in the following header files. 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | // libnuma functions and types 30 | #include 31 | 32 | // pcap functions and types 33 | #include 34 | 35 | // bindgen can't generate bindings for static functions defined in C 36 | // header files. these shims are necessary to expose them to FFI. 37 | 38 | /** 39 | * Error number value, stored per-thread, which can be queried after 40 | * calls to certain functions to determine why those functions failed. 41 | */ 42 | int _rte_errno(void); 43 | 44 | /** 45 | * Allocate a new mbuf from a mempool. 46 | */ 47 | struct rte_mbuf *_rte_pktmbuf_alloc(struct rte_mempool *mp); 48 | 49 | /** 50 | * Free a packet mbuf back into its original mempool. 51 | */ 52 | void _rte_pktmbuf_free(struct rte_mbuf *m); 53 | 54 | /** 55 | * Allocate a bulk of mbufs, initialize refcnt and reset the fields to 56 | * default values. 57 | */ 58 | int _rte_pktmbuf_alloc_bulk( 59 | struct rte_mempool *pool, 60 | struct rte_mbuf **mbufs, 61 | unsigned count); 62 | 63 | /** 64 | * Put several objects back in the mempool. 65 | */ 66 | void _rte_mempool_put_bulk( 67 | struct rte_mempool *mp, 68 | void *const *obj_table, 69 | unsigned int n); 70 | 71 | /** 72 | * Retrieve a burst of input packets from a receive queue of an Ethernet 73 | * device. The retrieved packets are stored in *rte_mbuf* structures whose 74 | * pointers are supplied in the *rx_pkts* array. 75 | */ 76 | uint16_t _rte_eth_rx_burst( 77 | uint16_t port_id, 78 | uint16_t queue_id, 79 | struct rte_mbuf **rx_pkts, 80 | const uint16_t nb_pkts); 81 | 82 | /** 83 | * Send a burst of output packets on a transmit queue of an Ethernet device. 84 | */ 85 | uint16_t _rte_eth_tx_burst( 86 | uint16_t port_id, 87 | uint16_t queue_id, 88 | struct rte_mbuf **tx_pkts, 89 | uint16_t nb_pkts); 90 | -------------------------------------------------------------------------------- /ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! FFI bindings for [Capsule]. 20 | //! 21 | //! [Capsule]: https://crates.io/crates/capsule 22 | 23 | #![allow(clippy::all)] 24 | #![allow(dead_code)] 25 | #![allow(non_upper_case_globals)] 26 | #![allow(non_camel_case_types)] 27 | #![allow(non_snake_case)] 28 | #![cfg(target_os = "linux")] 29 | 30 | #[cfg(not(feature = "rustdoc"))] 31 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 32 | 33 | #[cfg(feature = "rustdoc")] 34 | include!("bindings_rustdoc.rs"); 35 | -------------------------------------------------------------------------------- /ffi/src/shim.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | int _rte_errno(void) { 25 | return rte_errno; 26 | } 27 | 28 | struct rte_mbuf *_rte_pktmbuf_alloc(struct rte_mempool *mp) { 29 | return rte_pktmbuf_alloc(mp); 30 | } 31 | 32 | void _rte_pktmbuf_free(struct rte_mbuf *m) { 33 | rte_pktmbuf_free(m); 34 | } 35 | 36 | int _rte_pktmbuf_alloc_bulk( 37 | struct rte_mempool *pool, 38 | struct rte_mbuf **mbufs, 39 | unsigned count) { 40 | return rte_pktmbuf_alloc_bulk(pool, mbufs, count); 41 | } 42 | 43 | void _rte_mempool_put_bulk( 44 | struct rte_mempool *mp, 45 | void *const *obj_table, 46 | unsigned int n) { 47 | rte_mempool_put_bulk(mp, obj_table, n); 48 | } 49 | 50 | uint16_t _rte_eth_rx_burst( 51 | uint16_t port_id, 52 | uint16_t queue_id, 53 | struct rte_mbuf **rx_pkts, 54 | const uint16_t nb_pkts) { 55 | return rte_eth_rx_burst(port_id, queue_id, rx_pkts, nb_pkts); 56 | } 57 | 58 | uint16_t _rte_eth_tx_burst( 59 | uint16_t port_id, 60 | uint16_t queue_id, 61 | struct rte_mbuf **tx_pkts, 62 | uint16_t nb_pkts) { 63 | return rte_eth_tx_burst(port_id, queue_id, tx_pkts, nb_pkts); 64 | } 65 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "capsule-macros" 3 | version = "0.1.5" 4 | authors = ["Capsule Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | repository = "https://github.com/capsule-rs/capsule" 8 | description = """ 9 | Capsule procedural macros. 10 | """ 11 | 12 | [lib] 13 | name = "capsule_macros" 14 | proc-macro = true 15 | doctest = false 16 | 17 | [dependencies] 18 | darling ="0.12" 19 | quote = "1.0" 20 | syn = { version = "1.0", features = ["full"] } 21 | -------------------------------------------------------------------------------- /macros/src/derive_packet.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | use proc_macro::TokenStream; 20 | use quote::quote; 21 | 22 | pub fn gen_icmpv6(input: syn::DeriveInput) -> TokenStream { 23 | let name = input.ident; 24 | 25 | let expanded = quote! { 26 | impl ::capsule::packets::icmp::v6::Icmpv6Packet for #name { 27 | #[inline] 28 | fn msg_type(&self) -> ::capsule::packets::icmp::v6::Icmpv6Type { 29 | self.icmp().msg_type() 30 | } 31 | 32 | #[inline] 33 | fn code(&self) -> u8 { 34 | self.icmp().code() 35 | } 36 | 37 | #[inline] 38 | fn set_code(&mut self, code: u8) { 39 | self.icmp_mut().set_code(code) 40 | } 41 | 42 | #[inline] 43 | fn checksum(&self) -> u16 { 44 | self.icmp().checksum() 45 | } 46 | } 47 | 48 | impl ::capsule::packets::Packet for #name { 49 | type Envelope = E; 50 | 51 | #[inline] 52 | fn envelope(&self) -> &Self::Envelope { 53 | self.icmp().envelope() 54 | } 55 | 56 | #[inline] 57 | fn envelope_mut(&mut self) -> &mut Self::Envelope { 58 | self.icmp_mut().envelope_mut() 59 | } 60 | 61 | #[inline] 62 | fn offset(&self) -> usize { 63 | self.icmp().offset() 64 | } 65 | 66 | #[inline] 67 | fn header_len(&self) -> usize { 68 | self.icmp().header_len() 69 | } 70 | 71 | #[inline] 72 | unsafe fn clone(&self, internal: Internal) -> Self { 73 | ::capsule::packets::icmp::v6::Icmpv6Message::clone(self, internal) 74 | } 75 | 76 | #[inline] 77 | fn try_parse(envelope: Self::Envelope, _internal: Internal) -> ::anyhow::Result { 78 | envelope.parse::<::capsule::packets::icmp::v6::Icmpv6>()?.downcast::<#name>() 79 | } 80 | 81 | #[inline] 82 | fn try_push(mut envelope: Self::Envelope, internal: Internal) -> ::anyhow::Result { 83 | use ::capsule::packets::icmp::v6::{Icmpv6, Icmpv6Header, Icmpv6Message}; 84 | use ::capsule::packets::ip::{IpPacket, ProtocolNumbers}; 85 | 86 | let offset = envelope.payload_offset(); 87 | let mbuf = envelope.mbuf_mut(); 88 | 89 | mbuf.extend(offset, Icmpv6Header::size_of())?; 90 | let header = mbuf.write_data(offset, &Icmpv6Header::default())?; 91 | 92 | let mut icmp = Icmpv6 { 93 | envelope, 94 | header, 95 | offset, 96 | }; 97 | 98 | icmp.header_mut().msg_type = <#name as Icmpv6Message>::msg_type().0; 99 | icmp.envelope_mut() 100 | .set_next_protocol(ProtocolNumbers::Icmpv6); 101 | 102 | <#name as Icmpv6Message>::try_push(icmp, internal) 103 | } 104 | 105 | #[inline] 106 | fn deparse(self) -> Self::Envelope { 107 | self.into_icmp().deparse() 108 | } 109 | 110 | #[inline] 111 | fn reconcile(&mut self) { 112 | ::capsule::packets::icmp::v6::Icmpv6Message::reconcile(self); 113 | } 114 | } 115 | }; 116 | 117 | expanded.into() 118 | } 119 | 120 | pub fn gen_icmpv4(input: syn::DeriveInput) -> TokenStream { 121 | let name = input.ident; 122 | 123 | let expanded = quote! { 124 | impl ::capsule::packets::icmp::v4::Icmpv4Packet for #name { 125 | #[inline] 126 | fn msg_type(&self) -> ::capsule::packets::icmp::v4::Icmpv4Type { 127 | self.icmp().msg_type() 128 | } 129 | 130 | #[inline] 131 | fn code(&self) -> u8 { 132 | self.icmp().code() 133 | } 134 | 135 | #[inline] 136 | fn set_code(&mut self, code: u8) { 137 | self.icmp_mut().set_code(code) 138 | } 139 | 140 | #[inline] 141 | fn checksum(&self) -> u16 { 142 | self.icmp().checksum() 143 | } 144 | } 145 | 146 | impl ::capsule::packets::Packet for #name { 147 | type Envelope = ::capsule::packets::ip::v4::Ipv4; 148 | 149 | #[inline] 150 | fn envelope(&self) -> &Self::Envelope { 151 | self.icmp().envelope() 152 | } 153 | 154 | #[inline] 155 | fn envelope_mut(&mut self) -> &mut Self::Envelope { 156 | self.icmp_mut().envelope_mut() 157 | } 158 | 159 | #[inline] 160 | fn offset(&self) -> usize { 161 | self.icmp().offset() 162 | } 163 | 164 | #[inline] 165 | fn header_len(&self) -> usize { 166 | self.icmp().header_len() 167 | } 168 | 169 | #[inline] 170 | unsafe fn clone(&self, internal: Internal) -> Self { 171 | ::capsule::packets::icmp::v4::Icmpv4Message::clone(self, internal) 172 | } 173 | 174 | #[inline] 175 | fn try_parse(envelope: Self::Envelope, _internal: ::capsule::packets::Internal) -> ::anyhow::Result { 176 | envelope.parse::<::capsule::packets::icmp::v4::Icmpv4>()?.downcast::<#name>() 177 | } 178 | 179 | #[inline] 180 | fn try_push(mut envelope: Self::Envelope, internal: ::capsule::packets::Internal) -> ::anyhow::Result { 181 | use ::capsule::packets::icmp::v4::{Icmpv4, Icmpv4Header, Icmpv4Message}; 182 | use ::capsule::packets::ip::{IpPacket, ProtocolNumbers}; 183 | 184 | let offset = envelope.payload_offset(); 185 | let mbuf = envelope.mbuf_mut(); 186 | 187 | mbuf.extend(offset, Icmpv4Header::size_of())?; 188 | let header = mbuf.write_data(offset, &Icmpv4Header::default())?; 189 | 190 | let mut icmp = Icmpv4 { 191 | envelope, 192 | header, 193 | offset, 194 | }; 195 | 196 | icmp.header_mut().msg_type = <#name as Icmpv4Message>::msg_type().0; 197 | icmp.envelope_mut() 198 | .set_next_protocol(ProtocolNumbers::Icmpv4); 199 | 200 | <#name as Icmpv4Message>::try_push(icmp, internal) 201 | } 202 | 203 | #[inline] 204 | fn deparse(self) -> Self::Envelope { 205 | self.into_icmp().deparse() 206 | } 207 | 208 | #[inline] 209 | fn reconcile(&mut self) { 210 | ::capsule::packets::icmp::v4::Icmpv4Message::reconcile(self); 211 | } 212 | } 213 | }; 214 | 215 | expanded.into() 216 | } 217 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Comcast Cable Communications Management, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | //! Procedural macros for [`Capsule`]. 20 | //! 21 | //! [`Capsule`]: https://crates.io/crates/capsule 22 | 23 | #![recursion_limit = "128"] 24 | #![allow(broken_intra_doc_links)] 25 | 26 | mod derive_packet; 27 | 28 | use darling::FromMeta; 29 | use proc_macro::TokenStream; 30 | use quote::quote; 31 | use syn::{self, parse_macro_input}; 32 | 33 | /// Derive macro for [`SizeOf`]. 34 | /// 35 | /// [`SizeOf`]: crate::SizeOf 36 | #[proc_macro_derive(SizeOf)] 37 | pub fn derive_size_of(input: TokenStream) -> TokenStream { 38 | let input = parse_macro_input!(input as syn::DeriveInput); 39 | let name = input.ident; 40 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 41 | 42 | let expanded = quote! { 43 | impl #impl_generics SizeOf for #name #ty_generics #where_clause { 44 | fn size_of() -> usize { 45 | std::mem::size_of::() 46 | } 47 | } 48 | }; 49 | 50 | expanded.into() 51 | } 52 | 53 | /// Derive macro for [`Icmpv4Packet`]. 54 | /// 55 | /// Also derives the associated [`Packet`] implementation. 56 | /// 57 | /// [`Packet`]: crate::packets::Packet 58 | /// [`Icmpv4Packet`]: crate::packets::icmp::v4::Icmpv4Packet 59 | #[proc_macro_derive(Icmpv4Packet)] 60 | pub fn derive_icmpv4_packet(input: TokenStream) -> TokenStream { 61 | let input = parse_macro_input!(input as syn::DeriveInput); 62 | derive_packet::gen_icmpv4(input) 63 | } 64 | 65 | /// Derive macro for [`Icmpv6Packet`]. 66 | /// 67 | /// Also derives the associated [`Packet`] implementation. 68 | /// 69 | /// [`Packet`]: crate::packets::Packet 70 | /// [`Icmpv6Packet`]: crate::packets::icmp::v6::Icmpv6Packet 71 | #[proc_macro_derive(Icmpv6Packet)] 72 | pub fn derive_icmpv6_packet(input: TokenStream) -> TokenStream { 73 | let input = parse_macro_input!(input as syn::DeriveInput); 74 | derive_packet::gen_icmpv6(input) 75 | } 76 | 77 | // Arguments and defaults to our test/bench macros 78 | #[derive(Debug, FromMeta)] 79 | #[darling(default)] 80 | struct AttrArgs { 81 | mempool_capacity: usize, 82 | mempool_cache_size: usize, 83 | } 84 | 85 | impl Default for AttrArgs { 86 | fn default() -> Self { 87 | AttrArgs { 88 | mempool_capacity: 15, 89 | mempool_cache_size: 0, 90 | } 91 | } 92 | } 93 | 94 | /// Procedural macro for running DPDK based tests. 95 | /// 96 | /// Each test will create a new one-use `Mempool` with a maximum capacity 97 | /// of 15. The `Mempool` is not shared with other tests, allowing tests to 98 | /// run in isolation and in parallel. 99 | /// 100 | /// # Example 101 | /// 102 | /// ``` 103 | /// #[cfg(test)] 104 | /// pub mod tests { 105 | /// use super::*; 106 | /// 107 | /// #[capsule::test] 108 | /// fn test_drop() { 109 | /// ... 110 | /// assert!(drop); 111 | /// } 112 | /// } 113 | /// ``` 114 | #[proc_macro_attribute] 115 | pub fn test(args: TokenStream, input: TokenStream) -> TokenStream { 116 | let input = parse_macro_input!(input as syn::ItemFn); 117 | 118 | let ret = &input.sig.output; 119 | let name = &input.sig.ident; 120 | let inputs = &input.sig.inputs; 121 | let body = &input.block; 122 | 123 | let attr_args = parse_macro_input!(args as syn::AttributeArgs); 124 | 125 | let AttrArgs { 126 | mempool_capacity, 127 | mempool_cache_size, 128 | } = match AttrArgs::from_list(&attr_args) { 129 | Ok(v) => v, 130 | Err(e) => { 131 | return e.write_errors().into(); 132 | } 133 | }; 134 | 135 | let result = quote! { 136 | #[test] 137 | fn #name(#inputs) #ret { 138 | ::capsule::testils::cargo_test_init(); 139 | let _guard = ::capsule::testils::new_mempool(#mempool_capacity, #mempool_cache_size); 140 | 141 | #body 142 | } 143 | }; 144 | 145 | result.into() 146 | } 147 | 148 | /// Procedural macro for running DPDK based benches. 149 | /// 150 | /// Each bench loop will create a new one-use `Mempool` with a maximum capacity 151 | /// of 15. The `Mempool` is not shared with other bench runs, allowing benches to 152 | /// run in isolation and in parallel. 153 | /// 154 | /// # Example 155 | /// 156 | /// ``` 157 | /// #[capsule::bench(mempool_capcity = 30)] 158 | /// fn run_benchmark(c: &mut Criterion) { 159 | /// c.bench_function("bench:run_benchmark", |b| { 160 | /// b.iter(|| bench_thing()); 161 | /// }); 162 | /// } 163 | /// ``` 164 | #[proc_macro_attribute] 165 | pub fn bench(args: TokenStream, input: TokenStream) -> TokenStream { 166 | let input = parse_macro_input!(input as syn::ItemFn); 167 | 168 | let ret = &input.sig.output; 169 | let name = &input.sig.ident; 170 | let inputs = &input.sig.inputs; 171 | let body = &input.block; 172 | 173 | let attr_args = parse_macro_input!(args as syn::AttributeArgs); 174 | 175 | let AttrArgs { 176 | mempool_capacity, 177 | mempool_cache_size, 178 | } = match AttrArgs::from_list(&attr_args) { 179 | Ok(v) => v, 180 | Err(e) => { 181 | return e.write_errors().into(); 182 | } 183 | }; 184 | 185 | let result = quote! { 186 | fn #name(#inputs) #ret { 187 | ::capsule::testils::cargo_test_init(); 188 | let _guard = ::capsule::testils::new_mempool(#mempool_capacity, #mempool_cache_size); 189 | 190 | #body 191 | } 192 | }; 193 | 194 | result.into() 195 | } 196 | --------------------------------------------------------------------------------