File Coverage

lib/Wireguard/WGmeta/Wrapper/Config.pm
Criterion Covered Total %
statement 354 385 91.9
branch 84 114 73.6
condition 16 21 76.1
subroutine 40 43 93.0
pod 22 25 88.0
total 516 588 87.7


line stmt bran cond sub pod time code
1             =pod
2              
3             =head1 NAME
4              
5             WGmeta::Wrapper::Config - Class for interfacing the wireguard configuration
6              
7             =head1 SYNOPSIS
8              
9             use Wireguard::WGmeta::Wrapper::Config;
10             my $wg_meta = Wireguard::WGmeta::Wrapper::Config->new('');
11              
12             =head1 DESCRIPTION
13              
14             This class provides wrapper-functions around a wireguard configuration parsed by L which
15             allow to edit, add and remove interfaces and peers.
16              
17             =head1 CONCURRENCY
18              
19             Please refer to L
20              
21             =head1 EXAMPLES
22              
23             use Wireguard::WGmeta::Wrapper::Config;
24             my $wg-meta = Wireguard::WGmeta::Wrapper::Config->new('');
25              
26             # set an attribute (non wg-meta attributes forwarded to the original `wg set` command)
27             wg_meta->set('wg0', 'WG_0_PEER_A_PUBLIC_KEY', '', '');
28              
29             # set an alias for a peer
30             wg_meta->set('wg0', 'WG_0_PEER_A_PUBLIC_KEY', 'alias', 'some_fancy_alias');
31              
32             # disable peer (this comments out the peer in the configuration file)
33             wg_meta->disable('wg0', 'some_fancy_alias');
34              
35             # write config (if parameter is set to True, the config is overwritten, if set to False the resulting file is suffixed with '.not_applied'
36             wg_meta->commit(1);
37              
38             =head1 METHODS
39              
40             =cut
41              
42 5     5   234893 use v5.20.0;
  5         46  
43             package Wireguard::WGmeta::Wrapper::Config;
44 5     5   27 use strict;
  5         10  
  5         113  
45 5     5   23 use warnings;
  5         10  
  5         163  
46 5     5   45 use experimental 'signatures';
  5         8  
  5         38  
47 5     5   3046 use Wireguard::WGmeta::Wrapper::Bridge;
  5         12  
  5         323  
48 5     5   2469 use Wireguard::WGmeta::Parser::Middleware;
  5         15  
  5         288  
49 5     5   37 use Wireguard::WGmeta::ValidAttributes;
  5         16  
  5         322  
50 5     5   30 use Wireguard::WGmeta::Utils;
  5         12  
  5         286  
51 5     5   106 use Wireguard::WGmeta::Parser::Conf qw(INTERNAL_KEY_PREFIX);
  5         14  
  5         264  
52              
53             our $VERSION = "0.3.4"; # do not change manually, this variable is updated when calling make
54              
55 5     5   29 use constant FALSE => 0;
  5         10  
  5         264  
56 5     5   29 use constant TRUE => 1;
  5         10  
  5         23864  
