Skip to content

Commit

Permalink
Mdns (#17)
Browse files Browse the repository at this point in the history
* WIP

* WIP

* New mDNS API

* Generate random delays as per spec

* Move handler as a parameter to run()

* Fix service type and subtype records

* Ipv4 address now optional

* Fill-in the additional section as well

* Process legacy queries

* Remove unwraps

* Make the broadcast signal a shared resource

* Implement Display; fix bugs

* Lower logging level

* Always add the root label to NameLabels

* Rename NameLabels to NameSlice

* Create queries using the visitor pattern as well

* Host questions chaining

* Fix chaining

* Fix chaining

* Allow shared buffers

* Switch to rs-matter-style buffers
  • Loading branch information
ivmarkov authored Jun 26, 2024
1 parent 5fea439 commit 2a8a05b
Show file tree
Hide file tree
Showing 10 changed files with 1,532 additions and 562 deletions.
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description = "no_std and no-alloc async implementations of various network prot
repository = "https://github.com/ivmarkov/edge-net"
license = "MIT OR Apache-2.0"
readme = "README.md"
rust-version = "1.77"
rust-version = "1.78"

[features]
default = ["io"]
Expand Down Expand Up @@ -109,8 +109,7 @@ embedded-io-async = { version = "0.6", default-features = false }
embedded-svc = { version = "0.28", default-features = false }
log = { version = "0.4", default-features = false }
heapless = { version = "0.8", default-features = false }
domain = { version = "0.9.3", default-features = false, features = ["heapless"] }
octseq = { version = "0.3.2", default-features = false }
domain = { version = "0.10", default-features = false, features = ["heapless"] }

edge-captive = { version = "0.2.0", path = "edge-captive", default-features = false }
edge-dhcp = { version = "0.2.0", path = "edge-dhcp", default-features = false }
Expand Down
1 change: 0 additions & 1 deletion edge-captive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,4 @@ io = ["edge-nal"]
[dependencies]
log = { workspace = true }
domain = { workspace = true }
octseq = { workspace = true }
edge-nal = { workspace = true, optional = true }
13 changes: 6 additions & 7 deletions edge-captive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use core::fmt::{self, Display};
use core::time::Duration;

use domain::base::wire::Composer;
use domain::dep::octseq::OctetsBuilder;
use domain::dep::octseq::{OctetsBuilder, Truncate};
use log::debug;

use domain::{
Expand All @@ -20,7 +20,6 @@ use domain::{
dep::octseq::ShortBuf,
rdata::A,
};
use octseq::Truncate;

#[cfg(feature = "io")]
pub mod io;
Expand Down Expand Up @@ -80,15 +79,15 @@ pub fn reply(

let mut responseb = domain::base::MessageBuilder::from_target(buf)?;

let buf = if matches!(message.header().opcode(), Opcode::Query) {
let buf = if matches!(message.header().opcode(), Opcode::QUERY) {
debug!("Message is of type Query, processing all questions");

let mut answerb = responseb.start_answer(&message, Rcode::NoError)?;
let mut answerb = responseb.start_answer(&message, Rcode::NOERROR)?;

for question in message.question() {
let question = question?;

if matches!(question.qtype(), Rtype::A) && matches!(question.qclass(), Class::In) {
if matches!(question.qtype(), Rtype::A) && matches!(question.qclass(), Class::IN) {
log::info!(
"Question {:?} is of type A, answering with IP {:?}, TTL {:?}",
question,
Expand All @@ -98,7 +97,7 @@ pub fn reply(

let record = Record::new(
question.qname(),
Class::In,
Class::IN,
Ttl::from_duration_lossy(ttl),
A::from_octets(ip[0], ip[1], ip[2], ip[3]),
);
Expand All @@ -118,7 +117,7 @@ pub fn reply(
headerb.set_id(message.header().id());
headerb.set_opcode(message.header().opcode());
headerb.set_rd(message.header().rd());
headerb.set_rcode(domain::base::iana::Rcode::NotImp);
headerb.set_rcode(domain::base::iana::Rcode::NOTIMP);

responseb.finish()
};
Expand Down
2 changes: 0 additions & 2 deletions edge-mdns/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ io = ["embassy-futures", "embassy-sync", "embassy-time", "edge-nal"]
log = { workspace = true }
heapless = { workspace = true }
domain = { workspace = true }
octseq = { workspace = true }
heapless07 = { package = "heapless", version = "0.7" }
embassy-futures = { workspace = true, optional = true }
embassy-sync = { workspace = true, optional = true }
embassy-time = { workspace = true, optional = true }
Expand Down
70 changes: 46 additions & 24 deletions edge-mdns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,22 @@ For other protocols, look at the [edge-net](https://github.com/ivmarkov/edge-net
## Example

```rust
use core::net::Ipv4Addr;
use core::net::{Ipv4Addr, Ipv6Addr};

use edge_mdns::io::{self, MdnsIoError, MdnsRunBuffers, DEFAULT_SOCKET};
use edge_mdns::Host;
use edge_nal::{Multicast, UdpBind, UdpSplit};
use edge_mdns::buf::BufferAccess;
use edge_mdns::domain::base::Ttl;
use edge_mdns::io::{self, MdnsIoError, DEFAULT_SOCKET};
use edge_mdns::{host::Host, HostAnswersMdnsHandler};
use edge_nal::{UdpBind, UdpSplit};

use embassy_sync::blocking_mutex::raw::NoopRawMutex;

use embassy_sync::mutex::Mutex;
use embassy_sync::signal::Signal;
use log::*;

use rand::{thread_rng, RngCore};

// Change this to the IP address of the machine where you'll run this example
const OUR_IP: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);

Expand All @@ -33,45 +41,59 @@ fn main() {

let stack = edge_nal_std::Stack::new();

let mut buffers = MdnsRunBuffers::new();
let (recv_buf, send_buf) = (
Mutex::<NoopRawMutex, _>::new([0; 1500]),
Mutex::<NoopRawMutex, _>::new([0; 1500]),
);

futures_lite::future::block_on(run::<edge_nal_std::Stack>(
&stack,
&mut buffers,
OUR_NAME,
OUR_IP,
futures_lite::future::block_on(run::<edge_nal_std::Stack, _, _>(
&stack, &recv_buf, &send_buf, OUR_NAME, OUR_IP,
))
.unwrap();
}

async fn run<T>(
async fn run<T, RB, SB>(
stack: &T,
buffers: &mut MdnsRunBuffers,
recv_buf: RB,
send_buf: SB,
our_name: &str,
our_ip: Ipv4Addr,
) -> Result<(), MdnsIoError<T::Error>>
where
T: UdpBind,
for<'a> <T as UdpBind>::Socket<'a>: Multicast<Error = T::Error> + UdpSplit<Error = T::Error>,
RB: BufferAccess,
SB: BufferAccess,
RB::BufferSurface: AsMut<[u8]>,
SB::BufferSurface: AsMut<[u8]>,
{
info!("About to run an mDNS responder for our PC. It will be addressable using {our_name}.local, so try to `ping {our_name}.local`.");

let mut socket = io::bind(stack, DEFAULT_SOCKET, Some(Ipv4Addr::UNSPECIFIED), Some(0)).await?;

let (recv, send) = socket.split();

let host = Host {
id: 0,
hostname: our_name,
ip: our_ip.octets(),
ipv6: None,
ipv4: our_ip,
ipv6: Ipv6Addr::UNSPECIFIED,
ttl: Ttl::from_secs(60),
};

io::run(
&host,
// A way to notify the mDNS responder that the data in `Host` had changed
// We don't use it in this example, because the data is hard-coded
let signal = Signal::new();

let mdns = io::Mdns::<NoopRawMutex, _, _, _, _>::new(
Some(Ipv4Addr::UNSPECIFIED),
Some(0),
[],
stack,
DEFAULT_SOCKET,
buffers,
)
.await
recv,
send,
recv_buf,
send_buf,
|buf| thread_rng().fill_bytes(buf),
&signal,
);

mdns.run(HostAnswersMdnsHandler::new(&host)).await
}
```
106 changes: 106 additions & 0 deletions edge-mdns/src/buf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use core::ops::{Deref, DerefMut};

use embassy_sync::{
blocking_mutex::raw::RawMutex,
mutex::{Mutex, MutexGuard},
};

/// A trait for getting access to a `&mut T` buffer, potentially awaiting until a buffer becomes available.
pub trait BufferAccess<T>
where
T: ?Sized,
{
type Buffer<'a>: DerefMut<Target = T>
where
Self: 'a;

/// Get a reference to a buffer.
/// Might await until a buffer is available, as it might be in use by somebody else.
///
/// Depending on its internal implementation details, access to a buffer might also be denied
/// immediately, or after a certain amount of time (subject to the concrete implementation of the method).
/// In that case, the method will return `None`.
async fn get(&self) -> Option<Self::Buffer<'_>>;
}

impl<B, T> BufferAccess<T> for &B
where
B: BufferAccess<T>,
T: ?Sized,
{
type Buffer<'a> = B::Buffer<'a> where Self: 'a;

async fn get(&self) -> Option<Self::Buffer<'_>> {
(*self).get().await
}
}

pub struct VecBufAccess<M, const N: usize>(Mutex<M, heapless::Vec<u8, N>>)
where
M: RawMutex;

impl<M, const N: usize> VecBufAccess<M, N>
where
M: RawMutex,
{
pub const fn new() -> Self {
Self(Mutex::new(heapless::Vec::new()))
}
}

pub struct VecBuf<'a, M, const N: usize>(MutexGuard<'a, M, heapless::Vec<u8, N>>)
where
M: RawMutex;

impl<'a, M, const N: usize> Drop for VecBuf<'a, M, N>
where
M: RawMutex,
{
fn drop(&mut self) {
self.0.clear();
}
}

impl<'a, M, const N: usize> Deref for VecBuf<'a, M, N>
where
M: RawMutex,
{
type Target = [u8];

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<'a, M, const N: usize> DerefMut for VecBuf<'a, M, N>
where
M: RawMutex,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl<M, const N: usize> BufferAccess<[u8]> for VecBufAccess<M, N>
where
M: RawMutex,
{
type Buffer<'a> = VecBuf<'a, M, N> where Self: 'a;

async fn get(&self) -> Option<Self::Buffer<'_>> {
let mut guard = self.0.lock().await;

guard.resize_default(N).unwrap();

Some(VecBuf(guard))
}
}

impl<M, const N: usize> Default for VecBufAccess<M, N>
where
M: RawMutex,
{
fn default() -> Self {
Self::new()
}
}
Loading

0 comments on commit 2a8a05b

Please sign in to comment.