This commit is contained in:
Frank Denis 2017-08-12 14:32:05 +02:00
parent 45ac24c589
commit 06b34c0e39
3 changed files with 119 additions and 80 deletions

View File

@ -2,7 +2,7 @@ use flate2::read::GzDecoder;
use hyper::net::HttpsConnector; use hyper::net::HttpsConnector;
use hyper::{self, Client}; use hyper::{self, Client};
use hyper_native_tls::NativeTlsClient; use hyper_native_tls::NativeTlsClient;
use std::cmp::{Eq, PartialOrd, PartialEq, Ord, Ordering}; use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd};
use std::collections::Bound::{Included, Unbounded}; use std::collections::Bound::{Included, Unbounded};
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::io::prelude::*; use std::io::prelude::*;

View File

@ -1,25 +1,25 @@
#![cfg_attr(feature="clippy", feature(plugin))] #![cfg_attr(feature = "clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))] #![cfg_attr(feature = "clippy", plugin(clippy))]
extern crate clap; extern crate clap;
extern crate flate2; extern crate flate2;
extern crate iron;
#[macro_use]
extern crate router;
#[macro_use] #[macro_use]
extern crate horrorshow; extern crate horrorshow;
extern crate hyper; extern crate hyper;
extern crate hyper_native_tls; extern crate hyper_native_tls;
extern crate serde; extern crate iron;
extern crate serde_json;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
#[macro_use]
extern crate router;
extern crate serde;
extern crate serde_json;
mod asns; mod asns;
mod webservice; mod webservice;
use asns::*; use asns::*;
use clap::{Arg, App}; use clap::{App, Arg};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
@ -48,20 +48,24 @@ fn main() {
.version("0.2.0") .version("0.2.0")
.author("Frank Denis") .author("Frank Denis")
.about("Webservice for https://iptoasn.com") .about("Webservice for https://iptoasn.com")
.arg(Arg::with_name("listen_addr") .arg(
.short("l") Arg::with_name("listen_addr")
.long("listen") .short("l")
.value_name("ip:port") .long("listen")
.help("Webservice IP and port") .value_name("ip:port")
.takes_value(true) .help("Webservice IP and port")
.default_value("0.0.0.0:53661")) .takes_value(true)
.arg(Arg::with_name("db_url") .default_value("0.0.0.0:53661"),
.short("u") )
.long("dburl") .arg(
.value_name("url") Arg::with_name("db_url")
.help("URL of the gzipped database") .short("u")
.takes_value(true) .long("dburl")
.default_value("https://iptoasn.com/data/ip2asn-combined.tsv.gz")) .value_name("url")
.help("URL of the gzipped database")
.takes_value(true)
.default_value("https://iptoasn.com/data/ip2asn-combined.tsv.gz"),
)
.get_matches(); .get_matches();
let db_url = matches.value_of("db_url").unwrap().to_owned(); let db_url = matches.value_of("db_url").unwrap().to_owned();
let listen_addr = matches.value_of("listen_addr").unwrap(); let listen_addr = matches.value_of("listen_addr").unwrap();
@ -69,9 +73,9 @@ fn main() {
let asns_arc = Arc::new(RwLock::new(Arc::new(asns))); let asns_arc = Arc::new(RwLock::new(Arc::new(asns)));
let asns_arc_copy = asns_arc.clone(); let asns_arc_copy = asns_arc.clone();
thread::spawn(move || loop { thread::spawn(move || loop {
thread::sleep(Duration::from_secs(3600)); thread::sleep(Duration::from_secs(3600));
update_asns(&asns_arc_copy, &db_url); update_asns(&asns_arc_copy, &db_url);
}); });
info!("Starting the webservice"); info!("Starting the webservice");
WebService::start(asns_arc, listen_addr); WebService::start(asns_arc, listen_addr);
} }

View File

