File Coverage

lib/Wireguard/WGmeta/Parser/Middleware.pm
Criterion Covered Total %
statement 99 100 99.0
branch 31 34 91.1
condition 6 6 100.0
subroutine 12 12 100.0
pod 2 2 100.0
total 150 154 97.4


line stmt bran cond sub pod time code
1             =pod
2              
3             =head1 NAME
4              
5             WGmeta::Parser::Middleware - Middleware between the parser and wrapper class(es)
6              
7             =head1 SYNOPSIS
8              
9             use Wireguard::WGmeta::Parser::Middleware;
10             use Wireguard::WGmeta::Util;
11              
12             # Parse a wireguard configuration file
13             my $config_contents = read_file('/path/to/config.conf', 'interface_name');
14             my $parsed_config = parse_wg_config2($config_contents);
15              
16             # And convert it to string representation again
17             my $new_config_content = create_wg_config2($parsed_config);
18              
19             =head1 DESCRIPTION
20              
21             Acts as a middleware between L and L. Most importantly it
22             implements the I and I callbacks of L.
23              
24             =head1 METHODS
25              
26             =cut
27             package Wireguard::WGmeta::Parser::Middleware;
28 5     5   39 use strict;
  5         10  
  5         197  
29 5     5   30 use warnings FATAL => 'all';
  5         22  
  5         177  
30 5     5   27 use experimental qw(signatures);
  5         9  
  5         50  
31              
32 5     5   2794 use Wireguard::WGmeta::Parser::Conf qw(INTERNAL_KEY_PREFIX parse_raw_wg_config);
  5         11  
  5         305  
33 5     5   2592 use Wireguard::WGmeta::ValidAttributes;
  5         13  
  5         344  
34 5     5   2200 use Wireguard::WGmeta::Utils;
  5         13  
  5         348  
35              
36 5     5   43 use base 'Exporter';
  5         14  
  5         5285  
