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::{self, Client};
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::BTreeSet;
use std::io::prelude::*;

View File

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

View File

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