@ -1,7 +1,7 @@
use asns::*; use asns::*;
use horrorshow::prelude::*; use horrorshow::prelude::*;
use iron::{BeforeMiddleware, typemap}; use iron::{typemap, BeforeMiddleware};
use iron::headers::{CacheControl, CacheDirective, Accept}; use iron::headers::{Accept, CacheControl, CacheDirective};
use iron::mime::*; use iron::mime::*;
use iron::modifiers::Header; use iron::modifiers::Header;
use iron::prelude::*; use iron::prelude::*;
@ -45,13 +45,18 @@ pub struct WebService;
impl WebService { impl WebService {
fn index(_: &mut Request) -> IronResult<Response> { fn index(_: &mut Request) -> IronResult<Response> {
Ok(Response::with((status::Ok, Ok(Response::with((
Mime(TopLevel::Text, status::Ok,
SubLevel::Plain, Mime(
vec![(Attr::Charset, Value::Utf8)]), TopLevel::Text,
Header(CacheControl(vec![CacheDirective::Public, SubLevel::Plain,
CacheDirective::MaxAge(TTL)])), vec![(Attr::Charset, Value::Utf8)],
"See https://iptoasn.com"))) ),
Header(CacheControl(
vec![CacheDirective::Public, CacheDirective::MaxAge(TTL)],
)),
"See https://iptoasn.com",
)))
} }
fn accept_type(req: &Request) -> OutputType { fn accept_type(req: &Request) -> OutputType {
@ -74,22 +79,28 @@ impl WebService {
output_type output_type
} }
fn output_json(map: serde_json::Map<String, serde_json::value::Value>, fn output_json(
cache_header: Header<CacheControl>) map: serde_json::Map<String, serde_json::value::Value>,
-> IronResult<Response> { cache_header: Header<CacheControl>,
) -> IronResult<Response> {
let json = serde_json::to_string(&map).unwrap(); let json = serde_json::to_string(&map).unwrap();
let mime_json = Mime(TopLevel::Application, let mime_json = Mime(
SubLevel::Json, TopLevel::Application,
vec![(Attr::Charset, Value::Utf8)]); SubLevel::Json,
vec![(Attr::Charset, Value::Utf8)],
);
Ok(Response::with((status::Ok, mime_json, cache_header, json))) Ok(Response::with((status::Ok, mime_json, cache_header, json)))
} }
fn output_html(map: serde_json::Map<String, serde_json::value::Value>, fn output_html(
cache_header: Header<CacheControl>) map: serde_json::Map<String, serde_json::value::Value>,
-> IronResult<Response> { cache_header: Header<CacheControl>,
let mime_html = Mime(TopLevel::Text, ) -> IronResult<Response> {
SubLevel::Html, let mime_html = Mime(
vec![(Attr::Charset, Value::Utf8)]); TopLevel::Text,
SubLevel::Html,
vec![(Attr::Charset, Value::Utf8)],
);
let html = html!{ let html = html!{
head { head {
title { : "iptoasn lookup" } title { : "iptoasn lookup" }
@ -145,10 +156,11 @@ impl WebService {
Ok(Response::with((status::Ok, mime_html, cache_header, html))) Ok(Response::with((status::Ok, mime_html, cache_header, html)))
} }
fn output(output_type: OutputType, fn output(
map: serde_json::Map<String, serde_json::value::Value>, output_type: OutputType,
cache_header: Header<CacheControl>) map: serde_json::Map<String, serde_json::value::Value>,
-> IronResult<Response> { cache_header: Header<CacheControl>,
) -> IronResult<Response> {
match output_type { match output_type {
OutputType::Json => Self::output_json(map, cache_header), OutputType::Json => Self::output_json(map, cache_header),
_ => Self::output_html(map, cache_header), _ => Self::output_html(map, cache_header),
@ -156,54 +168,77 @@ impl WebService {
} }
fn ip_lookup(req: &mut Request) -> IronResult<Response> { fn ip_lookup(req: &mut Request) -> IronResult<Response> {
let mime_text = Mime(TopLevel::Text, let mime_text = Mime(
SubLevel::Plain, TopLevel::Text,
vec![(Attr::Charset, Value::Utf8)]); SubLevel::Plain,
let cache_header = Header(CacheControl(vec![CacheDirective::Public, vec![(Attr::Charset, Value::Utf8)],
CacheDirective::MaxAge(TTL)])); );
let cache_header = Header(CacheControl(
vec![CacheDirective::Public, CacheDirective::MaxAge(TTL)],
));
let ip_str = match req.extensions.get::<Router>().unwrap().find("ip") { let ip_str = match req.extensions.get::<Router>().unwrap().find("ip") {
None => { None => {
let response = Response::with((status::BadRequest, let response = Response::with((
mime_text, status::BadRequest,
cache_header, mime_text,
"Missing IP address")); cache_header,
"Missing IP address",
));
return Ok(response); return Ok(response);
} }
Some(ip_str) => ip_str, Some(ip_str) => ip_str,
}; };
let ip = match IpAddr::from_str(ip_str) { let ip = match IpAddr::from_str(ip_str) {
Err(_) => { Err(_) => {
return Ok(Response::with((status::BadRequest, return Ok(Response::with((
mime_text, status::BadRequest,
cache_header, mime_text,
"Invalid IP address"))); cache_header,
"Invalid IP address",
)));
} }
Ok(ip) => ip, Ok(ip) => ip,
}; };
let asns = req.extensions.get::<ASNsMiddleware>().unwrap(); let asns = req.extensions.get::<ASNsMiddleware>().unwrap();
let mut map = serde_json::Map::new(); let mut map = serde_json::Map::new();
map.insert("ip".to_string(), map.insert(
serde_json::value::Value::String(ip_str.to_string())); "ip".to_string(),
serde_json::value::Value::String(ip_str.to_string()),
);
let found = match asns.lookup_by_ip(ip) { let found = match asns.lookup_by_ip(ip) {
None => { None => {
map.insert("announced".to_string(), map.insert(
serde_json::value::Value::Bool(false)); "announced".to_string(),
serde_json::value::Value::Bool(false),
);
return Self::output(Self::accept_type(&req), map, cache_header); return Self::output(Self::accept_type(&req), map, cache_header);
} }
Some(found) => found, Some(found) => found,
}; };
map.insert("announced".to_string(), map.insert(
serde_json::value::Value::Bool(true)); "announced".to_string(),
map.insert("first_ip".to_string(), serde_json::value::Value::Bool(true),
serde_json::value::Value::String(found.first_ip.to_string())); );
map.insert("last_ip".to_string(), map.insert(
serde_json::value::Value::String(found.last_ip.to_string())); "first_ip".to_string(),
map.insert("as_number".to_string(), serde_json::value::Value::String(found.first_ip.to_string()),
serde_json::value::Value::Number(serde_json::Number::from(found.number))); );
map.insert("as_country_code".to_string(), map.insert(
serde_json::value::Value::String(found.country.clone())); "last_ip".to_string(),
map.insert("as_description".to_string(), serde_json::value::Value::String(found.last_ip.to_string()),
serde_json::value::Value::String(found.description.clone())); );
map.insert(
"as_number".to_string(),
serde_json::value::Value::Number(serde_json::Number::from(found.number)),
);
map.insert(
"as_country_code".to_string(),
serde_json::value::Value::String(found.country.clone()),
);
map.insert(
"as_description".to_string(),
serde_json::value::Value::String(found.description.clone()),
);
Self::output(Self::accept_type(&req), map, cache_header) Self::output(Self::accept_type(&req), map, cache_header)
} }