1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
45use super::*;
6use anyhow::Context as _;
7use fidl_fuchsia_net_name::DnsServerWatcherMarker;
8use fuchsia_sync::Mutex;
9use openthread::ot::DnsUpstream;
10use openthread_sys::*;
11use std::collections::HashMap;
12use std::hash::{Hash, Hasher};
13use std::net::{Ipv6Addr, SocketAddr};
14use std::sync::Arc;
15use std::task::{Context, Poll, Waker};
1617const MAX_DNS_RESPONSE_SIZE: usize = 2048;
1819struct DnsUpstreamQueryRefWrapper(&'static ot::PlatDnsUpstreamQuery);
2021impl DnsUpstreamQueryRefWrapper {
22fn as_ptr(&self) -> *const ot::PlatDnsUpstreamQuery {
23 std::ptr::from_ref(self.0)
24 }
25}
2627impl PartialEq for DnsUpstreamQueryRefWrapper {
28fn eq(&self, other: &Self) -> bool {
29self.as_ptr().eq(&other.as_ptr())
30 }
31}
3233impl Eq for DnsUpstreamQueryRefWrapper {}
3435impl Hash for DnsUpstreamQueryRefWrapper {
36fn hash<H>(&self, state: &mut H)
37where
38H: Hasher,
39 {
40self.as_ptr().hash(state);
41 }
42}
4344struct Transaction {
45// This field is set but never accessed directly, so we need to silence this warning
46 // so that we can compile.
47#[allow(unused)]
48// The task that performs socket poll and forwards the DNS reply from socket.
49task: fasync::Task<Result<(), anyhow::Error>>,
50// Receive the DNS reply from the `task` which stores the corresponding sender.
51receiver: fmpsc::UnboundedReceiver<(DnsUpstreamQueryRefWrapper, Vec<u8>)>,
52}
5354struct LocalDnsServerList {
55// A local copy of the DNS server list
56dns_server_list: Vec<fidl_fuchsia_net_name::DnsServer_>,
57// This field is set but never accessed directly, so we need to silence this warning
58 // so that we can compile
59#[allow(unused)]
60// The task that awaits on the DNS server list change.
61task: fasync::Task<Result<(), anyhow::Error>>,
62// Receive the DNS server list from the `task` which stores the corresponding sender.
63receiver: fmpsc::UnboundedReceiver<Vec<fidl_fuchsia_net_name::DnsServer_>>,
64}
6566pub(crate) struct Resolver {
67// The Map that uses the `DnsUpstreamQueryRefWrapper` as key to quickly locate the Transaction
68transactions_map: Arc<Mutex<HashMap<DnsUpstreamQueryRefWrapper, Transaction>>>,
69// Maintains a local DNS record for immediately sending out the DNS upstream query.
70local_dns_record: RefCell<Option<LocalDnsServerList>>,
71 waker: Cell<Option<Waker>>,
72}
7374impl Resolver {
75pub fn new() -> Resolver {
76if let Ok(proxy) =
77 fuchsia_component::client::connect_to_protocol::<DnsServerWatcherMarker>()
78 {
79let (mut sender, receiver) = fmpsc::unbounded();
8081// Create a future that await for the latest DNS server list, and forward it to the
82 // corresponding receiver. The future is executed in the task in `LocalDnsServerList`.
83let dns_list_watcher_fut = async move {
84loop {
85let vec = proxy.watch_servers().await?;
86info!(tag = "resolver"; "getting latest DNS server list: {:?}", vec);
87if let Err(e) = sender.send(vec).await {
88warn!(
89 tag = "resolver";
90"error when sending out latest dns list to process_poll, {:?}", e
91 );
92 }
93 }
94 };
95 Resolver {
96 transactions_map: Default::default(),
97 waker: Default::default(),
98 local_dns_record: RefCell::new(Some(LocalDnsServerList {
99 dns_server_list: Vec::new(),
100 task: fuchsia_async::Task::spawn(dns_list_watcher_fut),
101 receiver,
102 })),
103 }
104 } else {
105warn!(
106 tag = "resolver";
107"failed to connect to `DnsServerWatcherMarker`, \
108 DNS upstream query will not be supported"
109);
110 Resolver {
111 transactions_map: Arc::new(Mutex::new(HashMap::new())),
112 waker: Cell::new(None),
113 local_dns_record: RefCell::new(None),
114 }
115 }
116 }
117118pub fn process_poll_resolver(&self, instance: &ot::Instance, cx: &mut Context<'_>) {
119// Update the waker so that we can later signal when we need to be polled again
120self.waker.replace(Some(cx.waker().clone()));
121122// Poll the DNS server list task
123if let Some(local_dns_record) = self.local_dns_record.borrow_mut().as_mut() {
124while let Poll::Ready(Some(dns_server_list)) =
125 local_dns_record.receiver.poll_next_unpin(cx)
126 {
127// DNS server watcher proxy returns the new DNS server list when something changed
128 // in netstack. The outdated list should be replaced by the new one.
129local_dns_record.dns_server_list = dns_server_list;
130 }
131 }
132133let mut remove_key_vec = Vec::new();
134// Poll the socket in each transaction. If a response is ready, forward it to the OpenThread
135 // and remove the corresponding transaction.
136for (_, transaction) in self.transactions_map.lock().iter_mut() {
137while let Poll::Ready(Some((context, message_vec))) =
138 transaction.receiver.poll_next_unpin(cx)
139 {
140if let Ok(mut message) =
141 ot::Message::udp_new(instance, None).context("cannot create UDP message")
142 {
143match message.append(&message_vec) {
144Ok(_) => {
145 instance.plat_dns_upstream_query_done(context.0, message);
146 }
147Err(e) => {
148warn!(tag = "resolver"; "failed to append to `ot::Message`: {}", e);
149 }
150 }
151 } else {
152warn!(
153 tag = "resolver";
154"failed to create `ot::Message`, drop the upstream DNS response"
155);
156 }
157 remove_key_vec.push(context);
158 }
159 }
160161// cancel the transaction
162for key in remove_key_vec {
163self.transactions_map.lock().remove(&key);
164 }
165 }
166167fn on_start_dns_upstream_query<'a>(
168&self,
169 _instance: &ot::Instance,
170 thread_context: &'static ot::PlatDnsUpstreamQuery,
171 dns_query: &ot::Message<'_>,
172 ) {
173let sockaddr = SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 53);
174let socket = match fuchsia_async::net::UdpSocket::bind(&sockaddr) {
175Ok(socket) => socket,
176Err(_) => {
177warn!(
178 tag = "resolver";
179"on_start_dns_upstream_query() failed to create UDP socket, ignoring the query"
180);
181return;
182 }
183 };
184185let query_bytes = dns_query.to_vec();
186187// Get the DNS server list, and send out the query to all the available DNS servers.
188if let Some(local_dns_record) = self.local_dns_record.borrow().as_ref() {
189for dns_server in &local_dns_record.dns_server_list {
190if let Some(address) = dns_server.address {
191match address {
192 fidl_fuchsia_net::SocketAddress::Ipv4(ipv4_sock_addr) => {
193let sock_addr = SocketAddr::new(
194 std::net::IpAddr::V4(std::net::Ipv4Addr::from(
195 ipv4_sock_addr.address.addr,
196 )),
197 ipv4_sock_addr.port,
198 );
199info!(
200 tag = "resolver";
201"sending DNS query to IPv4 server {}", sock_addr
202 );
203if let Some(Err(e)) =
204 socket.send_to(&query_bytes, sock_addr).now_or_never()
205 {
206warn!(
207 tag = "resolver";
208"Failed to send DNS query to IPv4 server {}: {}", sock_addr, e
209 );
210 }
211 }
212 fidl_fuchsia_net::SocketAddress::Ipv6(ipv6_sock_addr) => {
213let sock_addr = SocketAddr::new(
214 std::net::IpAddr::V6(std::net::Ipv6Addr::from(
215 ipv6_sock_addr.address.addr,
216 )),
217 ipv6_sock_addr.port,
218 );
219220info!(
221 tag = "resolver";
222"sending DNS query to IPv6 server {}", sock_addr
223 );
224if let Some(Err(e)) =
225 socket.send_to(&query_bytes, sock_addr).now_or_never()
226 {
227warn!(
228 tag = "resolver";
229"Failed to send DNS query to IPv6 server {}: {}", sock_addr, e
230 );
231 }
232 }
233 }
234 }
235 }
236237let (mut sender, receiver) = fmpsc::unbounded();
238239// Create a poll_fn for the socket that can be await on
240let receive_from_fut = futures::future::poll_fn(move |cx| {
241let mut buffer = [0u8; MAX_DNS_RESPONSE_SIZE];
242match socket.async_recv_from(&mut buffer, cx) {
243 Poll::Ready(Ok((len, sockaddr))) => {
244let message = buffer[..len].to_vec();
245 Poll::Ready(Ok((message, sockaddr)))
246 }
247 Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
248 Poll::Pending => Poll::Pending,
249 }
250 });
251252// Create a future that forward the DNS reply from socket to process_poll
253let fut = async move {
254let (message_vec, sockaddr) =
255 receive_from_fut.await.context("error receiving from dns upstream socket")?;
256257info!(
258 tag = "resolver";
259"Incoming {} bytes DNS response from {:?}",
260 message_vec.len(),
261 sockaddr
262 );
263if let Err(e) =
264 sender.send((DnsUpstreamQueryRefWrapper(thread_context), message_vec)).await
265{
266warn!(
267 tag = "resolver";
268"error when sending out dns upstream reply to process_poll, {:?}", e
269 );
270 }
271Ok(())
272 };
273274// Socket and the sender is owned by the task now
275let transaction = Transaction { task: fuchsia_async::Task::spawn(fut), receiver };
276277self.transactions_map
278 .lock()
279 .insert(DnsUpstreamQueryRefWrapper(thread_context), transaction);
280 } else {
281warn!(
282 tag = "resolver";
283"on_start_dns_upstream_query() failed to get local_dns_record, ignoring the query"
284);
285 }
286287// Trigger the waker so that our poll method gets called by the executor
288self.waker.replace(None).and_then(|waker| {
289 waker.wake();
290Some(())
291 });
292 }
293294// Cancel the pending query
295fn on_cancel_dns_upstream_query(
296&self,
297 _instance: &ot::Instance,
298 thread_context: &'static ot::PlatDnsUpstreamQuery,
299 ) {
300if let None =
301self.transactions_map.lock().remove(&DnsUpstreamQueryRefWrapper(thread_context))
302 {
303warn!(
304 tag = "resolver";
305"on_cancel_dns_upstream_query() target transaction not presented for remove, ignoring"
306);
307 }
308 }
309}
310311#[no_mangle]
312unsafe extern "C" fn otPlatDnsStartUpstreamQuery(
313 a_instance: *mut otInstance,
314 a_txn: *mut otPlatDnsUpstreamQuery,
315 a_query: *const otMessage,
316) {
317 Resolver::on_start_dns_upstream_query(
318&PlatformBacking::as_ref().resolver,
319// SAFETY: `instance` must be a pointer to a valid `otInstance`,
320 // which is guaranteed by the caller.
321ot::Instance::ref_from_ot_ptr(a_instance).unwrap(),
322// SAFETY: no dereference is happening in fuchsia platform side
323ot::PlatDnsUpstreamQuery::mut_from_ot_mut_ptr(a_txn).unwrap(),
324// SAFETY: caller ensures the dns query is valid
325ot::Message::ref_from_ot_ptr(a_query as *mut otMessage).unwrap(),
326 )
327}
328329#[no_mangle]
330unsafe extern "C" fn otPlatDnsCancelUpstreamQuery(
331 a_instance: *mut otInstance,
332 a_txn: *mut otPlatDnsUpstreamQuery,
333) {
334 Resolver::on_cancel_dns_upstream_query(
335&PlatformBacking::as_ref().resolver,
336// SAFETY: `instance` must be a pointer to a valid `otInstance`,
337 // which is guaranteed by the caller.
338ot::Instance::ref_from_ot_ptr(a_instance).unwrap(),
339// SAFETY: no dereference is happening in fuchsia platform side
340ot::PlatDnsUpstreamQuery::mut_from_ot_mut_ptr(a_txn).unwrap(),
341 )
342}