aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Garrelou <simon@sixfoisneuf.fr>2023-04-16 21:27:34 +0200
committerSimon Garrelou <simon@sixfoisneuf.fr>2023-04-27 15:59:59 +0200
commita1b23498ddb6f6d0129528bd295dfb490be723da (patch)
treedf55d2dae763e5bccdd27bf776938698bd39b954
parent5871891af447e0a49108309c60af64fe9a6f59bc (diff)
downloadwgmgr-a1b23498ddb6f6d0129528bd295dfb490be723da.tar.gz
wgmgr-a1b23498ddb6f6d0129528bd295dfb490be723da.zip
reoganize project + start cli
-rw-r--r--Cargo.lock275
-rw-r--r--Cargo.toml2
-rw-r--r--src/main.rs117
-rw-r--r--src/wg.rs3
-rw-r--r--src/wg/config.rs (renamed from src/lib.rs)125
-rw-r--r--src/wg/error.rs31
-rw-r--r--src/wg/peer.rs56
7 files changed, 535 insertions, 74 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7ee9b3d..7e505bb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,5 +3,280 @@
3version = 3 3version = 3
4 4
5[[package]] 5[[package]]
6name = "anstream"
7version = "0.3.0"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371"
10dependencies = [
11 "anstyle",
12 "anstyle-parse",
13 "anstyle-query",
14 "anstyle-wincon",
15 "colorchoice",
16 "is-terminal",
17 "utf8parse",
18]
19
20[[package]]
21name = "anstyle"
22version = "1.0.0"
23source = "registry+https://github.com/rust-lang/crates.io-index"
24checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
25
26[[package]]
27name = "anstyle-parse"
28version = "0.2.0"
29source = "registry+https://github.com/rust-lang/crates.io-index"
30checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
31dependencies = [
32 "utf8parse",
33]
34
35[[package]]
36name = "anstyle-query"
37version = "1.0.0"
38source = "registry+https://github.com/rust-lang/crates.io-index"
39checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
40dependencies = [
41 "windows-sys",
42]
43
44[[package]]
45name = "anstyle-wincon"
46version = "1.0.0"
47source = "registry+https://github.com/rust-lang/crates.io-index"
48checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd"
49dependencies = [
50 "anstyle",
51 "windows-sys",
52]
53
54[[package]]
55name = "bitflags"
56version = "1.3.2"
57source = "registry+https://github.com/rust-lang/crates.io-index"
58checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
59
60[[package]]
61name = "cc"
62version = "1.0.79"
63source = "registry+https://github.com/rust-lang/crates.io-index"
64checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
65
66[[package]]
67name = "clap"
68version = "4.2.2"
69source = "registry+https://github.com/rust-lang/crates.io-index"
70checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a"
71dependencies = [
72 "clap_builder",
73]
74
75[[package]]
76name = "clap_builder"
77version = "4.2.2"
78source = "registry+https://github.com/rust-lang/crates.io-index"
79checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6"
80dependencies = [
81 "anstream",
82 "anstyle",
83 "bitflags",
84 "clap_lex",
85 "once_cell",
86 "strsim",
87]
88
89[[package]]
90name = "clap_lex"
91version = "0.4.1"
92source = "registry+https://github.com/rust-lang/crates.io-index"
93checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
94
95[[package]]
96name = "colorchoice"
97version = "1.0.0"
98source = "registry+https://github.com/rust-lang/crates.io-index"
99checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
100
101[[package]]
102name = "errno"
103version = "0.3.1"
104source = "registry+https://github.com/rust-lang/crates.io-index"
105checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
106dependencies = [
107 "errno-dragonfly",
108 "libc",
109 "windows-sys",
110]
111
112[[package]]
113name = "errno-dragonfly"
114version = "0.1.2"
115source = "registry+https://github.com/rust-lang/crates.io-index"
116checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
117dependencies = [
118 "cc",
119 "libc",
120]
121
122[[package]]
123name = "hermit-abi"
124version = "0.3.1"
125source = "registry+https://github.com/rust-lang/crates.io-index"
126checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
127
128[[package]]
129name = "io-lifetimes"
130version = "1.0.10"
131source = "registry+https://github.com/rust-lang/crates.io-index"
132checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
133dependencies = [
134 "hermit-abi",
135 "libc",
136 "windows-sys",
137]
138
139[[package]]
140name = "ipnetwork"
141version = "0.20.0"
142source = "registry+https://github.com/rust-lang/crates.io-index"
143checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e"
144dependencies = [
145 "serde",
146]
147
148[[package]]
149name = "is-terminal"
150version = "0.4.7"
151source = "registry+https://github.com/rust-lang/crates.io-index"
152checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
153dependencies = [
154 "hermit-abi",
155 "io-lifetimes",
156 "rustix",
157 "windows-sys",
158]
159
160[[package]]
161name = "libc"
162version = "0.2.141"
163source = "registry+https://github.com/rust-lang/crates.io-index"
164checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
165
166[[package]]
167name = "linux-raw-sys"
168version = "0.3.1"
169source = "registry+https://github.com/rust-lang/crates.io-index"
170checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
171
172[[package]]
173name = "once_cell"
174version = "1.17.1"
175source = "registry+https://github.com/rust-lang/crates.io-index"
176checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
177
178[[package]]
179name = "rustix"
180version = "0.37.11"
181source = "registry+https://github.com/rust-lang/crates.io-index"
182checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
183dependencies = [
184 "bitflags",
185 "errno",
186 "io-lifetimes",
187 "libc",
188 "linux-raw-sys",
189 "windows-sys",
190]
191
192[[package]]
193name = "serde"
194version = "1.0.160"
195source = "registry+https://github.com/rust-lang/crates.io-index"
196checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
197
198[[package]]
199name = "strsim"
200version = "0.10.0"
201source = "registry+https://github.com/rust-lang/crates.io-index"
202checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
203
204[[package]]
205name = "utf8parse"
206version = "0.2.1"
207source = "registry+https://github.com/rust-lang/crates.io-index"
208checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
209
210[[package]]
6name = "wgmgr" 211name = "wgmgr"
7version = "0.1.0" 212version = "0.1.0"
213dependencies = [
214 "clap",
215 "ipnetwork",
216]
217
218[[package]]
219name = "windows-sys"
220version = "0.48.0"
221source = "registry+https://github.com/rust-lang/crates.io-index"
222checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
223dependencies = [
224 "windows-targets",
225]
226
227[[package]]
228name = "windows-targets"
229version = "0.48.0"
230source = "registry+https://github.com/rust-lang/crates.io-index"
231checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
232dependencies = [
233 "windows_aarch64_gnullvm",
234 "windows_aarch64_msvc",
235 "windows_i686_gnu",
236 "windows_i686_msvc",
237 "windows_x86_64_gnu",
238 "windows_x86_64_gnullvm",
239 "windows_x86_64_msvc",
240]
241
242[[package]]
243name = "windows_aarch64_gnullvm"
244version = "0.48.0"
245source = "registry+https://github.com/rust-lang/crates.io-index"
246checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
247
248[[package]]
249name = "windows_aarch64_msvc"
250version = "0.48.0"
251source = "registry+https://github.com/rust-lang/crates.io-index"
252checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
253
254[[package]]
255name = "windows_i686_gnu"
256version = "0.48.0"
257source = "registry+https://github.com/rust-lang/crates.io-index"
258checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
259
260[[package]]
261name = "windows_i686_msvc"
262version = "0.48.0"
263source = "registry+https://github.com/rust-lang/crates.io-index"
264checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
265
266[[package]]
267name = "windows_x86_64_gnu"
268version = "0.48.0"
269source = "registry+https://github.com/rust-lang/crates.io-index"
270checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
271
272[[package]]
273name = "windows_x86_64_gnullvm"
274version = "0.48.0"
275source = "registry+https://github.com/rust-lang/crates.io-index"
276checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
277
278[[package]]
279name = "windows_x86_64_msvc"
280version = "0.48.0"
281source = "registry+https://github.com/rust-lang/crates.io-index"
282checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
diff --git a/Cargo.toml b/Cargo.toml
index 433e4a6..48f40cd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,3 +6,5 @@ edition = "2021"
6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 7
8[dependencies] 8[dependencies]
9clap = { version = "4.2.2", features = ["cargo"] }
10ipnetwork = "0.20.0"
diff --git a/src/main.rs b/src/main.rs
index 028e0e2..6c4787e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,118 @@
1use std::{env, process::exit};
2use std::error::Error;
3
4use clap::command;
5use clap::Command;
6use clap::arg;
7
8mod wg;
9
1fn main() { 10fn main() {
2 println!("{:?}", wgmgr::WireguardConfig::new("wg.conf").unwrap()) 11 let matches = command!()
12 .subcommand_required(true)
13 .subcommand(
14 Command::new("ls")
15 .about("List known clients")
16 )
17 .subcommand(
18 Command::new("config")
19 .about("Generate the configuration file for a client")
20 .arg(
21 arg!(
22 -t --type <TYPE> "Type of configuration: \"split\" or \"full\""
23 )
24 .required(false)
25 )
26 .arg(arg!(<PEER> "Name of the peer"))
27 )
28 .get_matches();
29
30 let conf_path = match env::var("WG_CONF") {
31 Ok(s) => s,
32 Err(_) => String::from("/etc/wireguard/wg0.conf")
33 };
34
35 let conf = match wg::config::WireguardConfig::new(&conf_path) {
36 Ok(c) => c,
37 Err(e) => {
38 eprintln!("Error loading the configuration file '{}'", conf_path);
39 eprintln!("{}", e);
40 exit(1);
41 }
42 };
43
44 match matches.subcommand() {
45 Some(("ls", _)) => {
46 do_list(&conf);
47 }
48
49 Some(("config", args)) => {
50 match args.get_one::<String>("PEER") {
51 Some(peer_name) => {
52 let is_full = match args.get_one::<String>("type") {
53 Some(t) => {
54 match t.to_string().as_str() {
55 "split" => false,
56 "full" => true,
57 _ => {
58 eprintln!("Error: '--type' must be either 'split' or 'full'");
59 exit(3);
60 }
61 }
62 },
63 None => false
64 };
65
66 do_config(&conf, peer_name.to_string(), is_full).unwrap();
67 },
68 None => {}
69 }
70 }
71
72 _ => {
73 unimplemented!();
74 }
75 }
76
3} 77}
78
79fn do_list(conf: &wg::config::WireguardConfig) {
80 let mut max_length = 0;
81 for p in conf.peers.iter() {
82 if p.name.len() > max_length {
83 max_length = p.name.len();
84 }
85 }
86
87 for p in conf.peers.iter() {
88 println!("{:max_length$} | {}", p.name, p.ip);
89 }
90}
91
92fn do_config(conf: &wg::config::WireguardConfig, peer_name: String, is_full: bool) -> Result<(), Box<dyn Error>> {
93 let peer = match conf.peers.iter().filter(|p| { p.name == peer_name }).nth(0) {
94 Some(p) => p,
95 None => {
96 eprintln!("No such peer: {}", peer_name);
97 exit(1);
98 }
99 };
100
101 println!("[Interface]");
102 println!("PrivateKey = {}", peer.private_key()?);
103 println!("Address = {}/32", peer.ip);
104 println!("DNS = TODO\n");
105
106 println!("[Peer]");
107 println!("PublicKey = TODO");
108
109 let allowed_ips = match is_full {
110 true => String::from("0.0.0.0/0"),
111 false => conf.network.to_string()
112 };
113 println!("AllowedIPs = {}", allowed_ips);
114 println!("Endpoint = TODO");
115 println!("PersistentKeepAlive = 25");
116
117 Ok(())
118} \ No newline at end of file
diff --git a/src/wg.rs b/src/wg.rs
new file mode 100644
index 0000000..39cfc0c
--- /dev/null
+++ b/src/wg.rs
@@ -0,0 +1,3 @@
1mod error;
2pub mod config;
3mod peer; \ No newline at end of file
diff --git a/src/lib.rs b/src/wg/config.rs
index c9bce25..03d2ce0 100644
--- a/src/lib.rs
+++ b/src/wg/config.rs
@@ -1,52 +1,22 @@
1use std::env::join_paths; 1use std::str::FromStr;
2use std::fs::read_to_string;
3use std::{net::Ipv4Addr, fs, error::Error}; 2use std::{net::Ipv4Addr, fs, error::Error};
4use std::fmt::{Formatter, Display};
5 3
6#[derive(Debug)] 4use ipnetwork::Ipv4Network;
7struct WgMgrError {
8 msg: String
9}
10
11impl WgMgrError {
12 fn new(msg: String) -> WgMgrError {
13 WgMgrError {
14 msg
15 }
16 }
17}
18
19impl Error for WgMgrError {
20 fn description(&self) -> &str {
21 return self.msg.as_str()
22 }
23 5
24 fn cause(&self) -> Option<&dyn Error> { 6use crate::wg::error::WgMgrError;
25 None 7use crate::wg::peer::Peer;
26 }
27}
28
29impl Display for WgMgrError {
30 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31 write!(f, "{}", self.msg)
32 }
33}
34 8
35#[derive(Debug)] 9#[derive(Debug)]
36pub struct WireguardConfig { 10pub struct WireguardConfig {
37 private_key: String, 11 private_key: String,
38 listen_port: u32, 12 listen_port: u32,
39 address: Ipv4Addr, 13 pub network: Ipv4Network,
40 net_range: u32, 14 pub peers: Vec<Peer>,
41 peers: Vec<Peer> 15 pre_ups: Vec<String>,
16 post_downs: Vec<String>
42} 17}
43 18
44#[derive(Debug)] 19
45struct Peer {
46 name: String,
47 public_key: String,
48 ip: Ipv4Addr
49}
50 20
51impl WireguardConfig { 21impl WireguardConfig {
52 pub fn new(config_path: &str) -> Result<WireguardConfig, Box<dyn Error>> { 22 pub fn new(config_path: &str) -> Result<WireguardConfig, Box<dyn Error>> {
@@ -55,9 +25,10 @@ impl WireguardConfig {
55 let mut conf = WireguardConfig{ 25 let mut conf = WireguardConfig{
56 private_key: String::new(), 26 private_key: String::new(),
57 listen_port: 0, 27 listen_port: 0,
58 address: Ipv4Addr::UNSPECIFIED, 28 network: Ipv4Network::from_str("0.0.0.0/0")?,
59 net_range: 0, 29 peers: vec![],
60 peers: vec![] 30 pre_ups: vec![],
31 post_downs: vec![]
61 }; 32 };
62 let mut current_peer = Peer { 33 let mut current_peer = Peer {
63 ip: Ipv4Addr::UNSPECIFIED, 34 ip: Ipv4Addr::UNSPECIFIED,
@@ -107,36 +78,29 @@ impl WireguardConfig {
107 } 78 }
108 else if line.starts_with("Address") { 79 else if line.starts_with("Address") {
109 let addr = config_value(line)?; 80 let addr = config_value(line)?;
110 match addr.find("/") { 81 conf.network = Ipv4Network::from_str(addr)?;
111 Some(idx) => { 82 }
112 conf.address = addr[..idx].parse()?; 83 else if line.starts_with("PreUp") {
113 conf.net_range = addr[idx+1..].parse()?; 84 let pre_up = String::from(config_value(line)?);
114 } 85 conf.pre_ups.push(pre_up);
115 None => { 86 }
116 return Err(Box::new(WgMgrError::new(String::from("'Interface' > 'Address' is missing a subnet")))); 87 else if line.starts_with("PostDown") {
117 } 88 let post_down = String::from(config_value(line)?);
118 } 89 conf.post_downs.push(post_down);
119 } 90 }
120 } 91 }
121 92
122 else if in_peer { 93 else if in_peer {
123 if line.starts_with("AllowedIPs") { 94 if line.starts_with("AllowedIPs") {
124 let addr = config_value(line)?; 95 let addr = config_value(line)?;
125 match addr.find("/") { 96 let ip = Ipv4Network::from_str(addr)?;
126 Some(idx) => { 97
127 current_peer.ip = addr[..idx].parse()?; 98 if ip.prefix() != 32 {
128 let net_range: i32 = addr[idx+1..].parse()?; 99 let msg = format!("Peer '{}' has invalid net range {}", current_peer.name, ip.prefix());
129 100 return Err(Box::new(WgMgrError::new(msg)));
130 if net_range != 32 {
131 let msg = format!("Peer '{}' has invalid net range {}", current_peer.name, net_range);
132 return Err(Box::new(WgMgrError::new(msg)));
133 }
134 }
135 None => {
136 let msg = format!("Peer '{}' is missing net range", current_peer.name);
137 return Err(Box::new(WgMgrError::new(msg)));
138 }
139 } 101 }
102
103 current_peer.ip = ip.ip();
140 } 104 }
141 else if line.starts_with("PublicKey") { 105 else if line.starts_with("PublicKey") {
142 current_peer.public_key = String::from(config_value(line)?); 106 current_peer.public_key = String::from(config_value(line)?);
@@ -152,21 +116,36 @@ impl WireguardConfig {
152 116
153 Ok(conf) 117 Ok(conf)
154 } 118 }
155}
156 119
157impl Peer {
158 pub fn private_key(&self) -> Result<String, Box<dyn Error>> {
159 // TODO: do not hardcode where private keys are stored
160 120
161 let pk_folder = String::from("./private_keys/"); 121 pub fn next_free_ip(&self) -> Result<Ipv4Addr, WgMgrError> {
162 let pk_path = join_paths(&[pk_folder, self.name.clone()])?; 122 let mut iter = self.network.iter();
123 iter.next(); // Skip the first IP (identification)
163 124
164 let pk = read_to_string(pk_path)?; 125 for ip in iter{
126 if ip == self.network.ip(){
127 continue;
128 }
165 129
166 Ok(pk) 130 let mut ok = true;
131 for peer in self.peers.iter() {
132 if peer.ip == ip {
133 ok = false;
134 break;
135 }
136 }
137
138 if ok {
139 return Ok(ip);
140 }
141 }
142
143 Err(WgMgrError::new(String::from("No more free IP addresses")))
167 } 144 }
168} 145}
169 146
147
148
170fn config_value(line: &str) -> Result<&str, &str> { 149fn config_value(line: &str) -> Result<&str, &str> {
171 match line.find("=") { 150 match line.find("=") {
172 Some(i) => { 151 Some(i) => {
diff --git a/src/wg/error.rs b/src/wg/error.rs
new file mode 100644
index 0000000..0167b22
--- /dev/null
+++ b/src/wg/error.rs
@@ -0,0 +1,31 @@
1use std::fmt::{Formatter, Display};
2use std::error::Error;
3
4#[derive(Debug)]
5pub struct WgMgrError {
6 msg: String
7}
8
9impl WgMgrError {
10 pub fn new(msg: String) -> WgMgrError {
11 WgMgrError {
12 msg
13 }
14 }
15}
16
17impl Error for WgMgrError {
18 fn description(&self) -> &str {
19 return self.msg.as_str()
20 }
21
22 fn cause(&self) -> Option<&dyn Error> {
23 None
24 }
25}
26
27impl Display for WgMgrError {
28 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
29 write!(f, "{}", self.msg)
30 }
31} \ No newline at end of file
diff --git a/src/wg/peer.rs b/src/wg/peer.rs
new file mode 100644
index 0000000..0a25841
--- /dev/null
+++ b/src/wg/peer.rs
@@ -0,0 +1,56 @@
1use std::error::Error;
2use std::net::Ipv4Addr;
3use std::env::join_paths;
4use std::fs::read_to_string;
5
6#[derive(Debug)]
7pub struct Peer {
8 pub name: String,
9 pub public_key: String,
10 pub ip: Ipv4Addr
11}
12
13impl Peer {
14 pub fn private_key(&self) -> Result<String, Box<dyn Error>> {
15 // TODO: do not hardcode where private keys are stored
16
17 let pk_folder = String::from("./private_keys/");
18 let pk_path = join_paths(&[pk_folder, self.name.clone()])?;
19
20 let pk = read_to_string(pk_path)?;
21
22 Ok(pk)
23 }
24}
25
26impl PartialOrd for Peer {
27 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
28 return self.ip.partial_cmp(&other.ip)
29 }
30
31 fn ge(&self, other: &Self) -> bool {
32 return self.ip.ge(&other.ip)
33 }
34
35 fn gt(&self, other: &Self) -> bool {
36 return self.ip.gt(&other.ip)
37 }
38
39 fn le(&self, other: &Self) -> bool {
40 return self.ip.le(&other.ip)
41 }
42
43 fn lt(&self, other: &Self) -> bool {
44 return self.ip.lt(&other.ip)
45 }
46}
47
48impl PartialEq for Peer {
49 fn eq(&self, other: &Self) -> bool {
50 return self.ip.eq(&other.ip)
51 }
52
53 fn ne(&self, other: &Self) -> bool {
54 return self.ip.ne(&other.ip)
55 }
56} \ No newline at end of file