jamesmunns

Doing embedded stuff

Be nice, work hard. Build tools, tell stories. Start five year fights. Kein Ort für Nazis.


A list of all the other services I'm on around the internet


Posts with code highlighted with codehost by @wavebeem.


All posts licensed under CC-BY-SA 4.0, unless otherwise stated.


wavebeem
@wavebeem
use fastly::http::{Method, StatusCode};
use fastly::{Error, Request, Response};
use http::header;
use serde_json::Value;
use std::collections::HashMap;
use std::net::Ipv4Addr;

/// The name of a backend server associated with this service.
/// When configuring the backend using Fastly's UI, make sure it points to "dns.google.com".
const DNS_RESOLVER: &str = "origin_0";

/// The outcome of a lookup request.
enum Outcome {
    /// The client request had no query string.
    MissingQueryString,
    /// The client request had an invalid query string.
    InvalidQueryString,
    /// Google DNS failed.
    GoogleDnsFailed,
    /// The client request came from a googlebot.
    IsGoogleBot { ptr_record: String },
    /// The client request did not come from a googlebot.
    NotGoogleBot { ptr_record: String },
    /// No PTR Answer was found.
    NoPtrAnswer,
}

/// Convert a lookup request's [`Outcome`] into an HTTP [`Response`].
impl Into<Response> for Outcome {
    fn into(self) -> fastly::Response {
        use Outcome::*;
        let (result, reason, status) = match self {
            MissingQueryString => (
                "error",
                "Missing query string ?ip=a.b.c.d".to_string(),
                StatusCode::BAD_REQUEST,
            ),
            InvalidQueryString => (
                "error",
                "Invalid query string ?ip=a.b.c.d".to_string(),
                StatusCode::BAD_REQUEST,
            ),
            GoogleDnsFailed => (
                "error",
                "Google DNS failed".to_string(),
                StatusCode::BAD_GATEWAY,
            ),
            IsGoogleBot { ptr_record } => (
                "yes",
                format!("Reverse lookup is {}", ptr_record),
                StatusCode::OK,
            ),
            NotGoogleBot { ptr_record } => (
                "no",
                format!(
                    "Reverse lookup is {}, not an *.google.com or *.googlebot.com domain.",
                    ptr_record
                ),
                StatusCode::OK,
            ),
            NoPtrAnswer => (
                "no",
                "No PTR Answer for this reverse lookup.".to_string(),
                StatusCode::OK,
            ),
        };
        let body_json = serde_json::json!({
            "result": result,
            "reason": reason,
        });

        Response::from_status(status)
            .with_header(header::CONTENT_TYPE, "application/json")
            .with_header("x-googlebot-verified", result)
            .with_body_json(&body_json)
            .unwrap()
    }
}

#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
    // Pattern match on the request method and path.
    match (req.get_method(), req.get_path()) {
        (&Method::GET, "/verify") => match handle_lookup_request(req) {
            Ok(response) => Ok(response),
            Err(error) => Ok(Response::from_status(StatusCode::BAD_REQUEST)
                .with_body_text_plain(&format!("ERROR: {}", error))),
        },

        // Catch all other requests and return a 404.
        _ => Ok(Response::from_status(StatusCode::NOT_FOUND).with_body(
            "Either the page you requested could not be found or the HTTP method is not GET.\n",
        )),
    }
}

fn handle_lookup_request(req: Request) -> Result<Response, Error> {
    // extract the ip address from query string ?ip=value
    let qs_params: HashMap<String, String> = req.get_query()?;

    let ip = match qs_params.get("ip") {
        Some(ip) => ip,
        // handle missing param
        _ => {
            return Ok(Outcome::MissingQueryString.into());
        }
    };

    match ip.parse::<Ipv4Addr>() {
        Ok(ipv4) => {
            let ipv4_octets = ipv4.octets();
            let uri = format!(
                "https://dns.google.com/resolve?name={}.{}.{}.{}.in-addr.arpa&type=PTR",
                ipv4_octets[3], ipv4_octets[2], ipv4_octets[1], ipv4_octets[0],
            );

            let dns_request = Request::get(&uri);

            let mut beresp = dns_request.send(DNS_RESOLVER)?;
            if !beresp.get_status().is_success() {
                return Ok(Outcome::GoogleDnsFailed.into());
            }

            let beresp_body = beresp.take_body_str();
            let dns_data: Value = serde_json::from_str(&beresp_body).unwrap();
            let ptr_record = &dns_data["Answer"][0]["data"].as_str();

            let is_googlebot_decision = match ptr_record {
                Some(domain)
                    if domain.ends_with(".google.com.") || domain.ends_with(".googlebot.com.") =>
                {
                    Outcome::IsGoogleBot {
                        ptr_record: domain.to_string(),
                    }
                }
                Some(domain) => Outcome::NotGoogleBot {
                    ptr_record: domain.to_string(),
                },

                _ => Outcome::NoPtrAnswer,
            };

            Ok(is_googlebot_decision.into())
        }
        _ => Ok(Outcome::InvalidQueryString.into()),
    }
}

jamesmunns
@jamesmunns

Thank you @wavebeem!

You can now use wavebeem's "codehost" tool to syntax highlight Rust (and other language) snippets!


You must log in to comment.