37             our @EXPORT = qw(parse_wg_config2 create_wg_config2);
38              
39             our $VERSION = "0.3.3";
40              
41             =head3 parse_wg_config2($config_file_content, $interface_name [, $wg_meta_prefix, $disabled_prefix, $use_checksum])
42              
43             Using the I and I, this method enriches the parsed config by several artefacts:
44             I, I, and I. Considering this minimal config example:
45              
46             #+root_attr1 = value1
47              
48             [Interface]
49             ListenPort = 12345
50              
51             [Peer]
52             #+Alias = some_alias
53             PublicKey = peer_1
54              
55             We end up with the following structure:
56              
57             {
58             'root_attr1' => value1,
59             INT_PREFIX.'root_order' => [
60             'root_attr1'
61             ],
62             INT_PREFIX.'section_order' => [
63             'interface_1',
64             'peer_1'
65             ],
66             INT_PREFIX.'n_peers' => 1,
67             INT_PREFIX.'observer_wg_meta_attrs => {
68             'alias' => 1
69             },
70             INT_PREFIX.'alias_map => {
71             'some_alias' => 'peer_1'
72             },
73             'interface_name => 'interface_name',
74             'interface_1 => {
75             'listen-port' => 12345,
76             INT_PREFIX.'type' => 'Interface',
77             INT_PREFIX.'order' => [
78             'listen-port',
79             ]
80             },
81             'peer_1' => {
82             'alias' => 'some_alias',
83             INT_PREFIX.'type => 'Peer',
84             INT_PREFIX.'order => [
85             'alias',
86             ]
87             }
88             }
89              
90             B
91              
92             =over 1
93              
94             =item *
95              
96             All attributes listed in L are referenced by their key. This means, if you want for
97             example access I the key would be I. Any attribute not present in L
98             is stored (and written back) as they appear in Config.
99              
100             =item *
101              
102             This method can be used as stand-alone in conjunction with the L<>
103              
104             =item *
105              
106             If the section is of type 'Peer' the identifier equals to its public-key, otherwise its the interface name again.
107              
108             =item *
109              
110             wg-meta attributes are always prefixed with C<$wg_meta_prefix>.
111              
112              
113             =back
114              
115             B
116              
117             =over 1
118              
119             =item *
120              
121             C<$config_file_content> String containing the contents of a Wireguard configuration file.
122              
123             =item *
124              
125             C<$interface_name> Interface name
126              
127             =item *
128              
129             C<[$wg_meta_prefix = '#+']> wg-meta prefix. Must start with '#' or ';'
130              
131             =item *
132              
133             C<[$disabled_prefix = '#-']> disabled prefix. Must start with '#' or ';'
134              
135             =item *
136              
137             C<[$use_checksum = TRUE]> If set to False, checksum is not checked
138              
139              
140             =back
141              
142             B
143              
144             An exceptions if:
145              
146             =over 1
147              
148             =item *
149              
150             If the parser ends up in an invalid state (e.g a section without information). Or An alias is defined twice.
151              
152             =back
153              
154             A warning:
155              
156             =over 1
157              
158             =item *
159              
160             On a checksum mismatch
161              
162             =back
163              
164             B
165              
166             A reference to a hash with the structure described above. Or if the configuration file is not a Wireguard configuration: undef.
167              
168             =cut
169 117     117 1 226 sub parse_wg_config2($config_file_content, $interface_name, $wg_meta_prefix = '#+', $disabled_prefix = '#-', $use_checksum = 1) {
  117         207  
  117         195  
  117         211  
  117         179  
  117         192  
  117         176  
170              
171 117 100       495 return undef unless ($config_file_content =~ /\[Interface\]/);
172              
173 43         87 my %alias_map;
174             my %observed_wg_meta_attrs;
175 43         71 my $peer_count = 0;
176 43         75 my $alias_to_consume;
177             my $old_checksum;
178              
179 500     500   694 my $entry_handler = sub($raw_key, $raw_value, $is_wg_meta) {
  500         758  
  500         1145  
  500         738  
  500         679  
180 500         783 my $final_key = $raw_key;
181 500         714 my $final_value = $raw_value;
182              
183              
184             # Convert known Keys to attr-name style
185 500 100       1503 $final_key = NAME_2_KEYS_MAPPING->{$raw_key} if exists NAME_2_KEYS_MAPPING->{$raw_key};
186              
187 500 100       1009 $observed_wg_meta_attrs{$final_key} = 1 if $is_wg_meta;
188             # register alias to consume (if any)
189 500 100       944 $alias_to_consume = $raw_value if $raw_key eq 'Alias';
190              
191 500 100       903 if ($raw_key eq 'Checksum') {
192 17         30 $old_checksum = $raw_value;
193             # discard old checksum
194 17         67 return undef, undef, 1;
195             }
196              
197 483         1264 return $final_key, $final_value, 0;
198 43         244 };
199              
200 117     117   185 my $new_section_handler = sub($identifier, $section_type, $is_active) {
  117         177  
  117         196  
  117         169  
  117         184  
201 117 100       291 $peer_count++ if $section_type eq 'Peer';
202              
203             # Consume alias (if any)
204 117 100       252 if (defined $alias_to_consume) {
205 56 50       125 die "Alias `$alias_to_consume` is already defined on $interface_name" if exists $alias_map{$alias_to_consume};
206 56         115 $alias_map{$alias_to_consume} = $identifier;
207 56         100 $alias_to_consume = undef;
208             }
209              
210 117 100       316 return ($section_type eq 'Interface') ? $interface_name : $identifier;
211              
212 43         155 };
213              
214 43         149 my $parsed_config = parse_raw_wg_config($config_file_content, $entry_handler, $new_section_handler, 0, $wg_meta_prefix, $disabled_prefix);
215 43         95 $parsed_config->{INTERNAL_KEY_PREFIX . 'alias_map'} = \%alias_map;
216 43         89 $parsed_config->{INTERNAL_KEY_PREFIX . 'n_peers'} = $peer_count;
217 43         94 $parsed_config->{INTERNAL_KEY_PREFIX . 'interface_name'} = $interface_name;
218 43         91 $parsed_config->{INTERNAL_KEY_PREFIX . 'observed_wg_meta_attrs'} = \%observed_wg_meta_attrs;
219              
220 43 100 100     179 if ($use_checksum == 1 && defined $old_checksum) {
221 8         25 my $new_checksum = compute_md5_checksum(create_wg_config2($parsed_config, $wg_meta_prefix, $disabled_prefix, 1));
222 8 50       30 warn("Checksum mismatch `$interface_name` has been altered in the meantime") if not $new_checksum eq $old_checksum;
223             }
224              
225 43         427 return $parsed_config;
226             }
227              
228             =head3 create_wg_config2($ref_interface_config [, $wg_meta_prefix, $disabled_prefix, $no_checksum])
229              
230             Turns a reference of interface-config hash (just a single interface!) back into a wireguard config.
231              
232             B
233              
234             =over 1
235              
236             =item *
237              
238             C<$ref_interface_config> Reference to hash containing B interface config.
239              
240             =item *
241              
242             C<[$wg_meta_prefix = '#+']> Has to start with a '#' or ';' character and is ideally the
243             same as in L
244              
245             =item *
246              
247             C<[$wg_meta_prefix = '#-']> Same restrictions as parameter C<$wg_meta_prefix>
248              
249             =item *
250              
251             C<[$no_checksum = FALSE]> If set to true, no header checksum is calculated and added to the output
252              
253             =back
254              
255             B
256              
257             A string, ready to be written down as a config file.
258              
259             =cut
260 29     29 1 51 sub create_wg_config2($ref_interface_config, $wg_meta_prefix = '#+', $disabled_prefix = '#-', $no_checksum = 0) {
  29         50  
  29         53  
  29         63  
  29         54  
  29         42  
261 29         53 my $new_config = "";
262              
263 29         57 for my $identifier (@{$ref_interface_config->{INTERNAL_KEY_PREFIX . 'section_order'}}) {
  29         89  
264 84 50       238 if (not ref($ref_interface_config->{$identifier}) eq 'HASH') {
265             # We are in root section
266 0         0 $new_config .= _write_line($identifier, $ref_interface_config->{$identifier}, '', $wg_meta_prefix);
267             }
268             else {
269             # First lets check if the following section is active
270             my $is_disabled = (exists $ref_interface_config->{$identifier}{'disabled'}
271 84 100 100     347 and $ref_interface_config->{$identifier}{'disabled'} == 1) ? $disabled_prefix : '';
272              
273             # Add [Interface] or [Peer]
274 84         239 $new_config .= "\n$is_disabled" . "[$ref_interface_config->{$identifier}{INTERNAL_KEY_PREFIX . 'type'}]\n";
275              
276             # Add config lines
277 84         136 for my $attr_name (@{$ref_interface_config->{$identifier}{INTERNAL_KEY_PREFIX . 'order'}}) {
  84         216  
278              
279 362 100       725 my $is_wg_meta = (exists $ref_interface_config->{INTERNAL_KEY_PREFIX .'observed_wg_meta_attrs'}{$attr_name}) ? $wg_meta_prefix : '';
280 362         684 $new_config .= _write_line($attr_name, $ref_interface_config->{$identifier}{$attr_name}, $is_disabled, $is_wg_meta);
281             }
282             }
283             }
284 29 100       81 if ($no_checksum == 0) {
285 10         32 return "#+Checksum = " . compute_md5_checksum($new_config) . "\n" . $new_config;
286             }
287 19         104 return $new_config;
288             }
289              
290             # internal method to create on config line
291 362     362   511 sub _write_line($attr_name, $attr_value, $is_disabled, $is_wg_meta) {
  362         545  
  362         505  
  362         485  
  362         512  
  362         476  
292 362         506 my $cfg_line = '';
293             # if we have a comment
294 362 100       749 if (substr($attr_name, 0, 7) eq 'comment') {
295 9         22 $cfg_line .= $attr_value . "\n";
296             }
297             else {
298 353 100       829 my $inconfig_name = exists KNOWN_ATTRIBUTES->{$attr_name} ? KNOWN_ATTRIBUTES->{$attr_name}{in_config_name} : $attr_name;
299 353         821 $cfg_line .= "$is_disabled$is_wg_meta$inconfig_name = $attr_value\n";
300             }
301 362         868 return $cfg_line;
302             }
303              
304              
305             1;