commit 6cd2b9c11558a831389bd204cec470e345b0bef4
parent 104912e8a633bba00405f5d00e135923e73c4f77
Author: Daniel Barlow <dan@telent.net>
Date: Wed, 24 Dec 2025 23:59:29 +0000
WIP answer SRV queries
Diffstat:
| M | ecu-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);