From ebb7bbdf47aca3974d27a0929f0d36d94545f185 Mon Sep 17 00:00:00 2001 From: David Skrundz Date: Tue, 11 Mar 2025 21:16:25 -0600 Subject: [PATCH] Initial commit Includes IPv4 and IPv6 support, and a CLI for generating IpRanges from ip2location DB1 CSV files. --- .gitignore | 7 + .rustfmt.toml | 2 + .vscode/extensions.json | 9 + .vscode/settings.json | 33 ++++ CONTRIBUTING.md | 6 + Cargo.lock | 363 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 31 ++++ LICENSE.md | 7 + README.md | 25 +++ cli/Cargo.toml | 26 +++ cli/src/cli.rs | 38 +++++ cli/src/main.rs | 109 ++++++++++++ ipfilter/Cargo.toml | 28 ++++ ipfilter/src/lib.rs | 67 ++++++++ private/.gitignore | 5 + private/dl.sh | 31 ++++ rust-toolchain.toml | 2 + 17 files changed, 789 insertions(+) create mode 100644 .gitignore create mode 100644 .rustfmt.toml create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 CONTRIBUTING.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 cli/Cargo.toml create mode 100644 cli/src/cli.rs create mode 100644 cli/src/main.rs create mode 100644 ipfilter/Cargo.toml create mode 100644 ipfilter/src/lib.rs create mode 100644 private/.gitignore create mode 100755 private/dl.sh create mode 100644 rust-toolchain.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32be835 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# OS Files +*~ +._* +.DS_Store + +# Rust +/target diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..5e7f9e2 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,2 @@ +hard_tabs = true +wrap_comments = true diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c70ed43 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "vadimcn.vscode-lldb", + "barbosshack.crates-io", + "usernamehw.errorlens", + "tamasfe.even-better-toml", + "rust-lang.rust-analyzer", + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2f432d8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,33 @@ +{ + // VSCode + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "files.exclude": { + "**/target": true, + "**/Cargo.lock": true, + }, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "files.trimTrailingWhitespace": true, + "files.watcherExclude": { + "**/.git/**": true, + "**/target/**": true, + }, + // Extensions + "crates.listPreReleases": true, + "evenBetterToml.formatter.alignComments": true, + "evenBetterToml.formatter.alignEntries": false, + "evenBetterToml.formatter.allowedBlankLines": 1, + "evenBetterToml.formatter.arrayAutoExpand": true, + "evenBetterToml.formatter.arrayTrailingComma": true, + "evenBetterToml.formatter.columnWidth": 80, + "evenBetterToml.formatter.reorderKeys": true, + "evenBetterToml.formatter.trailingNewline": true, + "rust-analyzer.imports.granularity.enforce": true, + "rust-analyzer.imports.granularity.group": "module", + "rust-analyzer.imports.group.enable": true, + "rust-analyzer.imports.merge.glob": false, + "rust-analyzer.imports.preferNoStd": true, + "rust-analyzer.showUnlinkedFileNotification": false, +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2f4b83c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,6 @@ +How to Contribute +================= + +We'd love to accept your patches and contributions to this project. +We just need you to follow the Contributor License Agreement outlined +in the latest v0.0.x of https://github.com/Skrunix/license diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4357f15 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,363 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "bincode" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" +dependencies = [ + "serde", +] + +[[package]] +name = "clap" +version = "4.5.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +dependencies = [ + "memchr", +] + +[[package]] +name = "either" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ipfilter" +version = "0.0.0" +dependencies = [ + "bincode", + "ipnet", + "iprange", +] + +[[package]] +name = "ipfilter-cli" +version = "0.0.0" +dependencies = [ + "anyhow", + "clap", + "csv", + "ipfilter", + "ipnet", + "itertools", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iprange" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37209be0ad225457e63814401415e748e2453a5297f9b637338f5fb8afa4ec00" +dependencies = [ + "ipnet", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[package]] +name = "serde" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..95d5afe --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +[workspace] +members = ["cli", "ipfilter"] +resolver = "2" + +[workspace.package] +authors = ["David Skrundz"] +edition = "2024" +license-file = "LICENSE.md" +rust-version = "1.85.0" + +[workspace.lints.rust] +unsafe_code = "forbid" + +[profile.release] +codegen-units = 1 +lto = "fat" +opt-level = 3 +strip = "debuginfo" + +[workspace.dependencies] +ipfilter = { path = "ipfilter", version = "=0.0.0", default-features = false } + +bincode = { version = "2.0.0-rc.3", default-features = false } + +anyhow = { version = "^1", default-features = false } +clap = { version = "^4", default-features = false, features = ["std"] } +csv = { version = "^1", default-features = false } +ipnet = { version = "^2", default-features = false } +iprange = { version = "^0.6", default-features = false } +itertools = { version = "^0.14", default-features = false } +serde = { version = "^1", default-features = false } diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..161d3b0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +Skrunix Software License +======================== + +Permission to use, copy, modify, and/or distribute this software is +outlined in the latest v0.0.x of https://github.com/Skrunix/license + +THE SOFTWARE IS PROVIDED "AS IS" diff --git a/README.md b/README.md new file mode 100644 index 0000000..77225a4 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# IP Filter + +A set of crates for the construction and use of optimized IP filtering mechanisms. + +### Setup + +1. Create `private/secret.sh` containing `export IP2LOCATION_TOKEN=''` +1. Run `./private/dl.sh DB1LITECSV DB1LITECSVIPV6` +1. List available countries using `cargo run -- -f private/DB1-LITE-V4.CSV list` +1. Generate a filter using `cargo run -- -i ./private/DB1-LITE-V4.CSV merge -c BD,BR,CN,HK,IL,IN,IQ,IR,KP,PK,QA,RO,RS,RU,SA,SG,SO,SS,SY,TR,TW,UA,DZ -o ./private/filter4.bin` +1. Generate a filter using `cargo run -- -i ./private/DB1-LITE-V6.CSV merge -c BD,BR,CN,HK,IL,IN,IQ,IR,KP,PK,QA,RO,RS,RU,SA,SG,SO,SS,SY,TR,TW,UA,DZ -6 -o ./private/filter6.bin` +1. Verify IP ranges using `cargo run -- -i ./private/filter4.bin load` or `cargo run -- -i ./private/filter6.bin load -6` + +###### Round trip checks +1. `diff <(cargo run -- -i ./private/DB1-LITE-V4.CSV merge -c BD,BR,CN,HK,IL,IN,IQ,IR,KP,PK,QA,RO,RS,RU,SA,SG,SO,SS,SY,TR,TW,UA,DZ) <(cargo run -- -i ./private/filter4.bin load)` +1. `diff <(cargo run -- -i ./private/DB1-LITE-V6.CSV merge -c BD,BR,CN,HK,IL,IN,IQ,IR,KP,PK,QA,RO,RS,RU,SA,SG,SO,SS,SY,TR,TW,UA,DZ -6) <(cargo run -- -i ./private/filter6.bin load -6)` + +## Various builds + +- build: `cargo hack --feature-powerset build` +- clippy: `cargo hack --feature-powerset clippy -- -D warnings` +- fmt: `cargo fmt --check` +- miri: `cargo +nightly hack --feature-powerset miri test` +- docs: `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` +- publish: `cargo publish --dry-run -p ipfilter` diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000..56fea17 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "ipfilter-cli" +version = "0.0.0" + +authors.workspace = true +edition.workspace = true +license-file.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +ipfilter = { workspace = true, features = ["std"] } + +anyhow = { workspace = true } +clap = { workspace = true, features = [ + "derive", + "color", + "help", + "suggestions", + "usage", +] } +csv = { workspace = true } +ipnet = { workspace = true } +itertools = { workspace = true } diff --git a/cli/src/cli.rs b/cli/src/cli.rs new file mode 100644 index 0000000..f89cc2c --- /dev/null +++ b/cli/src/cli.rs @@ -0,0 +1,38 @@ +use std::path::PathBuf; + +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +pub struct Cli { + #[arg(short, long, value_name = "FILE")] + pub input: PathBuf, + + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Print a list of all countries and their codes + List, + /// Merge country IP blocks + Merge { + /// Comma separated list of country codes + #[arg(short, long)] + countries: String, + + /// IPv6 + #[arg(short, default_value = "false")] + _6: bool, + + #[arg(short, long, value_name = "FILE")] + output: Option, + }, + /// Load a + Load { + /// IPv6 + #[arg(short, default_value = "false")] + _6: bool, + }, +} diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 0000000..069f807 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,109 @@ +use core::net::{Ipv4Addr, Ipv6Addr}; +use std::collections::HashSet; +use std::fs; + +use ipfilter::{v4, v6}; + +use anyhow::Result; +use clap::Parser; +use ipnet::{Ipv4Subnets, Ipv6Subnets}; + +mod cli; +use cli::{Cli, Commands}; +use itertools::Itertools; + +fn main() -> Result<()> { + let cli = Cli::parse(); + + let input = fs::File::open(cli.input)?; + + match cli.command { + Commands::List => { + let mut reader = csv::ReaderBuilder::new() + .has_headers(false) + .from_reader(input); + + let mut countries = HashSet::<(String, String)>::new(); + + for result in reader.records() { + let record = result?; + countries.insert((record[2].to_owned(), record[3].to_owned())); + } + + let mut countries: Vec<_> = countries.drain().collect(); + countries.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); + + for (code, country) in countries { + println!("{code} - {country}"); + } + } + Commands::Merge { + countries, + output, + _6, + } => { + let mut reader = csv::ReaderBuilder::new() + .has_headers(false) + .from_reader(input); + + let countries: HashSet = countries.split(',').map(ToOwned::to_owned).collect(); + + let records = reader + .records() + .filter_ok(|r| countries.contains(&r[2])) + .map(|r| r.map_err(anyhow::Error::from)); + + macro_rules! merge_ip { + ($v:ident, $net:ty, $addr:ty) => { + let r = records + .map(|r| { + r.and_then(|record| { + Ok(<$net>::new( + <$addr>::from_bits(record[0].parse()?), + <$addr>::from_bits(record[1].parse()?), + 0, + )) + }) + }) + .flatten_ok(); + + let range = $v::from_fallible_iter(r)?; + + if let Some(o) = output { + let mut writer = fs::File::create(o)?; + $v::write_to(range, &mut writer)?; + } else { + for subnet in &range { + println!("{subnet}"); + } + }; + }; + } + + if _6 { + merge_ip!(v6, Ipv6Subnets, Ipv6Addr); + } else { + merge_ip!(v4, Ipv4Subnets, Ipv4Addr); + } + } + Commands::Load { _6 } => { + macro_rules! load_ip { + ($v:ident, $net:ty, $addr:ty) => { + let range = $v::read_from(&mut &input)?; + + for subnet in &range { + println!("{subnet}"); + } + }; + } + + if _6 { + load_ip!(v6, Ipv6Subnets, Ipv6Addr); + } else { + load_ip!(v4, Ipv4Subnets, Ipv4Addr); + } + } + } + + Ok(()) +} diff --git a/ipfilter/Cargo.toml b/ipfilter/Cargo.toml new file mode 100644 index 0000000..07afa49 --- /dev/null +++ b/ipfilter/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "ipfilter" +version = "0.0.0" + +description = "A library to streamline IP filtering" +repository = "https://github.com/QuantumShade/ipfilter" + +authors.workspace = true +edition.workspace = true +license-file.workspace = true +rust-version.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true + +[features] +default = [] +std = ["bincode/std"] + +[dependencies] +ipnet = { workspace = true } +iprange = { workspace = true, features = ["serde"] } + +bincode = { workspace = true, optional = true, features = ["serde"] } diff --git a/ipfilter/src/lib.rs b/ipfilter/src/lib.rs new file mode 100644 index 0000000..edece1e --- /dev/null +++ b/ipfilter/src/lib.rs @@ -0,0 +1,67 @@ +//! A library to streamline IP filtering. Supports creating, serializing, and +//! deserializing [Ipv4Range] and [Ipv6Range] for optimized filtering. + +#![forbid(unused_doc_comments)] +#![forbid(missing_docs)] +#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +macro_rules! declare_filter { + ($mod:ident, $net:ident) => { + #[doc = concat!("Types and functions for IP", stringify!($mod), " filtering")] + pub mod $mod { + use ipnet::$net; + + /// The range type for this module + pub type IpRange = iprange::IpRange<$net>; + + /// Create a simplified [IpRange] from a fallible [Iterator] + pub fn from_fallible_iter(iter: I) -> Result + where + I: Iterator>, + { + let mut range = IpRange::new(); + + for subnet in iter { + range.add(subnet?); + } + + range.simplify(); + Ok(range) + } + + /// Encode the [IpRange] into any type that implements [std::io::Write] + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub fn write_to( + range: IpRange, + writer: &mut W, + ) -> Result { + bincode::encode_into_std_write( + bincode::serde::Compat(range), + writer, + bincode::config::standard(), + ) + } + + /// Decode a [IpRange] from any type that implements [std::io::Read] + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub fn read_from( + reader: &mut R, + ) -> Result { + bincode::decode_from_std_read::, _, R>( + reader, + bincode::config::standard(), + ) + .map(|x| x.0) + } + } + }; +} + +declare_filter!(v4, Ipv4Net); +pub use v4::IpRange as Ipv4Range; + +declare_filter!(v6, Ipv6Net); +pub use v6::IpRange as Ipv6Range; diff --git a/private/.gitignore b/private/.gitignore new file mode 100644 index 0000000..9161747 --- /dev/null +++ b/private/.gitignore @@ -0,0 +1,5 @@ +* + +!.gitignore + +!dl.sh diff --git a/private/dl.sh b/private/dl.sh new file mode 100755 index 0000000..4f02822 --- /dev/null +++ b/private/dl.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +# This script takes a list of arguments for each database to download +# and extract. Keep in mind that IP2LOCATION has a rate limit. + +cd "$(dirname "$0")" + +# This script requires `secret.sh` to define the following environment variables: +# - IP2LOCATION_TOKEN +. secret.sh + +BASE_URL="https://www.ip2location.com/download/?token=$IP2LOCATION_TOKEN&file=" + +for CODE in "$@" +do + if [[ $CODE == *IPV6 ]] + then + DB="${CODE::${#CODE}-4}" + ver="V6" + else + DB="$CODE" + ver="V4" + fi + + dbx="${DB::${#DB}-7}" + ext="${DB:(-3)}" + + curl -L "$BASE_URL$CODE" > tmp.zip + unzip -p tmp.zip "*.$ext" > "$dbx-LITE-$ver.$ext" + rm tmp.zip +done diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..292fe49 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "stable"