Skip to content

Commit

Permalink
Merge pull request #63 from bgpkit/features/ip-command
Browse files Browse the repository at this point in the history
add new `ip` command
  • Loading branch information
digizeph authored Nov 27, 2024
2 parents 1ea2925 + daf5b2e commit a79c3b9
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 18 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ itertools = "0.13.0"
rayon = "1.8"
tracing = "0.1"
tracing-subscriber = "0.3"
ipnetwork = { version = "0.20.0", default-features = false }
ipnet = { version = "2.10", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = "0.4"
chrono-humanize = "0.2"
anyhow = "1.0"
tabled = "0.15.0"
json_to_table = "0.9.0"
tabled = "0.17.0"
config = { version = "0.13", features = ["toml"] }
dirs = "5"
rusqlite = { version = "0.31", features = ["bundled"] }
Expand Down
40 changes: 38 additions & 2 deletions src/bin/monocle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use bgpkit_parser::encoder::MrtUpdatesEncoder;
use bgpkit_parser::BgpElem;
use chrono::DateTime;
use clap::{Args, Parser, Subcommand};
use ipnetwork::IpNetwork;
use ipnet::IpNet;
use json_to_table::json_to_table;
use monocle::*;
use radar_rs::RadarClient;
use rayon::prelude::*;
Expand Down Expand Up @@ -299,6 +300,21 @@ enum Commands {
commands: RpkiCommands,
},

/// IP information lookup
Ip {
/// IP address to look up (optional)
#[clap()]
ip: Option<IpAddr>,

/// Print IP address only (e.g. for getting the public IP address quickly)
#[clap(long)]
simple: bool,

/// Output as JSON objects
#[clap(long)]
json: bool,
},

/// Cloudflare Radar API lookup (set CF_API_TOKEN to enable)
Radar {
#[clap(subcommand)]
Expand Down Expand Up @@ -817,7 +833,7 @@ fn main() {
RpkiCommands::List { resource } => {
let resources = match resource.parse::<u32>() {
Ok(asn) => list_by_asn(asn).unwrap(),
Err(_) => match resource.parse::<IpNetwork>() {
Err(_) => match resource.parse::<IpNet>() {
Ok(prefix) => list_by_prefix(&prefix).unwrap(),
Err(_) => {
eprintln!("list resource not an AS number or a prefix: {}", resource);
Expand Down Expand Up @@ -1034,5 +1050,25 @@ fn main() {
}
}
}
Commands::Ip { ip, json, simple } => match fetch_ip_info(ip, simple) {
Ok(ipinfo) => {
if simple {
println!("{}", ipinfo.ip);
return;
}

let json_value = json!(&ipinfo);
if json {
serde_json::to_writer_pretty(std::io::stdout(), &json_value).unwrap();
} else {
let mut table = json_to_table(&json_value);
table.collapse();
println!("{}", table);
}
}
Err(e) => {
eprintln!("unable to get ip information: {e}");
}
},
}
}
58 changes: 58 additions & 0 deletions src/datasets/ip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use anyhow::Result;
use ipnet::IpNet;
use serde::{Deserialize, Serialize};
use std::net::IpAddr;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RpkiValidationState {
#[serde(rename = "valid")]
Valid,
#[serde(rename = "invalid")]
Invalid,
#[serde(rename = "unknown")]
NotFound,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AsnRouteInfo {
pub asn: i64,
#[serde(rename(serialize = "prefix"))]
pub prefix: IpNet,
pub rpki: RpkiValidationState,
pub name: String,
pub country: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpInfo {
pub ip: String,
#[serde(rename(serialize = "location"))]
pub country: String,
#[serde(rename(serialize = "network"))]
pub asn: Option<AsnRouteInfo>,
}

const IP_INFO_API: &str = "https://api.bgpkit.com/v3/utils/ip";
pub fn fetch_ip_info(ip_opt: Option<IpAddr>, simple: bool) -> Result<IpInfo> {
let mut params = vec![];
if let Some(ip) = ip_opt {
params.push(format!("ip={}", ip));
}
if simple {
params.push("simple=true".to_string());
}
let url = format!("{}?{}", IP_INFO_API, params.join("&"));
let resp = ureq::get(&url).call()?.into_json::<IpInfo>()?;
Ok(resp)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_fetch_ip_info() {
let my_public_ip_info = fetch_ip_info(None, false).unwrap();
dbg!(my_public_ip_info);
}
}
2 changes: 2 additions & 0 deletions src/datasets/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod as2org;
mod country;
mod ip;
mod radar;
mod rpki;

pub use crate::datasets::as2org::*;
pub use crate::datasets::country::*;
pub use crate::datasets::ip::*;
pub use crate::datasets::radar::*;
pub use crate::datasets::rpki::*;
20 changes: 11 additions & 9 deletions src/datasets/rpki/roa.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::str::FromStr;
use anyhow::Result;
use ipnetwork::IpNetwork;
use ipnet::IpNet;
use rpki::repository::Roa;
use std::str::FromStr;
use tabled::Tabled;

#[derive(Debug, Tabled)]
pub struct RoaObject {
pub asn: u32,
pub prefix: IpNetwork,
pub prefix: IpNet,
pub max_len: u8,
}

Expand All @@ -17,19 +17,21 @@ pub fn read_roa(file_path: &str) -> Result<Vec<RoaObject>> {
reader.read_to_end(&mut data)?;
let roa = Roa::decode(data.as_ref(), true)?;
let asn: u32 = roa.content().as_id().into_u32();
let objects = roa.content().iter()
let objects = roa
.content()
.iter()
.map(|addr| {
let prefix_str = addr.to_string();
let fields = prefix_str.as_str().split('/').collect::<Vec<&str>>();
let p = format!("{}/{}", fields[0], fields[1]);
let prefix = IpNetwork::from_str(p.as_str()).unwrap();
let prefix = IpNet::from_str(p.as_str()).unwrap();
let max_len = addr.max_length();
RoaObject{
RoaObject {
asn,
prefix,
max_len
max_len,
}
}).collect();
})
.collect();
Ok(objects)
}

10 changes: 5 additions & 5 deletions src/datasets/rpki/validator.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::Result;
use ipnetwork::IpNetwork;
use ipnet::IpNet;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt::{Display, Formatter};
Expand All @@ -9,7 +9,7 @@ use tabled::Tabled;
#[derive(Debug, Tabled)]
pub struct RpkiValidity {
asn: u32,
prefix: IpNetwork,
prefix: IpNet,
validity: ValidationState,
}

Expand Down Expand Up @@ -123,7 +123,7 @@ impl Display for ValidationState {

/// https://rpki-validator.ripe.net/api/v1/validity/13335/1.1.0.0/23
pub fn validate(asn: u32, prefix_str: &str) -> Result<(RpkiValidity, Vec<Roa>)> {
let prefix = IpNetwork::from_str(prefix_str)?;
let prefix = IpNet::from_str(prefix_str)?;
let query_string = format!(
r#"
query GetValidation {{
Expand Down Expand Up @@ -164,7 +164,7 @@ pub fn validate(asn: u32, prefix_str: &str) -> Result<(RpkiValidity, Vec<Roa>)>
))
}

pub fn list_by_prefix(prefix: &IpNetwork) -> Result<Vec<RoaResource>> {
pub fn list_by_prefix(prefix: &IpNet) -> Result<Vec<RoaResource>> {
let query_string = format!(
r#"
query GetResources {{
Expand Down Expand Up @@ -304,7 +304,7 @@ mod tests {

#[test]
fn test_list_prefix() {
let res = list_by_prefix(&"1.0.0.0/25".parse::<IpNetwork>().unwrap()).unwrap();
let res = list_by_prefix(&"1.0.0.0/25".parse::<IpNet>().unwrap()).unwrap();
dbg!(&res);
}

Expand Down

0 comments on commit a79c3b9

Please sign in to comment.