diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 68eb2bd..4bc5d01 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -89,6 +89,37 @@ jobs: name: 'Cargo.toml' path: 'Cargo.toml' + build: + if: github.event_name == 'push' || (github.base_ref == 'main' && github.event.pull_request.merged == true) + strategy: + matrix: + platform: [macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + needs: [tag] + steps: + - name: Checkout + uses: actions/checkout@v3.5.2 + - name: Download Build Artifacts + uses: actions/download-artifact@v3.0.2 + with: + name: 'Cargo.toml' + - name: Build + shell: bash + run: | + RAW_BINARY_NAME=fcidr + BINARY_NAME=${RAW_BINARY_NAME} + if [[ ${{ startsWith(matrix.platform, 'windows') }} == true ]] + then + BINARY_NAME=${BINARY_NAME}.exe + fi + cargo build --release --verbose + cp target/release/${BINARY_NAME} ./ + tar czf ${RAW_BINARY_NAME}-${{ runner.os }}-${{ runner.arch }}.tar.gz ${BINARY_NAME} + - name: Upload Build Artifact + uses: actions/upload-artifact@v3.1.2 + with: + path: '*.tar.gz' + publish: if: github.event_name == 'push' || (github.base_ref == 'main' && github.event.pull_request.merged == true) runs-on: ubuntu-latest @@ -108,11 +139,12 @@ jobs: release: if: github.event_name == 'push' || (github.base_ref == 'main' && github.event.pull_request.merged == true) runs-on: ubuntu-latest - needs: [tag] + needs: [tag, build] steps: - name: Download Build Artifacts uses: actions/download-artifact@v3.0.2 - name: Release uses: softprops/action-gh-release@v0.1.15 with: + files: 'artifact/*.tar.gz' tag_name: ${{ needs.tag.outputs.version }} diff --git a/Cargo.toml b/Cargo.toml index 7c13068..c2ca141 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,23 +4,28 @@ version = "0.0.0" authors = ["Nicholas Omer Chiasson <nicholasomerchiasson@gmail.com>"] edition = "2021" license = "MIT" -description = """ -Fragmented Classless Inter-Domain Routing (FCIDR) - -A library exposing a data structure to represent a set of CIDR ranges and -easily manipulate its entries using set-like operations. -""" +description = """Fragmented Classless Inter-Domain Routing (FCIDR)""" readme = "README.md" homepage = "https://github.com/nicholaschiasson/fcidr" repository = "https://github.com/nicholaschiasson/fcidr" -keywords = ["network", "ip", "ipv4", "cidr"] -categories = ["data-structures", "network-programming"] +keywords = ["network", "ip", "ipv4", "cidr", "cli"] +categories = ["command-line-utilities", "data-structures", "network-programming"] +rust-version = "1.70.0" + +[lib] +name = "fcidr" +path = "src/lib.rs" + +[[bin]] +name = "fcidr" +path = "src/main.rs" [badges] github = { repository = "nicholaschiasson/fcidr" } maintenance = { status = "passively-maintained" } [dependencies] +clap = { version = "4.3", features = ["derive"] } serde = { version = "1.0", optional = true } [dev-dependencies] diff --git a/README.md b/README.md index 4c50998..59ea015 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ Fragmented Classless Inter-Domain Routing (FCIDR) -A library exposing a data structure to represent a set of CIDR ranges and -easily manipulate its entries using set-like operations. +A library exposing a data structure to represent a set of CIDR ranges as well +as an interface to compute set operations over CIDRs. This data structure can be applied, for example, in configuring firewalls that *implicitly deny* (AWS Security Groups) using a rule set that explicitly @@ -13,3 +13,80 @@ expresses rules for both allow and deny. > **Note** > Currently, only IPv4 is supported. IPv6 support is tracked by [#6](https://github.com/nicholaschiasson/fcidr/issues/6). + +## CLI + +This project also publishes a binary application for use on the command line to +support composing chains of set operations on CIDRs by reading from standard +input. + +### Installation + +For now, crates.io is the only place this is being distributed. + +``` +cargo install fcidr +``` + +### Usage + +``` +Fragmented Classless Inter-Domain Routing (FCIDR) + +Usage: fcidr [CIDR] <COMMAND> + +Commands: + complement Compute the complement of the input CIDR(s) + difference Compute the set difference between the input CIDR(s) and another CIDR [aliases: exclude, minus] + union Compute the set union of the input CIDR(s) and another CIDR [aliases: include, plus] + help Print this message or the help of the given subcommand(s) + +Arguments: + [CIDR] The input CIDR range and first operand to the computation. If omitted, input is taken from stdin. In this way, multiple computations can be chained together + +Options: + -h, --help Print help + -V, --version Print version +``` + +### Example + +``` +fcidr 10.0.0.0/8 difference 10.0.64.0/20 | fcidr difference 10.0.82.0/24 | fcidr union 10.0.82.74/31 +10.0.0.0/18 +10.0.80.0/23 +10.0.82.74/31 +10.0.83.0/24 +10.0.84.0/22 +10.0.88.0/21 +10.0.96.0/19 +10.0.128.0/17 +10.1.0.0/16 +10.2.0.0/15 +10.4.0.0/14 +10.8.0.0/13 +10.16.0.0/12 +10.32.0.0/11 +10.64.0.0/10 +10.128.0.0/9 +``` + +``` +fcidr 10.0.0.0/8 difference 10.0.64.0/20 | fcidr difference 10.0.82.0/24 | fcidr union 10.0.82.74/31 | fcidr complement +0.0.0.0/5 +8.0.0.0/7 +10.0.64.0/20 +10.0.82.0/26 +10.0.82.64/29 +10.0.82.72/31 +10.0.82.76/30 +10.0.82.80/28 +10.0.82.96/27 +10.0.82.128/25 +11.0.0.0/8 +12.0.0.0/6 +16.0.0.0/4 +32.0.0.0/3 +64.0.0.0/2 +128.0.0.0/1 +``` diff --git a/src/fcidr.rs b/src/fcidr.rs index 23823fb..1792a7e 100644 --- a/src/fcidr.rs +++ b/src/fcidr.rs @@ -248,7 +248,11 @@ impl Iterator for FcidrIntoIterator { // #[test] // fn it_works() { -// // let mut fcidr = Fcidr::default(); +// let mut fcidr = Fcidr::default(); +// fcidr.union("10.0.0.0/8".parse().unwrap()); +// fcidr.union("10.0.128.0/24".parse().unwrap()); +// fcidr.difference("10.0.80.0/20".parse().unwrap()); +// fcidr.union("10.0.82.0/24".parse().unwrap()); // // fcidr.union("10.0.0.0/24".parse().unwrap()); // // fcidr.union("10.0.128.0/25".parse().unwrap()); // // fcidr.union("11.0.0.0/8".parse().unwrap()); @@ -262,9 +266,9 @@ impl Iterator for FcidrIntoIterator { // // fcidr.union("0.0.0.0/0".parse().unwrap()); // // fcidr.difference("10.0.0.1/32".parse().unwrap()); // // println!("{:?}", fcidr.iter().collect::<Vec<_>>()); -// // for cidr in &fcidr { -// // println!("{cidr}"); -// // } -// // println!("{fcidr:?}"); +// for cidr in &fcidr { +// println!("{cidr}"); +// } +// println!("{fcidr:?}"); // } // } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..762ee8f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,71 @@ +use std::{ + error::Error, + io::{stdin, IsTerminal}, +}; + +use clap::{CommandFactory, Parser, Subcommand}; +use fcidr::{Cidr, Fcidr}; + +#[derive(Debug, Parser)] +#[command(about, author, version, long_about = None)] +struct Cli { + /// The input CIDR range and first operand to the computation. If omitted, + /// input is taken from stdin. In this way, multiple computations can be + /// chained together. + cidr: Option<Cidr>, + #[command(subcommand)] + command: FcidrCommand, +} + +#[derive(Debug, Subcommand)] +enum FcidrCommand { + /// Compute the complement of the input CIDR(s) + Complement, + /// Compute the set difference between the input CIDR(s) and another CIDR + #[command(visible_alias = "exclude", visible_alias = "minus")] + Difference { + /// The second CIDR range operand for the difference function + cidr: Cidr, + }, + #[command(visible_alias = "include", visible_alias = "plus")] + /// Compute the set union of the input CIDR(s) and another CIDR + Union { + /// The second CIDR range operand for the union function + cidr: Cidr, + }, +} + +fn main() -> Result<(), Box<dyn Error>> { + let cli = Cli::parse(); + + let mut fcidr: Fcidr = if let Some(cidr) = cli.cidr { + Fcidr::new(cidr) + } else { + if stdin().is_terminal() { + Cli::command().print_help().unwrap(); + ::std::process::exit(2); + } + stdin().lines().fold( + Ok(Fcidr::default()), + |fcidr: Result<Fcidr, Box<dyn Error>>, l| { + if let Ok(mut fcidr) = fcidr { + fcidr.union(l?.parse()?); + return Ok(fcidr); + } + fcidr + }, + )? + }; + + match cli.command { + FcidrCommand::Complement => fcidr.complement(), + FcidrCommand::Difference { cidr } => fcidr.difference(cidr), + FcidrCommand::Union { cidr } => fcidr.union(cidr), + }; + + for cidr in fcidr { + println!("{cidr}"); + } + + Ok(()) +}