Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ libp2p-gossipsub = { version = "0.50.0", path = "protocols/gossipsub" }
libp2p-identify = { version = "0.47.0", path = "protocols/identify" }
libp2p-identity = { version = "0.2.12" }
libp2p-kad = { version = "0.49.0", path = "protocols/kad" }
libp2p-mdns = { version = "0.48.0", path = "protocols/mdns" }
libp2p-mdns = { version = "0.48.1", path = "protocols/mdns" }
libp2p-memory-connection-limits = { version = "0.5.0", path = "misc/memory-connection-limits" }
libp2p-metrics = { version = "0.17.1", path = "misc/metrics" }
libp2p-mplex = { version = "0.43.1", path = "muxers/mplex" }
Expand Down
4 changes: 4 additions & 0 deletions protocols/mdns/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.48.1

- Implements support for querying individual peer SRV and TXT queries via dns-sd style queries.

## 0.48.0

- Remove `async_std` dependency [PR 5958](https://github.com/libp2p/rust-libp2p/pull/5958)
Expand Down
2 changes: 1 addition & 1 deletion protocols/mdns/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "libp2p-mdns"
edition.workspace = true
rust-version = { workspace = true }
version = "0.48.0"
version = "0.48.1"
description = "Implementation of the libp2p mDNS discovery method"
authors = ["Parity Technologies <[email protected]>"]
license = "MIT"
Expand Down
55 changes: 53 additions & 2 deletions protocols/mdns/src/behaviour/iface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use std::{
};

use futures::{channel::mpsc, SinkExt, StreamExt};
use libp2p_core::Multiaddr;
use libp2p_core::{multiaddr::Protocol, Multiaddr};
use libp2p_identity::PeerId;
use libp2p_swarm::ListenAddresses;
use socket2::{Domain, Socket, Type};
Expand All @@ -43,7 +43,11 @@ use self::{
query::MdnsPacket,
};
use crate::{
behaviour::{socket::AsyncSocket, timer::Builder},
behaviour::{
iface::dns::{build_individual_query_srv_response, build_individual_query_txt_response},
socket::AsyncSocket,
timer::Builder,
},
Config,
};

Expand Down Expand Up @@ -315,6 +319,53 @@ where
.push_back(build_service_discovery_response(disc.query_id(), this.ttl));
continue;
}
Poll::Ready(Ok(Ok(Some(MdnsPacket::IndividualPeerResponse(response))))) => {
tracing::trace!(
address=%this.addr,
remote_address=%response.remote_addr(),
"received service discovery from remote address on address"
);

let buffer_bytes = match response.query_type() {
hickory_proto::rr::RecordType::SRV => {
let port = this
.listen_addresses
.read()
.unwrap_or_else(|e| e.into_inner())
.iter()
.next()
.and_then(|addr| {
addr.iter().find_map(|proto| match proto {
Protocol::Tcp(port) => Some(port),
Protocol::Udp(port) => Some(port),
_ => None,
})
})
.unwrap_or(0);

build_individual_query_srv_response(
response.query_id(),
response.peer_name(),
port,
this.ttl,
)
}
hickory_proto::rr::RecordType::TXT => build_individual_query_txt_response(
response.query_id(),
response.peer_name(),
this.local_peer_id,
this.listen_addresses
.read()
.unwrap_or_else(|e| e.into_inner())
.iter(),
this.ttl,
),
_ => continue,
};

this.send_buffer.push_back(buffer_bytes);
continue;
}
Poll::Ready(Err(err)) if err.kind() == std::io::ErrorKind::WouldBlock => {
// No more bytes available on the socket to read
continue;
Expand Down
120 changes: 120 additions & 0 deletions protocols/mdns/src/behaviour/iface/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,99 @@ pub(crate) fn build_service_discovery_response(id: u16, ttl: Duration) -> MdnsPa
out
}

/// Builds the response to an individual peer SRV query.
pub(crate) fn build_individual_query_srv_response(
id: u16,
peer_name: &str,
port: u16,
ttl: Duration,
) -> MdnsPacket {
let ttl = duration_to_secs(ttl);
let mut out = Vec::with_capacity(200);

append_u16(&mut out, id);
// 0x84 flag for an answer.
append_u16(&mut out, 0x8400);
// Number of questions, answers, authorities, additionals.
append_u16(&mut out, 0x0);
append_u16(&mut out, 0x1);
append_u16(&mut out, 0x0);
append_u16(&mut out, 0x0);

// Answer to the query: peer_name._p2p._udp.local. SRV record
let full_name = format!("{}._p2p._udp.local", peer_name);
append_qname(&mut out, full_name.as_bytes());
// SRV type
append_u16(&mut out, 0x0021);
// Cache-flush bit (IN)
append_u16(&mut out, 0x8001);

// TTL for the answer
append_u32(&mut out, ttl);

// Calculate RDATA length
let mut rdata = Vec::new();
// Priority
append_u16(&mut rdata, 0);
// Weight
append_u16(&mut rdata, 0);
append_u16(&mut rdata, port);

// Use the peer name as hostname for mDNS
append_qname(&mut rdata, format!("{}.local", peer_name).as_bytes());

append_u16(&mut out, rdata.len() as u16);
out.extend_from_slice(&rdata);

out
}

