Skip to content

Commit 9f9cf5f

Browse files
committed
Merge branch 'main' into fix/additionals
2 parents 85ca3e6 + 352c930 commit 9f9cf5f

File tree

20 files changed

+433
-243
lines changed

20 files changed

+433
-243
lines changed

.github/workflows/rust.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ jobs:
1414
steps:
1515
- uses: actions/checkout@v2
1616
- name: Check formatting
17-
run: cargo fmt -- --check
17+
run: cargo +stable fmt -- --check
18+
- name: Check linting
19+
run: cargo +stable clippy -- -Dwarnings

.github/workflows/zeroconf_test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
- name: Install our MSRV
1414
uses: dtolnay/rust-toolchain@stable
1515
with:
16-
toolchain: "1.70"
16+
toolchain: "1.74"
1717

1818
- name: Compile example
1919
run: cargo build --example register

Cargo.toml

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
[package]
22
name = "libmdns"
3-
version = "0.8.0"
3+
version = "0.9.1"
44
authors = ["Will Stott <[email protected]>"]
5-
65
description = "mDNS Responder library for building discoverable LAN services in Rust"
76
repository = "https://github.com/librespot-org/libmdns"
87
readme = "README.md"
98
license = "MIT"
109
edition = "2018"
10+
rust-version = "1.74"
1111

1212
[dependencies]
1313
byteorder = "1.5"
14-
if-addrs = "0.11.0"
15-
hostname = "0.3.1"
16-
log = "0.4"
17-
multimap = "0.9"
18-
rand = "0.8"
1914
futures-util = "0.3"
20-
thiserror = "1.0"
21-
tokio = { version = "1.0", features = ["sync", "net", "rt"] }
22-
socket2 = { version = "0.5", features = ["all"] }
23-
24-
[target.'cfg(windows)'.dependencies]
25-
winapi = { version = "0.3", features = ["netioapi"] }
26-
27-
[target.'cfg(not(windows))'.dependencies]
28-
nix = { version = "0.27", features = ["net"] }
15+
hostname = "0.4"
16+
if-addrs = { version = "0.14", features = ["link-local"] }
17+
log = "0.4"
18+
multimap = { version = "0.10", default-features = false }
19+
rand = "0.9"
20+
socket2 = { version = "0.6", features = ["all"] }
21+
thiserror = "2"
22+
tokio = { version = "1", default-features = false, features = [
23+
"sync",
24+
"net",
25+
"rt",
26+
] }
2927

