use std::{
io,
net::SocketAddr,
path::{Path, PathBuf},
sync::Arc,
};
use clap::Parser;
use ddns::{publishers::H3Publisher, resolvers::DHTTP_NAME_SERVICE};
use dquic::qbase::net::addr::EndpointAddr;
use h3x::dquic::{
Identity, Network, QuicEndpoint,
cert::handy::{ToCertificate, ToPrivateKey},
client::{ClientQuicConfig, ServerCertVerifierChoice},
resolver::{Publish, handy::SystemResolver},
};
use rustls::{RootCertStore, client::WebPkiServerVerifier};
use tracing::{Level, info};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Options {
#[arg(long, default_value_t = default_h3_base_url())]
base_url: String,
#[arg(long)]
server_ca: PathBuf,
#[arg(long)]
client_name: String,
#[arg(long)]
client_cert: PathBuf,
#[arg(long)]
client_key: PathBuf,
#[arg(long)]
host: String,
#[arg(long, value_delimiter = ',', num_args = 1..)]
addr: Vec<SocketAddr>,
}
fn default_h3_base_url() -> String {
format!("{}/", DHTTP_NAME_SERVICE.trim_end_matches('/'))
}
fn load_root_store_from_pem(path: &Path) -> io::Result<RootCertStore> {
let pem = std::fs::read(path)?;
let mut store = RootCertStore::empty();
let mut reader: &[u8] = pem.as_slice();
for cert in rustls_pemfile::certs(&mut reader) {
let cert = cert.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
store
.add(cert)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
}
Ok(store)
}
fn expand_tilde(path: &Path) -> io::Result<PathBuf> {
let path = path.to_str().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("Path is not valid UTF-8: {}", path.display()),
)
})?;
Ok(PathBuf::from(shellexpand::tilde(path).into_owned()))
}
#[tokio::main]
async fn main() -> io::Result<()> {
tracing_subscriber::fmt()
.with_max_level(Level::DEBUG)
.init();
let opt = Options::parse();
let server_ca = expand_tilde(&opt.server_ca)?;
let client_cert = expand_tilde(&opt.client_cert)?;
let client_key = expand_tilde(&opt.client_key)?;
let root_store = load_root_store_from_pem(&server_ca)?;
let cert_chain_pem = std::fs::read(&client_cert)?;
let private_key_pem = std::fs::read(&client_key)?;
let verifier = WebPkiServerVerifier::builder(Arc::new(root_store))
.build()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let identity = Arc::new(Identity {
name: opt.client_name.parse().unwrap(),
certs: Arc::new(cert_chain_pem.to_certificate()),
key: Arc::new(private_key_pem.to_private_key()),
ocsp: Arc::new(None),
});
let network = Network::builder().build();
let quic = QuicEndpoint::builder()
.network(network)
.identity(identity.clone())
.resolver(Arc::new(SystemResolver))
.client(ClientQuicConfig {
verifier: ServerCertVerifierChoice::WebPki(verifier),
..Default::default()
})
.build()
.await;
let h3_endpoint = h3x::dquic::H3Endpoint::new(quic);
let resolver = H3Publisher::new(opt.base_url.clone(), h3_endpoint)?;
info!(host = %opt.host, addrs = ?opt.addr, base_url = %opt.base_url, "publish.start");
for &addr in &opt.addr {
info!("creating endpoint for address: {}", addr);
let endpoint = EndpointAddr::direct(addr);
info!("publishing endpoint: {:?}", endpoint);
let mut endpoints = std::iter::once(endpoint);
resolver
.publish(&opt.host, &mut endpoints)
.await
.map_err(io::Error::other)?;
info!("successfully published endpoint for {}", addr);
}
info!("publish.ok");
Ok(())
}