57              
58             =head3 new($wireguard_home [, $wg_meta_prefix, $wg_meta_disabled_prefix, $custom_attributes])
59              
60             Creates a new instance of this class.
61              
62             B
63              
64             =over 1
65              
66             =item *
67              
68             C<$wireguard_home> Path to Wireguard configuration files. Make sure the path ends with a `/`.
69              
70             =item *
71              
72             C<[$wg_meta_prefix]> A custom wg-meta comment prefix, has to begin with either `;` or `#`.
73             It is recommended to not change this setting, especially in a already deployed installation.
74              
75             =item *
76              
77             C<[$wg_meta_disabled_prefix]> A custom prefix for the commented out (disabled) sections,
78             has to begin with either `;` or `#` and must not be equal with C<$wg_meta_prefix>! (This is enforced and an exception is thrown if violated)
79             It is recommended to not change this setting, especially in an already deployed installation.
80              
81             =item *
82              
83             C<[$not_applied_suffix]> Suffix to add if C is set to not override an existing config.
84              
85             =item *
86              
87             C<[$custom_attributes]> A reference to a hash defining custom attributes. Expects the following structure:
88              
89             {
90             'attr_key' => {
91             'validator' => 'Ref to validation function'
92             },
93             'example' => {
94             'validator' => sub ($attr, $value) {
95             return ($attr eq 'example') ? 1 : 0;
96             }
97             },
98             ...
99             }
100              
101             =back
102              
103             B
104              
105             An instance of WGmeta::Wrapper::Config
106              
107             =cut
108 10     10 1 77 sub new($class, $wireguard_home, $wg_meta_prefix = '#+', $wg_meta_disabled_prefix = '#-', $not_applied_suffix = '.not_applied', $custom_attributes = undef) {
  10         19  
  10         20  
  10         18  
  10         27  
  10         20  
  10         20  
  10         15  
109              
110 10 50       39 if ($wg_meta_prefix eq $wg_meta_disabled_prefix) {
111 0         0 die '`$wg_meta_prefix` and `$wg_meta_disabled_prefix` have to be different';
112             }
113              
114 10 100       107 my $self = {
115             'wireguard_home' => $wireguard_home,
116             'wg_meta_prefix' => $wg_meta_prefix,
117             'wg_meta_disabled_prefix' => $wg_meta_disabled_prefix,
118             'not_applied_suffix' => $not_applied_suffix,
119             'n_conf_files' => {},
120             'parsed_config' => {},
121             'reload_listeners' => {},
122             'custom_attributes' => defined $custom_attributes ? $custom_attributes : {}
123             };
124              
125 10         39 _read_configs_from_folder2($self);
126              
127 10         53 bless $self, $class;
128 10         35 return $self;
129             }
130              
131 10     10   16 sub _read_configs_from_folder2($self) {
  10         24  
  10         16  
132 10         31 my ($all_dot_conf, $count) = get_all_conf_files($self->{wireguard_home});
133 10         20 for my $possible_config_path (@{$all_dot_conf}) {
  10         33  
134 40         73 my $interface = $possible_config_path;
135 40         464 $interface =~ s/^\/|\\|.*\/|.*\\|.conf$//g;
136 40         110 may_reload_from_disk($self, $interface, TRUE, TRUE, TRUE);
137             }
138             }
139              
140             =head3 set($interface, $identifier, $attribute, $value [, $unknown_callback])
141              
142             Sets a value on a specific interface section. If C == C<$value> this sub is essentially a No-Op.
143              
144             B
145              
146             =over 1
147              
148             =item *
149              
150             C<$interface> Valid interface identifier (e.g 'wg0')
151              
152             =item *
153              
154             C<$identifier> Either an interface name, an alias or public-key of a peer
155              
156             =item *
157              
158             C<$attribute> Attribute name (case does matter!)
159              
160             =item *
161              
162             C<[$unknown_callback = undef]> A reference to a callback function which is fired when a previously unknown attribute is set.
163             Expected signature:
164              
165             sub my_unknown_callback($attribute, $value) {
166             # Handling of this particular case
167             return $attribute, $value;
168             }
169              
170             If not defined, a warning is emitted
171              
172             =back
173              
174             B
175              
176             Exception if:
177              
178             =over 1
179              
180             =item *
181              
182             Value is not defined
183              
184             =item *
185              
186             Interface is invalid
187              
188             =item *
189              
190             Identifier is invalid (also if alias translation fails)
191              
192             =item *
193              
194             Attribute is not valid for target section (Interface, Peer)
195              
196             =item *
197              
198             Validation for the attribute value fails
199              
200             =back
201              
202             B
203              
204             None
205              
206             =cut
207 41     41 1 2576 sub set($self, $interface, $identifier, $attribute, $value, $unknown_callback = undef) {
  41         67  
  41         64  
  41         64  
  41         61  
  41         60  
  41         69  
  41         50  
208             # Assertions
209 41 50       95 die "Undefined value for `$attribute` in interface `$interface` NOT SET" unless defined($value);
210 41 50       107 die "Invalid interface name `$interface`" unless $self->is_valid_interface($interface);
211 41         286 $identifier = $self->try_translate_alias($interface, $identifier);
212 41 50       101 die "Invalid identifier `$identifier` for interface `$interface`" unless $self->is_valid_identifier($interface, $identifier);
213 41         123 my $attr_type = get_attr_type($attribute);
214 41 100       103 if ($interface eq $identifier) {
215             # We have an interface
216 6 100       24 die "Attribute `$attribute` it not valid for the interface section" if $attr_type == ATTR_TYPE_IS_WG_ORIG_PEER;
217             }
218             else {
219 35 100 66     153 die "Attribute `$attribute` is not valid for a peer section" if $attr_type == ATTR_TYPE_IS_WG_ORIG_INTERFACE or $attr_type == ATTR_TYPE_IS_WG_QUICK;
220             }
221              
222             # skip if same value
223 39 50 66     168 if (exists $self->{parsed_config}{$interface}{$identifier}{$attribute} && $self->{parsed_config}{$interface}{$identifier}{$attribute} eq $value) {
224 0         0 return;
225             }
226              
227             # Call attribute validation function
228 39 100       92 die "Invalid attribute value `$value` for `$attribute`" unless $self->attr_value_is_valid($attribute, $value);
229              
230 38 100       106 unless (exists $self->{parsed_config}{$interface}{$identifier}{$attribute}) {
231              
232 25 100       65 if (not exists $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'observed_wg_meta_attrs'}{$attribute}) {
233 23 100       73 if (exists KNOWN_ATTRIBUTES->{$attribute}) {
    100          
234             # we have to first occurrence of a known but yet unseen wg-meta attribute
235 14 100       39 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'observed_wg_meta_attrs'}{$attribute} = 1 if KNOWN_ATTRIBUTES->{$attribute}{type} == ATTR_TYPE_IS_WG_META;
236             }
237             elsif (exists $self->{custom_attributes}{$attribute}) {
238             # we have a registered custom attribute
239 1         5 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'observed_wg_meta_attrs'}{$attribute} = 1
240             }
241             else {
242             # we have a completely new, unknown attribute
243 8 50       29 if (defined $unknown_callback) {
244 8         17 ($attribute, $value) = &{$unknown_callback}($attribute, $value);
  8         23  
245             }
246             else {
247 0         0 warn "Attribute `$attribute` was previously not known on interface `$interface`";
248             }
249 7         196 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'observed_wg_meta_attrs'}{$attribute} = 1;
250             }
251             }
252             # the attribute does not (yet) exist in the configuration, lets add it to the list
253 24         41 push @{$self->{parsed_config}{$interface}{$identifier}{INTERNAL_KEY_PREFIX . 'order'}}, $attribute;
  24         85  
