commit 6757ad28f856943e985d80f362fb29e3fbd4dcc3 Author: Jan Christian Grünhage Date: Sun Apr 19 15:40:43 2020 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8b62ae1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "yapilt" +version = "0.1.0" +authors = ["Jan Christian Grünhage "] +edition = "2018" + +description = "Yet another public IP lookup tool - Get your public IP" + +[[bin]] +name = "yapilt4" +path = "src/bin/yapilt4.rs" +required-features = ["reqwest-client", "tokio-threaded"] + +[[bin]] +name = "yapilt6" +path = "src/bin/yapilt6.rs" +required-features = ["reqwest-client", "tokio-threaded"] + +[features] +reqwest-client = ["reqwest"] +tokio-threaded = ["tokio"] +default = ["reqwest-client"] + +[dependencies] +http = "0.2.1" +log = "0.4.8" +reqwest = { version = "0.10.4", optional = true } +tokio = { version = "0.2.18", features = ["rt-threaded", "macros"], optional = true } + + +[dev-dependencies] +reqwest = "0.10.4" diff --git a/src/bin/yapilt4.rs b/src/bin/yapilt4.rs new file mode 100644 index 0000000..6f36fb8 --- /dev/null +++ b/src/bin/yapilt4.rs @@ -0,0 +1,4 @@ +#[tokio::main] +async fn main() { + println!("{}", yapilt::reqwest_client::ipv4(&mut reqwest::Client::new()).await); +} diff --git a/src/bin/yapilt6.rs b/src/bin/yapilt6.rs new file mode 100644 index 0000000..49f28a4 --- /dev/null +++ b/src/bin/yapilt6.rs @@ -0,0 +1,4 @@ +#[tokio::main] +async fn main() { + println!("{}", yapilt::reqwest_client::ipv6(&mut reqwest::Client::new()).await); +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..526c8d7 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,30 @@ +use crate::{IpType, Provider}; + +use std::convert::From; + +#[derive(Debug)] +pub enum Error { + Unsupported(IpType, Provider), + Http(http::Error), + #[cfg(feature = "reqwest-client")] + Reqwest(reqwest::Error), +} + +impl From<(IpType, &Provider)> for Error { + fn from(tuple: (IpType, &Provider)) -> Self { + Self::Unsupported(tuple.0, tuple.1.clone()) + } +} + +impl From for Error { + fn from(error: http::Error) -> Self { + Self::Http(error) + } +} + +#[cfg(feature = "reqwest-client")] +impl From for Error { + fn from(error: reqwest::Error) -> Self { + Self::Reqwest(error) + } +} diff --git a/src/ip_type.rs b/src/ip_type.rs new file mode 100644 index 0000000..dadc025 --- /dev/null +++ b/src/ip_type.rs @@ -0,0 +1,12 @@ +#[derive(Debug, Copy, Clone)] +pub enum IpType { V4, V6 } + +impl ToString for IpType { + fn to_string(&self) -> String { + match self { + Self::V4 => String::from("IPv4"), + Self::V6 => String::from("IPv6"), + } + } +} + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d995399 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,83 @@ + +mod error; +mod provider; +mod ip_type; + +pub use error::Error; +pub use provider::Provider; +pub use ip_type::IpType; + +pub mod http { + use crate::{Error, IpType, Provider}; + use std::default::Default; + + pub fn ipv4() -> http::Request<&'static [u8]> { + ip(IpType::V4, Provider::default()).unwrap() + } + + pub fn ipv6() -> http::Request<&'static [u8]> { + ip(IpType::V6, Provider::default()).unwrap() + } + + pub fn ip>(ip_type: IpType, provider: T) -> Result, Error> { + let provider : Provider = provider.into(); + provider.ip(ip_type) + } +} + +#[cfg(feature = "reqwest-client")] +pub mod reqwest_client { + use reqwest::Client; + + use crate::{Error, IpType, Provider, http::ip as http_ip}; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + use std::default::Default; + use std::convert::TryInto; + + pub async fn ipv4(client: &mut Client) -> Ipv4Addr { + match ip(client, IpType::V4, Provider::default()).await.unwrap() { + IpAddr::V4(ip) => ip, + _ => { + log::error!("This should be unreachable, if the provider is working and configured correctly"); + panic!(); + } + } + } + + pub async fn ipv6(client: &mut Client) -> Ipv6Addr { + match ip(client, IpType::V6, Provider::default()).await.unwrap() { + IpAddr::V6(ip) => ip, + _ => { + log::error!("This should be unreachable, if the provider is working and configured correctly"); + panic!(); + } + } + } + + pub async fn ip>(client: &mut Client, ip_type: IpType, provider: T) -> Result { + let request = http_ip(ip_type, provider)?; + let response = client + .execute(request.try_into().unwrap()) + .await? + .text() + .await?; + Ok(match ip_type { + IpType::V4 => { + let addr : Ipv4Addr = response.parse().unwrap(); + IpAddr::from(addr) + }, + IpType::V6 => { + let addr : Ipv6Addr = response.parse().unwrap(); + IpAddr::from(addr) + }, + }) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/src/provider.rs b/src/provider.rs new file mode 100644 index 0000000..8870905 --- /dev/null +++ b/src/provider.rs @@ -0,0 +1,58 @@ +use crate::{Error, IpType}; + +#[derive(Debug, PartialEq, Clone)] +pub struct Provider { + pub v4: Option<&'static str>, + pub v6: Option<&'static str>, +} + +impl Provider { + pub fn ipify_org() -> Self { + Self { v4: Some("https://api.ipify.org"), v6: Some("https://api6.ipify.org") } + } + + pub fn ident_me() -> Self { + Self { v4: Some("https://v4.ident.me"), v6: Some("https://v6.ident.me") } + } + + pub fn ip(&self, ip_type: IpType) -> Result, Error> { + let uri = match ip_type { + IpType::V4 => self.v4.ok_or((ip_type, self))?, + IpType::V6 => self.v6.ok_or((ip_type, self))?, + }; + Ok(http::Request::get(uri).body(&[][..] as &'static [u8])?) + } +} + +impl Default for Provider { + fn default() -> Self { + Self::ident_me() + } +} + +impl std::fmt::Display for Provider { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + let provider = match self { + provider if provider == &Self::ipify_org() => String::from("ipify.org"), + provider if provider == &Self::ident_me() => String::from("ident.me"), + _ => String::from("unknown"), + }; + write!(f, "{}", provider) + } +} + +impl Into for String { + fn into(self) -> Provider { + match self.as_ref() { + "ipify.org" => Provider::ipify_org(), + "ident.me" => Provider::ident_me(), + provider => { + // TODO: Handle this better + // I tried using TryInto here, but I couldn't figure out how to + // implement From<>::Error>. + log::error!("Couldn't find provider {}, falling back to the default", provider); + Provider::default() + } + } + } +}