/// Builds the response to an individual peer TXT query.
pub(crate) fn build_individual_query_txt_response<'a>(
id: u16,
peer_name: &str,
peer_id: PeerId,
addresses: impl Iterator<Item = &'a Multiaddr>,
ttl: Duration,
) -> MdnsPacket {
let ttl = duration_to_secs(ttl);
let mut out = Vec::with_capacity(500);

append_u16(&mut out, id);
// 0x84 flag for an answer.
append_u16(&mut out, 0x8400);
// Number of questions, answers, authorities, additionals.
append_u16(&mut out, 0x0);
append_u16(&mut out, 0x1);
append_u16(&mut out, 0x0);
append_u16(&mut out, 0x0);

// Answer: peer_name._p2p._udp.local. TXT record
let full_name = format!("{}._p2p._udp.local", peer_name);
append_qname(&mut out, full_name.as_bytes());

// TXT type
append_u16(&mut out, 0x0010);
// Cache-flush bit (IN)
append_u16(&mut out, 0x8001);
append_u32(&mut out, ttl);

// Build TXT record data with all addresses
let mut txt_data = Vec::new();
for addr in addresses {
let txt_value = format!("dnsaddr={}/p2p/{}", addr, peer_id.to_base58());
if txt_value.len() <= MAX_TXT_VALUE_LENGTH {
txt_data.push(txt_value.len() as u8);
txt_data.extend_from_slice(txt_value.as_bytes());
}
}

append_u16(&mut out, txt_data.len() as u16);
out.extend_from_slice(&txt_data);

out
}

/// Constructs an MDNS query response packet for an address lookup.
fn query_response_packet(id: u16, peer_id: &[u8], records: &[Vec<u8>], ttl: u32) -> MdnsPacket {
let mut out = Vec::with_capacity(records.len() * MAX_TXT_RECORD_SIZE);
Expand Down Expand Up @@ -427,6 +520,33 @@ mod tests {
assert!(Message::from_vec(&query).is_ok());
}

#[test]
fn build_individual_query_txt_response_correct() {
let peer_id = identity::Keypair::generate_ed25519().public().to_peer_id();
let addr: Multiaddr = "/ip4/127.0.0.1/tcp/4001".parse().unwrap();

let packet = build_individual_query_txt_response(
0x5678,
"testpeer",
peer_id,
std::iter::once(&addr),
Duration::from_secs(60),
);
assert!(Message::from_vec(&packet).is_ok());
}

#[test]
fn build_individual_query_srv_response_correct() {
let packet = build_individual_query_srv_response(
0x1234,
"my-peer-abc123",
30333,
Duration::from_secs(120),
);

assert!(Message::from_vec(&packet).is_ok());
}

#[test]
fn test_random_string() {
let varsize = thread_rng().gen_range(0..32);
Expand Down
81 changes: 81 additions & 0 deletions protocols/mdns/src/behaviour/iface/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ pub(crate) enum MdnsPacket {
Response(MdnsResponse),
/// A request for service discovery.
ServiceDiscovery(MdnsServiceDiscovery),
/// A query made by a remote for an individual peer.
IndividualPeerResponse(MdnsIndividualPeerQuery),
}

impl MdnsPacket {
Expand Down Expand Up @@ -82,6 +84,45 @@ impl MdnsPacket {
})));
}

for query in packet.queries() {
let query_name = query.name().to_utf8();

if query_name.ends_with("._p2p._udp.local.") || query_name.ends_with("._p2p._udp.local")
{
let peer_and_query_parts = query_name.splitn(2, '.').collect::<Vec<&str>>();
if peer_and_query_parts.is_empty() {
continue;
}

let peer_str = peer_and_query_parts[0];

// Check what type of record is being asked for
match query.query_type() {
hickory_proto::rr::RecordType::SRV => {
return Ok(Some(MdnsPacket::IndividualPeerResponse(
MdnsIndividualPeerQuery {
from,
query_id: packet.header().id(),
query_type: hickory_proto::rr::RecordType::SRV,
peer_name: peer_str.to_string(),
},
)));
}
hickory_proto::rr::RecordType::TXT => {
return Ok(Some(MdnsPacket::IndividualPeerResponse(
MdnsIndividualPeerQuery {
from,
query_id: packet.header().id(),
query_type: hickory_proto::rr::RecordType::TXT,
peer_name: peer_str.to_string(),
},
)))
}
_ => continue,
}
}
}

Ok(None)
}
}
Expand Down Expand Up @@ -115,6 +156,46 @@ impl fmt::Debug for MdnsQuery {
}
}

pub(crate) struct MdnsIndividualPeerQuery {
/// Sender of the address.
from: SocketAddr,
/// Id of the received DNS query. We need to pass this ID back in the results.
query_id: u16,
/// The RecordType of the query
query_type: hickory_proto::rr::RecordType,
/// The name of the peer being queried
peer_name: String,
}

impl fmt::Debug for MdnsIndividualPeerQuery {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MdnsIndividualPeerQuery")
.field("from", self.remote_addr())
.field("query_id", &self.query_id())
.field("query_type", self.query_type())
.field("peer_name", self.peer_name())
.finish()
}
}

impl MdnsIndividualPeerQuery {
pub(crate) fn remote_addr(&self) -> &SocketAddr {
&self.from
}

pub(crate) fn query_id(&self) -> u16 {
self.query_id
}

pub(crate) fn query_type(&self) -> &hickory_proto::rr::RecordType {
&self.query_type
}

pub(crate) fn peer_name(&self) -> &String {
&self.peer_name
}
}

/// A received mDNS service discovery query.
pub(crate) struct MdnsServiceDiscovery {
/// Sender of the address.
Expand Down
Loading