3028
[dev-dependencies]
31-
env_logger = { version = "0.10", default-features = false, features = [
32-
"color",
33-
"humantime",
34-
"auto-color",
29+
env_logger = { version = "0.11", default-features = false, features = [
30+
"color",
31+
"humantime",
32+
"auto-color",
3533
] }

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ To use it, add this to your `Cargo.toml`:
1313

1414
```toml
1515
[dependencies]
16-
libmdns = "0.7"
16+
libmdns = "0.9"
1717
```
1818

19-
See the [example](https://github.com/librespot-org/libmdns/blob/stable-0.7.x/examples/register.rs) for use within code.
19+
See the [example](https://github.com/librespot-org/libmdns/blob/stable-0.9.x/examples/register.rs) for use within code.
2020

2121
## Dependencies
2222

23-
libmdns' oldest supported Rust toolchain is `1.70.0`, _however it may compile fine on older versions of rust._
23+
libmdns' MSRV (oldest supported Rust toolchain) is 1.74.0.
2424

2525
**We hold no strong garantees for sticking to a Minimum Supported Rust Version**. Please open an issue on GitHub if you need support for older compilers or different platforms.
2626

examples/register.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,8 @@ pub fn main() {
33
builder.parse_filters("libmdns=debug");
44
builder.init();
55

6-
let responder = libmdns::Responder::new().unwrap();
7-
let _svc = responder.register(
8-
"_http._tcp".to_owned(),
9-
"libmdns Web Server".to_owned(),
10-
80,
11-
&["path=/"],
12-
);
6+
let responder = libmdns::Responder::new();
7+
let _svc = responder.register("_http._tcp", "libmdns Web Server", 80, &["path=/"]);
138

149
loop {
1510
::std::thread::sleep(::std::time::Duration::from_secs(10));

examples/register_with_ip_list.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,7 @@ pub fn main() {
1010
];
1111

1212
let responder = libmdns::Responder::new_with_ip_list(vec).unwrap();
13-
let _svc = responder.register(
14-
"_http._tcp".to_owned(),
15-
"libmdns Web Server".to_owned(),
16-
80,
17-
&["path=/"],
18-
);
13+
let _svc = responder.register("_http._tcp", "libmdns Web Server", 80, &["path=/"]);
1914

2015
loop {
2116
::std::thread::sleep(::std::time::Duration::from_secs(10));

examples/zeroconf_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
from zeroconf import ServiceBrowser, Zeroconf, IPVersion, ZeroconfServiceTypes
1+
from zeroconf import ServiceBrowser, ServiceListener, Zeroconf, IPVersion, ZeroconfServiceTypes
22
from time import sleep
33

44

55
TYPE = "_http._tcp.local."
66
NAME = "libmdns Web Server"
77

88

9-
class MyListener:
9+
class MyListener(ServiceListener):
1010
def __init__(self):
1111
self.found = []
1212

src/address_family.rs

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
use super::MDNS_PORT;
2-
use if_addrs::get_if_addrs;
2+
use if_addrs::{get_if_addrs, IfAddr};
33
use socket2::{Domain, Protocol, SockAddr, Socket, Type};
4+
use std::collections::HashSet;
45
use std::io;
56
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket};
67

7-
#[cfg(not(windows))]
8-
use nix::net::if_::if_nametoindex;
9-
10-
#[cfg(windows)]
11-
use win::if_nametoindex;
12-
138
pub enum Inet {}
149

1510
pub enum Inet6 {}
@@ -34,8 +29,7 @@ pub trait AddressFamily {
3429
socket.set_reuse_address(true)?;
3530
socket.set_nonblocking(true)?;
3631

37-
#[cfg(not(windows))]
38-
#[cfg(not(target_os = "illumos"))]
32+
#[cfg(all(unix, not(any(target_os = "solaris", target_os = "illumos"))))]
3933
socket.set_reuse_port(true)?;
4034

4135
socket.bind(&addr)?;
@@ -53,14 +47,13 @@ impl AddressFamily for Inet {
5347
const DOMAIN: Domain = Domain::IPV4;
5448

5549
fn join_multicast(socket: &Socket, multiaddr: &Self::Addr) -> io::Result<()> {
56-
let addresses = get_address_list()?;
57-
if addresses.is_empty() {
50+
let addrs = get_one_nonloopback_ipv4_addr_per_iface()?;
51+
if addrs.is_empty() {
5852
socket.join_multicast_v4(multiaddr, &Ipv4Addr::UNSPECIFIED)
5953
} else {
60-
for (_, address) in addresses {
61-
if let IpAddr::V4(ip) = address {
62-
socket.join_multicast_v4(multiaddr, &ip)?;
63-
}
54+
// TODO: If any join succeeds return success (log failures)
55+
for ip in addrs {
56+
socket.join_multicast_v4(multiaddr, &ip)?;
6457
}
6558
Ok(())
6659
}
@@ -76,44 +69,61 @@ impl AddressFamily for Inet6 {
7669
const DOMAIN: Domain = Domain::IPV6;
7770

7871
fn join_multicast(socket: &Socket, multiaddr: &Self::Addr) -> io::Result<()> {
79-
let addresses = get_address_list()?;
80-
if addresses.is_empty() {
72+
let indexes = get_one_nonloopback_ipv6_index_per_iface()?;
73+
if indexes.is_empty() {
8174
socket.join_multicast_v6(multiaddr, 0)
8275
} else {
83-
// We join multicast by interface, but each interface can have more than one ipv6 address.
84-
// So we have to check we're not registering more than once, as the resulting error is then
85-
// fatal to ipv6 listening.
86-
// TODO: Make each interface resilient to failures on another.
87-
let mut registered = Vec::new();
88-
for (iface_name, address) in addresses {
89-
if let IpAddr::V6(_) = address {
90-
let ipv6_index = if_nametoindex(iface_name.as_str()).unwrap_or(0);
91-
if ipv6_index != 0 && !registered.contains(&ipv6_index) {
92-
socket.join_multicast_v6(multiaddr, ipv6_index)?;
93-
registered.push(ipv6_index);
94-
}
95-
}
76+
// TODO: If any join succeeds return success (log failures)
77+
for ipv6_index in indexes {
78+
socket.join_multicast_v6(multiaddr, ipv6_index)?;
9679
}
9780
Ok(())
9881
}
9982
}
10083
}
10184

102-
fn get_address_list() -> io::Result<Vec<(String, IpAddr)>> {
85+
fn get_one_nonloopback_ipv6_index_per_iface() -> io::Result<Vec<u32>> {
86+
// There may be multiple ip addresses on a single interface and we join multicast by interface.
87+
// Joining multicast on the same interface multiple times returns an error
88+
// so we filter duplicate interfaces.
89+
let mut collected_interfaces = HashSet::new();
10390
Ok(get_if_addrs()?
104-
.iter()
105-
.filter(|iface| !iface.is_loopback())
106-
.map(|iface| (iface.name.clone(), iface.ip()))
91+
.into_iter()
92+
.filter_map(|iface| {
93+
if iface.is_loopback() {
94+
None
95+
} else if matches!(iface.addr, IfAddr::V6(_)) {
96+
if collected_interfaces.insert(iface.name.clone()) {
97+
iface.index
98+
} else {
99+
None
100+
}
101+
} else {
102+
None
103+
}
104+
})
107105
.collect())
108106
}
109107

110-
#[cfg(windows)]
111-
mod win {
112-
use std::ffi::{CString, NulError};
113-
use winapi::shared::netioapi;
114-
115-
pub fn if_nametoindex(ifname: &str) -> Result<u32, NulError> {
116-
let c_str = CString::new(ifname)?;
117-
Ok(unsafe { netioapi::if_nametoindex(c_str.as_ptr()) })
118-
}
108+
fn get_one_nonloopback_ipv4_addr_per_iface() -> io::Result<Vec<Ipv4Addr>> {
109+
// There may be multiple ip addresses on a single interface and we join multicast by interface.
110+
// Joining multicast on the same interface multiple times returns an error
111+
// so we filter duplicate interfaces.
112+
let mut collected_interfaces = HashSet::new();
113+
Ok(get_if_addrs()?
114+
.into_iter()
115+
.filter_map(|iface| {
116+
if iface.is_loopback() {
117+
None
118+
} else if let IpAddr::V4(ip) = iface.ip() {
119+
if collected_interfaces.insert(iface.name.clone()) {
120+
Some(ip)
121+
} else {
122+
None
123+
}
124+
} else {
125+
None
126+
}
127+
})
128+
.collect())
119129
}

src/dns_parser/builder.rs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ impl Builder<Questions> {
4141
pub fn new_query(id: u16, recursion: bool) -> Builder<Questions> {
4242
let mut buf = Vec::with_capacity(512);
4343
let head = Header {
44-
id: id,
44+
id,
4545
query: true,
4646
opcode: Opcode::StandardQuery,
4747
authoritative: false,
@@ -57,7 +57,7 @@ impl Builder<Questions> {
5757
buf.extend([0u8; 12].iter());
5858
head.write(&mut buf[..12]);
5959
Builder {
60-
buf: buf,
60+
buf,
6161
max_size: Some(512),
6262
_state: PhantomData,
6363
}
@@ -66,10 +66,10 @@ impl Builder<Questions> {
6666
pub fn new_response(id: u16, recursion: bool, authoritative: bool) -> Builder<Questions> {
6767
let mut buf = Vec::with_capacity(512);
6868
let head = Header {
69-
id: id,
69+
id,
7070
query: false,
7171
opcode: Opcode::StandardQuery,
72-
authoritative: authoritative,
72+
authoritative,
7373
truncated: false,
7474
recursion_desired: recursion,
7575
recursion_available: false,
@@ -82,15 +82,15 @@ impl Builder<Questions> {
8282
buf.extend([0u8; 12].iter());
8383
head.write(&mut buf[..12]);
8484
Builder {
85-
buf: buf,
85+
buf,
8686
max_size: Some(512),
8787
_state: PhantomData,
8888
}
8989
}
9090
}
9191

9292
impl<T> Builder<T> {
93-
fn write_rr(&mut self, name: &Name, cls: QueryClass, ttl: u32, data: &RRData) {
93+
fn write_rr(&mut self, name: &Name<'_>, cls: QueryClass, ttl: u32, data: &RRData<'_>) {
9494
name.write_to(&mut self.buf).unwrap();
9595
self.buf.write_u16::<BigEndian>(data.typ() as u16).unwrap();
9696
self.buf.write_u16::<BigEndian>(cls as u16).unwrap();
@@ -103,6 +103,12 @@ impl<T> Builder<T> {
103103
data.write_to(&mut self.buf).unwrap();
104104
let data_size = self.buf.len() - data_offset;
105105

106+
assert!(
107+
(data_offset <= 65535),
108+
"{} is too long to write to a RR record",
109+
data_offset
110+
);
111+
#[allow(clippy::cast_possible_truncation)]
106112
BigEndian::write_u16(
107113
&mut self.buf[size_offset..size_offset + 2],
108114
data_size as u16,
@@ -166,7 +172,7 @@ impl<T: MoveTo<Questions>> Builder<T> {
166172
#[allow(dead_code)]
167173
pub fn add_question(
168174
self,
169-
qname: &Name,
175+
qname: &Name<'_>,
170176
qtype: QueryType,
171177
qclass: QueryClass,
172178
) -> Builder<Questions> {
@@ -183,10 +189,10 @@ impl<T: MoveTo<Questions>> Builder<T> {
183189
impl<T: MoveTo<Answers>> Builder<T> {
184190
pub fn add_answer(
185191
self,
186-
name: &Name,
192+
name: &Name<'_>,
187193
cls: QueryClass,
188194
ttl: u32,
189-
data: &RRData,
195+
data: &RRData<'_>,
190196
) -> Builder<Answers> {
191197
let mut builder = self.move_to::<Answers>();
192198

@@ -216,10 +222,10 @@ impl<T: MoveTo<Nameservers>> Builder<T> {
216222
#[allow(dead_code)]
217223
pub fn add_nameserver(
218224
self,
219-
name: &Name,
225+
name: &Name<'_>,
220226
cls: QueryClass,
221227
ttl: u32,
222-
data: &RRData,
228+
data: &RRData<'_>,
223229
) -> Builder<Nameservers> {
224230
let mut builder = self.move_to::<Nameservers>();
225231

@@ -249,10 +255,10 @@ impl<T: MoveTo<Nameservers>> Builder<T> {
249255
impl<T: MoveTo<Additional>> Builder<T> {
250256
pub fn add_additional(
251257
self,
252-
name: &Name,
258+
name: &Name<'_>,
253259
cls: QueryClass,
254260
ttl: u32,
255-
data: &RRData,
261+
data: &RRData<'_>,
256262
) -> Builder<Additional> {
257263
let mut builder = self.move_to::<Additional>();
258264

@@ -288,7 +294,7 @@ mod test {
288294
#[test]
289295
fn build_query() {
290296
let mut bld = Builder::new_query(1573, true);
291-
let name = Name::from_str("example.com").unwrap();
297+
let name = Name::from_str("example.com");
292298
bld = bld.add_question(&name, QT::A, QC::IN);
293299
let result = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\
294300
\x07example\x03com\x00\x00\x01\x00\x01";
@@ -298,7 +304,7 @@ mod test {
298304
#[test]
299305
fn build_srv_query() {
300306
let mut bld = Builder::new_query(23513, true);
301-
let name = Name::from_str("_xmpp-server._tcp.gmail.com").unwrap();
307+
let name = Name::from_str("_xmpp-server._tcp.gmail.com");
302308
bld = bld.add_question(&name, QT::SRV, QC::IN);
303309
let result = b"[\xd9\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\
304310
\x0c_xmpp-server\x04_tcp\x05gmail\x03com\x00\x00!\x00\x01";

0 commit comments

Comments
 (0)