bindizr-dns 0.1.0-beta.4

DNS transfer, NOTIFY, TSIG, and nsupdate support for bindizr
Documentation
use std::net::{IpAddr, SocketAddr};

use domain::base::iana::Rtype;
use tokio::net::{TcpStream, UdpSocket};

use crate::{
    log_info,
    service::zone::ZoneService,
    xfr::{catalog, error::XfrError, wire},
};

pub(crate) async fn handle_tcp_soa(
    stream: &mut TcpStream,
    client_addr: SocketAddr,
    query_data: &[u8],
) -> Result<(), XfrError> {
    let client_ip = client_addr.ip();

    let response = match build_soa_response(query_data, client_ip).await {
        Ok(response) => response,
        Err(XfrError::ZoneNotFound(_)) => {
            let (zone_name, qtype, _client_serial, query_id) = wire::parse_query(query_data)?;
            wire::build_error_response(query_id, &zone_name, qtype, wire::RCODE_NOTAUTH)
        }
        Err(err) => return Err(err),
    };
    wire::write_tcp_message(stream, &response).await?;

    Ok(())
}

pub(crate) async fn handle_udp_soa(
    socket: &UdpSocket,
    client_addr: SocketAddr,
    query_data: &[u8],
) -> Result<(), XfrError> {
    let client_ip = client_addr.ip();

    let response = match build_soa_response(query_data, client_ip).await {
        Ok(response) => response,
        Err(XfrError::ZoneNotFound(_)) => {
            let (zone_name, qtype, _client_serial, query_id) = wire::parse_query(query_data)?;
            wire::build_error_response(query_id, &zone_name, qtype, wire::RCODE_NOTAUTH)
        }
        Err(err) => return Err(err),
    };
    socket.send_to(&response, client_addr).await?;

    Ok(())
}

async fn build_soa_response(query_data: &[u8], client_ip: IpAddr) -> Result<Vec<u8>, XfrError> {
    let (zone_name, qtype, _client_serial, query_id) = wire::parse_query(query_data)?;
    if qtype != Rtype::SOA {
        return Err(XfrError::InvalidQuery(format!(
            "Expected SOA query, got {:?}",
            qtype
        )));
    }

    log_info!(
        "SOA query for zone {:?} from {}",
        zone_name.to_string(),
        client_ip
    );

    let zone_name_owned = zone_name.to_string();
    let zone_name_str = zone_name_owned.trim_end_matches('.');

    if catalog::is_catalog_zone(zone_name_str) {
        log_info!("SOA query for catalog zone: {}", catalog::CATALOG_ZONE_NAME);
        let (catalog_zone, _member_zones) = catalog::generate_catalog_zone().await?;

        let mut builder = wire::DnsMessageBuilder::new(query_id, &zone_name, Rtype::SOA);
        builder.add_catalog_soa(&catalog_zone, catalog_zone.serial as u32)?;
        return Ok(builder.build());
    }

    let zone = ZoneService::find(zone_name_str)
        .await
        .map_err(|e| XfrError::DatabaseError(e.to_string()))?
        .ok_or_else(|| XfrError::ZoneNotFound(zone_name_str.to_string()))?;

    log_info!(
        "SOA response: zone {} serial={}",
        zone_name_str,
        zone.serial
    );

    let mut builder = wire::DnsMessageBuilder::new(query_id, &zone_name, Rtype::SOA);
    builder.add_soa(&zone, zone.serial as u32)?;

    log_info!("SOA response sent for zone {}", zone_name_str);

    Ok(builder.build())
}