Skip to main content

surge_ping/
config.rs

1use std::{net::SocketAddr, num::NonZeroU32};
2
3use socket2::{SockAddr, Type};
4
5use crate::ICMP;
6
7/// Config is the packaging of various configurations of `sockets`. If you want to make
8/// some `set_socket_opt` and other modifications, please define and implement them in `Config`.
9#[derive(Debug)]
10pub struct Config {
11    pub sock_type_hint: Type,
12    pub kind: ICMP,
13    pub bind: Option<SockAddr>,
14    pub interface: Option<String>,
15    pub interface_index: Option<NonZeroU32>,
16    pub ttl: Option<u32>,
17    pub fib: Option<u32>,
18}
19
20impl Default for Config {
21    fn default() -> Self {
22        Self {
23            sock_type_hint: Type::DGRAM,
24            kind: ICMP::default(),
25            bind: None,
26            interface: None,
27            interface_index: None,
28            ttl: None,
29            fib: None,
30        }
31    }
32}
33
34impl Config {
35    /// A structure that can be specially configured for socket.
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    pub fn builder() -> ConfigBuilder {
41        ConfigBuilder::default()
42    }
43}
44
45#[derive(Debug)]
46pub struct ConfigBuilder {
47    sock_type_hint: Type,
48    kind: ICMP,
49    bind: Option<SockAddr>,
50    interface: Option<String>,
51    interface_index: Option<NonZeroU32>,
52    ttl: Option<u32>,
53    fib: Option<u32>,
54}
55
56impl Default for ConfigBuilder {
57    fn default() -> Self {
58        Self {
59            sock_type_hint: Type::DGRAM,
60            kind: ICMP::default(),
61            bind: None,
62            interface: None,
63            interface_index: None,
64            ttl: None,
65            fib: None,
66        }
67    }
68}
69
70impl ConfigBuilder {
71    /// Binds this socket to the specified address.
72    ///
73    /// This function directly corresponds to the `bind(2)` function on Windows
74    /// and Unix.
75    pub fn bind(mut self, bind: SocketAddr) -> Self {
76        self.bind = Some(SockAddr::from(bind));
77        self
78    }
79
80    /// Sets the value for the `SO_BINDTODEVICE` option on this socket.
81    ///
82    /// If a socket is bound to an interface, only packets received from that
83    /// particular interface are processed by the socket. Note that this only
84    /// works for some socket types, particularly `AF_INET` sockets.
85    pub fn interface(mut self, interface: &str) -> Self {
86        self.interface = Some(interface.to_string());
87        self
88    }
89
90    /// Sets the value for the `IP_BOUND_IF`, `IPV6_BOUND_IF` or `SO_BINDTOIFINDEX` option on this socket depending on the platform and IP address family.
91    pub fn interface_index(mut self, interface_index: NonZeroU32) -> Self {
92        self.interface_index = Some(interface_index);
93        self
94    }
95
96    /// Set the value of the `IP_TTL` option for this socket.
97    ///
98    /// This value sets the time-to-live field that is used in every packet sent
99    /// from this socket.
100    pub fn ttl(mut self, ttl: u32) -> Self {
101        self.ttl = Some(ttl);
102        self
103    }
104
105    pub fn fib(mut self, fib: u32) -> Self {
106        self.fib = Some(fib);
107        self
108    }
109
110    /// Identify which ICMP the socket handles.(default: ICMP::V4)
111    pub fn kind(mut self, kind: ICMP) -> Self {
112        self.kind = kind;
113        self
114    }
115
116    /// Try to open the socket with provided at first (DGRAM or RAW)
117    pub fn sock_type_hint(mut self, typ: Type) -> Self {
118        self.sock_type_hint = typ;
119        self
120    }
121
122    pub fn build(self) -> Config {
123        Config {
124            sock_type_hint: self.sock_type_hint,
125            kind: self.kind,
126            bind: self.bind,
127            interface: self.interface,
128            interface_index: self.interface_index,
129            ttl: self.ttl,
130            fib: self.fib,
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use std::net::{IpAddr, Ipv4Addr, SocketAddr};
139
140    #[test]
141    fn test_config_default() {
142        let config = Config::default();
143        assert_eq!(config.kind, ICMP::V4);
144        assert_eq!(config.sock_type_hint, Type::DGRAM);
145        assert!(config.bind.is_none());
146        assert!(config.interface.is_none());
147        assert!(config.interface_index.is_none());
148        assert!(config.ttl.is_none());
149        assert!(config.fib.is_none());
150    }
151
152    #[test]
153    fn test_config_builder_kind() {
154        let config = ConfigBuilder::default().kind(ICMP::V6).build();
155        assert_eq!(config.kind, ICMP::V6);
156    }
157
158    #[test]
159    fn test_config_builder_ttl() {
160        let config = ConfigBuilder::default().ttl(64).build();
161        assert_eq!(config.ttl, Some(64));
162    }
163
164    #[test]
165    fn test_config_builder_bind() {
166        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
167        let config = ConfigBuilder::default().bind(addr).build();
168        assert!(config.bind.is_some());
169    }
170
171    #[test]
172    fn test_config_builder_interface() {
173        let config = ConfigBuilder::default().interface("eth0").build();
174        assert_eq!(config.interface, Some("eth0".to_string()));
175    }
176
177    #[test]
178    fn test_config_builder_sock_type_hint() {
179        let config = ConfigBuilder::default().sock_type_hint(Type::RAW).build();
180        assert_eq!(config.sock_type_hint, Type::RAW);
181    }
182
183    #[test]
184    fn test_config_builder_fib() {
185        let config = ConfigBuilder::default().fib(100).build();
186        assert_eq!(config.fib, Some(100));
187    }
188
189    #[test]
190    fn test_config_builder_interface_index() {
191        let index = NonZeroU32::new(1).unwrap();
192        let config = ConfigBuilder::default().interface_index(index).build();
193        assert_eq!(config.interface_index, Some(index));
194    }
195
196    #[test]
197    fn test_config_builder_chained() {
198        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
199        let index = NonZeroU32::new(1).unwrap();
200
201        let config = ConfigBuilder::default()
202            .kind(ICMP::V6)
203            .ttl(128)
204            .bind(addr)
205            .interface("eth0")
206            .interface_index(index)
207            .sock_type_hint(Type::RAW)
208            .fib(200)
209            .build();
210
211        assert_eq!(config.kind, ICMP::V6);
212        assert_eq!(config.ttl, Some(128));
213        assert!(config.bind.is_some());
214        assert_eq!(config.interface, Some("eth0".to_string()));
215        assert_eq!(config.interface_index, Some(index));
216        assert_eq!(config.sock_type_hint, Type::RAW);
217        assert_eq!(config.fib, Some(200));
218    }
219
220    #[test]
221    fn test_config_build_preserves_all_fields() {
222        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 8080);
223        let index = NonZeroU32::new(5).unwrap();
224
225        let config = ConfigBuilder::default()
226            .kind(ICMP::V4)
227            .ttl(64)
228            .bind(addr)
229            .interface("wlan0")
230            .interface_index(index)
231            .sock_type_hint(Type::DGRAM)
232            .fib(50)
233            .build();
234
235        assert_eq!(config.kind, ICMP::V4);
236        assert_eq!(config.ttl, Some(64));
237        assert!(config.bind.is_some());
238        assert_eq!(config.interface, Some("wlan0".to_string()));
239        assert_eq!(config.interface_index, Some(index));
240        assert_eq!(config.sock_type_hint, Type::DGRAM);
241        assert_eq!(config.fib, Some(50));
242    }
243
244    #[test]
245    fn test_icmp_default() {
246        assert_eq!(ICMP::default(), ICMP::V4);
247    }
248
249    #[test]
250    fn test_config_preserves_none_values() {
251        let config = ConfigBuilder::default().build();
252        assert!(config.bind.is_none());
253        assert!(config.interface.is_none());
254        assert!(config.interface_index.is_none());
255        assert!(config.ttl.is_none());
256        assert!(config.fib.is_none());
257    }
258
259    #[test]
260    fn test_config_builder_multiple_calls() {
261        // Test that builder methods can be called multiple times
262        let config = ConfigBuilder::default()
263            .ttl(64)
264            .ttl(128)
265            .build();
266        assert_eq!(config.ttl, Some(128)); // Last call wins
267    }
268}