1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
// Copyright 2018 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.
use id::PublicId;
use itertools::Itertools;
use message_filter::MessageFilter;
use std::collections::hash_map::Entry;
use std::collections::{BTreeSet, HashMap};
use std::time::Duration;
/// The maximum number of pairs of nodes that this node will act as a tunnel for.
const MAX_TUNNEL_CLIENT_PAIRS: usize = 40;
/// A container for managing tunnel connections.
///
/// The Kademlia routing scheme requires specific nodes to be directly connected to each other,
/// which may not always be possible due to e. g. NAT devices. Tunnel nodes act as proxies in these
/// cases, relaying messages between the two nodes in a way that is transparent to the rest of the
/// routing logic.
pub struct Tunnels {
/// Maps the peer we failed to directly connect to to the one that acts as a tunnel.
tunnels: HashMap<PublicId, PublicId>,
/// Contains peers that are looking for a tunnel, with the lower ID first. Only once it sends
/// a message to the latter via us, the pair is moved to `clients`.
new_clients: MessageFilter<(PublicId, PublicId)>,
/// Contains all pairs of names we act as a tunnel node for, with the lower ID first.
clients: BTreeSet<(PublicId, PublicId)>,
}
impl Tunnels {
/// Returns `true` if we are acting as a tunnel for the given clients.
pub fn has_clients(&self, src_id: PublicId, dst_id: PublicId) -> bool {
if src_id < dst_id {
self.clients.contains(&(src_id, dst_id))
} else {
self.clients.contains(&(dst_id, src_id))
}
}
/// Returns the ordered pair if the given client pair is eligible for tunnelling. If that is
/// the case, adds them to the `new_clients` map.
pub fn consider_clients(
&mut self,
src_id: PublicId,
dst_id: PublicId,
) -> Option<(PublicId, PublicId)> {
if self.clients.len() >= MAX_TUNNEL_CLIENT_PAIRS
|| self.tunnels.contains_key(&src_id)
|| self.tunnels.contains_key(&dst_id)
{
return None;
}
let (id0, id1) = if src_id < dst_id {
(src_id, dst_id)
} else {
(dst_id, src_id)
};
let _ = self.new_clients.insert(&(id0, id1));
Some((id0, id1))
}
/// Returns `true` if the given client pair can be made permanent, and does so.
///
/// `consider_clients` must be called with the client pair before this.
pub fn accept_clients(&mut self, src_id: PublicId, dst_id: PublicId) -> bool {
let pair = (src_id, dst_id);
if self.new_clients.contains(&pair) {
self.new_clients.remove(&pair);
let _ = self.clients.insert(pair);
true
} else {
false
}
}
/// Removes all pairs with the given client and returns a list of all clients that used us as a
/// tunnel for them.
pub fn drop_client(&mut self, pub_id: &PublicId) -> Vec<PublicId> {
let pairs = self
.clients
.iter()
.filter(|pair| pair.0 == *pub_id || pair.1 == *pub_id)
.cloned()
.collect_vec();
pairs
.into_iter()
.map(|pair| {
let _ = self.clients.remove(&pair);
if pair.0 == *pub_id {
pair.1
} else {
pair.0
}
}).collect()
}
/// Removes the pair matching `src_id` and `dst_id` from our tunnel clients
pub fn drop_client_pair(&mut self, src_id: PublicId, dst_id: PublicId) -> bool {
let (id0, id1) = if src_id < dst_id {
(src_id, dst_id)
} else {
(dst_id, src_id)
};
self.clients.remove(&(id0, id1))
}
/// Adds the given `tunnel_id` as a tunnel to `dst_id` if one is needed, otherwise returns
/// `false`.
pub fn add(&mut self, dst_id: PublicId, tunnel_id: PublicId) -> bool {
match self.tunnels.entry(dst_id) {
Entry::Occupied(_) => false,
Entry::Vacant(entry) => {
let _ = entry.insert(tunnel_id);
true
}
}
}
/// Removes the given tunnel to the given destination, and return whether it was present.
pub fn remove(&mut self, dst_id: PublicId, tunnel_id: PublicId) -> bool {
if let Entry::Occupied(entry) = self.tunnels.entry(dst_id) {
if entry.get() == &tunnel_id {
let _ = entry.remove();
return true;
}
}
false
}
/// Removes and the peer that is acting as a tunnel for the given peer, if any.
pub fn remove_tunnel_for(&mut self, dst_id: &PublicId) -> Option<PublicId> {
self.tunnels.remove(dst_id)
}
/// Is the given `tunnel_id` acting as a tunnel node?
pub fn is_tunnel_node(&self, tunnel_id: &PublicId) -> bool {
self.tunnels.values().any(|id| id == tunnel_id)
}
/// Removes the given tunnel node and returns a list of all peers it was acting as a tunnel
/// for.
pub fn remove_tunnel(&mut self, tunnel_id: &PublicId) -> Vec<PublicId> {
let dst_ids = self
.tunnels
.iter()
.filter(|&(_, id)| id == tunnel_id)
.map(|(&dst_id, _)| dst_id)
.collect_vec();
for dst_id in &dst_ids {
let _ = self.tunnels.remove(dst_id);
}
dst_ids
}
/// Returns the peer that is acting as a tunnel to the given peer, if any.
pub fn tunnel_for(&self, dst_id: &PublicId) -> Option<&PublicId> {
self.tunnels.get(dst_id)
}
/// Returns the number of client pairs we are acting as a tunnel for.
pub fn client_count(&self) -> usize {
self.clients.len()
}
/// Returns the number of peers we are indirectly connected to via a tunnel.
pub fn tunnel_count(&self) -> usize {
self.tunnels.len()
}
}
impl Default for Tunnels {
fn default() -> Tunnels {
Tunnels {
tunnels: HashMap::new(),
new_clients: MessageFilter::with_expiry_duration(Duration::from_secs(60)),
clients: BTreeSet::new(),
}
}
}
#[cfg(all(test, feature = "use-mock-crust"))]
mod tests {
use super::*;
use id::FullId;
use itertools::Itertools;
#[test]
fn tunnel_nodes_test() {
let our_id = *FullId::new().public_id();
let their_id = *FullId::new().public_id();
let mut tunnels: Tunnels = Default::default();
assert_eq!(None, tunnels.tunnel_for(&our_id));
// Peer 1 is acting as a tunnel for peer 0.
let _ = tunnels.add(our_id, their_id);
assert_eq!(Some(&their_id), tunnels.tunnel_for(&our_id));
assert_eq!(None, tunnels.tunnel_for(&their_id));
let _ = tunnels.remove(our_id, their_id);
assert_eq!(None, tunnels.tunnel_for(&our_id));
}
#[test]
fn remove_tunnel_test() {
let mut sorted_ids = vec![];
for _ in 0..5 {
sorted_ids.push(*FullId::new().public_id());
}
sorted_ids.sort();
let mut tunnels: Tunnels = Default::default();
// Peer 0 is acting as a tunnel for 1 and 2, but not 3.
let _ = tunnels.add(sorted_ids[1], sorted_ids[0]);
let _ = tunnels.add(sorted_ids[2], sorted_ids[0]);
let _ = tunnels.add(sorted_ids[3], sorted_ids[4]);
let removed_peers = tunnels.remove_tunnel(&sorted_ids[0]).into_iter().sorted();
assert_eq!(&[sorted_ids[1], sorted_ids[2]], &*removed_peers);
assert_eq!(None, tunnels.tunnel_for(&sorted_ids[1]));
assert_eq!(None, tunnels.tunnel_for(&sorted_ids[2]));
assert_eq!(Some(&sorted_ids[4]), tunnels.tunnel_for(&sorted_ids[3]));
}
#[test]
fn clients_test() {
let mut sorted_ids = vec![];
for _ in 0..6 {
sorted_ids.push(*FullId::new().public_id());
}
sorted_ids.sort();
let mut tunnels: Tunnels = Default::default();
// We are directly connected to 1, but not 0.
let _ = tunnels.add(sorted_ids[0], sorted_ids[1]);
// consider_clients has not been called yet.
assert!(!tunnels.accept_clients(sorted_ids[1], sorted_ids[2]));
assert!(!tunnels.accept_clients(sorted_ids[3], sorted_ids[4]));
// Reject 0 as client, as we are not directly connected to them.
assert_eq!(None, tunnels.consider_clients(sorted_ids[5], sorted_ids[0]));
assert_eq!(
Some((sorted_ids[1], sorted_ids[2])),
tunnels.consider_clients(sorted_ids[1], sorted_ids[2])
);
assert_eq!(
Some((sorted_ids[3], sorted_ids[4])),
tunnels.consider_clients(sorted_ids[4], sorted_ids[3])
);
assert!(tunnels.accept_clients(sorted_ids[1], sorted_ids[2]));
assert!(tunnels.accept_clients(sorted_ids[3], sorted_ids[4]));
assert!(tunnels.has_clients(sorted_ids[2], sorted_ids[1]));
assert!(tunnels.has_clients(sorted_ids[3], sorted_ids[4]));
}
}