зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1567201 - Add query support to mdns_service; r=mjf
This adds basic query support to the mdns_service. Support for limiting the number of pending queries, timeouts and retries is added in another commit in this series. Differential Revision: https://phabricator.services.mozilla.com/D46975 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
6b05979f00
Коммит
340d659b0c
|
@ -14,6 +14,12 @@ MDNSService* mdns_service_start(const char* ifaddr);
|
|||
|
||||
void mdns_service_stop(MDNSService* serv);
|
||||
|
||||
void mdns_service_query_hostname(MDNSService* serv, void* data,
|
||||
void (*resolved)(void* data,
|
||||
const char* hostname,
|
||||
const char* address),
|
||||
const char* hostname);
|
||||
|
||||
void mdns_service_unregister_hostname(MDNSService* serv, const char* hostname);
|
||||
|
||||
const char* mdns_service_generate_uuid();
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
use socket2::{Domain, Socket, Type};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::CString;
|
||||
use std::ffi::{c_void, CStr, CString};
|
||||
use std::io;
|
||||
use std::net;
|
||||
use std::os::raw::c_char;
|
||||
|
@ -18,9 +17,24 @@ use uuid::Uuid;
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
struct Callback {
|
||||
data: *const c_void,
|
||||
resolved: extern "C" fn(*const c_void, *const c_char, *const c_char),
|
||||
}
|
||||
|
||||
unsafe impl Send for Callback {}
|
||||
|
||||
fn hostname_resolved(callback: &Callback, hostname: &str, addr: &str) {
|
||||
if let Ok(hostname) = CString::new(hostname) {
|
||||
if let Ok(addr) = CString::new(addr) {
|
||||
(callback.resolved)(callback.data, hostname.as_ptr(), addr.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This code is derived from code for creating questions in the dns-parser
|
||||
// crate. It would be nice to upstream this, or something similar.
|
||||
fn create_answer(id: u16, answers: &Vec<(String, &[u8])>) -> Result<Vec<u8>, &'static str> {
|
||||
fn create_answer(id: u16, answers: &Vec<(String, &[u8])>) -> Result<Vec<u8>, io::Error> {
|
||||
let mut buf = Vec::with_capacity(512);
|
||||
let head = dns_parser::Header {
|
||||
id: id,
|
||||
|
@ -45,7 +59,10 @@ fn create_answer(id: u16, answers: &Vec<(String, &[u8])>) -> Result<Vec<u8>, &'s
|
|||
for (name, addr) in answers {
|
||||
for part in name.split('.') {
|
||||
if part.len() > 62 {
|
||||
return Err("Name part length too long");
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Name part length too long",
|
||||
));
|
||||
}
|
||||
let ln = part.len() as u8;
|
||||
buf.push(ln);
|
||||
|
@ -54,30 +71,75 @@ fn create_answer(id: u16, answers: &Vec<(String, &[u8])>) -> Result<Vec<u8>, &'s
|
|||
buf.push(0);
|
||||
|
||||
if addr.len() == 4 {
|
||||
buf.write_u16::<BigEndian>(dns_parser::Type::A as u16)
|
||||
.unwrap();
|
||||
buf.write_u16::<BigEndian>(dns_parser::Type::A as u16)?;
|
||||
} else {
|
||||
buf.write_u16::<BigEndian>(dns_parser::Type::AAAA as u16)
|
||||
.unwrap();
|
||||
buf.write_u16::<BigEndian>(dns_parser::Type::AAAA as u16)?;
|
||||
}
|
||||
// set cache flush bit
|
||||
buf.write_u16::<BigEndian>(dns_parser::Class::IN as u16 | (0x1 << 15))
|
||||
.unwrap();
|
||||
buf.write_u32::<BigEndian>(120).unwrap();
|
||||
buf.write_u16::<BigEndian>(addr.len() as u16).unwrap();
|
||||
buf.write_u16::<BigEndian>(dns_parser::Class::IN as u16 | (0x1 << 15))?;
|
||||
buf.write_u32::<BigEndian>(120)?;
|
||||
buf.write_u16::<BigEndian>(addr.len() as u16)?;
|
||||
buf.extend(*addr);
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn create_query(id: u16, queries: &Vec<String>) -> Result<Vec<u8>, io::Error> {
|
||||
let mut buf = Vec::with_capacity(512);
|
||||
let head = dns_parser::Header {
|
||||
id: id,
|
||||
query: true,
|
||||
opcode: dns_parser::Opcode::StandardQuery,
|
||||
authoritative: false,
|
||||
truncated: false,
|
||||
recursion_desired: false,
|
||||
recursion_available: false,
|
||||
authenticated_data: false,
|
||||
checking_disabled: false,
|
||||
response_code: dns_parser::ResponseCode::NoError,
|
||||
questions: queries.len() as u16,
|
||||
answers: 0,
|
||||
nameservers: 0,
|
||||
additional: 0,
|
||||
};
|
||||
|
||||
buf.extend([0u8; 12].iter());
|
||||
head.write(&mut buf[..12]);
|
||||
|
||||
for name in queries {
|
||||
for part in name.split('.') {
|
||||
assert!(part.len() < 63);
|
||||
let ln = part.len() as u8;
|
||||
buf.push(ln);
|
||||
buf.extend(part.as_bytes());
|
||||
}
|
||||
buf.push(0);
|
||||
|
||||
buf.write_u16::<BigEndian>(dns_parser::QueryType::A as u16)?;
|
||||
buf.write_u16::<BigEndian>(dns_parser::QueryClass::IN as u16)?;
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
enum ServiceControl {
|
||||
Register { hostname: String, address: String },
|
||||
Unregister { hostname: String },
|
||||
Register {
|
||||
hostname: String,
|
||||
address: String,
|
||||
},
|
||||
Query {
|
||||
callback: Callback,
|
||||
hostname: String,
|
||||
},
|
||||
Unregister {
|
||||
hostname: String,
|
||||
},
|
||||
Stop,
|
||||
}
|
||||
|
||||
pub struct MDNSService {
|
||||
handle: Option<std::thread::JoinHandle<()>>,
|
||||
sender: Option<std::sync::mpsc::Sender<ServiceControl>>,
|
||||
}
|
||||
|
||||
|
@ -96,6 +158,20 @@ impl MDNSService {
|
|||
}
|
||||
}
|
||||
|
||||
fn query_hostname(&mut self, callback: Callback, hostname: &str) {
|
||||
if let Some(sender) = &self.sender {
|
||||
if let Err(err) = sender.send(ServiceControl::Query {
|
||||
callback: callback,
|
||||
hostname: hostname.to_string(),
|
||||
}) {
|
||||
warn!(
|
||||
"Could not send query hostname {} message: {}",
|
||||
hostname, err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unregister_hostname(&mut self, hostname: &str) {
|
||||
if let Some(sender) = &self.sender {
|
||||
if let Err(err) = sender.send(ServiceControl::Unregister {
|
||||
|
@ -131,10 +207,11 @@ impl MDNSService {
|
|||
socket.join_multicast_v4(&std::net::Ipv4Addr::new(224, 0, 0, 251), &addr)?;
|
||||
|
||||
let builder = thread::Builder::new().name("mdns_service".to_string());
|
||||
builder.spawn(move || {
|
||||
self.handle = Some(builder.spawn(move || {
|
||||
let mdns_addr = std::net::SocketAddr::from(([224, 0, 0, 251], port));
|
||||
let mut buffer: [u8; 1024] = [0; 1024];
|
||||
let mut hosts = HashMap::new();
|
||||
let mut queries = HashMap::new();
|
||||
loop {
|
||||
match receiver.recv_timeout(time::Duration::from_millis(10)) {
|
||||
Ok(msg) => match msg {
|
||||
|
@ -159,6 +236,16 @@ impl MDNSService {
|
|||
}
|
||||
}
|
||||
}
|
||||
ServiceControl::Query { callback, hostname } => {
|
||||
trace!("Querying {}", hostname);
|
||||
queries.insert(hostname.to_string(), callback);
|
||||
//TODO: limit pending queries
|
||||
if let Ok(buf) = create_query(0, &vec![hostname.to_string()]) {
|
||||
if let Err(err) = socket.send_to(&buf, &mdns_addr) {
|
||||
warn!("Sending mDNS query failed: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
ServiceControl::Unregister { hostname } => {
|
||||
trace!("Unregistering {}", hostname);
|
||||
hosts.remove(&hostname);
|
||||
|
@ -180,24 +267,73 @@ impl MDNSService {
|
|||
match dns_parser::Packet::parse(&buffer) {
|
||||
Ok(parsed) => {
|
||||
let mut answers: Vec<(String, &[u8])> = Vec::new();
|
||||
parsed
|
||||
.questions
|
||||
.iter()
|
||||
.filter(|question| {
|
||||
question.qtype == dns_parser::QueryType::A
|
||||
})
|
||||
.for_each(|question| {
|
||||
let qname = question.qname.to_string();
|
||||
trace!("mDNS question: {} {:?}", qname, question.qtype);
|
||||
if let Some(octets) = hosts.get(&qname) {
|
||||
|
||||
// If a packet contains both both questions and
|
||||
// answers, the questions should be ignored.
|
||||
if parsed.answers.len() == 0 {
|
||||
parsed
|
||||
.questions
|
||||
.iter()
|
||||
.filter(|question| {
|
||||
question.qtype == dns_parser::QueryType::A
|
||||
})
|
||||
.for_each(|question| {
|
||||
let qname = question.qname.to_string();
|
||||
trace!(
|
||||
"Sending mDNS answer for {}: {:?}",
|
||||
"mDNS question: {} {:?}",
|
||||
qname,
|
||||
octets
|
||||
question.qtype
|
||||
);
|
||||
answers.push((qname, &octets));
|
||||
if let Some(octets) = hosts.get(&qname) {
|
||||
trace!(
|
||||
"Sending mDNS answer for {}: {:?}",
|
||||
qname,
|
||||
octets
|
||||
);
|
||||
answers.push((qname, &octets));
|
||||
}
|
||||
});
|
||||
}
|
||||
for answer in parsed.answers {
|
||||
let hostname = answer.name.to_string();
|
||||
match queries.get(&hostname) {
|
||||
Some(callback) => {
|
||||
match answer.data {
|
||||
dns_parser::RData::A(
|
||||
dns_parser::rdata::a::Record(addr),
|
||||
) => {
|
||||
let addr = addr.to_string();
|
||||
trace!(
|
||||
"mDNS response: {} {}",
|
||||
hostname,
|
||||
addr
|
||||
);
|
||||
hostname_resolved(
|
||||
callback, &hostname, &addr,
|
||||
);
|
||||
}
|
||||
dns_parser::RData::AAAA(
|
||||
dns_parser::rdata::aaaa::Record(addr),
|
||||
) => {
|
||||
let addr = addr.to_string();
|
||||
trace!(
|
||||
"mDNS response: {} {}",
|
||||
hostname,
|
||||
addr
|
||||
);
|
||||
hostname_resolved(
|
||||
callback, &hostname, &addr,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
queries.remove(&hostname);
|
||||
}
|
||||
});
|
||||
None => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: If we did not answer every query
|
||||
// in this question, we should wait for a
|
||||
// random amount of time so as to not
|
||||
|
@ -227,7 +363,7 @@ impl MDNSService {
|
|||
}
|
||||
}
|
||||
}
|
||||
})?;
|
||||
})?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -237,11 +373,19 @@ impl MDNSService {
|
|||
if let Err(err) = sender.send(ServiceControl::Stop) {
|
||||
warn!("Could not stop mDNS Service: {}", err);
|
||||
}
|
||||
if let Some(handle) = self.handle {
|
||||
if let Err(_) = handle.join() {
|
||||
error!("Error on thread join");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new() -> MDNSService {
|
||||
MDNSService { sender: None }
|
||||
MDNSService {
|
||||
handle: None,
|
||||
sender: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,6 +395,9 @@ pub extern "C" fn mdns_service_register_hostname(
|
|||
hostname: *const c_char,
|
||||
address: *const c_char,
|
||||
) {
|
||||
assert!(!serv.is_null());
|
||||
assert!(!hostname.is_null());
|
||||
assert!(!address.is_null());
|
||||
unsafe {
|
||||
let hostname = CStr::from_ptr(hostname).to_string_lossy();
|
||||
let address = CStr::from_ptr(address).to_string_lossy();
|
||||
|
@ -260,6 +407,7 @@ pub extern "C" fn mdns_service_register_hostname(
|
|||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn mdns_service_start(ifaddr: *const c_char) -> *mut MDNSService {
|
||||
assert!(!ifaddr.is_null());
|
||||
let mut r = Box::new(MDNSService::new());
|
||||
unsafe {
|
||||
let ifaddr = CStr::from_ptr(ifaddr).to_string_lossy();
|
||||
|
@ -276,17 +424,40 @@ pub extern "C" fn mdns_service_start(ifaddr: *const c_char) -> *mut MDNSService
|
|||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn mdns_service_stop(serv: *mut MDNSService) {
|
||||
assert!(!serv.is_null());
|
||||
unsafe {
|
||||
let boxed = Box::from_raw(serv);
|
||||
boxed.stop();
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn mdns_service_query_hostname(
|
||||
serv: *mut MDNSService,
|
||||
data: *const c_void,
|
||||
resolved: extern "C" fn(*const c_void, *const c_char, *const c_char),
|
||||
hostname: *const c_char,
|
||||
) {
|
||||
assert!(!serv.is_null());
|
||||
assert!(!data.is_null());
|
||||
assert!(!hostname.is_null());
|
||||
unsafe {
|
||||
let hostname = CStr::from_ptr(hostname).to_string_lossy();
|
||||
let callback = Callback {
|
||||
data: data,
|
||||
resolved: resolved,
|
||||
};
|
||||
(*serv).query_hostname(callback, &hostname);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn mdns_service_unregister_hostname(
|
||||
serv: *mut MDNSService,
|
||||
hostname: *const c_char,
|
||||
) {
|
||||
assert!(!serv.is_null());
|
||||
assert!(!hostname.is_null());
|
||||
unsafe {
|
||||
let hostname = CStr::from_ptr(hostname).to_string_lossy();
|
||||
(*serv).unregister_hostname(&hostname);
|
||||
|
@ -297,15 +468,14 @@ pub extern "C" fn mdns_service_unregister_hostname(
|
|||
pub extern "C" fn mdns_service_generate_uuid() -> *const c_char {
|
||||
let uuid = Uuid::new_v4().to_hyphenated().to_string();
|
||||
match CString::new(uuid) {
|
||||
Ok(uuid) => {
|
||||
uuid.into_raw()
|
||||
}
|
||||
Err(_) => unreachable!() // UUID should not contain 0 byte
|
||||
Ok(uuid) => uuid.into_raw(),
|
||||
Err(_) => unreachable!(), // UUID should not contain 0 byte
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn mdns_service_free_uuid(uuid: *mut c_char) {
|
||||
assert!(!uuid.is_null());
|
||||
unsafe {
|
||||
CString::from_raw(uuid);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче