wol_relay/
lib.rs

1#![deny(missing_docs)]
2#![forbid(unsafe_code)]
3#![doc = include_str!("../README.md")]
4#![doc(issue_tracker_base_url = "https://github.com/Finomnis/wol-relay/issues")]
5
6use std::{
7    io,
8    net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs, UdpSocket},
9};
10
11use wol::MacAddr6;
12
13mod recursion_prevention;
14mod wol_message;
15
16/// Used to configure and bind Wake-on-LAN receiver socket
17pub struct WolReceiverConfig {
18    addr: SocketAddr,
19}
20
21impl WolReceiverConfig {
22    /// Create a new WoL receiver config.
23    pub fn new() -> Self {
24        Self {
25            addr: SocketAddr::from((Ipv4Addr::UNSPECIFIED, 9)),
26        }
27    }
28
29    /// Set the IP address to listen on.
30    ///
31    /// Default: `0.0.0.0`.
32    ///
33    /// # Arguments
34    ///
35    /// * `ip` - The IP address.
36    ///
37    pub fn with_ip(mut self, ip: IpAddr) -> Self {
38        self.addr.set_ip(ip);
39        self
40    }
41
42    /// Set the port to listen on.
43    ///
44    /// Default: `9`.
45    ///
46    /// # Arguments
47    ///
48    /// * `port` - The port number.
49    ///
50    pub fn with_port(mut self, port: u16) -> Self {
51        self.addr.set_port(port);
52        self
53    }
54
55    /// Bind the socket and start listening.
56    ///
57    /// # Return
58    ///
59    /// An iterator of WoL messages
60    pub fn bind(self) -> io::Result<WolReceiver> {
61        Ok(WolReceiver {
62            socket: UdpSocket::bind(self.addr)?,
63        })
64    }
65}
66
67impl Default for WolReceiverConfig {
68    fn default() -> Self {
69        Self::new()
70    }
71}
72
73/// A socket that listens for Wake-on-LAN packets
74///
75/// Implements [`Iterator`], delivering a continuous stream of
76/// received WoL requests.
77///
78/// Filters out requests that originate from this machine, to
79/// prevent recursion.
80pub struct WolReceiver {
81    socket: UdpSocket,
82}
83
84impl WolReceiver {
85    /// Relay all received WoL packets to the given address.
86    ///
87    /// # Arguments
88    ///
89    /// * `host` - The target hostname/ip address. In most cases, this will be a broadcast address.
90    /// * `port` - The port number. In most cases `9`.
91    ///
92    pub fn relay_to(&mut self, host: &str, port: u16) -> io::Result<()> {
93        for target_mac in self {
94            let target_mac = target_mac?;
95            log::info!("Relaying WoL packet for '{target_mac}'");
96
97            let target_addrs = match (host, port).to_socket_addrs() {
98                Ok(addr) => addr,
99                Err(e) => {
100                    log::error!("Unable to resolve '{host}:{port}': {e}");
101                    continue;
102                }
103            };
104
105            for target_addr in target_addrs {
106                log::debug!("Sending WoL packet for '{target_mac}' to '{target_addr}'");
107                if let Err(e) = wol::send_magic_packet(target_mac, None, target_addr) {
108                    log::error!("Failed to send WoL packet to '{target_addr}': {e}");
109                }
110            }
111        }
112
113        Ok(())
114    }
115
116    /// Returns the socket address that this socket was created from.
117    ///
118    /// See [`UdpSocket::local_addr`].
119    pub fn local_addr(&self) -> io::Result<SocketAddr> {
120        self.socket.local_addr()
121    }
122}
123
124impl Iterator for WolReceiver {
125    type Item = io::Result<MacAddr6>;
126
127    fn next(&mut self) -> Option<Self::Item> {
128        loop {
129            let mut buf = vec![0u8; wol_message::WOL_MAX_SIZE];
130            match self.socket.recv_from(&mut buf) {
131                Ok((size, addr)) => {
132                    if !recursion_prevention::is_our_ip(addr) {
133                        if let Some(buf) = buf.get(..size) {
134                            if let Some(target_mac) = wol_message::parse_wol_message(buf) {
135                                log::debug!(
136                                    "Received WoL from {} to wake {}",
137                                    addr.ip(),
138                                    target_mac
139                                );
140                                return Some(Ok(target_mac));
141                            } else {
142                                log::debug!("Received non-WoL message: {buf:x?}");
143                            }
144                        }
145                    } else {
146                        log::debug!("Detected recursion, skipping packet ...");
147                    }
148                }
149                Err(e) => {
150                    return Some(Err(e));
151                }
152            }
153        }
154    }
155}