254             }
255 37 100       105 if ($attribute eq 'alias') {
256 8         28 $self->_update_alias_map($interface, $identifier, $value);
257             }
258 36         84 $self->{parsed_config}{$interface}{$identifier}{$attribute} = $value;
259 36         111 $self->_set_changed($interface);
260             }
261              
262             =head3 attr_value_is_valid($attribute, $value, $ref_valid_attrs)
263              
264             Simply calls the C function defined in L or C<$custom_attributs>
265              
266             B
267              
268             =over 1
269              
270             =item
271              
272             C<$attribute> Attribute name
273              
274             =item
275              
276             C<$value> Attribute value
277              
278             =back
279              
280             B
281              
282             True if validation was successful (or no validator function present), False if not.
283              
284             =cut
285 39     39 1 54 sub attr_value_is_valid($self, $attribute, $value) {
  39         57  
  39         56  
  39         61  
  39         76  
286 39 100       91 return &{KNOWN_ATTRIBUTES->{$attribute}{validator}}($value) if exists KNOWN_ATTRIBUTES->{$attribute};
  27         78  
287 12 100       43 return &{$self->{custom_attributes}{$attribute}{validator}}($value) if exists $self->{custom_attributes}{$attribute};
  1         4  
288 11         31 return 1;
289             }
290              
291 8     8   13 sub _update_alias_map($self, $interface, $identifier, $alias) {
  8         20  
  8         15  
  8         11  
  8         12  
  8         18  
292 8 100       24 unless (exists $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'alias_map'}{$alias}) {
293 7         27 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'alias_map'}{$alias} = $identifier;
294             }
295             else {
296 1         22 die "Alias `$alias` is already defined on interface `$interface`";
297             }
298             }
299              
300              
301             =head3 disable($interface, $identifier)
302              
303             Disables an interface/peer and setting the wg-meta attribute `Disabled` to C<1>.
304              
305             B
306              
307             =over 1
308              
309             =item *
310              
311             C<$interface> Valid interface name (e.g 'wg0').
312              
313             =item *
314              
315             C<$identifier> A valid identifier (or alias): If the target section is a peer, this is usually the public key of this peer. If target is an interface,
316             its again the interface name.
317              
318             =back
319              
320             B
321              
322             None
323              
324             =cut
325 3     3 1 272 sub disable($self, $interface, $identifier,) {
  3         6  
  3         5  
  3         5  
  3         10  
326 3         9 $self->_toggle($interface, $identifier, TRUE);
327             }
328              
329             =head3 enable($interface, $identifier)
330              
331             Inverse method if L
332              
333             =cut
334 7     7 1 15 sub enable($self, $interface, $identifier) {
  7         11  
  7         10  
  7         14  
  7         13  
335 7         25 $self->_toggle($interface, $identifier, FALSE);
336             }
337              
338             # internal toggle method (DRY)
339 10     10   19 sub _toggle($self, $interface, $identifier, $enable) {
  10         16  
  10         15  
  10         15  
  10         16  
  10         11  
340 10         29 $identifier = $self->try_translate_alias($interface, $identifier);
341             # we can bypass an "expensive" set() here
342 10         27 $self->{parsed_config}{$interface}{$identifier}{'disabled'} = $enable;
343 10         23 $self->_set_changed($interface);
344             }
345              
346             =head3 is_valid_interface($interface)
347              
348             Checks if an interface name is valid (present in parsed config)
349              
350             B
351              
352             =over 1
353              
354             =item
355              
356             C<$interface> An interface name
357              
358             =back
359              
360             B
361              
362             True if present, undef if not.
363              
364             =cut
365 59     59 1 90 sub is_valid_interface($self, $interface) {
  59         89  
  59         88  
  59         87  
366 59         222 return (exists $self->{parsed_config}{$interface});
367             }
368              
369              
370             =head3 is_valid_alias($interface, $alias)
371              
372             Simply checks if an alias is valid for a given interface
373              
374             =cut
375 2     2 1 4 sub is_valid_alias($self, $interface, $alias) {
  2         4  
  2         3  
  2         3  
  2         4  
376 2         11 return exists $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'alias_map'}{$alias}
377             }
378              
379             =head3 is_valid_identifier($interface, $identifier)
380              
381             Checks if an identifier is valid for a given interface
382              
383             B
384              
385             =over 1
386              
387             =item
388              
389             C<$interface> An interface name
390              
391             =item
392              
393             C<$identifier> An identifier (no alias!)
394              
395             =back
396              
397             B
398              
399             True if present, undef if not.
400              
401             =cut
402 51     51 1 69 sub is_valid_identifier($self, $interface, $identifier) {
  51         77  
  51         75  
  51         73  
  51         82  
403 51         186 return (exists $self->{parsed_config}{$interface}{$identifier});
404             }
405              
406             =head3 try_translate_alias($interface, $may_alias)
407              
408             Tries to translate an identifier (which may be an alias).
409             no exception is thrown on failure, instead the C<$may_alias> is returned.
410              
411             B
412              
413             =over 1
414              
415             =item
416              
417             C<$interface> A valid interface name
418              
419             =item
420              
421             C<$may_alias> An identifier which could be a valid alias for this interface
422              
423             =back
424              
425             B
426              
427             If the alias is valid for the specified interface, the corresponding identifier is returned, else C<$may_alias>
428              
429             =cut
430 62     62 1 105 sub try_translate_alias($self, $interface, $may_alias) {
  62         104  
  62         95  
  62         88  
  62         81  
431 62 100       159 if (exists $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'alias_map'}{$may_alias}) {
432 5         31 return $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'alias_map'}{$may_alias};
433             }
434             else {
435 57         134 return $may_alias;
436             }
437             }
438              
439             =head3 get_all_conf_files($wireguard_home)
440              
441             Returns a list of all files in C<$wireguard_home> matching I.
442              
443             B
444              
445             =over 1
446              
447             =item
448              
449             C<$wireguard_home> Path to a folder where wireguard configuration files are located
450              
451             =back
452              
453             B
454              
455             A reference to a list with absolute paths to the config files (possibly empty)
456              
457             =cut
458 10     10 1 21 sub get_all_conf_files($wireguard_home) {
  10         17  
  10         15  
459 10         60 my @config_files = read_dir($wireguard_home, qr/.*\.conf$/);
460 10 50       45 if (@config_files == 0) {
461 0         0 die "No matching interface configuration(s) in " . $wireguard_home;
462             }
463 10         21 my $count = @config_files;
464 10         38 return \@config_files, $count;
465             }
466              
467              
468             =head3 commit([$is_hot_config = FALSE, $no_checksum = FALSE])
469              
470             Writes down the parsed config to the wireguard configuration folder
471              
472             B
473              
474             =over 1
475              
476             =item
477              
478             C<[$is_hot_config = FALSE])> If set to TRUE, the existing configuration is overwritten (and possibly existing, not applied configs are deleted). Otherwise,
479             the suffix '.not_applied' is appended to the filename
480              
481             =item
482              
483             C<[$no_checksum = FALSE])> If set to TRUE, no checksum is written
484              
485             =back
486              
487             B
488              
489             Exception if: Folder or file is not writeable
490              
491             B
492              
493             None
494              
495             =cut
496 7     7 1 292 sub commit($self, $is_hot_config = FALSE, $no_checksum = FALSE) {
  7         11  
  7         12  
  7         16  
  7         12  
497 7         14 for my $interface (keys %{$self->{parsed_config}}) {
  7         30  
498 14 100       36 if ($self->_has_changed($interface)) {
499 7         32 my $new_config = create_wg_config2($self->{parsed_config}{$interface}, $self->{wg_meta_prefix}, $self->{wg_meta_disabled_prefix}, $no_checksum);
500 7         14 my $fh;
501 7         37 my $hot_path = $self->{wireguard_home} . $interface . '.conf';
502 7         22 my $safe_path = $self->{wireguard_home} . $interface . $self->{not_applied_suffix};
503 7 100       19 if ($is_hot_config == TRUE) {
504 6 50       574 open $fh, '>', $hot_path or die $!;
505 6         36 $self->{parsed_config}->{$interface}{INTERNAL_KEY_PREFIX . 'is_hot_config'} = 1;
506             }
507             else {
508 1 50       120 open $fh, '>', $safe_path or die $!;
509 1         6 $self->{parsed_config}->{$interface}{INTERNAL_KEY_PREFIX . 'is_hot_config'} = 0;
510             }
511             # write down to file
512 7         92 print $fh $new_config;
513 7         35 $self->_reset_changed($interface);
514 7 50       778 close $fh or die $!;
515              
516             # if there is an not applied version around delete it (if is_hot_config = True)
517 7 100 100     126 if (-e $safe_path && $is_hot_config) {
518 1         71 unlink $safe_path;
519             }
520             # Notify listeners about a file change
521 7         33 $self->_call_reload_listeners($interface);
522             }
523             }
524             }
525              
526              
527             =head3 get_interface_list()
528              
529             Return a list of all interfaces.
530              
531             B
532              
533             A list of all valid interface names. If no interfaces are available, an empty list is returned
534              
535             =cut
536 4     4 1 21 sub get_interface_list($self) {
  4         7  
  4         5  
537 4         7 return sort keys %{$self->{parsed_config}};
  4         26  
538             }
539              
540             =head3 get_interface_section($interface, $identifier)
541              
542             Returns a hash representing a section of a given interface
543              
544             B
545              
546             =over 1
547              
548             =item *
549              
550             C<$interface> Valid interface name
551              
552             =item *
553              
554             C<$identifier> Valid section identifier
555              
556             =back
557              
558             B
559              
560             A hash containing the requested section. If the requested section/interface is not present, an empty hash is returned.
561              
562             =cut
563 3     3 1 26 sub get_interface_section($self, $interface, $identifier) {
  3         14  
  3         16  
  3         7  
  3         5  
564 3         8 $identifier = $self->try_translate_alias($interface, $identifier);
565 3 50       9 if (exists $self->{parsed_config}{$interface}{$identifier}) {
566 3         6 my %r = %{$self->{parsed_config}{$interface}{$identifier}};
  3         27  
567 3         29 return %r;
568             }
569             else {
570 0         0 return ();
571             }
572             }
573              
574             =head3 get_section_list($interface)
575              
576             Returns a list of valid sections of an interface (ordered as in the original config file).
577              
578             B
579              
580             =over 1
581              
582             =item *
583              
584             C<$interface> A valid interface name
585              
586             =back
587              
588             B
589              
590             A list of all sections of an interface. If interface is not present, an empty list is returned.
591              
592             =cut
593 5     5 1 1616 sub get_section_list($self, $interface) {
  5         14  
  5         11  
  5         7  
594 5 100       13 if ($self->is_valid_interface($interface)) {
595 4         87 return @{$self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'section_order'}};
  4         44  
596             }
597             else {
598 1         4 return ();
599             }
600             }
601              
602 0     0 0 0 sub get_wg_meta_prefix($self) {
  0         0  
  0         0  
603 0         0 return $self->{wg_meta_prefix};
604             }
605              
606 0     0 0 0 sub get_disabled_prefix($self) {
  0         0  
  0         0  
607 0         0 return $self->{wg_meta_disabled_prefix};
608             }
609              
610             =head3 add_interface($interface_name, $ip_address, $listen_port, $private_key)
611              
612             Adds a (minimally configured) interface. If more attributes are needed, please set them using the C method.
613              
614             B No validation is performed on the values!
615              
616             B
617              
618             =over 1
619              
620             =item *
621              
622             C<$interface_name> A new interface name, must be unique.
623              
624             =item *
625              
626             C<$ip_address> A string describing the ip net(s) (e.g '10.0.0.0/24, fdc9:281f:04d7:9ee9::2/64')
627              
628             =item *
629              
630             C<$listen_port> The listen port for this interface.
631              
632             =item *
633              
634             C<$private_key> A private key for this interface
635              
636             =back
637              
638             B
639              
640             An exception if the interface name already exists.
641              
642             B
643              
644             None
645              
646             =cut
647 1     1 1 5 sub add_interface($self, $interface_name, $ip_address, $listen_port, $private_key) {
  1         2  
  1         3  
  1         3  
  1         2  
  1         2  
  1         2  
648 1 50       4 if ($self->is_valid_interface($interface_name)) {
649 0         0 die "Interface `$interface_name` already exists";
650             }
651 1         10 my %interface = (
652             'address' => $ip_address,
653             'listen-port' => $listen_port,
654             'private-key' => $private_key,
655             INTERNAL_KEY_PREFIX . 'type' => 'Interface',
656             INTERNAL_KEY_PREFIX . 'order' => [ 'address', 'listen-port', 'private-key' ]
657             );
658 1         5 $self->{parsed_config}{$interface_name}{$interface_name} = \%interface;
659 1         3 $self->{parsed_config}{$interface_name}{INTERNAL_KEY_PREFIX . 'alias_map'} = {};
660 1         4 $self->{parsed_config}{$interface_name}{INTERNAL_KEY_PREFIX . 'section_order'} = [ $interface_name ];
661 1         4 $self->{parsed_config}{$interface_name}{checksum} = 'none';
662 1         4 $self->{parsed_config}{$interface_name}{INTERNAL_KEY_PREFIX . 'mtime'} = 0.0;
663 1         5 $self->{parsed_config}{$interface_name}{INTERNAL_KEY_PREFIX . 'config_path'} = $self->{wireguard_home} . $interface_name . '.conf';
664 1         4 $self->{parsed_config}{$interface_name}{has_changed} = 1;
665              
666             }
667              
668             =head3 add_peer($interface, $ip_address, $public_key [, $alias, $preshared_key])
669              
670             Adds a peer to an exiting interface.
671              
672             B
673              
674             =over 1
675              
676             =item *
677              
678             C<$interface> A valid interface.
679              
680             =item *
681              
682             C<$ip_address> A string describing the ip-address(es) of this this peer.
683              
684             =item *
685              
686             C<$public_key> Public-key for this interface. This becomes the identifier of this peer.
687              
688             =item *
689              
690             C<[$preshared_key]> Optional argument defining the psk.
691              
692             =item *
693              
694             C<[$alias]> Optional argument defining an alias for this peer (wg-meta)
695              
696             =back
697              
698             B
699              
700             An exception if either the interface is invalid, the alias is already assigned or the public-key is
701             already present on an other peer.
702              
703             B
704              
705             A tuple consisting of the iface private-key and listen port
706              
707             =cut
708 5     5 1 1235 sub add_peer($self, $interface, $ip_address, $public_key, $alias = undef, $preshared_key = undef) {
  5         8  
  5         11  
  5         7  
  5         8  
  5         9  
  5         8  
  5         9  
709             # generate new key pair if not defined
710 5 50       19 if ($self->is_valid_interface($interface)) {
711 5 50       20 if ($self->is_valid_identifier($interface, $public_key)) {
712 0         0 die "An interface with this public-key already exists on `$interface`";
713             }
714             # generate peer config
715 5         15 my %peer = ();
716 5         16 $self->{parsed_config}{$interface}{$public_key} = \%peer;
717 5         22 $self->set($interface, $public_key, 'public-key', $public_key);
718 5         50 $self->set($interface, $public_key, 'allowed-ips', $ip_address);
719 5 100       24 if (defined $alias) {
720 4         12 $self->set($interface, $public_key, 'alias', $alias);
721             }
722 5 50       15 if (defined $preshared_key) {
723 0         0 $self->set($interface, $public_key, 'preshared-key', $preshared_key);
724             }
725 5         20 $self->enable($interface, $public_key);
726             # set type to to Peer
727 5         14 $self->{parsed_config}{$interface}{$public_key}{INTERNAL_KEY_PREFIX . 'type'} = 'Peer';
728             # add section to global section list
729 5         8 push @{$self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'section_order'}}, $public_key;
  5         14  
730 5         24 return $self->{parsed_config}{$interface}{$interface}{'private-key'}, $self->{parsed_config}{$interface}{$interface}{'listen-port'};
731             }
732             else {
733 0         0 die "Invalid interface `$interface`";
734             }
735             }
736              
737             =head3 remove_peer($interface, $identifier)
738              
739             Removes a peer (identified by it's public key or alias) from an interface.
740              
741             B
742              
743             =over 1
744              
745             =item
746              
747             C<$interface> A valid interface name
748              
749             =item
750              
751             C<$identifier> A valid identifier (or an alias)
752              
753             =back
754              
755             B
756              
757             Exception if interface or identifier is invalid
758              
759             B
760              
761             None
762              
763             =cut
764 2     2 1 505 sub remove_peer($self, $interface, $identifier) {
  2         5  
  2         4  
  2         3  
  2         5  
765 2 50       6 if ($self->is_valid_interface($interface)) {
766 2         9 $identifier = $self->try_translate_alias($interface, $identifier);
767 2 50       9 if ($self->is_valid_identifier($interface, $identifier)) {
768              
769             # delete section
770 2         11 delete $self->{parsed_config}{$interface}{$identifier};
771              
772             # delete from section list
773 2         7 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'section_order'} = [ grep {$_ ne $identifier} @{$self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'section_order'}} ];
  5         15  
  2         8  
774              
775             # decrease peer count
776 2         5 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'n_peers'}--;
777              
778             # delete alias (if exists)
779 2         6 while (my ($alias, $a_identifier) = each %{$self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'alias_map'}}) {
  5         32  
780 3 100       12 if ($a_identifier eq $identifier) {
781 2         8 delete $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'alias_map'}{$alias};
782             }
783             }
784 2         11 $self->_set_changed($interface);
785             }
786             else {
787 0         0 die "Invalid identifier `$identifier` for `$interface`";
788             }
789             }
790             else {
791 0         0 die "Invalid interface `$interface`";
792             }
793             }
794              
795             =head3 remove_interface($interface [, $keep_file = FALSE])
796              
797             Removes an interface. This command deletes the config file immediately. I.e no rollback possible!
798              
799             B
800              
801             =over 1
802              
803             =item
804              
805             C<$interface> A valid interface name
806              
807             =back
808              
809             B
810              
811             Exception if interface or identifier is invalid
812              
813             B
814              
815             None
816              
817             =cut
818 3     3 1 807 sub remove_interface($self, $interface) {
  3         6  
  3         6  
  3         5  
819 3 50       19 if ($self->is_valid_interface($interface)) {
820             # delete interface
821 3         24 delete $self->{parsed_config}{$interface};
822 3 50       59 if (-e "$self->{wireguard_home}$interface.conf") {
823 3 50       295 unlink "$self->{wireguard_home}$interface.conf" or warn "Could not delete `$self->{wireguard_home}$interface.conf` do you have the needed permissions?";
824             }
825 3         20 $self->{n_conf_files}--;
826             }
827             }
828              
829             =head3 get_peer_count([$interface = undef])
830              
831             Returns the number of peers.
832              
833             B Does return the count represented in the current (parsed) configuration state.
834              
835             B
836              
837             =over 1
838              
839             =item
840              
841             C<[$interface = undef]> If defined and valid, only return counts for this specific interface
842              
843             =back
844              
845             B
846              
847             Number of peers
848              
849             =cut
850 3     3 1 790 sub get_peer_count($self, $interface = undef) {
  3         4  
  3         5  
  3         5  
851 3 100 66     15 if (defined $interface && $self->is_valid_interface($interface)) {
852 2         12 return $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'n_peers'};
853             }
854             else {
855 1         1 my $count = 0;
856 1         3 for ($self->get_interface_list()) {
857 2         5 $count += $self->{parsed_config}{$_}{INTERNAL_KEY_PREFIX . 'n_peers'};
858             }
859 1         10 return $count;
860             }
861             }
862              
863             =head3 may_reload_from_disk($interface [, $new = FALSE])
864              
865             Method to reload an interface configuration from disk. Also useful to add an new (externally) created
866             interface on-the-fly. If a config file with a I<.not_applied> suffix is present (and its mtime is newer
867             than the original one), it is taken as source for reloading the configuration data.
868              
869             B
870              
871             =over 1
872              
873             =item *
874              
875             C<$interface> A valid interface name
876              
877             =item *
878              
879             C<[$new = FALSE]> If set to True, the parser looks at C<$wireguard_home> for this new interface config.
880              
881             =item *
882              
883             C<[$force = FALSE]> When set to True, the configuration is reloaded regardless of its mtime.
884              
885             =back
886              
887             B
888              
889             Exception: If the interface is invalid (or the config file is not found)
890              
891             B
892              
893             None, or undef if C<$new == True> and the interface is in fact not a wg config.
894              
895             =cut
896 149     149 1 247 sub may_reload_from_disk($self, $interface, $new = FALSE, $force = FALSE, $_init = FALSE) {
  149         237  
  149         236  
  149         225  
  149         209  
  149         205  
  149         220  
897 149         361 my $config_path = $self->{wireguard_home} . $interface . '.conf';
898             # check if there is a newer, not applied version, if yes prefer this version
899 149         306 my $not_applied_path = $self->{wireguard_home} . $interface . $self->{not_applied_suffix};
900 149 100       1996 if (-e $not_applied_path) {
901 1 50       6 if (get_mtime($not_applied_path) > get_mtime($config_path)) {
902 1         3 $config_path = $not_applied_path;
903             }
904             }
905 149 100       440 if ($new == FALSE) {
906             # do not use is_valid_interface() here otherwise there is a risk of infinite recursion (in a concurrent environment)
907 54 50       145 if (exists $self->{parsed_config}{$interface}) {
908             # we only reload if the on-disk version is newer than our local one
909             # There is however one exception: The local config is based on a not applied version and this file somehow
910             # unexpectedly deleted (e.g by a sysadmin..)
911 54         164 my $on_disk_mtime = get_mtime($config_path);
912             my $unexpected_delete = (exists $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'is_hot_config'}
913             && $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'is_hot_config'} == 0
914 54   66     335 && $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'mtime'} > $on_disk_mtime);
915              
916 54 100 66     457 if ($force || $unexpected_delete || $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'mtime'} < $on_disk_mtime) {
      100        
917 11         38 my $contents = read_file($config_path);
918 11         62 $self->{parsed_config}{$interface} = parse_wg_config2($contents, $interface, $self->{wg_meta_prefix}, $self->{wg_meta_disabled_prefix}, FALSE);
919 11         33 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'config_path'} = $config_path;
920 11         39 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'mtime'} = get_mtime($config_path);
921 11 100       110 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'is_hot_config'} = ($config_path =~ /$self->{not_applied_suffix}/) ? 0 : 1;
922 11 50       58 $self->_call_reload_listeners($interface) if $_init == FALSE;
923             }
924             }
925             else {
926 0         0 die "Invalid interface $interface - if this is a new interface, set `\$new` to True";
927             }
928             }
929             # We have a completely new interface
930             else {
931 95 50       1161 if (-e $config_path) {
932 95         384 my $contents = read_file($config_path);
933 95         488 my $maybe_new_config = parse_wg_config2($contents, $interface, $self->{wg_meta_prefix}, $self->{wg_meta_disabled_prefix}, FALSE);
934 95 100       254 if (defined $maybe_new_config) {
935 21         42 $self->{n_conf_files}++;
936 21         48 $self->{parsed_config}{$interface} = $maybe_new_config;
937 21         54 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'config_path'} = $config_path;
938 21         89 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'mtime'} = get_mtime($config_path);
939 21 50       170 $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'is_hot_config'} = ($config_path =~ /$self->{not_applied_suffix}/) ? 0 : 1;
940 21 100       119 $self->_call_reload_listeners($interface) if $_init == FALSE;;
941             }
942             else {
943 74         285 return undef;
944             }
945             }
946             else {
947 0         0 die "The interface $interface was not found in $self->{wireguard_home}";
948             }
949              
950             }
951              
952             }
953              
954             # internal method to create a configuration file (this method exists primarily for testing purposes)
955 4     4 0 535 sub create_config($self, $interface, $plain = FALSE) {
  4         6  
  4         7  
  4         6  
  4         6  
956             return create_wg_config2(
957             $self->{parsed_config}{$interface},
958             $self->{wg_meta_prefix},
959             $self->{wg_meta_disabled_prefix},
960 4         17 $plain = $plain)
961             }
962              
963 40     40   56 sub _has_changed($self, $interface) {
  40         61  
  40         63  
  40         64  
964 40         199 return exists $self->{parsed_config}{$interface}{has_changed};
965             }
966              
967 48     48   69 sub _set_changed($self, $interface) {
  48         75  
  48         82  
  48         64  
968 48         156 $self->{parsed_config}{$interface}{has_changed} = 1;
969             }
970              
971 17     17   32 sub _reset_changed($self, $interface) {
  17         28  
  17         27  
  17         26  
972 17 50       81 delete $self->{parsed_config}{$interface}{has_changed} if (exists $self->{parsed_config}{$interface}{has_changed});
973             }
974              
975             =head3 register_on_reload_listener($ref_handler, $handler_id [, $ref_listener_args = []])
976              
977             Register your callback handlers for the C event here. Your handler is called
978             B the reload happened, is blocking and exceptions are caught in an C environment.
979              
980             B
981              
982             =over 1
983              
984             =item
985              
986             C<$ref_handler> Reference to a handler function. The following signature is expected:
987              
988             sub my_handler_function($interface, $ref_list_args){
989             ...
990             }
991              
992             =item
993              
994             C<$handler_id> An identifier for you handler function. Must be unique!
995              
996             =item
997              
998             C<[$ref_listener_args = []]> A reference to an argument list for your handler function
999              
1000             =back
1001              
1002             B
1003              
1004             None, exception if C<$handler_id> is already present.
1005              
1006             =cut
1007 1     1 1 435 sub register_on_reload_listener($self, $ref_handler, $handler_id, $ref_listener_args = []) {
  1         3  
  1         3  
  1         3  
  1         2  
  1         1  
1008 1 50       8 unless ($self->{reload_listeners}{$handler_id}) {
1009 1         6 my $listener_data = {
1010             'handler' => $ref_handler,
1011             'args' => $ref_listener_args
1012             };
1013 1         4 $self->{reload_listeners}{$handler_id} = $listener_data;
1014             }
1015             else {
1016 0         0 die "Handler id $handler_id already present";
1017             }
1018              
1019             }
1020              
1021             =head3 remove_on_reload_listener($handler_id)
1022              
1023             Removes a reload callback handler by it's C<$handler_id>.
1024              
1025             B
1026              
1027             =over 1
1028              
1029             =item
1030              
1031             C<$handler_id> A valid handler id
1032              
1033             =back
1034              
1035             B
1036              
1037             1 on success, undef on failure.
1038              
1039             =cut
1040 0     0 1 0 sub remove_on_reload_listener($self, $handler_id) {
  0         0  
  0         0  
  0         0  
1041 0 0       0 if (exists $self->{reload_listeners}{$handler_id}) {
1042 0         0 delete $self->{reload_listeners}{$handler_id};
1043 0         0 return 1;
1044             }
1045             else {
1046 0         0 return undef;
1047             }
1048             }
1049              
1050 29     29   47 sub _call_reload_listeners($self, $interface) {
  29         57  
  29         54  
  29         50  
1051 29         42 for my $listener_id (keys %{$self->{reload_listeners}}) {
  29         266  
1052 1         3 eval {
1053 1         2 &{$self->{reload_listeners}{$listener_id}{handler}}($interface, $self->{reload_listeners}{$listener_id}{args});
  1         4  
1054             };
1055 1 50       18 if ($@) {
1056 0           warn "Call to reload_listener $listener_id failed: $@";
1057             }
1058             }
1059             }
1060              
1061              
1062             1;