Skip to main content

ding/
dns.rs

1use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
2use anyhow::{Result, bail};
3use core::{
4    mem::{self, MaybeUninit},
5    pin::Pin,
6    ptr,
7};
8
9use crate::{
10    dns::query::Query,
11    entropy::Entropy,
12    net::{IpAddr, Ipv4Addr, Ipv6Addr, Socket, SocketAddr},
13    runtime::Runtime,
14};
15
16mod private {
17    pub trait Sealed {}
18}
19
20macro_rules! impl_upstream {
21
22    ($($n:expr),*) => {
23        $(
24            impl private::Sealed for [SocketAddr; $n] {}
25            impl UpstreamSized for [SocketAddr; $n] {}
26        )*
27    };
28}
29
30impl_upstream!(1, 2, 3, 4, 5, 6, 7, 8);
31
32pub struct Upstream(usize, [MaybeUninit<SocketAddr>; 8]);
33
34impl Upstream {
35    fn get_addr(&self, alt: usize) -> Option<SocketAddr> {
36        if alt < self.0 {
37            Some(unsafe { self.1[alt].assume_init() })
38        } else {
39            None
40        }
41    }
42}
43
44pub trait UpstreamSized: private::Sealed {}
45
46impl Upstream {
47    const fn create<const N: usize>(addrs: [SocketAddr; N]) -> Upstream
48    where
49        [SocketAddr; N]: UpstreamSized,
50    {
51        let mut data = [MaybeUninit::uninit(); 8];
52        unsafe {
53            ptr::write(
54                (&mut data as *mut _ as *mut _),
55                (&addrs as *const _ as *const [MaybeUninit<SocketAddr>; N]).read(),
56            )
57        };
58        Self(N, data)
59    }
60}
61
62pub const GOOGLE: Upstream = Upstream::create([
63    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 53),
64    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 4, 4)), 53),
65    SocketAddr::new(
66        IpAddr::V6(Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888)),
67        53,
68    ),
69    SocketAddr::new(
70        IpAddr::V6(Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8844)),
71        53,
72    ),
73]);
74
75pub const CLOUDFLARE: Upstream = Upstream::create([
76    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 53),
77    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 0, 0, 1)), 53),
78    SocketAddr::new(
79        IpAddr::V6(Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1111)),
80        53,
81    ),
82    SocketAddr::new(
83        IpAddr::V6(Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1001)),
84        53,
85    ),
86]);
87
88#[cfg(not(any(target_os = "linux")))]
89compile_error!("unsupported platform, check back soon or make an issue/PR");
90
91pub fn platform_resolver<R: Runtime>() -> Result<Arc<dyn Resolver<R>>> {
92    let mut upstreams = Vec::new();
93    upstreams.push(GOOGLE);
94    upstreams.push(CLOUDFLARE);
95    let config = Config { upstreams };
96    #[cfg(target_os = "linux")]
97    Ok(Arc::new(linux::Resolver::<R>::create(config)?))
98}
99
100pub struct Config {
101    upstreams: Vec<Upstream>,
102}
103
104pub struct Request(Vec<u8>);
105
106impl Request {
107    pub fn build(entropy: &mut impl Entropy, query: Query) -> Request {
108        let mut packet = Vec::new();
109        packet.extend(entropy.entropy().unwrap().to_be_bytes()); // Transaction ID
110        packet.extend(&0x0100u16.to_be_bytes()); // Flags: standard query with recursion
111        packet.extend(&1u16.to_be_bytes()); // Questions: 1
112        packet.extend(&0u16.to_be_bytes()); // Answers: 0
113        packet.extend(&0u16.to_be_bytes()); // Authority: 0
114        packet.extend(&0u16.to_be_bytes()); // Additional: 0
115
116        // Question section
117        // Encode hostname
118        for label in query.domain.split('.') {
119            packet.push(label.len() as u8);
120            packet.extend(label.as_bytes());
121        }
122        packet.push(0); // End of hostname
123
124        packet.extend(&(query.ty as u16).to_be_bytes()); // Query type
125        packet.extend(&(Class::IN as u16).to_be_bytes()); // Query class
126
127        Request(packet)
128    }
129}
130
131#[cfg(target_os = "linux")]
132mod linux {
133    use core::{marker::PhantomData, pin::Pin};
134
135    use crate::{
136        dns::{Config, Parse, Query, Request, Response},
137        entropy::Time,
138        net::Socket,
139        runtime::Runtime,
140    };
141    use alloc::{
142        boxed::Box,
143        string::{String, ToString},
144        sync::Arc,
145        vec,
146    };
147    use anyhow::{Result, bail};
148    use fast::sync::{Mutex, Waiter};
149    use futures::{AsyncReadExt, AsyncWriteExt, FutureExt};
150
151    pub struct Resolver<R: Runtime>(Arc<Mutex<Inner>>, PhantomData<R>);
152
153    pub struct Inner {
154        socket: Pin<Box<dyn Socket + Send>>,
155    }
156
157    pub struct Lookup {}
158
159    impl Future for Lookup {
160        type Output = Result<Response>;
161
162        fn poll(
163            self: core::pin::Pin<&mut Self>,
164            cx: &mut core::task::Context<'_>,
165        ) -> core::task::Poll<Self::Output> {
166            todo!()
167        }
168    }
169
170    impl<R: Runtime> super::Resolver<R> for Resolver<R> {
171        fn create(config: Config) -> Result<Self>
172        where
173            Self: Sized,
174        {
175            let mut range = (0..).into_iter();
176            let mut alt = 0;
177            try {
178                loop {
179                    let Some(i) = range.next() else {
180                        unreachable!();
181                    };
182                    match R::Socket::connect(
183                        config.upstreams[i % config.upstreams.len()]
184                            .get_addr(alt)
185                            .ok_or(anyhow::Error::msg("all options exhausted"))?,
186                    ) {
187                        Ok(socket) => {
188                            break Self(Arc::new(Mutex::new(Inner { socket })), PhantomData);
189                        }
190                        _ => (),
191                    }
192                    if i == config.upstreams.len() - 1 {
193                        alt += 1;
194                    }
195                }
196            }
197        }
198
199        fn resolve(
200            &self,
201            query: Query,
202        ) -> core::pin::Pin<alloc::boxed::Box<dyn Future<Output = Result<Response>>>> {
203            let Self(mutex, _) = self;
204            let mutex = mutex.clone();
205            Box::pin(R::spawn(async move || {
206                let packet = Request::build(&mut Time, query);
207                let mut guard = mutex.lock(Box::pin(Waiter::default())).await;
208                guard.socket.write_all(&packet.0).await;
209                let mut buf = vec![0u8; 512];
210                let Ok(count) = guard.socket.read(&mut buf).await else {
211                    bail!("failed to read authority response");
212                };
213                buf.truncate(count);
214                Ok(Response::parse(&buf))
215            }))
216        }
217    }
218}
219
220pub use query::*;
221
222mod query {
223    use core::borrow::Borrow;
224
225    use alloc::{
226        borrow::ToOwned,
227        string::{String, ToString},
228        sync::Arc,
229    };
230
231    pub struct Query {
232        pub(crate) domain: String,
233        pub(crate) ty: Type,
234    }
235
236    #[repr(u16)]
237    pub enum Type {
238        A = 1,     // IPv4 address
239        NS = 2,    // Name server
240        CNAME = 5, // Canonical name
241        SOA = 6,   // Start of authority
242        PTR = 12,  // Pointer (for reverse DNS)
243        MX = 15,   // Mail exchange
244        TXT = 16,  // Text
245        AAAA = 28, // IPv6 address
246        SRV = 33,  // Service record
247        ANY = 255, // Any record type
248    }
249
250    #[repr(u16)]
251    pub enum Class {
252        IN = 1,
253    }
254
255    pub fn a(domain: &str) -> Query {
256        Query {
257            domain: domain.into(),
258            ty: Type::A,
259        }
260    }
261
262    pub fn aaaa(domain: &str) -> Query {
263        Query {
264            domain: domain.into(),
265            ty: Type::AAAA,
266        }
267    }
268
269    pub fn cname(domain: &str) -> Query {
270        Query {
271            domain: domain.into(),
272            ty: Type::CNAME,
273        }
274    }
275
276    pub fn mx(domain: &str) -> Query {
277        Query {
278            domain: domain.into(),
279            ty: Type::MX,
280        }
281    }
282
283    pub fn ns(domain: &str) -> Query {
284        Query {
285            domain: domain.into(),
286            ty: Type::NS,
287        }
288    }
289
290    pub fn ptr(domain: &str) -> Query {
291        Query {
292            domain: domain.into(),
293            ty: Type::PTR,
294        }
295    }
296
297    pub fn soa(domain: &str) -> Query {
298        Query {
299            domain: domain.into(),
300            ty: Type::SOA,
301        }
302    }
303
304    pub fn srv(domain: &str) -> Query {
305        Query {
306            domain: domain.into(),
307            ty: Type::SRV,
308        }
309    }
310
311    pub fn txt(domain: &str) -> Query {
312        Query {
313            domain: domain.into(),
314            ty: Type::TXT,
315        }
316    }
317}
318
319pub trait Resolver<R: Runtime> {
320    fn create(config: Config) -> Result<Self>
321    where
322        Self: Sized;
323    fn resolve(&self, query: Query) -> Pin<Box<dyn Future<Output = Result<Response>>>>;
324}
325
326pub trait Authority {
327    fn create<T: Socket>(config: Config, socket: impl Socket) -> Self
328    where
329        Self: Sized;
330    fn answer(&self, query: Query) -> Pin<Box<dyn Future<Output = Result<Response>>>>;
331}
332
333pub struct Response {
334    pub answers: Vec<Answer>,
335    pub authorities: Vec<Answer>,
336    pub additionals: Vec<Answer>,
337}
338
339impl Parse for Response {
340    fn parse(response: &[u8]) -> Result<Self>
341    where
342        Self: Sized,
343    {
344        if response.len() < 12 {
345            bail!("invalid response");
346        }
347
348        // Parse header
349        let flags = u16::from_be_bytes([response[2], response[3]]);
350        let rcode = flags & 0x000F;
351
352        if rcode == 3 {
353            bail!("name not found");
354        } else if rcode != 0 {
355            bail!("server failure");
356        }
357
358        let question_count = u16::from_be_bytes([response[4], response[5]]);
359        let answer_count = u16::from_be_bytes([response[6], response[7]]);
360        let authority_count = u16::from_be_bytes([response[8], response[9]]);
361        let additional_count = u16::from_be_bytes([response[10], response[11]]);
362
363        let mut offset = 12;
364
365        // Parse questions to determine what type of query this was
366        let mut query_type = None;
367        for _ in 0..question_count {
368            let (name_end, _name) = util::parse_name(response, offset)?;
369            offset = name_end;
370
371            if offset + 4 > response.len() {
372                bail!("invalid response");
373            }
374
375            let qtype = u16::from_be_bytes([response[offset], response[offset + 1]]);
376            let _qclass = u16::from_be_bytes([response[offset + 2], response[offset + 3]]);
377            offset += 4;
378
379            // Store the first question's type
380            if query_type.is_none() {
381                query_type = Some(qtype);
382            }
383        }
384
385        // Parse answers
386        let mut answers = Vec::new();
387        for _ in 0..answer_count {
388            if let Ok(answer) = parse_match_record(response, &mut offset) {
389                answers.push(answer);
390            }
391        }
392
393        // Parse authorities
394        let mut authorities = Vec::new();
395        for _ in 0..authority_count {
396            if let Ok(answer) = parse_match_record(response, &mut offset) {
397                authorities.push(answer);
398            }
399        }
400
401        // Parse additionals
402        let mut additionals = Vec::new();
403        for _ in 0..additional_count {
404            if let Ok(answer) = parse_match_record(response, &mut offset) {
405                additionals.push(answer);
406            }
407        }
408
409        Ok(Response {
410            answers,
411            authorities,
412            additionals,
413        })
414    }
415}
416
417fn parse_match_record(response: &[u8], offset: &mut usize) -> Result<Answer> {
418    let rtype = u16::from_be_bytes([response[*offset], response[*offset + 1]]);
419    *offset += 10;
420    Ok(match unsafe { mem::transmute::<_, Type>(rtype) } {
421        Type::A => Answer::A(Record::<Ipv4Addr>::parse(response)?),
422        Type::NS => Answer::AAAA(Record::<Ipv6Addr>::parse(response)?),
423        Type::CNAME => todo!(),
424        Type::SOA => todo!(),
425        Type::PTR => todo!(),
426        Type::MX => todo!(),
427        Type::TXT => todo!(),
428        Type::AAAA => todo!(),
429        Type::SRV => todo!(),
430        Type::ANY => todo!(),
431    })
432}
433
434pub enum Answer {
435    A(Record<Ipv4Addr>),
436    AAAA(Record<Ipv6Addr>),
437    CNAME(Record<String>),
438    MX(Record<(u16, String)>),
439    TXT(Record<Vec<String>>),
440    PTR(Record<String>),
441    NS(Record<String>),
442    SOA(Record<StartOfAuthority>),
443}
444
445pub struct StartOfAuthority {
446    master: String,
447    responsible: String,
448    serial: u32,
449    refresh: u32,
450    retry: u32,
451    expire: u32,
452    minimum: u32,
453}
454
455pub struct Record<T> {
456    name: String,
457    ttl: u32,
458    data: T,
459}
460
461mod util {
462    use alloc::string::String;
463    use anyhow::Result;
464    use anyhow::bail;
465
466    pub(crate) fn skip_name(data: &[u8], mut offset: usize) -> Result<usize> {
467        loop {
468            if offset >= data.len() {
469                bail!("invalid response");
470            }
471
472            let len = data[offset];
473
474            if len == 0 {
475                return Ok(offset + 1);
476            }
477
478            if (len & 0xC0) == 0xC0 {
479                return Ok(offset + 2);
480            }
481
482            offset += 1 + len as usize;
483        }
484    }
485
486    pub fn parse_name(data: &[u8], mut offset: usize) -> Result<(usize, String)> {
487        let mut name = String::new();
488        let mut jumps = 0;
489        let max_jumps = 5;
490        let original_offset = offset;
491        let mut jumped = false;
492
493        loop {
494            if jumps > max_jumps {
495                bail!("invalid response");
496            }
497
498            if offset >= data.len() {
499                bail!("invalid response");
500            }
501
502            let len = data[offset];
503
504            if len == 0 {
505                offset += 1;
506                break;
507            }
508
509            if (len & 0xC0) == 0xC0 {
510                if offset + 1 >= data.len() {
511                    bail!("invalid response");
512                }
513
514                if !jumped {
515                    jumped = true;
516                }
517
518                let ptr = (((len & 0x3F) as u16) << 8) | (data[offset + 1] as u16);
519                offset = ptr as usize;
520                jumps += 1;
521                continue;
522            }
523
524            offset += 1;
525
526            if offset + len as usize > data.len() {
527                bail!("invalid response");
528            }
529
530            if !name.is_empty() {
531                name.push('.');
532            }
533
534            let Ok(x) = core::str::from_utf8(&data[offset..offset + len as usize]) else {
535                bail!("failed to parse")
536            };
537
538            name.push_str(x);
539
540            offset += len as usize;
541        }
542
543        let final_offset = if jumped { original_offset + 2 } else { offset };
544
545        Ok((final_offset, name))
546    }
547}
548
549pub trait Parse {
550    fn parse(response: &[u8]) -> Result<Self>
551    where
552        Self: Sized;
553}
554
555impl Parse for Record<Ipv4Addr> {
556    fn parse(response: &[u8]) -> Result<Self> {
557        if response.len() < 12 {
558            bail!("invalid response");
559        }
560
561        // Parse header
562        let flags = u16::from_be_bytes([response[2], response[3]]);
563        let rcode = flags & 0x000F;
564
565        if rcode == 3 {
566            bail!("name not found");
567        } else if rcode != 0 {
568            bail!("server failure");
569        }
570
571        let question_count = u16::from_be_bytes([response[4], response[5]]);
572        let answer_count = u16::from_be_bytes([response[6], response[7]]);
573
574        if answer_count == 0 {
575            bail!("no answers");
576        }
577
578        // Skip questions
579        let mut offset = 12;
580        for _ in 0..question_count {
581            offset = util::skip_name(response, offset)?;
582            offset += 4; // skip type and class
583        }
584
585        // Parse first A record found
586        let mut found_name = String::new();
587        let mut found_ttl = 0u32;
588        let mut found_addr = None;
589
590        for _ in 0..answer_count {
591            let (name_end, name) = util::parse_name(response, offset)?;
592            offset = name_end;
593
594            if offset + 10 > response.len() {
595                bail!("invalid response");
596            }
597
598            let rtype = u16::from_be_bytes([response[offset], response[offset + 1]]);
599            let rclass = u16::from_be_bytes([response[offset + 2], response[offset + 3]]);
600            let ttl = u32::from_be_bytes([
601                response[offset + 4],
602                response[offset + 5],
603                response[offset + 6],
604                response[offset + 7],
605            ]);
606            let rdlength = u16::from_be_bytes([response[offset + 8], response[offset + 9]]);
607            offset += 10;
608
609            if offset + rdlength as usize > response.len() {
610                bail!("invalid response");
611            }
612
613            // If this is an A record, extract it
614            if rtype == Type::A as u16 && rclass == Class::IN as u16 && rdlength == 4 {
615                if found_addr.is_none() {
616                    found_name = name;
617                    found_ttl = ttl;
618                    found_addr = Some(Ipv4Addr::new(
619                        response[offset],
620                        response[offset + 1],
621                        response[offset + 2],
622                        response[offset + 3],
623                    ));
624                }
625            }
626
627            offset += rdlength as usize;
628        }
629
630        match found_addr {
631            Some(addr) => Ok(Record {
632                name: found_name,
633                ttl: found_ttl,
634                data: addr,
635            }),
636            None => bail!("no A records found"),
637        }
638    }
639}
640
641impl Parse for Record<Ipv6Addr> {
642    fn parse(response: &[u8]) -> Result<Self> {
643        if response.len() < 12 {
644            bail!("invalid response");
645        }
646
647        // Parse header
648        let flags = u16::from_be_bytes([response[2], response[3]]);
649        let rcode = flags & 0x000F;
650
651        if rcode == 3 {
652            bail!("name not found");
653        } else if rcode != 0 {
654            bail!("server failure");
655        }
656
657        let question_count = u16::from_be_bytes([response[4], response[5]]);
658        let answer_count = u16::from_be_bytes([response[6], response[7]]);
659
660        if answer_count == 0 {
661            bail!("no answers");
662        }
663
664        // Skip questions
665        let mut offset = 12;
666        for _ in 0..question_count {
667            offset = util::skip_name(response, offset)?;
668            offset += 4; // skip type and class
669        }
670
671        // Parse first AAAA record found
672        let mut found_name = String::new();
673        let mut found_ttl = 0u32;
674        let mut found_addr = None;
675
676        for _ in 0..answer_count {
677            let (name_end, name) = util::parse_name(response, offset)?;
678            offset = name_end;
679
680            if offset + 10 > response.len() {
681                bail!("invalid response");
682            }
683
684            let rtype = u16::from_be_bytes([response[offset], response[offset + 1]]);
685            let rclass = u16::from_be_bytes([response[offset + 2], response[offset + 3]]);
686            let ttl = u32::from_be_bytes([
687                response[offset + 4],
688                response[offset + 5],
689                response[offset + 6],
690                response[offset + 7],
691            ]);
692            let rdlength = u16::from_be_bytes([response[offset + 8], response[offset + 9]]);
693            offset += 10;
694
695            if offset + rdlength as usize > response.len() {
696                bail!("invalid response");
697            }
698
699            // If this is an AAAA record, extract it
700            if rtype == Type::AAAA as u16 && rclass == Class::IN as u16 && rdlength == 16 {
701                if found_addr.is_none() {
702                    found_name = name;
703                    found_ttl = ttl;
704
705                    // Parse IPv6 address (16 bytes)
706                    let mut octets = [0u8; 16];
707                    octets.copy_from_slice(&response[offset..offset + 16]);
708                    found_addr = Some(Ipv6Addr(unsafe { mem::transmute(octets) }));
709                }
710            }
711
712            offset += rdlength as usize;
713        }
714
715        match found_addr {
716            Some(addr) => Ok(Record {
717                name: found_name,
718                ttl: found_ttl,
719                data: addr,
720            }),
721            None => bail!("no AAAA records found"),
722        }
723    }
724}