eculocate

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

mdns.rs (14415B)


      1 // MDNS responder, mostly but not entirely standards-compliant
      2 
      3 // ** Probe and Announce at startup
      4 
      5 // at startup it does Probe and Announce for "all those resource
      6 // records that a Multicast DNS responder desires to be unique on the
      7 // local link".  "Probe" means send packets as QU (unicast response)
      8 // containing ANY requests for each name: 3 packets with 250ms between
      9 // them; "Announce" means send 2 <= n <= 8 packets one second apart,
     10 // containing all the records we know
     11 
     12 // ** additional data in responses
     13 
     14 //  * for SRV, we send all the A and AAAA it names
     15 
     16 //  * for PTR "Service Instance Enumeration or Selective Instance
     17 //    Enumeration (subtype)": i.e. when the record data names an
     18 //    instance of the service, we sent the SRV and TXT records for the
     19 //    instance _and_ all the A/AAAA named by the SRV
     20 
     21 // I haven't investigated what happens if the packet gets too
     22 // large. There may be bugs
     23 
     24 // ** don't answer queries if the answers in the query already include what we'd send
     25 
     26 // This is unimplemented for now.  mdns is so fricking noisy already I
     27 // don't know if this is worth the extra effort
     28 
     29 // ** set the CACHE_FLUSH bit on unique records
     30 
     31 // "all the records with that name, rrtype, and rrclass are conceptually
     32 // under the control or ownership of a single responder"
     33 
     34 // ** NSEC?
     35 
     36 // not done
     37 
     38 // ** unicast response requested (QU)
     39 
     40 // - top bit in the class field of a DNS question is the unicast-response bit
     41 // - send response unicast, unless the record has not been sent recently
     42 //   ("within one quarter of its TTL")
     43 // - also for queries received fom unicast
     44 //   - but verify the source is local lan
     45 //   - and the port is 5353 (otherwise see "legacy")
     46 
     47 //  [ mostly done; we don't check "has been sent recently" and as we
     48 //  didn't open a unicast socket then we can't receive queries from
     49 //  unicast ]
     50 
     51 
     52 // ** Legacy Unicast Responses
     53 
     54 // if source port != 5353
     55 
     56 // - respond via unicast to source
     57 // - query id and question
     58 // - don't set cache-flush bit
     59 // - ttl <= 10s
     60 
     61 // This is implemented but has not really been tested
     62 
     63 // ** response compression
     64 
     65 // Unimplemented
     66 
     67 
     68 use alloc::vec::Vec;
     69 use alloc::string::String;
     70 use alloc::collections::BTreeSet;
     71 use crate::alloc::string::ToString;
     72 
     73 use embassy_net::{
     74     udp::{UdpSocket, UdpMetadata, PacketMetadata},
     75     IpAddress,
     76     IpEndpoint,
     77     Stack
     78 };
     79 
     80 use embassy_time::{Timer, Duration, Instant};
     81 use esp_alloc as _;
     82 use esp_backtrace as _;
     83 
     84 use log::{info,error};
     85 
     86 pub const DISCOVERY_NAME : &str = "_services._dns-sd._udp.local";
     87 
     88 use dns_protocol::{Message, Question,
     89 		   MessageType,
     90 		   ResourceRecord, ResourceType, Flags};
     91 
     92 const MDNS_IP: IpAddress = IpAddress::v4(224, 0, 0, 251);
     93 const MDNS_PORT: u16 = 5353;
     94 const MDNS_ENDPOINT: IpEndpoint = IpEndpoint::new(MDNS_IP, MDNS_PORT);
     95 
     96 const CACHE_FLUSH : u16 = 1<<15;
     97 
     98 pub enum ResponderState {
     99     Probing(Instant, usize),
    100     ProbeFailed,
    101     Announcing(Instant, usize),
    102     Running
    103 }
    104 
    105 pub struct Responder<'a> {
    106     sock : &'a UdpSocket<'a>,
    107     database : Database,
    108     pub state : ResponderState,
    109 }
    110 
    111 impl<'a> Responder<'a> {
    112     pub fn new(sock: &'a UdpSocket<'a>,
    113 	       records: Vec<Resource>) -> Responder<'a> {
    114 
    115 	Self {
    116 	    sock: sock,
    117 	    database: Database(records),
    118 	    state: ResponderState::Probing(Instant::now(), 0)
    119 	}
    120     }
    121 
    122     async fn advance_state(&mut self) {
    123 	match self.state {
    124 	    ResponderState::Probing(next_when, number_sent) => {
    125 		let ts = Instant::now();
    126 		if next_when < ts {
    127 		    if number_sent >= 3 {
    128 			self.state = ResponderState::Announcing(ts, 0);
    129 		    } else {
    130 			self.send_probe().await;
    131 			self.state = ResponderState::Probing(ts + Duration::from_millis(250), number_sent + 1);
    132 		    }
    133 		}
    134 	    },
    135 	    ResponderState::Announcing(next_when, number_sent) => {
    136 		let ts = Instant::now();
    137 		if next_when < ts {
    138 		    if number_sent > 6 {
    139 			self.state = ResponderState::Running;
    140 		    } else {
    141 			self.advertise().await;
    142 			self.state = ResponderState::Announcing(ts + Duration::from_millis(1000), number_sent + 1);
    143 		    }
    144 		}
    145 	    },
    146 	    ResponderState::Running => (),
    147 	    ResponderState::ProbeFailed => (),
    148 	}
    149     }
    150 
    151     async fn send_message(&self, message: Message<'_, '_>,  endpoint: IpEndpoint) {
    152 	let mut data_buf = [0; 4096];
    153 	// Serialize the message into the buffer
    154 	assert!(message.space_needed() <= data_buf.len());
    155 
    156 	match message.write(&mut data_buf) {
    157 	    Result::Ok(len) => {
    158 		if let Result::Err(x) = self.sock.send_to(&data_buf[..len], endpoint).await {
    159 		    info!("sock.send failed: {:?}", x);
    160 		}
    161 	    },
    162 	    Result::Err(err) => {
    163 		error!("message.write {:?}", err);
    164 	    }
    165 	};
    166     }
    167 
    168     async fn send_probe(&self) {
    169 	info!("sending probe at {}", Instant::now().as_millis());
    170 
    171 	let db = &self.database;
    172 
    173 	let mut questions : Vec<Question> = db.advertised().iter().filter_map( |record| {
    174 	    // FIXME this sends duplicates because there's both an SRV
    175 	    // and a TXT for the same name
    176 	    if record.is_unique() {
    177 		Some(
    178 		    Question::new(
    179 			&record.name[..],
    180 			ResourceType::Wildcard,
    181 			1 | (1<<15)	// QU
    182 		    ))
    183 	    } else {
    184 		None
    185 	    }
    186 	}).collect();
    187 
    188 	let message = Message::new(
    189 	    0, Flags::standard_query(), &mut questions, &mut [], &mut [], &mut []
    190 	);
    191 
    192 	self.send_message(message, MDNS_ENDPOINT).await;
    193     }
    194 
    195     async fn advertise(&self) {
    196 	info!("advertise at {}", Instant::now().as_millis());
    197 	self.send_response(0, MDNS_ENDPOINT, None, self.database.advertised()).await;
    198     }
    199 
    200     pub async fn may_recv(&mut self) -> bool {
    201 	self.advance_state().await;
    202 	match self.state {
    203 	    ResponderState::ProbeFailed => false,
    204 	    _ => self.sock.may_recv()
    205 	}
    206     }
    207 
    208     async fn send_response(&self,
    209 			   id: u16,
    210 			   endpoint: IpEndpoint,
    211 			   question: Option<&Question<'_>>,
    212 			   answers: Vec<&Resource>) {
    213 
    214 	let legacy = question != None;
    215 
    216 	let db = &self.database;
    217 	let mut flags = Flags::new();
    218 
    219 	flags.set_qr(MessageType::Reply);
    220 	flags.set_authoritative(true);
    221 
    222 	let mut additional : BTreeSet<ResourceRecord> = BTreeSet::new();
    223 	let mut answers : Vec<ResourceRecord> = {
    224 	    let mut rrs = Vec::new();
    225 	    for a in answers {
    226 		rrs.push(a.to_resource_record());
    227 		for r in db.all_additional(a).into_iter() {
    228 		    additional.insert(r.to_resource_record());
    229 		};
    230 	    }
    231 	    rrs
    232 	};
    233 
    234 	// don't send anything in "additional" that's already in the answers
    235 	for rr in &answers {
    236 	    additional.remove(&rr);
    237 	}
    238 
    239 	let mut additional : Vec<ResourceRecord> = additional.into_iter().collect();
    240 
    241 	let questions = match question {
    242 	    Some(q) =>&mut [*q][..],
    243 	    None => &mut [][..]
    244 	};
    245 
    246 	if legacy {
    247 	    // force class = IN (no cache-flush) and ttl to 10 seconds
    248 	    answers = answers.iter().map(|rr| {
    249 		ResourceRecord::new(rr.name(), rr.ty(), 1, 10, rr.data())
    250 	    }).collect();
    251 	    additional = additional.iter().map(|rr| {
    252 		ResourceRecord::new(rr.name(), rr.ty(), 1, 10, rr.data())
    253 	    }).collect();
    254 	}
    255 
    256 	let message = Message::new(
    257 	    id,
    258 	    flags,
    259 	    questions,
    260 	    &mut answers,
    261 	    &mut [],
    262 	    &mut additional);
    263 
    264 	self.send_message(message, endpoint).await;
    265     }
    266 
    267     async fn process_received_message(&mut self,
    268 			     message: &Message<'_,'_>,
    269 			     meta: &UdpMetadata) {
    270 
    271 	match self.state {
    272 	    ResponderState::Probing(_,_) => {
    273 		for rr in message.answers() {
    274 		    info!("received answer {:?}", rr.name());
    275 		    if self.database.find(rr.name().to_string(), ResourceType::Wildcard)
    276 			.iter().any(|record| record.is_unique()) {
    277 			    info!("probe failed, received answer for {:?}", rr.name());
    278 			    self.state = ResponderState::ProbeFailed;
    279 			}
    280 		}
    281 	    },
    282 	    ResponderState::ProbeFailed => (),
    283 	    _ => {
    284 		let q_id = message.id();
    285 
    286 		for q in message.questions() {
    287 		    let relevant = self.database.find(q.name().to_string(), q.ty());
    288 		    if relevant.len() > 0 {
    289 			log::info!("question {:?} {:?} {:?} flags {:?} from {:?}",
    290 				   q.name(), q.ty(), q.class(),
    291 				   message.flags().raw(),
    292 				   meta.endpoint);
    293 
    294 			let unicast = (q.class() & (1<<15)) != 0;
    295 			let legacy = meta.endpoint.port != 5353;
    296 			if legacy {
    297 			    self.send_response(q_id, meta.endpoint, Some(q), relevant).await
    298 			} else if unicast {
    299 			    self.send_response(0, meta.endpoint, None, relevant).await
    300 			} else {
    301 			    self.send_response(0, MDNS_ENDPOINT, None, relevant).await
    302 			}
    303 		    }
    304 		}
    305 	    }
    306 	}
    307     }
    308 
    309     pub async fn process_packet(&mut self) {
    310 	let mut questions = [Question::default(); 64];
    311 	let mut answers = [ResourceRecord::default(); 64];
    312 	let mut authorities = [ResourceRecord::default(); 64];
    313 	let mut addl = [ResourceRecord::default(); 64];
    314 
    315 	let mut data_buf = [0; 4096];
    316 
    317         let res = self.sock.recv_from(&mut data_buf).await;
    318         if let Ok((n, meta)) = res {
    319 	    let message = Message::read(
    320 		&data_buf[..n],
    321 		&mut questions,
    322 		&mut answers,
    323 		&mut authorities,
    324 		&mut addl,
    325 	    );
    326 
    327 	    match message {
    328 		Result::Ok(message) => {
    329 		    self.process_received_message(&message, &meta).await;
    330 		},
    331 		Result::Err(err) => {
    332 		    log::error!("parsing dns message from {:?} : {:?}",
    333 				meta.endpoint,
    334 				err);
    335 		}
    336 	    }
    337         }
    338     }
    339 }
    340 
    341 
    342 // dns_protocol's ResourceRecord doesn't make a good representation for
    343 // our needs because it doesn't own its "data" attribute - it's just a
    344 // reference.  So we need structures that can own the data
    345 
    346 #[derive(Clone,Debug)]
    347 pub enum RData {
    348     Srv { target: String, port: u16, priority: u16, weight: u16 },
    349     Ptr(String),
    350     A([u8; 4]),
    351     Txt(Vec<String>)
    352 }
    353 
    354 impl RData {
    355     fn copy_string<'a>(s: &str, out: &'a mut [u8], offset: usize) -> usize {
    356 	let end = s.len() + offset + 1;
    357 	out[offset] = (s.len() & 0xff) as u8;
    358 	let _ = &out[(offset+1) .. end].clone_from_slice(s.as_bytes());
    359 	end
    360     }
    361 
    362     fn encode_as_name<'a>(name: &str, out: &'a mut [u8], offset: usize) -> &'a [u8] {
    363 	let mut offset = offset;
    364 
    365 	for l in name.split('.') {
    366 	    offset = Self::copy_string(l, out, offset);
    367 	}
    368 	out[offset] = 0;
    369 	&out[..offset+1]
    370     }
    371 
    372     fn encode_as_strings<'a>(strings: &[String], out: &'a mut [u8], offset: usize) -> &'a [u8] {
    373 	let mut offset = offset;
    374 
    375 	for l in strings.into_iter() {
    376 	    offset = Self::copy_string(l, out, offset);
    377 	}
    378 	out[offset] = 0;
    379 	&out[..offset+1]
    380     }
    381 
    382     fn ty(&self) -> ResourceType {
    383 	match self {
    384 	    RData::Srv { .. } => ResourceType::Srv,
    385 	    RData::Ptr(_name) => ResourceType::Ptr,
    386 	    RData::A(_octets) => ResourceType::A,
    387 	    RData::Txt(_strings) => ResourceType::Txt
    388 	}
    389     }
    390 
    391     fn to_bytes<'a>(&self, out: &'a mut [u8]) -> &'a [u8]  {
    392 	match self {
    393 	    RData::Srv { target, port, priority, weight } => {
    394 		out[0] = (priority >> 8) as u8;
    395 		out[1] = (priority & 0xff) as u8;
    396 		out[2] = (weight >> 8) as u8;
    397 		out[3] = (weight & 0xff) as u8;
    398 		out[4] = (port >> 8) as u8;
    399 		out[5] = (port & 0xff) as u8;
    400 		Self::encode_as_name(target, out, 6)
    401 	    },
    402 	    RData::Ptr(name) => {
    403 		Self::encode_as_name(name, out, 0)
    404 	    }
    405 	    RData::A(octets) => {
    406 		out[..4].clone_from_slice(octets);
    407 		&out[..4]
    408 	    },
    409 	    RData::Txt(strings) => {
    410 		Self::encode_as_strings(strings, out, 0)
    411 	    }
    412 	}
    413     }
    414 }
    415 
    416 #[derive(Debug)]
    417 pub struct Resource {
    418     name : String,
    419     data : RData,
    420     data_raw : Vec<u8>
    421 }
    422 
    423 impl Resource {
    424     pub fn new(
    425 	name: impl Into<String>,
    426 	data: RData
    427     ) -> Self {
    428 	let mut buf : [u8; 80] = [0; 80];
    429 	let rdata_raw = Vec::from(data.to_bytes(&mut buf));
    430 	Self {
    431 	    name: name.into(),
    432 	    data,
    433 	    data_raw: rdata_raw
    434 	}
    435     }
    436 
    437     // Should we send cache-flush with this record? If it's "unique",
    438     // which means "all the records with that name, rrtype, and
    439     // rrclass are conceptually under the control or ownership of a
    440     // single responder". This works out (perhaps by coincidence) to
    441     // be everything except the PTR records
    442     fn is_unique(&self) -> bool {
    443 	self.data.ty() != ResourceType::Ptr
    444     }
    445     fn class(&self) -> u16 {
    446 	if self.is_unique() {
    447 	    1 | CACHE_FLUSH
    448 	} else {
    449 	    1
    450 	}
    451     }
    452 
    453     fn to_resource_record(&self) -> ResourceRecord<'_> {
    454 	let ttl = match self.data.ty() {
    455 	    ResourceType::A => 120,
    456 	    ResourceType::Srv => 120,
    457 	    _ => 4500
    458 	};
    459 
    460 	ResourceRecord::new(
    461 	    &self.name[..],
    462 	    self.data.ty(),
    463 	    self.class(),
    464 	    ttl,
    465 	    &self.data_raw[..]
    466 	)
    467     }
    468 }
    469 
    470 struct Database(Vec<Resource>);
    471 
    472 impl Database {
    473     fn find<'a>(&self, name: impl Into<String>, ty: ResourceType) -> Vec<&Resource> {
    474 	let name = name.into();
    475 	let results : Vec<&Resource> =
    476 	    self.0.iter().filter(
    477 		|r|
    478 		((ty == ResourceType::Wildcard) || (r.data.ty() == ty)) &&
    479 		    name == r.name
    480 	    ).collect();
    481 	results
    482     }
    483 
    484     // the records we announce unsolicited at startup
    485     fn advertised(&self) -> Vec<&Resource> {
    486 	self.0.iter().filter( |r| r.name != DISCOVERY_NAME ).collect()
    487     }
    488 
    489     fn additional(&self, r: &Resource) -> Vec<&Resource> {
    490 	match &r.data {
    491 	    RData::Srv { target, port:_, priority:_, weight:_ } => {
    492 		self.find(target, ResourceType::Wildcard)
    493 	    },
    494 	    RData::Ptr(name) => {
    495 		if r.name.eq(DISCOVERY_NAME) {
    496 		    Vec::from([])
    497 		} else {
    498 		    self.find(name, ResourceType::Wildcard)
    499 		}
    500 	    },
    501 	    _ =>  Vec::from([])
    502 	}
    503     }
    504 
    505     fn all_additional_<'a>(&'a self, r: &Resource, collect: &mut Vec<&'a Resource>) {
    506 	for r in self.additional(r) {
    507 	    collect.push(r);
    508 	    self.all_additional_(r, collect);
    509 	}
    510     }
    511 
    512     fn all_additional(&self, r: &Resource) -> Vec<&Resource> {
    513 	let mut collect : Vec<&Resource>  = Vec::new();
    514 	self.all_additional_(r, &mut collect);
    515 	collect
    516     }
    517 }
    518 
    519 pub async fn responder(stack: Stack<'_>, records: Vec<Resource>)  {
    520     let mut rx_buffer = [0u8; 4096];
    521     let mut tx_buffer = [0u8; 4096];
    522     let mut rx_meta = [PacketMetadata::EMPTY; 512];
    523     let mut tx_meta = [PacketMetadata::EMPTY; 512];
    524 
    525     let mut sock = UdpSocket::new(
    526         stack,
    527         &mut rx_meta,
    528         &mut rx_buffer,
    529         &mut tx_meta,
    530         &mut tx_buffer,
    531     );
    532     sock.set_hop_limit(Some(255));
    533     log::info!("sock.bind(5353) res: {:?}", sock.bind(MDNS_PORT));
    534     log::info!(
    535         "multicast_res: {:?}",
    536         stack.join_multicast_group(MDNS_IP)
    537     );
    538 
    539     let mut mdns_responder = Responder::new(&mut sock, records);
    540 
    541     loop {
    542 	match mdns_responder.state {
    543 	    ResponderState::ProbeFailed =>  {
    544 		sock.close();
    545 		_ = stack.leave_multicast_group(MDNS_IP);
    546 		return ;
    547 	    },
    548 	    _ => {
    549 		if mdns_responder.may_recv().await {
    550 		    mdns_responder.process_packet().await;
    551 		} else {
    552 		    Timer::after(Duration::from_millis(50)).await;
    553 		}
    554 	    }
    555 	}
    556     }
    557 }