rustydht_lib/common/
ipv4_addr_src.rs

1use dyn_clone::DynClone;
2use std::convert::TryInto;
3use std::net::Ipv4Addr;
4
5use log::debug;
6
7/// Represents an object with methods for figuring out the DHT node's external IPv4 address
8pub trait IPV4AddrSource: DynClone + Send {
9    /// Retrieves the IPv4 address that the source thinks we should have,
10    /// or None if it can't make a determination at this time.
11    ///
12    /// This method will be called periodically by the DHT. Implementations
13    /// should return their current best guess for the external (globally routable) IPv4 address
14    /// of the DHT.
15    fn get_best_ipv4(&self) -> Option<Ipv4Addr>;
16
17    /// Adds a "vote" from another node in the DHT in respose to our queries.
18    ///
19    /// DHT will call this method when it receive a "hint" from another DHT node
20    /// about our external IPv4 address. An IPV4AddrSource implementation can
21    /// use these "hints" or "votes", or ignore them.
22    ///
23    /// # Parameters
24    /// * `their_addr` - The IP address of the DHT node that we're learning this information from.
25    /// * `proposed_addr` - The external IP address that the other DHT node says we have.
26    fn add_vote(&mut self, their_addr: Ipv4Addr, proposed_addr: Ipv4Addr);
27
28    /// This will get called by DHT at some regular interval. Implementations
29    /// can use it to allow old information to "decay" over time.
30    fn decay(&mut self);
31}
32
33dyn_clone::clone_trait_object!(IPV4AddrSource);
34
35/// An IPV4AddrSource that always returns the same thing
36#[derive(Clone)]
37pub struct StaticIPV4AddrSource {
38    ip: Ipv4Addr,
39}
40
41impl IPV4AddrSource for StaticIPV4AddrSource {
42    fn get_best_ipv4(&self) -> Option<Ipv4Addr> {
43        Some(self.ip)
44    }
45    fn add_vote(&mut self, _: Ipv4Addr, _: Ipv4Addr) {}
46    fn decay(&mut self) {}
47}
48
49impl StaticIPV4AddrSource {
50    pub fn new(addr: Ipv4Addr) -> StaticIPV4AddrSource {
51        StaticIPV4AddrSource { ip: addr }
52    }
53}
54
55#[derive(Clone)]
56struct IPV4Vote {
57    ip: Ipv4Addr,
58    votes: i32,
59}
60
61/// An IPV4Source that takes a certain number of "votes" from other nodes on the network to make its decision.
62#[derive(Clone)]
63pub struct IPV4Consensus {
64    min_votes: usize,
65    max_votes: usize,
66    votes: Vec<IPV4Vote>,
67}
68
69impl IPV4Consensus {
70    pub fn new(min_votes: usize, max_votes: usize) -> IPV4Consensus {
71        IPV4Consensus {
72            min_votes,
73            max_votes,
74            votes: Vec::new(),
75        }
76    }
77}
78
79impl IPV4AddrSource for IPV4Consensus {
80    fn get_best_ipv4(&self) -> Option<Ipv4Addr> {
81        let first = self.votes.first();
82        match first {
83            Some(vote_info) => {
84                debug!(target: "rustydht_lib::IPV4AddrSource", "Best IPv4 address {:?} has {} votes", vote_info.ip, vote_info.votes);
85                if vote_info.votes >= self.min_votes.try_into().unwrap() {
86                    Some(vote_info.ip)
87                } else {
88                    None
89                }
90            }
91
92            None => None,
93        }
94    }
95
96    fn add_vote(&mut self, _: Ipv4Addr, proposed_addr: Ipv4Addr) {
97        let mut do_sort = false;
98        for vote in self.votes.iter_mut() {
99            if vote.ip == proposed_addr {
100                vote.votes = std::cmp::min(self.max_votes.try_into().unwrap(), vote.votes + 1);
101                do_sort = true;
102                break;
103            }
104        }
105
106        if do_sort {
107            self.votes.sort_by(|a, b| b.votes.cmp(&a.votes));
108        } else {
109            self.votes.push(IPV4Vote {
110                ip: proposed_addr,
111                votes: 1,
112            });
113        }
114    }
115
116    fn decay(&mut self) {
117        for vote in self.votes.iter_mut() {
118            vote.votes = std::cmp::max(0, vote.votes - 1);
119        }
120
121        // Optimize this if we care (hint: we probably don't)
122        self.votes.retain(|a| a.votes > 0)
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_static_src() {
132        let ip = Ipv4Addr::new(10, 0, 0, 107);
133        let mut src = StaticIPV4AddrSource::new(ip);
134        assert_eq!(Some(ip), src.get_best_ipv4());
135
136        // Doesn't matter
137        src.add_vote(Ipv4Addr::new(0, 0, 0, 0), Ipv4Addr::new(1, 1, 1, 1));
138        src.decay();
139
140        // Always returns the same thing
141        assert_eq!(Some(ip), src.get_best_ipv4());
142    }
143
144    #[test]
145    fn test_consensus_src() {
146        let mut src = IPV4Consensus::new(2, 4);
147        // Nothing yet
148        assert_eq!(None, src.get_best_ipv4());
149
150        // One vote, but not enough
151        src.add_vote(Ipv4Addr::new(0, 0, 0, 0), Ipv4Addr::new(1, 1, 1, 1));
152        assert_eq!(None, src.get_best_ipv4());
153
154        // Competing vote, still nothing
155        src.add_vote(Ipv4Addr::new(0, 0, 0, 0), Ipv4Addr::new(2, 2, 2, 2));
156        assert_eq!(None, src.get_best_ipv4());
157
158        // Another vote for the first one. Got something now
159        src.add_vote(Ipv4Addr::new(0, 0, 0, 0), Ipv4Addr::new(1, 1, 1, 1));
160        assert_eq!(Some(Ipv4Addr::new(1, 1, 1, 1)), src.get_best_ipv4());
161
162        // Another vote for the second one. Should still return the first one because in this house our sorts are stable
163        src.add_vote(Ipv4Addr::new(0, 0, 0, 0), Ipv4Addr::new(2, 2, 2, 2));
164        assert_eq!(Some(Ipv4Addr::new(1, 1, 1, 1)), src.get_best_ipv4());
165
166        // Dark horse takes the lead
167        src.add_vote(Ipv4Addr::new(0, 0, 0, 0), Ipv4Addr::new(2, 2, 2, 2));
168        assert_eq!(Some(Ipv4Addr::new(2, 2, 2, 2)), src.get_best_ipv4());
169
170        // Decay happens
171        src.decay();
172
173        // Dark horse still winning
174        assert_eq!(Some(Ipv4Addr::new(2, 2, 2, 2)), src.get_best_ipv4());
175
176        // Decay happens again
177        src.decay();
178
179        // Nobody wins now
180        assert_eq!(None, src.get_best_ipv4());
181    }
182}