Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e471b93a authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Snap for 7605053 from d969f12e to tm-release

Change-Id: Id28d8252cca1fd2d75d9d4c80fd7e72ce9635828
parents b9153da4 d969f12e
Loading
Loading
Loading
Loading

tests/doh/Android.bp

0 → 100644
+18 −0
Original line number Diff line number Diff line
rust_ffi_static {
    name: "libdoh_frontend_ffi",
    crate_name: "doh_frontend_ffi",
    srcs: ["src/lib.rs"],
    edition: "2018",

    rlibs: [
        "liblibc",
        "liblog_rust",
        "libandroid_logger",
        "libanyhow",
        "libquiche",
        "libring",
        "liblazy_static",
        "libtokio",
        "libbase64_rust",
    ],
}
+39 −0
Original line number Diff line number Diff line
# For documentation, see: https://github.com/eqrion/cbindgen/blob/master/docs.md

include_version = true
braces = "SameLine"
line_length = 100
tab_width = 4
language = "C++"
pragma_once = true
no_includes = true
sys_includes = ["stdint.h", "sys/types.h"]
header = """
// This file is autogenerated by:
//   cbindgen --config cbindgen.toml src/lib.rs -o include/lib.rs.h
// Don't modify manually.
"""
documentation = true
style = "tag"
namespaces = ["test", "rust"]

[export]
item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]

[parse]
parse_deps = true
include = ["doh"]

[fn]
args = "auto"

[struct]
associated_constants_in_body = true

[enum]
add_sentinel = true
derive_helper_methods = true
derive_ostream = true

[macro_expansion]
bitflags = true
+87 −0
Original line number Diff line number Diff line
// This file is autogenerated by:
//   cbindgen --config cbindgen.toml src/lib.rs -o include/lib.rs.h
// Don't modify manually.


#pragma once

/* Generated with cbindgen:0.19.0 */

#include <stdint.h>
#include <sys/types.h>

namespace test {
namespace rust {

static const uintptr_t DNS_HEADER_SIZE = 12;

static const uintptr_t MAX_UDP_PAYLOAD_SIZE = 1350;

/// Frontend object.
struct DohFrontend;

struct Stats {
    uint32_t queries_received;
};

extern "C" {

/// Creates a DohFrontend object by the given IP addresss and ports. Returns the pointer of
/// the object if the creation succeeds; otherwise, returns a null pointer.
///
/// # Safety
///
/// The parameters `addr`, `port`, `backend_addr`, and `backend_port` must all point to null
/// terminated UTF-8 encoded strings.
DohFrontend *frontend_new(const char *addr,
                          const char *port,
                          const char *backend_addr,
                          const char *backend_port);

/// Starts the `DohFrontend` worker thread. Returns true if the worker thread is spawned
/// successfully; otherwise, it returns false.
bool frontend_start(DohFrontend *doh);

/// Stops the `DohFrontend` worker thread.
bool frontend_stop(DohFrontend *doh);

/// Deletes the `DohFrontend` created from `frontend_new`.
/// If the caller has called `frontend_start` to start `DohFrontend`, it has to call
/// call `frontend_stop` to stop the worker thread before deleting the object.
///
/// # Safety
///
/// The DohFrontend is not set to null pointer, caller needs to do it on its own.
void frontend_delete(DohFrontend *doh);

/// Sets server certificate to `DohFrontend`.
///
/// # Safety
///
/// The given certificate must be a null-terminated UTF-8 encoded string.
bool frontend_set_certificate(DohFrontend *doh, const char *certificate);

/// Sets server private key to `DohFrontend`.
///
/// # Safety
///
/// The given private key must be a null-terminated UTF-8 encoded string.
bool frontend_set_private_key(DohFrontend *doh, const char *private_key);

/// Configures the `DohFrontend` not to process DoH queries until a given number of DoH queries
/// are received. This function works even in the middle of the worker thread.
bool frontend_set_delay_queries(DohFrontend *doh, int32_t count);

/// Gets the statistics of the `DohFrontend` and writes the result to |out|.
void frontend_stats(const DohFrontend *doh, Stats *out);

/// Resets `queries_received` field of `Stats` owned by the `DohFrontend`.
bool frontend_stats_clear_queries(const DohFrontend *doh);

/// Enable Rust debug logging.
void init_android_logger();

} // extern "C"

} // namespace rust
} // namespace test
+268 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Client management, including the communication with quiche I/O.

use anyhow::{anyhow, bail, ensure, Result};
use log::{debug, info, warn};
use quiche::h3::NameValue;
use ring::hmac;
use ring::rand::SystemRandom;
use std::collections::{hash_map, HashMap};
use std::net::SocketAddr;
use std::pin::Pin;
use std::time::Duration;

pub const DNS_HEADER_SIZE: usize = 12;
pub const MAX_UDP_PAYLOAD_SIZE: usize = 1350;

pub type ConnectionID = Vec<u8>;

const URL_PATH_PREFIX: &str = "/dns-query?dns=";

/// Manages a QUIC and HTTP/3 connection. No socket I/O operations.
pub struct Client {
    /// QUIC connection.
    conn: Pin<Box<quiche::Connection>>,

    /// HTTP/3 connection.
    h3_conn: Option<quiche::h3::Connection>,

    /// Socket address the client from.
    addr: SocketAddr,

    /// The unique ID for the client.
    id: ConnectionID,

    /// Queues the DNS queries being processed in backend.
    /// <Query ID, Stream ID>
    in_flight_queries: HashMap<[u8; 2], u64>,
}

impl Client {
    fn new(conn: Pin<Box<quiche::Connection>>, addr: &SocketAddr, id: ConnectionID) -> Client {
        Client { conn, h3_conn: None, addr: *addr, id, in_flight_queries: HashMap::new() }
    }

    fn create_http3_connection(&mut self) -> Result<()> {
        ensure!(self.h3_conn.is_none(), "HTTP/3 connection is already created");

        let config = quiche::h3::Config::new()?;
        let conn = quiche::h3::Connection::with_transport(&mut self.conn, &config)?;
        self.h3_conn = Some(conn);
        Ok(())
    }

    // Processes HTTP/3 request and returns the wire format DNS query or an empty vector.
    fn handle_http3_request(&mut self) -> Result<Vec<u8>> {
        ensure!(self.h3_conn.is_some(), "HTTP/3 connection not created");

        let h3_conn = self.h3_conn.as_mut().unwrap();
        let mut ret = vec![];

        loop {
            match h3_conn.poll(&mut self.conn) {
                Ok((stream_id, quiche::h3::Event::Headers { list, has_body })) => {
                    info!(
                        "Processing HTTP/3 Headers {:?} on stream id {} has_body {}",
                        list, stream_id, has_body
                    );

                    // Find ":path" field to get the query.
                    if let Some(target) = list.iter().find(|e| {
                        e.name() == b":path" && e.value().starts_with(URL_PATH_PREFIX.as_bytes())
                    }) {
                        let b64url_query = &target.value()[URL_PATH_PREFIX.len()..];
                        let decoded = base64::decode_config(b64url_query, base64::URL_SAFE_NO_PAD)?;
                        self.in_flight_queries.insert([decoded[0], decoded[1]], stream_id);
                        ret = decoded;
                    }
                }
                Ok((stream_id, quiche::h3::Event::Data)) => {
                    warn!("Received unexpected HTTP/3 data");
                    let mut buf = [0; 65535];
                    if let Ok(read) = h3_conn.recv_body(&mut self.conn, stream_id, &mut buf) {
                        warn!("Got {} bytes of response data on stream {}", read, stream_id);
                    }
                }
                Ok(n) => {
                    debug!("Got event {:?}", n);
                }
                Err(quiche::h3::Error::Done) => {
                    debug!("quiche::h3::Error::Done");
                    break;
                }
                Err(e) => bail!("HTTP/3 processing failed: {:?}", e),
            }
        }

        Ok(ret)
    }

    // Converts the clear-text DNS response to a DoH response, and sends it to the quiche.
    pub fn handle_backend_message(&mut self, response: &[u8]) -> Result<()> {
        ensure!(self.h3_conn.is_some(), "HTTP/3 connection not created");
        ensure!(response.len() >= DNS_HEADER_SIZE, "Insufficient bytes of DNS response");

        let len = response.len();
        let headers = vec![
            quiche::h3::Header::new(b":status", b"200"),
            quiche::h3::Header::new(b"content-type", b"application/dns-message"),
            quiche::h3::Header::new(b"content-length", &len.to_string().as_bytes()),
            // TODO: need to add cache-control?
        ];

        let h3_conn = self.h3_conn.as_mut().unwrap();
        let query_id = u16::from_be_bytes([response[0], response[1]]);
        let stream_id = self
            .in_flight_queries
            .remove(&[response[0], response[1]])
            .ok_or_else(|| anyhow!("query_id {:x} not found", query_id))?;

        info!("Preparing HTTP/3 response {:?} on stream {}", headers, stream_id);

        h3_conn.send_response(&mut self.conn, stream_id, &headers, false)?;
        h3_conn.send_body(&mut self.conn, stream_id, response, true)?;

        Ok(())
    }

