mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
Gateways, domains, and new service interface (#3001)
* add support for inbound proxies * backend changes * fix file type * proxy -> tunnel, implement backend apis * wip start-tunneld * add domains and gateways, remove routers, fix docs links * dont show hidden actions * show and test dns * edit instead of chnage acme and change gateway * refactor: domains page * refactor: gateways page * domains and acme refactor * certificate authorities * refactor public/private gateways * fix fe types * domains mostly finished * refactor: add file control to form service * add ip util to sdk * domains api + migration * start service interface page, WIP * different options for clearnet domains * refactor: styles for interfaces page * minor * better placeholder for no addresses * start sorting addresses * best address logic * comments * fix unnecessary export * MVP of service interface page * domains preferred * fix: address comments * only translations left * wip: start-tunnel & fix build * forms for adding domain, rework things based on new ideas * fix: dns testing * public domain, max width, descriptions for dns * nix StartOS domains, implement public and private domains at interface scope * restart tor instead of reset * better icon for restart tor * dns * fix sort functions for public and private domains * with todos * update types * clean up tech debt, bump dependencies * revert to ts-rs v9 * fix all types * fix dns form * add missing translations * it builds * fix: comments (#3009) * fix: comments * undo default --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> * fix: refactor legacy components (#3010) * fix: comments * fix: refactor legacy components * remove default again --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> * more translations * wip * fix deadlock * coukd work * simple renaming * placeholder for empty service interfaces table * honor hidden form values * remove logs * reason instead of description * fix dns * misc fixes * implement toggling gateways for service interface * fix showing dns records * move status column in service list * remove unnecessary truthy check * refactor: refactor forms components and remove legacy Taiga UI package (#3012) * handle wh file uploads * wip: debugging tor * socks5 proxy working * refactor: fix multiple comments (#3013) * refactor: fix multiple comments * styling changes, add documentation to sidebar * translations for dns page * refactor: subtle colors * rearrange service page --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> * fix file_stream and remove non-terminating test * clean up logs * support for sccache * fix gha sccache * more marketplace translations * install wizard clarity * stub hostnameInfo in migration * fix address info after setup, fix styling on SI page, new 040 release notes * remove tor logs from os * misc fixes * reset tor still not functioning... * update ts * minor styling and wording * chore: some fixes (#3015) * fix gateway renames * different handling for public domains * styling fixes * whole navbar should not be clickable on service show page * timeout getState request * remove links from changelog * misc fixes from pairing * use custom name for gateway in more places * fix dns parsing * closes #3003 * closes #2999 * chore: some fixes (#3017) * small copy change * revert hardcoded error for testing * dont require port forward if gateway is public * use old wan ip when not available * fix .const hanging on undefined * fix test * fix doc test * fix renames * update deps * allow specifying dependency metadata directly * temporarily make dependencies not cliackable in marketplace listings * fix socks bind * fix test --------- Co-authored-by: Aiden McClelland <me@drbonez.dev> Co-authored-by: waterplea <alexander@inkin.ru>
This commit is contained in:
@@ -1,69 +1,332 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use models::PackageId;
|
||||
use hickory_client::client::Client;
|
||||
use hickory_client::proto::runtime::TokioRuntimeProvider;
|
||||
use hickory_client::proto::tcp::TcpClientStream;
|
||||
use hickory_client::proto::udp::UdpClientStream;
|
||||
use hickory_client::proto::xfer::{DnsExchangeBackground, DnsRequestOptions};
|
||||
use hickory_client::proto::DnsHandle;
|
||||
use hickory_server::authority::MessageResponseBuilder;
|
||||
use hickory_server::proto::op::{Header, ResponseCode};
|
||||
use hickory_server::proto::rr::{Name, Record, RecordType};
|
||||
use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
|
||||
use hickory_server::ServerFuture;
|
||||
use imbl::OrdMap;
|
||||
use imbl_value::InternedString;
|
||||
use itertools::Itertools;
|
||||
use models::{GatewayId, OptionExt, PackageId};
|
||||
use rpc_toolkit::{
|
||||
from_fn_async, from_fn_blocking, Context, HandlerArgs, HandlerExt, ParentHandler,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::net::{TcpListener, UdpSocket};
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::instrument;
|
||||
use trust_dns_server::authority::MessageResponseBuilder;
|
||||
use trust_dns_server::proto::op::{Header, ResponseCode};
|
||||
use trust_dns_server::proto::rr::{Name, Record, RecordType};
|
||||
use trust_dns_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
|
||||
use trust_dns_server::ServerFuture;
|
||||
|
||||
use crate::net::forward::START9_BRIDGE_IFACE;
|
||||
use crate::util::sync::Watch;
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::public::NetworkInterfaceInfo;
|
||||
use crate::db::model::Database;
|
||||
use crate::net::gateway::NetworkInterfaceWatcher;
|
||||
use crate::prelude::*;
|
||||
use crate::util::io::file_string_stream;
|
||||
use crate::util::serde::{display_serializable, HandlerExtSerde};
|
||||
use crate::util::sync::{SyncRwLock, Watch};
|
||||
|
||||
pub fn dns_api<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"query",
|
||||
from_fn_blocking(query_dns::<C>)
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|HandlerArgs { params, .. }, res| {
|
||||
if let Some(format) = params.format {
|
||||
return display_serializable(format, res);
|
||||
}
|
||||
|
||||
if let Some(ip) = res {
|
||||
println!("{}", ip)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.with_about("Test the DNS configuration for a domain"),
|
||||
)
|
||||
.subcommand(
|
||||
"set-static",
|
||||
from_fn_async(set_static_dns)
|
||||
.no_display()
|
||||
.with_about("Set static DNS servers"),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct QueryDnsParams {
|
||||
pub fqdn: InternedString,
|
||||
}
|
||||
|
||||
pub fn query_dns<C: Context>(
|
||||
_: C,
|
||||
QueryDnsParams { fqdn }: QueryDnsParams,
|
||||
) -> Result<Option<Ipv4Addr>, Error> {
|
||||
let hints = dns_lookup::AddrInfoHints {
|
||||
flags: 0,
|
||||
address: libc::AF_INET,
|
||||
socktype: 0,
|
||||
protocol: 0,
|
||||
};
|
||||
dns_lookup::getaddrinfo(Some(&*fqdn), None, Some(hints))
|
||||
.map(Some)
|
||||
.or_else(|e| {
|
||||
if matches!(
|
||||
e.kind(),
|
||||
dns_lookup::LookupErrorKind::NoName | dns_lookup::LookupErrorKind::NoData
|
||||
) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(std::io::Error::from(e))
|
||||
}
|
||||
})
|
||||
.with_kind(ErrorKind::Network)?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.find_map(|a| match a.map(|a| a.sockaddr.ip()) {
|
||||
Ok(IpAddr::V4(a)) => Some(Ok(a)),
|
||||
Err(e) => Some(Err(e)),
|
||||
_ => None,
|
||||
})
|
||||
.transpose()
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct SetStaticDnsParams {
|
||||
pub servers: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub async fn set_static_dns(
|
||||
ctx: RpcContext,
|
||||
SetStaticDnsParams { servers }: SetStaticDnsParams,
|
||||
) -> Result<(), Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_dns_mut()
|
||||
.as_static_servers_mut()
|
||||
.ser(
|
||||
&servers
|
||||
.map(|s| {
|
||||
s.into_iter()
|
||||
.map(|s| {
|
||||
s.parse::<SocketAddr>()
|
||||
.or_else(|_| s.parse::<IpAddr>().map(|a| (a, 53).into()))
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.transpose()?,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.result
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ResolveMap {
|
||||
private_domains: BTreeMap<InternedString, Weak<()>>,
|
||||
services: BTreeMap<Option<PackageId>, BTreeMap<Ipv4Addr, Weak<()>>>,
|
||||
}
|
||||
|
||||
pub struct DnsController {
|
||||
services: Weak<RwLock<BTreeMap<Option<PackageId>, BTreeMap<Ipv4Addr, Weak<()>>>>>,
|
||||
resolve: Weak<SyncRwLock<ResolveMap>>,
|
||||
#[allow(dead_code)]
|
||||
dns_server: NonDetachingJoinHandle<Result<(), Error>>,
|
||||
dns_server: NonDetachingJoinHandle<()>,
|
||||
}
|
||||
|
||||
struct DnsClient {
|
||||
client: Arc<SyncRwLock<Vec<(SocketAddr, hickory_client::client::Client)>>>,
|
||||
_thread: NonDetachingJoinHandle<()>,
|
||||
}
|
||||
impl DnsClient {
|
||||
pub fn new(db: TypedPatchDb<Database>) -> Self {
|
||||
let client = Arc::new(SyncRwLock::new(Vec::new()));
|
||||
Self {
|
||||
client: client.clone(),
|
||||
_thread: tokio::spawn(async move {
|
||||
loop {
|
||||
if let Err::<(), Error>(e) = async {
|
||||
let mut stream = file_string_stream("/run/systemd/resolve/resolv.conf")
|
||||
.filter_map(|a| futures::future::ready(a.transpose()))
|
||||
.boxed();
|
||||
let mut conf: String = stream
|
||||
.next()
|
||||
.await
|
||||
.or_not_found("/run/systemd/resolve/resolv.conf")??;
|
||||
let mut prev_nameservers = Vec::new();
|
||||
let mut bg = BTreeMap::<SocketAddr, BoxFuture<_>>::new();
|
||||
loop {
|
||||
let nameservers = conf
|
||||
.lines()
|
||||
.map(|l| l.trim())
|
||||
.filter_map(|l| l.strip_prefix("nameserver "))
|
||||
.skip(2)
|
||||
.map(|n| {
|
||||
n.parse::<SocketAddr>()
|
||||
.or_else(|_| n.parse::<IpAddr>().map(|a| (a, 53).into()))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let static_nameservers = db
|
||||
.mutate(|db| {
|
||||
let dns = db
|
||||
.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_dns_mut();
|
||||
dns.as_dhcp_servers_mut().ser(&nameservers)?;
|
||||
dns.as_static_servers().de()
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
let nameservers = static_nameservers.unwrap_or(nameservers);
|
||||
if nameservers != prev_nameservers {
|
||||
let mut existing: BTreeMap<_, _> =
|
||||
client.peek(|c| c.iter().cloned().collect());
|
||||
let mut new = Vec::with_capacity(nameservers.len());
|
||||
for addr in &nameservers {
|
||||
if let Some(existing) = existing.remove(addr) {
|
||||
new.push((*addr, existing));
|
||||
} else {
|
||||
let client = if let Ok((client, bg_thread)) =
|
||||
Client::connect(
|
||||
UdpClientStream::builder(
|
||||
*addr,
|
||||
TokioRuntimeProvider::new(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
bg.insert(*addr, bg_thread.boxed());
|
||||
client
|
||||
} else {
|
||||
let (stream, sender) = TcpClientStream::new(
|
||||
*addr,
|
||||
None,
|
||||
Some(Duration::from_secs(30)),
|
||||
TokioRuntimeProvider::new(),
|
||||
);
|
||||
let (client, bg_thread) =
|
||||
Client::new(stream, sender, None)
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
bg.insert(*addr, bg_thread.boxed());
|
||||
client
|
||||
};
|
||||
new.push((*addr, client));
|
||||
}
|
||||
}
|
||||
bg.retain(|n, _| nameservers.iter().any(|a| a == n));
|
||||
prev_nameservers = nameservers;
|
||||
client.replace(new);
|
||||
}
|
||||
tokio::select! {
|
||||
c = stream.next() => conf = c.or_not_found("/run/systemd/resolve/resolv.conf")??,
|
||||
_ = futures::future::join(
|
||||
futures::future::join_all(bg.values_mut()),
|
||||
futures::future::pending::<()>(),
|
||||
) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("{e}");
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
}
|
||||
})
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
fn lookup(
|
||||
&self,
|
||||
query: hickory_client::proto::op::Query,
|
||||
options: DnsRequestOptions,
|
||||
) -> Vec<hickory_client::proto::xfer::DnsExchangeSend> {
|
||||
self.client.peek(|c| {
|
||||
c.iter()
|
||||
.map(|(_, c)| c.lookup(query.clone(), options.clone()))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct Resolver {
|
||||
services: Arc<RwLock<BTreeMap<Option<PackageId>, BTreeMap<Ipv4Addr, Weak<()>>>>>,
|
||||
client: DnsClient,
|
||||
net_iface: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
||||
resolve: Arc<SyncRwLock<ResolveMap>>,
|
||||
}
|
||||
impl Resolver {
|
||||
async fn resolve(&self, name: &Name) -> Option<Vec<Ipv4Addr>> {
|
||||
match name.iter().next_back() {
|
||||
Some(b"embassy") | Some(b"startos") => {
|
||||
if let Some(pkg) = name.iter().rev().skip(1).next() {
|
||||
if let Some(ip) = self.services.read().await.get(&Some(
|
||||
std::str::from_utf8(pkg)
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.unwrap_or_default(),
|
||||
)) {
|
||||
fn resolve(&self, name: &Name, src: IpAddr) -> Option<Vec<IpAddr>> {
|
||||
self.resolve.peek(|r| {
|
||||
if r.private_domains
|
||||
.get(&*name.to_lowercase().to_ascii())
|
||||
.map_or(false, |d| d.strong_count() > 0)
|
||||
{
|
||||
if let Some(res) = self.net_iface.peek(|i| {
|
||||
i.values()
|
||||
.chain([NetworkInterfaceInfo::lxc_bridge().1])
|
||||
.flat_map(|i| i.ip_info.as_ref())
|
||||
.find(|i| i.subnets.iter().any(|s| s.contains(&src)))
|
||||
.map(|ip_info| {
|
||||
let mut res = ip_info.subnets.iter().collect::<Vec<_>>();
|
||||
res.sort_by_cached_key(|a| !a.contains(&src));
|
||||
res.into_iter().map(|s| s.addr()).collect()
|
||||
})
|
||||
}) {
|
||||
return Some(res);
|
||||
}
|
||||
}
|
||||
match name.iter().next_back() {
|
||||
Some(b"embassy") | Some(b"startos") => {
|
||||
if let Some(pkg) = name.iter().rev().skip(1).next() {
|
||||
if let Some(ip) = r.services.get(&Some(
|
||||
std::str::from_utf8(pkg)
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.unwrap_or_default(),
|
||||
)) {
|
||||
Some(
|
||||
ip.iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.map(|(ip, _)| (*ip).into())
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Some(ip) = r.services.get(&None) {
|
||||
Some(
|
||||
ip.iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.map(|(ip, _)| *ip)
|
||||
.map(|(ip, _)| (*ip).into())
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Some(ip) = self.services.read().await.get(&None) {
|
||||
Some(
|
||||
ip.iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.map(|(ip, _)| *ip)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,132 +337,215 @@ impl RequestHandler for Resolver {
|
||||
request: &Request,
|
||||
mut response_handle: R,
|
||||
) -> ResponseInfo {
|
||||
let query = request.request_info().query;
|
||||
if let Some(ip) = self.resolve(query.name().borrow()).await {
|
||||
match query.query_type() {
|
||||
RecordType::A => {
|
||||
response_handle
|
||||
.send_response(
|
||||
MessageResponseBuilder::from_message_request(&*request).build(
|
||||
Header::response_from_request(request.header()),
|
||||
&ip.into_iter()
|
||||
.map(|ip| {
|
||||
Record::from_rdata(
|
||||
request.request_info().query.name().to_owned().into(),
|
||||
0,
|
||||
trust_dns_server::proto::rr::RData::A(ip.into()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
),
|
||||
)
|
||||
.await
|
||||
match async {
|
||||
let req = request.request_info()?;
|
||||
let query = req.query;
|
||||
if let Some(ip) = self.resolve(query.name().borrow(), req.src.ip()) {
|
||||
match query.query_type() {
|
||||
RecordType::A => {
|
||||
let mut header = Header::response_from_request(request.header());
|
||||
header.set_recursion_available(true);
|
||||
response_handle
|
||||
.send_response(
|
||||
MessageResponseBuilder::from_message_request(&*request).build(
|
||||
header,
|
||||
&ip.into_iter()
|
||||
.filter_map(|a| {
|
||||
if let IpAddr::V4(a) = a {
|
||||
Some(a)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|ip| {
|
||||
Record::from_rdata(
|
||||
query.name().to_owned().into(),
|
||||
0,
|
||||
hickory_server::proto::rr::RData::A(ip.into()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
RecordType::AAAA => {
|
||||
let mut header = Header::response_from_request(request.header());
|
||||
header.set_recursion_available(true);
|
||||
response_handle
|
||||
.send_response(
|
||||
MessageResponseBuilder::from_message_request(&*request).build(
|
||||
header,
|
||||
&ip.into_iter()
|
||||
.filter_map(|a| {
|
||||
if let IpAddr::V6(a) = a {
|
||||
Some(a)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|ip| {
|
||||
Record::from_rdata(
|
||||
query.name().to_owned().into(),
|
||||
0,
|
||||
hickory_server::proto::rr::RData::AAAA(ip.into()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => {
|
||||
let mut header = Header::response_from_request(request.header());
|
||||
header.set_recursion_available(true);
|
||||
response_handle
|
||||
.send_response(
|
||||
MessageResponseBuilder::from_message_request(&*request).build(
|
||||
header.into(),
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let res = Header::response_from_request(request.header());
|
||||
response_handle
|
||||
.send_response(
|
||||
MessageResponseBuilder::from_message_request(&*request).build(
|
||||
res.into(),
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
),
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
let query = query.original().clone();
|
||||
let mut streams = self.client.lookup(query, DnsRequestOptions::default());
|
||||
let mut err = None;
|
||||
for stream in streams.iter_mut() {
|
||||
match tokio::time::timeout(Duration::from_secs(5), stream.next()).await {
|
||||
Ok(Some(Err(e))) => err = Some(e),
|
||||
Ok(Some(Ok(msg))) => {
|
||||
return response_handle
|
||||
.send_response(
|
||||
MessageResponseBuilder::from_message_request(&*request).build(
|
||||
Header::response_from_request(request.header()),
|
||||
msg.answers(),
|
||||
msg.name_servers(),
|
||||
&msg.soa().map(|s| s.to_owned().into_record_of_rdata()),
|
||||
msg.additionals(),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if let Some(e) = err {
|
||||
tracing::error!("{e}");
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
let mut header = Header::response_from_request(request.header());
|
||||
header.set_recursion_available(true);
|
||||
header.set_response_code(ResponseCode::ServFail);
|
||||
response_handle
|
||||
.send_response(
|
||||
MessageResponseBuilder::from_message_request(&*request).build(
|
||||
header,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
.await
|
||||
{
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::error!("{}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
let mut header = Header::response_from_request(request.header());
|
||||
header.set_recursion_available(true);
|
||||
header.set_response_code(ResponseCode::ServFail);
|
||||
response_handle
|
||||
.send_response(
|
||||
MessageResponseBuilder::from_message_request(&*request).build(
|
||||
header,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(header.into())
|
||||
}
|
||||
} else {
|
||||
let mut res = Header::response_from_request(request.header());
|
||||
res.set_response_code(ResponseCode::NXDomain);
|
||||
response_handle
|
||||
.send_response(
|
||||
MessageResponseBuilder::from_message_request(&*request).build(
|
||||
res.into(),
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::error!("{}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
let mut res = Header::response_from_request(request.header());
|
||||
res.set_response_code(ResponseCode::ServFail);
|
||||
res.into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsController {
|
||||
#[instrument(skip_all)]
|
||||
pub async fn init(mut lxcbr_status: Watch<bool>) -> Result<Self, Error> {
|
||||
let services = Arc::new(RwLock::new(BTreeMap::new()));
|
||||
pub async fn init(
|
||||
db: TypedPatchDb<Database>,
|
||||
watcher: &NetworkInterfaceWatcher,
|
||||
) -> Result<Self, Error> {
|
||||
let resolve = Arc::new(SyncRwLock::new(ResolveMap::default()));
|
||||
|
||||
let mut server = ServerFuture::new(Resolver {
|
||||
services: services.clone(),
|
||||
client: DnsClient::new(db),
|
||||
net_iface: watcher.subscribe(),
|
||||
resolve: resolve.clone(),
|
||||
});
|
||||
|
||||
let dns_server = tokio::spawn(async move {
|
||||
server.register_listener(
|
||||
TcpListener::bind((Ipv4Addr::LOCALHOST, 53))
|
||||
let dns_server = tokio::spawn(
|
||||
async move {
|
||||
server.register_listener(
|
||||
TcpListener::bind((Ipv6Addr::UNSPECIFIED, 53))
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?,
|
||||
Duration::from_secs(30),
|
||||
);
|
||||
server.register_socket(
|
||||
UdpSocket::bind((Ipv6Addr::UNSPECIFIED, 53))
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?,
|
||||
);
|
||||
|
||||
server
|
||||
.block_until_done()
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?,
|
||||
Duration::from_secs(30),
|
||||
);
|
||||
server.register_socket(
|
||||
UdpSocket::bind((Ipv4Addr::LOCALHOST, 53))
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?,
|
||||
);
|
||||
|
||||
lxcbr_status.wait_for(|a| *a).await;
|
||||
|
||||
Command::new("resolvectl")
|
||||
.arg("dns")
|
||||
.arg(START9_BRIDGE_IFACE)
|
||||
.arg("127.0.0.1")
|
||||
.invoke(ErrorKind::Network)
|
||||
.await?;
|
||||
Command::new("resolvectl")
|
||||
.arg("domain")
|
||||
.arg(START9_BRIDGE_IFACE)
|
||||
.arg("embassy")
|
||||
.invoke(ErrorKind::Network)
|
||||
.await?;
|
||||
|
||||
server
|
||||
.block_until_done()
|
||||
.await
|
||||
.map_err(|e| Error::new(e, ErrorKind::Network))
|
||||
})
|
||||
.with_kind(ErrorKind::Network)
|
||||
}
|
||||
.map(|r| {
|
||||
r.log_err();
|
||||
}),
|
||||
)
|
||||
.into();
|
||||
|
||||
Ok(Self {
|
||||
services: Arc::downgrade(&services),
|
||||
resolve: Arc::downgrade(&resolve),
|
||||
dns_server,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn add(&self, pkg_id: Option<PackageId>, ip: Ipv4Addr) -> Result<Arc<()>, Error> {
|
||||
if let Some(services) = Weak::upgrade(&self.services) {
|
||||
let mut writable = services.write().await;
|
||||
let mut ips = writable.remove(&pkg_id).unwrap_or_default();
|
||||
let rc = if let Some(rc) = Weak::upgrade(&ips.remove(&ip).unwrap_or_default()) {
|
||||
rc
|
||||
} else {
|
||||
Arc::new(())
|
||||
};
|
||||
ips.insert(ip, Arc::downgrade(&rc));
|
||||
writable.insert(pkg_id, ips);
|
||||
Ok(rc)
|
||||
pub fn add_service(&self, pkg_id: Option<PackageId>, ip: Ipv4Addr) -> Result<Arc<()>, Error> {
|
||||
if let Some(resolve) = Weak::upgrade(&self.resolve) {
|
||||
resolve.mutate(|writable| {
|
||||
let ips = writable.services.entry(pkg_id).or_default();
|
||||
let weak = ips.entry(ip).or_default();
|
||||
let rc = if let Some(rc) = Weak::upgrade(&*weak) {
|
||||
rc
|
||||
} else {
|
||||
let new = Arc::new(());
|
||||
*weak = Arc::downgrade(&new);
|
||||
new
|
||||
};
|
||||
Ok(rc)
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("DNS Server Thread has exited"),
|
||||
@@ -208,17 +554,65 @@ impl DnsController {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn gc(&self, pkg_id: Option<PackageId>, ip: Ipv4Addr) -> Result<(), Error> {
|
||||
if let Some(services) = Weak::upgrade(&self.services) {
|
||||
let mut writable = services.write().await;
|
||||
let mut ips = writable.remove(&pkg_id).unwrap_or_default();
|
||||
if let Some(rc) = Weak::upgrade(&ips.remove(&ip).unwrap_or_default()) {
|
||||
ips.insert(ip, Arc::downgrade(&rc));
|
||||
}
|
||||
if !ips.is_empty() {
|
||||
writable.insert(pkg_id, ips);
|
||||
}
|
||||
Ok(())
|
||||
pub fn gc_service(&self, pkg_id: Option<PackageId>, ip: Ipv4Addr) -> Result<(), Error> {
|
||||
if let Some(resolve) = Weak::upgrade(&self.resolve) {
|
||||
resolve.mutate(|writable| {
|
||||
let mut ips = writable.services.remove(&pkg_id).unwrap_or_default();
|
||||
if let Some(rc) = Weak::upgrade(&ips.remove(&ip).unwrap_or_default()) {
|
||||
ips.insert(ip, Arc::downgrade(&rc));
|
||||
}
|
||||
if !ips.is_empty() {
|
||||
writable.services.insert(pkg_id, ips);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("DNS Server Thread has exited"),
|
||||
crate::ErrorKind::Network,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_private_domain(&self, fqdn: InternedString) -> Result<Arc<()>, Error> {
|
||||
if let Some(resolve) = Weak::upgrade(&self.resolve) {
|
||||
resolve.mutate(|writable| {
|
||||
let weak = writable.private_domains.entry(fqdn).or_default();
|
||||
let rc = if let Some(rc) = Weak::upgrade(&*weak) {
|
||||
rc
|
||||
} else {
|
||||
let new = Arc::new(());
|
||||
*weak = Arc::downgrade(&new);
|
||||
new
|
||||
};
|
||||
Ok(rc)
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("DNS Server Thread has exited"),
|
||||
crate::ErrorKind::Network,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gc_private_domains<'a, BK: Ord + 'a>(
|
||||
&self,
|
||||
domains: impl IntoIterator<Item = &'a BK> + 'a,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
InternedString: Borrow<BK>,
|
||||
{
|
||||
if let Some(resolve) = Weak::upgrade(&self.resolve) {
|
||||
resolve.mutate(|writable| {
|
||||
for domain in domains {
|
||||
if let Some((k, v)) = writable.private_domains.remove_entry(domain) {
|
||||
if v.strong_count() > 0 {
|
||||
writable.private_domains.insert(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("DNS Server Thread has exited"),
|
||||
|
||||
Reference in New Issue
Block a user