lanflare_core/discovery/
interface.rs1use std::net::{IpAddr, Ipv4Addr};
4
5#[derive(Clone, Debug, Default, PartialEq, Eq)]
11pub struct InterfaceFilter {
12 pub whitelist: Option<Vec<String>>,
14
15 pub blacklist: Option<Vec<String>>,
17}
18
19impl InterfaceFilter {
20 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn with_whitelist(mut self, whitelist: Vec<String>) -> Self {
27 self.whitelist = Some(whitelist);
28 self
29 }
30
31 pub fn with_blacklist(mut self, blacklist: Vec<String>) -> Self {
33 self.blacklist = Some(blacklist);
34 self
35 }
36
37 pub fn is_ignored(&self, addresses: &[IpAddr]) -> bool {
39 if matches!(&self.whitelist, Some(wl) if !any_address_matches(addresses, wl)) {
40 return true;
41 }
42
43 if matches!(&self.blacklist, Some(bl) if any_address_matches(addresses, bl)) {
44 return true;
45 }
46
47 false
48 }
49
50}
51
52fn any_address_matches(addresses: &[IpAddr], patterns: &[String]) -> bool {
54 addresses.iter().any(|address| {
55 let address = address.to_string();
56 patterns
57 .iter()
58 .any(|pattern| matches_ip_filter(pattern, &address))
59 })
60}
61
62pub fn matches_ip_filter(pattern: &str, address: &str) -> bool {
64 match pattern.split_once('*') {
65 None => pattern == address,
66 Some((prefix, rest)) => {
67 let Some(remainder) = address.strip_prefix(prefix) else {
68 return false;
69 };
70
71 let max_len = remainder
73 .find('.')
74 .unwrap_or(remainder.len())
75 .min(remainder.len());
76
77 (1..=max_len).any(|len| matches_ip_filter(rest, &remainder[len..]))
78 }
79 }
80}
81
82#[derive(Clone, Debug, Eq, PartialEq)]
84pub struct LocalInterfaceV4 {
85 pub name: String,
87
88 pub address: Ipv4Addr,
90}
91
92#[derive(Clone, Debug, Eq, PartialEq)]
97pub struct LocalInterfaceV6 {
98 pub name: String,
100
101 pub index: u32,
103}
104
105#[derive(Clone, Debug, Default, Eq, PartialEq)]
107pub struct LocalInterfaces {
108 pub v4: Vec<LocalInterfaceV4>,
110
111 pub v6: Vec<LocalInterfaceV6>,
113}
114
115pub fn local_interfaces(filter: &InterfaceFilter) -> std::io::Result<LocalInterfaces> {
117 struct Entry {
118 name: String,
119 index: Option<u32>,
120 addresses: Vec<IpAddr>,
121 }
122
123 let mut by_name: Vec<Entry> = Vec::new();
126 for interface in if_addrs::get_if_addrs()? {
127 if interface.is_loopback() {
128 continue;
129 }
130
131 match by_name
132 .iter_mut()
133 .find(|entry| entry.name == interface.name)
134 {
135 Some(entry) => {
136 entry.index = entry.index.or(interface.index);
137 entry.addresses.push(interface.ip());
138 }
139 None => by_name.push(Entry {
140 name: interface.name.clone(),
141 index: interface.index,
142 addresses: vec![interface.ip()],
143 }),
144 }
145 }
146
147 let mut result = LocalInterfaces {
148 v4: Vec::new(),
149 v6: Vec::new(),
150 };
151 for entry in by_name {
152 if filter.is_ignored(&entry.addresses) {
153 tracing::debug!(
154 "Ignoring network interface {} ({:?})",
155 entry.name,
156 entry.addresses
157 );
158 continue;
159 }
160
161 let mut has_v6 = false;
162 for address in &entry.addresses {
163 match address {
164 IpAddr::V4(address) => result.v4.push(LocalInterfaceV4 {
166 name: entry.name.clone(),
167 address: *address,
168 }),
169 IpAddr::V6(_) => has_v6 = true,
170 }
171 }
172
173 if has_v6 {
174 match entry.index {
175 Some(index) => result.v6.push(LocalInterfaceV6 {
176 name: entry.name,
177 index,
178 }),
179 None => tracing::debug!(
181 "Interface {} has no index, not using it for IPv6 multicast",
182 entry.name
183 ),
184 }
185 }
186 }
187
188 Ok(result)
189}
190
191pub fn is_sweepable_interface(name: &str, ip: &Ipv4Addr) -> bool {
196 if ip.is_loopback() {
197 return false;
198 }
199
200 let octets = ip.octets();
202 if octets[0] == 100 && (octets[1] & 0xC0) == 64 {
203 return false;
204 }
205
206 let lower = name.to_ascii_lowercase();
207 if lower.starts_with("tailscale")
208 || lower.starts_with("docker")
209 || lower.starts_with("vboxnet")
210 || lower.starts_with("veth")
211 || lower.starts_with("br-")
212 {
213 return false;
214 }
215
216 true
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use std::net::Ipv4Addr;
223
224 #[test]
225 fn test_matches_ip_filter() {
226 assert!(matches_ip_filter("192.168.1.*", "192.168.1.100"));
227 assert!(matches_ip_filter("192.168.1.*", "192.168.1.1"));
228 assert!(!matches_ip_filter("192.168.1.*", "192.168.2.1"));
229 assert!(!matches_ip_filter("192.168.1.*", "192.168.10.1"));
230 assert!(matches_ip_filter("10.0.*.*", "10.0.1.2"));
231 assert!(matches_ip_filter("127.0.0.1", "127.0.0.1"));
232 assert!(!matches_ip_filter("127.0.0.1", "127.0.0.2"));
233 }
234
235 #[test]
236 fn test_interface_filter_whitelist_blacklist() {
237 let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 50));
238 let filter = InterfaceFilter::new().with_whitelist(vec!["192.168.1.*".to_string()]);
239 assert!(!filter.is_ignored(&[ip]));
240
241 let filter = InterfaceFilter::new().with_whitelist(vec!["10.0.0.*".to_string()]);
242 assert!(filter.is_ignored(&[ip]));
243
244 let filter = InterfaceFilter::new().with_blacklist(vec!["192.168.1.*".to_string()]);
245 assert!(filter.is_ignored(&[ip]));
246 }
247}