You've already forked ipfilter
Restructure crates and switch to Zed
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
[package]
|
||||
name = "ipfilter-cli"
|
||||
version = "0.0.2"
|
||||
license-file.workspace = true
|
||||
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, features = [
|
||||
"color",
|
||||
"derive",
|
||||
"help",
|
||||
"std",
|
||||
"suggestions",
|
||||
"usage",
|
||||
] }
|
||||
csv = { workspace = true }
|
||||
ipfilter = { workspace = true, features = ["std"] }
|
||||
ipnet = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
|
||||
[lints.clippy]
|
||||
arithmetic_side_effects = "deny"
|
||||
as_conversions = "deny"
|
||||
checked_conversions = "deny"
|
||||
default_union_representation = "deny"
|
||||
expect_used = "deny"
|
||||
indexing_slicing = "deny"
|
||||
integer_division = "deny"
|
||||
integer_division_remainder_used = "deny"
|
||||
transmute_undefined_repr = "deny"
|
||||
unchecked_time_subtraction = "deny"
|
||||
unwrap_used = "deny"
|
||||
|
||||
[lints.rust]
|
||||
arithmetic_overflow = "forbid"
|
||||
unsafe_code = "forbid"
|
||||
@@ -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<PathBuf>,
|
||||
},
|
||||
/// Load a
|
||||
Load {
|
||||
/// IPv6
|
||||
#[arg(short, default_value = "false")]
|
||||
_6: bool,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
use core::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
|
||||
use ipfilter::{v4, v6};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use ipnet::{Ipv4Subnets, Ipv6Subnets};
|
||||
|
||||
mod cli;
|
||||
use cli::{Cli, Commands};
|
||||
use itertools::Itertools;
|
||||
|
||||
// RiRs
|
||||
//
|
||||
// APNIC - https://ftp.apnic.net/stats/apnic/delegated-apnic-extended-latest
|
||||
// ARIN - https://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest
|
||||
// RIPE - https://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest
|
||||
// LACNIC - https://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest
|
||||
// AFRINIC - https://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-extended-latest
|
||||
|
||||
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
|
||||
.get(2)
|
||||
.context("record is missing index 2")?
|
||||
.to_owned(),
|
||||
record
|
||||
.get(3)
|
||||
.context("record is missing index 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<String> = countries.split(',').map(ToOwned::to_owned).collect();
|
||||
|
||||
let records = reader
|
||||
.records()
|
||||
.filter_ok(|r| r.get(2).map(|r| countries.contains(r)).unwrap_or(false))
|
||||
.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
|
||||
.get(0)
|
||||
.context("record is missing index 0")?
|
||||
.parse()?,
|
||||
),
|
||||
<$addr>::from_bits(
|
||||
record
|
||||
.get(1)
|
||||
.context("record is missing index 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(())
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "ipfilter"
|
||||
version = "0.0.2"
|
||||
license-file.workspace = true
|
||||
|
||||
description = "A library to streamline IP filtering"
|
||||
repository = "https://github.com/QuantumShade/ipfilter"
|
||||
categories = ["network-programming"]
|
||||
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
bincode = { workspace = true, features = ["serde"], optional = true }
|
||||
ipnet = { workspace = true }
|
||||
iprange = { workspace = true, features = ["serde"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
std = ["bincode/std"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,17 @@
|
||||
# ipfilter
|
||||
|
||||
[](https://crates.io/crates/ipfilter)
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
use core::net::Ipv4Addr;
|
||||
use std::fs::File;
|
||||
|
||||
fn main() {
|
||||
let file = File::open("whitelist.bin").unwrap();
|
||||
let whitelist = ipfilter::v4::read_from(&mut &file).unwrap();
|
||||
|
||||
assert!(whitelist.contains(&Ipv4Addr::LOCALHOST));
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,64 @@
|
||||
//! A library to streamline IP filtering. Supports creating, serializing, and
|
||||
//! deserializing [Ipv4Range] and [Ipv6Range] for optimized filtering.
|
||||
|
||||
#![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<I, E>(iter: I) -> Result<IpRange, E>
|
||||
where
|
||||
I: Iterator<Item = Result<$net, E>>,
|
||||
{
|
||||
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<W: std::io::Write>(
|
||||
range: IpRange,
|
||||
writer: &mut W,
|
||||
) -> Result<usize, bincode::error::EncodeError> {
|
||||
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<R: std::io::Read>(
|
||||
reader: &mut R,
|
||||
) -> Result<IpRange, bincode::error::DecodeError> {
|
||||
bincode::decode_from_std_read::<bincode::serde::Compat<_>, _, 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;
|
||||
Reference in New Issue
Block a user