    // Returns the data the client wants to send.
    pub fn flush_egress(&mut self) -> Result<Vec<u8>> {
        let mut ret = vec![];
        let mut buf = [0; MAX_UDP_PAYLOAD_SIZE];
        loop {
            let (write, _) = match self.conn.send(&mut buf) {
                Ok(v) => v,
                Err(quiche::Error::Done) => break,

                // Maybe close the connection?
                Err(e) => bail!(e),
            };
            ret.append(&mut buf[..write].to_vec());
        }

        Ok(ret)
    }

    // Processes the packet received from the frontend socket. If |data| is a DoH query,
    // the function returns the wire format DNS query; otherwise, it returns empty vector.
    pub fn handle_frontend_message(&mut self, data: &mut [u8]) -> Result<Vec<u8>> {
        let recv_info = quiche::RecvInfo { from: self.addr };
        self.conn.recv(data, recv_info)?;

        if (self.conn.is_in_early_data() || self.conn.is_established()) && self.h3_conn.is_none() {
            // Create a HTTP3 connection as soon as the QUIC connection is established.
            self.create_http3_connection()?;
            info!("HTTP/3 connection created");
        }

        if self.h3_conn.is_some() {
            return self.handle_http3_request();
        }

        Ok(vec![])
    }

    pub fn is_waiting_for_query(&self, query_id: &[u8; 2]) -> bool {
        self.in_flight_queries.contains_key(query_id)
    }

    pub fn addr(&self) -> SocketAddr {
        self.addr
    }

    pub fn connection_id(&self) -> &ConnectionID {
        self.id.as_ref()
    }

    pub fn timeout(&self) -> Option<Duration> {
        self.conn.timeout()
    }

    pub fn on_timeout(&mut self) {
        self.conn.on_timeout();
    }
}

impl std::fmt::Debug for Client {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        f.debug_struct("Client")
            .field("addr", &self.addr())
            .field("conn_id", &self.conn.trace_id())
            .finish()
    }
}

pub struct ClientMap {
    clients: HashMap<ConnectionID, Client>,
    conn_id_seed: hmac::Key,
    config: quiche::Config,
}

impl ClientMap {
    pub fn new(config: quiche::Config) -> Result<ClientMap> {
        let conn_id_seed = match hmac::Key::generate(hmac::HMAC_SHA256, &SystemRandom::new()) {
            Ok(v) => v,
            Err(e) => bail!("Failed to generate a seed: {}", e),
        };

        Ok(ClientMap { clients: HashMap::new(), conn_id_seed, config })
    }

    pub fn get_or_create(
        &mut self,
        hdr: &quiche::Header,
        addr: &SocketAddr,
    ) -> Result<&mut Client> {
        let dcid = hdr.dcid.as_ref().to_vec();
        let client = if !self.clients.contains_key(&dcid) {
            ensure!(hdr.ty == quiche::Type::Initial, "Packet is not Initial");
            ensure!(quiche::version_is_supported(hdr.version), "Protocol version not supported");

            let scid = generate_conn_id(&self.conn_id_seed, &dcid);
            let conn = quiche::accept(
                &quiche::ConnectionId::from_ref(&scid),
                None, /* odcid */
                *addr,
                &mut self.config,
            )?;
            let client = Client::new(conn, addr, scid.clone());

            info!("New client: {:?}", client);
            self.clients.insert(scid.clone(), client);
            self.clients.get_mut(&scid).unwrap()
        } else {
            self.clients.get_mut(&dcid).unwrap()
        };

        Ok(client)
    }

    pub fn get_mut(&mut self, id: &[u8]) -> Option<&mut Client> {
        self.clients.get_mut(&id.to_vec())
    }

    pub fn get_mut_iter(&mut self) -> hash_map::IterMut<ConnectionID, Client> {
        self.clients.iter_mut()
    }
}

fn generate_conn_id(conn_id_seed: &hmac::Key, dcid: &[u8]) -> ConnectionID {
    let conn_id = hmac::sign(conn_id_seed, dcid);
    let conn_id = &conn_id.as_ref()[..quiche::MAX_CONN_ID_LEN];
    conn_id.to_vec()
}
+30 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Runtime configuration for DohFrontend.

use std::default::Default;

#[derive(Debug, Default)]
pub struct Config {
    pub delay_queries: i32,
}

impl Config {
    pub fn new() -> Self {
        Default::default()
    }
}
Loading