eculocate

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 6cd2b9c11558a831389bd204cec470e345b0bef4
parent 104912e8a633bba00405f5d00e135923e73c4f77
Author: Daniel Barlow <dan@telent.net>
Date:   Wed, 24 Dec 2025 23:59:29 +0000

WIP answer SRV queries

Diffstat:
Mecu-esp32/src/wifi.rs | 188+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
1 file changed, 147 insertions(+), 41 deletions(-)

diff --git a/ecu-esp32/src/wifi.rs b/ecu-esp32/src/wifi.rs @@ -2,6 +2,8 @@ //! //! This gets an ip address via DHCP and advertises it with mdns +use alloc::vec::Vec; +use alloc::string::String; use embassy_executor::Spawner; use embassy_net::{ @@ -59,43 +61,136 @@ const MDNS_ENDPOINT: IpEndpoint = IpEndpoint::new(MDNS_IP, MDNS_PORT); const CACHE_FLUSH : u16 = 1<<15; +// dns_protocol's ResourceRecord doesn't make a good representation for +// our needs because it doesn't own its "data" attribute - it's just a +// reference. So we need something that can own the data + +struct DnsResource { + name : String, + ty : ResourceType, + // class is always IN, ttl depends on the query + data : RData, + data_raw : Vec<u8> +} + +enum RData { + Raw(Vec<u8>), + Srv { target: String, port: u16, priority: u16, weight: u16 } +} + +fn rdata_to_bytes(rdata : &RData, out: &mut [u8]) { + match rdata { + RData::Raw(b) => out[..b.len()].clone_from_slice(&b), + RData::Srv { target, port, priority, weight } => + { + let mut b : [u8; 80] = [0; 80]; + out[0] = (priority >> 8) as u8; + out[1] = (priority & 0xff) as u8; + out[2] = (weight >> 8) as u8; + out[3] = (weight & 0xff) as u8; + out[4] = (port >> 8) as u8; + out[5] = (port & 0xff) as u8; + + let label = ecu_portable::ptr_data(&[&target[..]], &mut b); + for (i, l) in label.iter().enumerate() { + out[6+i] = *l; + } + } + } +} + + +impl DnsResource { + fn new( + name: impl Into<String>, + ty : ResourceType, + data: RData + ) -> Self { + let mut rdata_raw : [u8; 80] = [0; 80]; + rdata_to_bytes(&data, &mut rdata_raw); + Self { + name: name.into(), + ty, + data, + data_raw: Vec::from(rdata_raw) + } + } + + fn to_resource_record(&self) -> ResourceRecord<'_> { + ResourceRecord::new( + &self.name[..], + self.ty, + 1, + 120, + &self.data_raw[..] + ) + } + + fn all(my_ip_address : &[u8]) -> Vec<Self> { + let static_records = [ + + // ResourceRecord::new( + // "_keihin._services._dns-sd._udp.local", + // ResourceType::Ptr, + // 1, + // 120, + // Label::from("Motorbike._keihin._udp.local").serialize() + // ) + Self::new( + "eculogical.local", + ResourceType::A, + RData::Raw(my_ip_address.try_into().unwrap()) + ), + Self::new( + "Bike._keihin._udp.local", + ResourceType::Srv, + RData::Srv { + target: String::from("eculogical.local"), port: 5000, + weight: 0, priority: 0 + } + ), + Self::new( + "_services._dns-sd._udp.local", + ResourceType::Ptr, + RData::Raw(alloc_ptr_data(&["_keihin", "_udp", "local"])) + ) + ]; + Vec::from(static_records) + } + + fn find<'a>(name: Label, ty: ResourceType, resources: &'a Vec<Self>) -> Vec<&'a Self> { + resources.into_iter().filter( + |r| (ty == ResourceType::Wildcard || r.ty == ty) && + name.eq(&Label::from(&(r.name[..]))) + ).collect() + } +} + + +fn alloc_ptr_data(name: &[&str]) -> Vec<u8> { + let mut b : [u8; 80] = [0; 80]; + Vec::from(ecu_portable::ptr_data(name, &mut b)) +} + + // RFC: "Multicast DNS responses MUST NOT contain any questions in the // Question Section. ". However, responses to "legacy" queries (port != 5353) // should have a question section just like traditional DNS -async fn send_advert(sock: &UdpSocket<'_>, - id: u16, - endpoint: IpEndpoint, - question: Option<&Question<'_>>, - my_address: &[u8]) { +async fn send_response(sock: &UdpSocket<'_>, + id: u16, + endpoint: IpEndpoint, + question: Option<&Question<'_>>, + answer: ResourceRecord<'_>) { let questions = match question { Some(q) =>&mut [*q][..], None => &mut [][..] }; - let mut buf : [u8; 32] = [0; 32]; - - info!("ptr data bytes: {:?}", - ecu_portable::ptr_data( - &["myname", "_keihin", "_udp"], - &mut buf) - ); - - // FIXME for legacy unicast response max ttl is 10s - - let mut answer = [ - ResourceRecord::new( - "eculogical.local", - ResourceType::A, - 1 | CACHE_FLUSH, - 120, // ttl - &my_address - ) - ]; - let mut flags = Flags::new(); + let mut answers = [answer]; flags.set_qr(MessageType::Reply); flags.set_authoritative(true); @@ -103,7 +198,7 @@ async fn send_advert(sock: &UdpSocket<'_>, id, flags, questions, - &mut answer, + &mut answers, &mut [], &mut []); @@ -124,11 +219,10 @@ async fn send_advert(sock: &UdpSocket<'_>, } async fn send_responses(sock: &UdpSocket<'_>, - my_address: &[u8], + resources: &Vec<DnsResource>, message: &Message<'_,'_>, meta: &UdpMetadata) { - let my_hostname = Label::from("eculogical.local"); let q_id = message.id(); // for each relevant question we want to add RRs (as answers @@ -140,17 +234,27 @@ async fn send_responses(sock: &UdpSocket<'_>, q.name(), q.ty(), q.class(), message.flags().raw(), meta.endpoint); - if q.name().eq(&my_hostname) { - if meta.endpoint.port == 5353 { - send_advert(&sock, q_id, MDNS_ENDPOINT, None, &my_address).await; - } else if (q.class() & 0x8000) != 0 { - log::info!("unicast"); - send_advert(&sock, q_id, meta.endpoint, None, &my_address).await; - } else { - // legacy unicast, send questions - send_advert(&sock, q_id, meta.endpoint, Some(q), &my_address).await; - } - log::info!("IT ME"); + + let relevant = DnsResource::find(q.name(), q.ty(), resources); + for r in relevant { + log::info!("can send {:?} {:?}", + r.name, r.ty); + send_response(&sock, q_id, MDNS_ENDPOINT, None, + r.to_resource_record()).await + } + + + if q.name().eq(&Label::from("&my_hostname")) { + // if meta.endpoint.port == 5353 { + // send_advert(&sock, q_id, MDNS_ENDPOINT, None, &my_address).await; + // } else if (q.class() & 0x8000) != 0 { + // log::info!("unicast"); + // send_advert(&sock, q_id, meta.endpoint, None, &my_address).await; + // } else { + // // legacy unicast, send questions + // send_advert(&sock, q_id, meta.endpoint, Some(q), &my_address).await; + // } + // log::info!("IT ME"); } } } @@ -221,10 +325,12 @@ pub async fn start_wifi(spawner: &Spawner, ); - send_advert(&sock, rng.random() as u16, MDNS_ENDPOINT, None, &my_address).await; +// send_advert(&sock, rng.random() as u16, MDNS_ENDPOINT, None, &my_address).await; let mut data_buf = [0; 4096]; + let dns_resources = DnsResource::all(&my_address); + loop { if sock.may_recv() { let mut questions = [Question::default(); 16]; @@ -245,7 +351,7 @@ pub async fn start_wifi(spawner: &Spawner, match message { Result::Ok(message) => { - send_responses(&sock, &my_address, &message, &meta).await; + send_responses(&sock, &dns_resources, &message, &meta).await; }, Result::Err(err) => { log::error!("parsing dns message: {:?}", err);