summaryrefslogtreecommitdiff
path: root/src/conf.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf.rs')
-rw-r--r--src/conf.rs692
1 files changed, 692 insertions, 0 deletions
diff --git a/src/conf.rs b/src/conf.rs
new file mode 100644
index 0000000..b6e49a0
--- /dev/null
+++ b/src/conf.rs
@@ -0,0 +1,692 @@
1use std::{fmt::Write, str::FromStr};
2
3use ipnet::IpNet;
4
5use super::Key;
6
7const FIELD_PRIVATE_KEY: &str = "PrivateKey";
8const FIELD_LISTEN_PORT: &str = "ListenPort";
9const FIELD_FWMARK: &str = "FwMark";
10const FIELD_PUBLIC_KEY: &str = "PublicKey";
11const FIELD_PRE_SHARED_KEY: &str = "PresharedKey";
12const FIELD_ALLOWED_IPS: &str = "AllowedIPs";
13const FIELD_ENDPOINT: &str = "Endpoint";
14const FIELD_PERSISTENT_KEEPALIVE: &str = "PersistentKeepalive";
15
16// wg-quick fields
17const FIELD_ADDRESS: &str = "Address";
18const FIELD_DNS: &str = "DNS";
19
20macro_rules! header {
21 ($dest:expr, $h:expr) => {
22 writeln!($dest, "[{}]", $h).unwrap();
23 };
24}
25
26macro_rules! field {
27 ($dest:expr, $n:expr, $v:expr) => {
28 writeln!($dest, "{} = {}", $n, $v).unwrap();
29 };
30}
31
32macro_rules! field_csv {
33 ($dest:expr, $n:expr, $v:expr) => {
34 if !$v.is_empty() {
35 write!($dest, "{} = ", $n).unwrap();
36 let mut comma = false;
37 for e in $v.iter() {
38 if comma {
39 write!($dest, ", ").unwrap();
40 } else {
41 comma = true;
42 }
43 write!($dest, "{}", e).unwrap();
44 }
45 writeln!($dest).unwrap();
46 }
47 };
48}
49
50macro_rules! field_opt {
51 ($dest:expr, $n:expr, $v:expr) => {
52 if let Some(ref v) = $v {
53 field!($dest, $n, v);
54 }
55 };
56}
57
58#[derive(Debug, Clone)]
59pub struct WgInterface {
60 pub private_key: Key,
61 pub address: Vec<IpNet>,
62 pub listen_port: Option<u16>,
63 pub fw_mark: Option<u32>,
64 pub dns: Option<String>,
65}
66
67#[derive(Debug, Clone)]
68pub struct WgPeer {
69 pub public_key: Key,
70 pub preshared_key: Option<Key>,
71 pub allowed_ips: Vec<IpNet>,
72 pub endpoint: Option<String>,
73 pub keep_alive: Option<u16>,
74}
75
76impl WgPeer {
77 pub fn builder(public_key: Key) -> WgPeerBuilder {
78 WgPeerBuilder::new(public_key)
79 }
80}
81
82#[derive(Debug, Clone)]
83pub struct WgConf {
84 pub interface: WgInterface,
85 pub peers: Vec<WgPeer>,
86}
87
88impl WgConf {
89 pub fn builder() -> WgConfBuilder {
90 WgConfBuilder::new()
91 }
92}
93
94#[derive(Debug)]
95pub struct WgPeerBuilder {
96 pub public_key: Key,
97 pub preshared_key: Option<Key>,
98 pub allowed_ips: Vec<IpNet>,
99 pub endpoint: Option<String>,
100 pub keep_alive: Option<u16>,
101}
102
103impl WgPeerBuilder {
104 pub fn new(public_key: Key) -> WgPeerBuilder {
105 WgPeerBuilder {
106 public_key,
107 preshared_key: None,
108 allowed_ips: Vec::new(),
109 endpoint: None,
110 keep_alive: None,
111 }
112 }
113
114 pub fn preshared_key(mut self, preshared_key: Key) -> Self {
115 self.preshared_key = Some(preshared_key);
116 self
117 }
118
119 pub fn allowed_ip(mut self, allowed_ip: IpNet) -> Self {
120 self.allowed_ips.push(allowed_ip);
121 self
122 }
123
124 pub fn allowed_ips(mut self, allowed_ips: impl IntoIterator<Item = IpNet>) -> Self {
125 self.allowed_ips.extend(allowed_ips);
126 self
127 }
128
129 pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
130 self.endpoint = Some(endpoint.into());
131 self
132 }
133
134 pub fn endpoint_opt(mut self, endpoint: Option<impl Into<String>>) -> Self {
135 if let Some(endpoint) = endpoint {
136 self.endpoint = Some(endpoint.into());
137 }
138 self
139 }
140
141 pub fn keep_alive(mut self, keep_alive: u16) -> Self {
142 self.keep_alive = Some(keep_alive);
143 self
144 }
145
146 pub fn build(self) -> WgPeer {
147 WgPeer {
148 public_key: self.public_key,
149 preshared_key: self.preshared_key,
150 allowed_ips: self.allowed_ips,
151 endpoint: self.endpoint,
152 keep_alive: self.keep_alive,
153 }
154 }
155}
156
157#[derive(Debug)]
158pub struct WgConfBuilder {
159 private_key: Option<Key>,
160 address: Vec<IpNet>,
161 listen_port: Option<u16>,
162 fw_mark: Option<u32>,
163 dns: Option<String>,
164 peers: Vec<WgPeer>,
165}
166
167impl WgConfBuilder {
168 pub fn new() -> Self {
169 WgConfBuilder {
170 private_key: None,
171 address: Vec::new(),
172 listen_port: None,
173 fw_mark: None,
174 dns: None,
175 peers: Vec::new(),
176 }
177 }
178
179 pub fn private_key(mut self, private_key: Key) -> Self {
180 self.private_key = Some(private_key);
181 self
182 }
183
184 pub fn address(mut self, address: impl Into<IpNet>) -> Self {
185 self.address.push(address.into());
186 self
187 }
188
189 pub fn addresses(mut self, addresses: impl IntoIterator<Item = IpNet>) -> Self {
190 self.address.extend(addresses);
191 self
192 }
193
194 pub fn listen_port(mut self, listen_port: u16) -> Self {
195 self.listen_port = Some(listen_port);
196 self
197 }
198
199 pub fn fw_mark(mut self, fw_mark: u32) -> Self {
200 self.fw_mark = Some(fw_mark);
201 self
202 }
203
204 pub fn dns(mut self, dns: impl Into<String>) -> Self {
205 self.dns = Some(dns.into());
206 self
207 }
208
209 pub fn dns_opt(mut self, dns: Option<impl Into<String>>) -> Self {
210 if let Some(dns) = dns {
211 self.dns = Some(dns.into());
212 }
213 self
214 }
215
216 pub fn peer(mut self, peer: WgPeer) -> Self {
217 self.peers.push(peer);
218 self
219 }
220
221 pub fn peers(mut self, peers: impl IntoIterator<Item = WgPeer>) -> Self {
222 self.peers.extend(peers);
223 self
224 }
225
226 pub fn build(self) -> WgConf {
227 WgConf {
228 interface: WgInterface {
229 private_key: self.private_key.unwrap_or_else(Key::generate_private),
230 address: self.address,
231 listen_port: self.listen_port,
232 fw_mark: self.fw_mark,
233 dns: self.dns,
234 },
235 peers: self.peers,
236 }
237 }
238}
239
240#[derive(Default)]
241struct PartialConf {
242 interface: Option<WgInterface>,
243 peers: Vec<WgPeer>,
244}
245
246pub fn parse_conf(conf: &str) -> anyhow::Result<WgConf> {
247 let mut iter = conf.lines().filter_map(|l| {
248 // remove whitespace on the sides
249 let l = l.trim();
250 // remove the comment
251 let (l, _) = l.rsplit_once("#").unwrap_or((l, ""));
252 if l.is_empty() {
253 None
254 } else {
255 Some(l)
256 }
257 });
258
259 let mut partial = PartialConf::default();
260 parse_partial(&mut partial, &mut iter)?;
261
262 match partial.interface {
263 Some(interface) => Ok(WgConf {
264 interface,
265 peers: partial.peers,
266 }),
267 None => Err(anyhow::anyhow!("no interface found")),
268 }
269}
270
271pub fn serialize_conf(conf: &WgConf) -> String {
272 let mut conf_str = String::new();
273 header!(conf_str, "Interface");
274 field!(conf_str, FIELD_PRIVATE_KEY, conf.interface.private_key);
275 field_csv!(conf_str, FIELD_ADDRESS, conf.interface.address);
276 field_opt!(conf_str, FIELD_LISTEN_PORT, conf.interface.listen_port);
277 field_opt!(conf_str, FIELD_FWMARK, conf.interface.fw_mark);
278 field_opt!(conf_str, FIELD_DNS, conf.interface.dns);
279 for peer in conf.peers.iter() {
280 writeln!(conf_str).unwrap();
281 header!(conf_str, "Peer");
282 field!(conf_str, FIELD_PUBLIC_KEY, peer.public_key);
283 field_opt!(conf_str, FIELD_PRE_SHARED_KEY, peer.preshared_key);
284 field_csv!(conf_str, FIELD_ALLOWED_IPS, peer.allowed_ips);
285 field_opt!(conf_str, FIELD_ENDPOINT, peer.endpoint);
286 field_opt!(conf_str, FIELD_PERSISTENT_KEEPALIVE, peer.keep_alive);
287 }
288 conf_str
289}
290
291fn parse_partial<'s, I: Iterator<Item = &'s str>>(
292 cfg: &mut PartialConf,
293 iter: &mut I,
294) -> anyhow::Result<()> {
295 match iter.next() {
296 Some("[Interface]") => parse_interface(cfg, iter),
297 Some("[Peer]") => parse_peer(cfg, iter),
298 Some(line) => Err(anyhow::anyhow!("unexpected line: {}", line)),
299 None => Err(anyhow::anyhow!("unexpected end of file")),
300 }
301}
302
303fn parse_interface<'s, I: Iterator<Item = &'s str>>(
304 cfg: &mut PartialConf,
305 iter: &mut I,
306) -> anyhow::Result<()> {
307 let mut private_key = None;
308 let mut address = Vec::new();
309 let mut listen_port = None;
310 let mut fw_mark = None;
311 let mut dns = None;
312 let mut peer_next = false;
313
314 if cfg.interface.is_some() {
315 anyhow::bail!("cannot have more than one interface");
316 }
317
318 while let Some(line) = iter.next() {
319 if line == "[Peer]" {
320 peer_next = true;
321 break;
322 }
323
324 let (key, value) = parse_key_value(line)?;
325 match key {
326 FIELD_PRIVATE_KEY => private_key = Some(value.parse()?),
327 FIELD_LISTEN_PORT => listen_port = Some(value.parse()?),
328 FIELD_FWMARK => fw_mark = Some(value.parse()?),
329 FIELD_ADDRESS => address = parse_csv(value)?,
330 FIELD_DNS => dns = Some(value.to_string()),
331 _ => anyhow::bail!("unexpected key: {}", key),
332 }
333 }
334
335 cfg.interface = Some(WgInterface {
336 private_key: private_key.ok_or_else(|| anyhow::anyhow!("interface missing private key"))?,
337 address,
338 listen_port,
339 fw_mark,
340 dns,
341 });
342
343 if peer_next {
344 parse_peer(cfg, iter)
345 } else {
346 Ok(())
347 }
348}
349
350fn parse_peer<'s, I: Iterator<Item = &'s str>>(
351 cfg: &mut PartialConf,
352 iter: &mut I,
353) -> anyhow::Result<()> {
354 let mut public_key = None;
355 let mut preshared_key = None;
356 let mut allowed_ips = Vec::new();
357 let mut endpoint = None;
358 let mut keep_alive = None;
359 let mut interface_next = false;
360 let mut peer_next = false;
361
362 while let Some(line) = iter.next() {
363 if line == "[Interface]" {
364 interface_next = true;
365 break;
366 }
367 if line == "[Peer]" {
368 peer_next = true;
369 break;
370 }
371
372 let (key, value) = parse_key_value(line)?;
373 match key {
374 FIELD_PUBLIC_KEY => public_key = Some(value.parse()?),
375 FIELD_PRE_SHARED_KEY => preshared_key = Some(value.parse()?),
376 FIELD_ALLOWED_IPS => allowed_ips = parse_csv(value)?,
377 FIELD_ENDPOINT => endpoint = Some(value.to_string()),
378 FIELD_PERSISTENT_KEEPALIVE => keep_alive = Some(value.parse()?),
379 _ => anyhow::bail!("unexpected key: {}", key),
380 }
381 }
382
383 cfg.peers.push(WgPeer {
384 public_key: public_key.ok_or_else(|| anyhow::anyhow!("peer missing public key"))?,
385 preshared_key,
386 allowed_ips,
387 endpoint,
388 keep_alive,
389 });
390
391 if interface_next {
392 parse_interface(cfg, iter)
393 } else if peer_next {
394 parse_peer(cfg, iter)
395 } else {
396 Ok(())
397 }
398}
399
400fn parse_key_value<'s>(line: &'s str) -> anyhow::Result<(&'s str, &'s str)> {
401 line.split_once("=")
402 .map(|(k, v)| (k.trim(), v.trim()))
403 .ok_or_else(|| anyhow::anyhow!("invalid line: {}", line))
404}
405
406fn parse_csv<
407 'v,
408 T: FromStr<Err = impl std::error::Error + std::marker::Sync + std::marker::Send + 'static>,
409>(
410 value: &'v str,
411) -> anyhow::Result<Vec<T>> {
412 let mut values = Vec::new();
413 for v in value.split(',').map(str::trim) {
414 values.push(v.parse()?);
415 }
416 Ok(values)
417}
418
419#[cfg(test)]
420mod tests {
421 use std::net::Ipv4Addr;
422
423 use ipnet::{IpNet, Ipv4Net};
424
425 use crate::Key;
426
427 use super::{WgConfBuilder, WgPeerBuilder};
428
429 const TEST_CONF_1: &str = r#"
430 [Interface]
431 PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
432 ListenPort = 51820
433
434 [Peer]
435 PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
436 Endpoint = 192.95.5.67:1234
437 AllowedIPs = 10.192.122.3/32, 10.192.124.1/24
438
439 [Peer]
440 PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
441 Endpoint = [2607:5300:60:6b0::c05f:543]:2468
442 AllowedIPs = 10.192.122.4/32, 192.168.0.0/16
443
444 [Peer]
445 PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
446 Endpoint = test.wireguard.com:18981
447
448 AllowedIPs = 10.10.10.230/32
449 PersistentKeepalive = 54
450"#;
451
452 const TEST_CONF_2: &str = r#"
453 [Peer]
454 PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
455 Endpoint = 192.95.5.67:1234
456 AllowedIPs = 10.192.122.3/32, 10.192.124.1/24
457
458 [Peer]
459 PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
460 Endpoint = [2607:5300:60:6b0::c05f:543]:2468
461 AllowedIPs = 10.192.122.4/32, 192.168.0.0/16
462
463 [Interface]
464 PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
465 ListenPort = 51820
466
467 [Peer]
468 PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
469 Endpoint = test.wireguard.com:18981
470
471 AllowedIPs = 10.10.10.230/32
472 PersistentKeepalive = 54
473"#;
474
475 const TEST_CONF_3: &str = r#"
476 [Interface]
477 PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
478 ListenPort = 51820
479
480 [Interface]
481 PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
482 ListenPort = 51821
483
484 [Peer]
485 PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
486 Endpoint = test.wireguard.com:18981
487 AllowedIPs = 10.10.10.230/32
488"#;
489
490 const TEST_CONF_4: &str = "";
491
492 const TEST_CONF_5: &str = r#"
493 PublicKey = 1
494
495 [Interface]
496 PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
497 ListenPort = 51820
498
499 [Peer]
500 PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
501 Endpoint = test.wireguard.com:18981
502 AllowedIPs = 10.10.10.230/32
503"#;
504
505 const TEST_CONF_6: &str = r#"
506 [Interface]
507 PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
508 ListenPort = 51820
509 Unknown = 1
510
511 [Peer]
512 PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
513 Endpoint = test.wireguard.com:18981
514 AllowedIPs = 10.10.10.230/32
515"#;
516
517 const TEST_CONF_7: &str = r#"
518 [Interface]
519 PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
520 ListenPort = 51820
521"#;
522
523 #[test]
524 fn parse_config() {
525 parse_config_1_and_2(TEST_CONF_1);
526 }
527
528 #[test]
529 fn parse_config_out_of_order_interface() {
530 parse_config_1_and_2(TEST_CONF_2);
531 }
532
533 #[test]
534 #[should_panic]
535 fn parse_config_duplicate_interface() {
536 super::parse_conf(TEST_CONF_3).unwrap();
537 }
538
539 #[test]
540 #[should_panic]
541 fn parse_config_empty() {
542 super::parse_conf(TEST_CONF_4).unwrap();
543 }
544
545 #[test]
546 #[should_panic]
547 fn parse_config_out_of_order_field() {
548 super::parse_conf(TEST_CONF_5).unwrap();
549 }
550
551 #[test]
552 #[should_panic]
553 fn parse_config_unkown_field() {
554 super::parse_conf(TEST_CONF_6).unwrap();
555 }
556
557 #[test]
558 fn parse_config_no_peers() {
559 let cfg = super::parse_conf(TEST_CONF_7).unwrap();
560
561 assert_eq!(
562 "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=",
563 cfg.interface.private_key.to_string(),
564 );
565 assert_eq!(Some(51820), cfg.interface.listen_port);
566 assert_eq!(None, cfg.interface.fw_mark);
567
568 assert_eq!(0, cfg.peers.len());
569 }
570
571 fn parse_config_1_and_2(conf_str: &str) {
572 let cfg = super::parse_conf(conf_str).unwrap();
573
574 assert_eq!(
575 "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=",
576 cfg.interface.private_key.to_string()
577 );
578 assert_eq!(Some(51820), cfg.interface.listen_port);
579 assert_eq!(None, cfg.interface.fw_mark);
580
581 assert_eq!(3, cfg.peers.len());
582
583 let peer = &cfg.peers[0];
584 assert_eq!(
585 "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=",
586 peer.public_key.to_string()
587 );
588 assert_eq!(None, peer.preshared_key);
589 assert_eq!(2, peer.allowed_ips.len());
590 assert_eq!(Some("192.95.5.67:1234"), peer.endpoint.as_deref());
591 assert_eq!(None, peer.keep_alive);
592
593 let peer = &cfg.peers[1];
594 assert_eq!(
595 "TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=",
596 peer.public_key.to_string()
597 );
598 assert_eq!(None, peer.preshared_key);
599 assert_eq!(2, peer.allowed_ips.len());
600 assert_eq!(
601 Some("[2607:5300:60:6b0::c05f:543]:2468"),
602 peer.endpoint.as_deref()
603 );
604 assert_eq!(None, peer.keep_alive);
605
606 let peer = &cfg.peers[2];
607 assert_eq!(
608 "gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=",
609 peer.public_key.to_string()
610 );
611 assert_eq!(None, peer.preshared_key);
612 assert_eq!(1, peer.allowed_ips.len());
613 assert_eq!(Some("test.wireguard.com:18981"), peer.endpoint.as_deref());
614 assert_eq!(Some(54), peer.keep_alive);
615 }
616
617 #[test]
618 fn serialize_no_peers() {
619 let key = Key::decode("yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=").unwrap();
620 let conf = WgConfBuilder::new()
621 .fw_mark(10)
622 .listen_port(6000)
623 .dns("dns.example.com")
624 .address(IpNet::V4(
625 Ipv4Net::new(Ipv4Addr::new(10, 0, 0, 5), 24).unwrap(),
626 ))
627 .private_key(key)
628 .build();
629 let serialized = super::serialize_conf(&conf);
630
631 assert_eq!(
632 r#"[Interface]
633PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
634Address = 10.0.0.5/24
635ListenPort = 6000
636FwMark = 10
637DNS = dns.example.com
638"#,
639 serialized
640 );
641 }
642
643 #[test]
644 fn serialize_with_peers() {
645 let key1 = Key::decode("xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=").unwrap();
646 let key2 = Key::decode("TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=").unwrap();
647 let key3 = Key::decode("gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=").unwrap();
648
649 let conf = WgConfBuilder::new()
650 .private_key(key1)
651 .listen_port(51820)
652 .dns("dns.example.com")
653 .peer(
654 WgPeerBuilder::new(key2)
655 .keep_alive(10)
656 .endpoint("test.wireguard.com:18981")
657 .allowed_ip(ipnet::IpNet::V4(
658 Ipv4Net::new(Ipv4Addr::new(10, 0, 0, 2), 24).unwrap(),
659 ))
660 .build(),
661 )
662 .peer(
663 WgPeerBuilder::new(key3)
664 .allowed_ip(ipnet::IpNet::V4(
665 Ipv4Net::new(Ipv4Addr::new(10, 0, 0, 3), 24).unwrap(),
666 ))
667 .build(),
668 )
669 .build();
670
671 let serialized = super::serialize_conf(&conf);
672
673 assert_eq!(
674 r#"[Interface]
675PrivateKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
676ListenPort = 51820
677DNS = dns.example.com
678
679[Peer]
680PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
681AllowedIPs = 10.0.0.2/24
682Endpoint = test.wireguard.com:18981
683PersistentKeepalive = 10
684
685[Peer]
686PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
687AllowedIPs = 10.0.0.3/24
688"#,
689 serialized
690 );
691 }
692}