line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
=pod |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
WGmeta::Wrapper::ConfigT - Class for interfacing the wireguard configuration supporting concurrent access |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 DESCRIPTION |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
Specialized child class of L which is capable of handling concurrent access. |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 SYNOPSIS |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
The interface is almost identical with the exception |
14
|
|
|
|
|
|
|
of L |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
use Wireguard::WGmeta::Wrapper::ConfigT; |
17
|
|
|
|
|
|
|
my $wg_metaT = Wireguard::WGmeta::Wrapper::ConfigT->new(''); |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 CONCURRENCY |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
To ensure that no inconsistent config files are generated, calls to a C may result in a reload from disk - namely |
22
|
|
|
|
|
|
|
when the config file on disk is newer than the current (parsed) one. So keep in mind to C as soon as possible |
23
|
|
|
|
|
|
|
(this is obviously only true for environments where such situations are possible to occur) |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# thread/process A |
26
|
|
|
|
|
|
|
$wg_metaT->set('wg0', 'WG_0_PEER_A_PUBLIC_KEY', 'alias', 'A'); |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# thread/process B |
29
|
|
|
|
|
|
|
$wg_metaT->set('wg0', 'WG_0_PEER_A_PUBLIC_KEY', 'alias', 'B'); |
30
|
|
|
|
|
|
|
$wg_metaT->commit(1); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# thread/process A (alias 'A' is overwritten by 'B') |
33
|
|
|
|
|
|
|
wg_metaT->disable_by_alias('wg0', 'A'); # throws exception `invalid alias`! |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
For more details about the reloading behaviour please refer to L. |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
B |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
FUNCTION commit($integrity_hashes) |
40
|
|
|
|
|
|
|
FOR $interface IN $known_interfaces |
41
|
|
|
|
|
|
|
IF has_changed($interface) THEN |
42
|
|
|
|
|
|
|
lock_exclusive($interface) |
43
|
|
|
|
|
|
|
UNLESS my_config_is_latest THEN |
44
|
|
|
|
|
|
|
$on_disk <- read_from_disk($interface) |
45
|
|
|
|
|
|
|
$contents <- create_wg_config($interface, $on_disk,$integrity_hashes) |
46
|
|
|
|
|
|
|
write($contents) |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
FUNCTION create_wg_config($interface, $on_disk, $integrity_hashes); |
49
|
|
|
|
|
|
|
$may_conflicting <- search_for_common_data($interface, $on_disk) |
50
|
|
|
|
|
|
|
FOR $section IN $may_conflicting |
51
|
|
|
|
|
|
|
$sha_internal <- calculate_sha_from_internal() |
52
|
|
|
|
|
|
|
$sha_disk <- calculate_sha_from_disk() |
53
|
|
|
|
|
|
|
IF $sha_internal NE $sha_disk |
54
|
|
|
|
|
|
|
IF $sha_disk EQ $integrity_hashes[$section] |
55
|
|
|
|
|
|
|
$section_data <- take_from_internal() |
56
|
|
|
|
|
|
|
ELSE |
57
|
|
|
|
|
|
|
$section_data <- take_from_disk() |
58
|
|
|
|
|
|
|
ELSE |
59
|
|
|
|
|
|
|
$section_data <- take_from_disk() |
60
|
|
|
|
|
|
|
$config_content .= create_section($section_data) |
61
|
|
|
|
|
|
|
$config_content .= create_non_conflicting() |
62
|
|
|
|
|
|
|
return $config_content |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head1 EXAMPLES |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
use Wireguard::WGmeta::Wrapper::ConfigT; |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# thread A |
69
|
|
|
|
|
|
|
my $wg_metaT = Wireguard::WGmeta::Wrapper::ConfigT->new(''); |
70
|
|
|
|
|
|
|
$wg_metaT->set('wg0', 'WG_0_PEER_A_PUBLIC_KEY', 'name', 'set_in_thread_A'); |
71
|
|
|
|
|
|
|
# Assumption: Our internal version is equal with the on-disk version at this point |
72
|
|
|
|
|
|
|
my $integrity_hash = $wg_metaT->calculate_sha_from_internal(); |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
# thread B |
75
|
|
|
|
|
|
|
my $wg_metaT = Wireguard::WGmeta::Wrapper::ConfigT->new(''); |
76
|
|
|
|
|
|
|
$wg_metaT->set('wg0', 'AN_OTHER_PUBLIC_KEY', 'name', 'set_in_thread_B'); |
77
|
|
|
|
|
|
|
$wg_metaT->commit(1); # works fine (internal & on_disk have same version) |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
# thread A (non conflicting changes -> same file, different section) |
80
|
|
|
|
|
|
|
$wg_metaT->commit(1); # "Your changes for `WG_0_PEER_A_PUBLIC_KEY` were not applied" |
81
|
|
|
|
|
|
|
$wg_metaT->commit(1, 0, {'WG_0_PEER_A_PUBLIC_KEY' => $integrity_hash}); # works fine -> non conflicting changes |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# Reload callbacks |
84
|
|
|
|
|
|
|
sub my_reload_callback($interface, $ref_list_args){ |
85
|
|
|
|
|
|
|
my @args = @{$ref_list_args}; |
86
|
|
|
|
|
|
|
print "$interface, reloaded and $args[0]!"; |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# register our callback handler |
90
|
|
|
|
|
|
|
$wg_metaT->register_on_reload_listener(\&my_reload_callback, 'handler_id', [ 'hello from listener' ]); |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# Everytime an interface is reloaded, our handler is called until we uninstall our handler |
93
|
|
|
|
|
|
|
$wg_metaT->remove_on_reload_listener('handler_id'); |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=head1 METHODS |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
=cut |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
package Wireguard::WGmeta::Wrapper::ConfigT; |
100
|
1
|
|
|
1
|
|
87208
|
use strict; |
|
1
|
|
|
|
|
16
|
|
|
1
|
|
|
|
|
40
|
|
101
|
1
|
|
|
1
|
|
9
|
use warnings FATAL => 'all'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
53
|
|
102
|
1
|
|
|
1
|
|
586
|
use Digest::SHA qw(sha1_hex); |
|
1
|
|
|
|
|
3636
|
|
|
1
|
|
|
|
|
124
|
|
103
|
1
|
|
|
1
|
|
12
|
use Fcntl qw(:flock); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
142
|
|
104
|
1
|
|
|
1
|
|
6
|
use File::Basename; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
80
|
|
105
|
1
|
|
|
1
|
|
8
|
use experimental 'signatures'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
8
|
|
106
|
|
|
|
|
|
|
|
107
|
1
|
|
|
1
|
|
828
|
use Wireguard::WGmeta::Wrapper::Config; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
56
|
|
108
|
1
|
|
|
1
|
|
16
|
use Wireguard::WGmeta::Parser::Middleware; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
108
|
|
109
|
1
|
|
|
1
|
|
7
|
use Wireguard::WGmeta::Parser::Conf qw(INTERNAL_KEY_PREFIX); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
45
|
|
110
|
1
|
|
|
1
|
|
7
|
use Wireguard::WGmeta::ValidAttributes; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
68
|
|
111
|
1
|
|
|
1
|
|
6
|
use Wireguard::WGmeta::Utils; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
62
|
|
112
|
|
|
|
|
|
|
|
113
|
1
|
|
|
1
|
|
638
|
use parent 'Wireguard::WGmeta::Wrapper::Config'; |
|
1
|
|
|
|
|
351
|
|
|
1
|
|
|
|
|
6
|
|
114
|
|
|
|
|
|
|
|
115
|
1
|
|
|
1
|
|
78
|
use constant FALSE => 0; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
157
|
|
116
|
1
|
|
|
1
|
|
9
|
use constant TRUE => 1; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
61
|
|
117
|
1
|
|
|
1
|
|
7
|
use constant INTEGRITY_HASH_SALT => 'wefnwioefh9032ur3'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2237
|
|
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
our $VERSION = "0.3.2"; # do not change manually, this variable is updated when calling make |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=head3 is_valid_interface($interface) |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
L |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=cut |
126
|
25
|
|
|
25
|
1
|
79
|
sub is_valid_interface($self, $interface) { |
|
25
|
|
|
|
|
42
|
|
|
25
|
|
|
|
|
39
|
|
|
25
|
|
|
|
|
35
|
|
127
|
25
|
|
|
|
|
81
|
$self->_sync_interfaces(); |
128
|
25
|
|
|
|
|
166
|
return $self->SUPER::is_valid_interface($interface); |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
|
132
|
1
|
|
|
1
|
1
|
3
|
sub is_valid_alias($self, $interface, $alias) { |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
11
|
|
133
|
1
|
|
|
|
|
8
|
$self->may_reload_from_disk($interface); |
134
|
1
|
|
|
|
|
17
|
return $self->SUPER::is_valid_alias($interface, $alias); |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=head3 is_valid_identifier($interface, $identifier) |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
L |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=cut |
142
|
24
|
|
|
24
|
1
|
57
|
sub is_valid_identifier($self, $interface, $identifier) { |
|
24
|
|
|
|
|
41
|
|
|
24
|
|
|
|
|
48
|
|
|
24
|
|
|
|
|
36
|
|
|
24
|
|
|
|
|
40
|
|
143
|
24
|
|
|
|
|
82
|
$self->may_reload_from_disk($interface); |
144
|
24
|
|
|
|
|
93
|
return $self->SUPER::is_valid_identifier($interface, $identifier); |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=head3 try_translate_alias($interface, $may_alias) |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
L |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=cut |
152
|
21
|
|
|
21
|
1
|
33
|
sub try_translate_alias($self, $interface, $may_alias) { |
|
21
|
|
|
|
|
35
|
|
|
21
|
|
|
|
|
33
|
|
|
21
|
|
|
|
|
31
|
|
|
21
|
|
|
|
|
31
|
|
153
|
21
|
|
|
|
|
71
|
$self->may_reload_from_disk($interface); |
154
|
21
|
|
|
|
|
92
|
return $self->SUPER::try_translate_alias($interface, $may_alias); |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=head3 get_interface_section($interface, $identifier) |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
L |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=cut |
162
|
2
|
|
|
2
|
1
|
21
|
sub get_interface_section($self, $interface, $identifier) { |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
6
|
|
163
|
2
|
|
|
|
|
12
|
$self->may_reload_from_disk($interface); |
164
|
2
|
50
|
|
|
|
12
|
if (exists $self->{parsed_config}{$interface}{$identifier}) { |
165
|
2
|
|
|
|
|
5
|
my %r = %{$self->{parsed_config}{$interface}{$identifier}}; |
|
2
|
|
|
|
|
17
|
|
166
|
2
|
|
|
|
|
25
|
return %r; |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
else { |
169
|
0
|
|
|
|
|
0
|
return (); |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=head3 get_section_list($interface) |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
L |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=cut |
178
|
2
|
|
|
2
|
1
|
12
|
sub get_section_list($self, $interface) { |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
4
|
|
179
|
2
|
|
|
|
|
8
|
$self->may_reload_from_disk($interface); |
180
|
2
|
|
|
|
|
17
|
return $self->SUPER::get_section_list($interface); |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=head3 get_peer_count([$interface]) |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
L |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
=cut |
188
|
0
|
|
|
0
|
1
|
0
|
sub get_peer_count($self, $interface = undef) { |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
189
|
0
|
|
|
|
|
0
|
$self->may_reload_from_disk($interface); |
190
|
0
|
|
|
|
|
0
|
return $self->SUPER::get_peer_count($interface); |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
27
|
|
|
27
|
|
38
|
sub _get_all_conf_files($wireguard_home) { |
|
27
|
|
|
|
|
48
|
|
|
27
|
|
|
|
|
39
|
|
194
|
27
|
|
|
|
|
201
|
my @config_files = read_dir($wireguard_home, qr/.*\.conf$/); |
195
|
27
|
50
|
|
|
|
111
|
if (@config_files == 0) { |
196
|
0
|
|
|
|
|
0
|
die "No matching interface configuration(s) in " . $wireguard_home; |
197
|
|
|
|
|
|
|
} |
198
|
27
|
|
|
|
|
56
|
my $count = @config_files; |
199
|
27
|
|
|
|
|
94
|
return \@config_files, $count; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=head3 get_interface_list() |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
L |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=cut |
207
|
2
|
|
|
2
|
1
|
7
|
sub get_interface_list($self) { |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4
|
|
208
|
2
|
|
|
|
|
9
|
$self->_sync_interfaces(); |
209
|
|
|
|
|
|
|
# $self->may_reload_from_disk(); |
210
|
2
|
|
|
|
|
6
|
return sort keys %{$self->{parsed_config}}; |
|
2
|
|
|
|
|
20
|
|
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=head3 commit([$is_hot_config = FALSE, $plain = FALSE, $ref_hash_integrity_keys = undef]) |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
Writes down the parsed config to the wireguard configuration folder. |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
B |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=over 1 |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=item |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
C<[$is_hot_config = FALSE])> If set to TRUE, the existing configuration is overwritten. Otherwise, |
224
|
|
|
|
|
|
|
the suffix '_not_applied' is appended to the filename |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
=item |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
C<[$plain = FALSE])> If set to TRUE, no header is generated |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=item |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
C<[$ref_hash_integrity_keys = undef])> Reference to a hash of integrity keys. Expected structure: |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
{ |
235
|
|
|
|
|
|
|
=> 'integrity_hash_of_corresponding_section', |
236
|
|
|
|
|
|
|
=> 'integrity_hash_of_corresponding_section' |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
For a more detailed explanation when this information is needed please refer to L. |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=back |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
B |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
Exception if: Folder or file is not writeable |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
B |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
None |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=cut |
252
|
11
|
|
|
11
|
1
|
31070
|
sub commit($self, $is_hot_config = FALSE, $plain = FALSE, $ref_hash_integrity_keys = undef) { |
|
11
|
|
|
|
|
37
|
|
|
11
|
|
|
|
|
22
|
|
|
11
|
|
|
|
|
27
|
|
|
11
|
|
|
|
|
22
|
|
|
11
|
|
|
|
|
25
|
|
253
|
11
|
|
|
|
|
23
|
for my $interface_name (keys %{$self->{parsed_config}}) { |
|
11
|
|
|
|
|
95
|
|
254
|
25
|
100
|
|
|
|
131
|
if ($self->_has_changed($interface_name)) { |
255
|
11
|
|
|
|
|
25
|
my $file_name; |
256
|
11
|
50
|
|
|
|
44
|
if ($is_hot_config == TRUE) { |
257
|
11
|
|
|
|
|
64
|
$file_name = $self->{wireguard_home} . $interface_name . '.conf'; |
258
|
11
|
|
|
|
|
42
|
$self->{parsed_config}->{$interface_name}{is_hot_config} = 1; |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
else { |
261
|
0
|
|
|
|
|
0
|
$file_name = $self->{wireguard_home} . $interface_name . $self->{not_applied_suffix}; |
262
|
0
|
|
|
|
|
0
|
$self->{parsed_config}->{$interface_name}{is_hot_config} = 0; |
263
|
|
|
|
|
|
|
} |
264
|
11
|
|
|
|
|
20
|
my $on_disk_config = undef; |
265
|
11
|
|
|
|
|
20
|
my $is_new = undef; |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# --- From here we lock the affected configuration file exclusively ---- |
268
|
11
|
|
|
|
|
24
|
my $fh; |
269
|
|
|
|
|
|
|
# check if interface exists - if not, we have a new interface |
270
|
11
|
100
|
|
|
|
449
|
if (-e $file_name) { |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# in this case open the file for RW |
273
|
10
|
50
|
|
|
|
654
|
open $fh, '+<', $file_name or die "Could not open $file_name: $!"; |
274
|
10
|
|
|
|
|
136
|
flock $fh, LOCK_EX; |
275
|
10
|
|
|
|
|
78
|
my $config_contents = read_file($fh, TRUE); |
276
|
10
|
|
|
|
|
76
|
$on_disk_config = parse_wg_config2($config_contents, $interface_name, $self->{wg_meta_prefix}, $self->{wg_meta_disabled_prefix}); |
277
|
10
|
|
|
|
|
177
|
seek $fh, 0, 0; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
else { |
280
|
1
|
|
|
|
|
94
|
open $fh, '>', $file_name; |
281
|
1
|
|
|
|
|
13
|
flock $fh, LOCK_EX; |
282
|
1
|
|
|
|
|
5
|
$is_new = 1; |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
11
|
|
|
|
|
92
|
$self->_sync_changes( |
286
|
|
|
|
|
|
|
$interface_name, |
287
|
|
|
|
|
|
|
$on_disk_config, |
288
|
|
|
|
|
|
|
$ref_hash_integrity_keys |
289
|
|
|
|
|
|
|
); |
290
|
|
|
|
|
|
|
# write down to file |
291
|
10
|
|
|
|
|
763
|
truncate $fh, 0; |
292
|
10
|
|
|
|
|
87
|
print $fh create_wg_config2($self->{parsed_config}{$interface_name}); |
293
|
10
|
|
|
|
|
45
|
$self->{parsed_config}{$interface_name}{mtime} = get_mtime($file_name); |
294
|
10
|
100
|
|
|
|
36
|
$self->{n_conf_files}++ if (defined $is_new); |
295
|
10
|
|
|
|
|
83
|
$self->_reset_changed($interface_name); |
296
|
|
|
|
|
|
|
# Notify listeners about a file change |
297
|
10
|
|
|
|
|
47
|
$self->_call_reload_listeners($interface_name); |
298
|
10
|
|
|
|
|
1393
|
close $fh; |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
} |
302
|
|
|
|
|
|
|
|
303
|
11
|
|
|
11
|
|
20
|
sub _sync_changes($self, $interface, $ref_on_disk_config = undef, $ref_hash_integrity_keys = undef) { |
|
11
|
|
|
|
|
23
|
|
|
11
|
|
|
|
|
29
|
|
|
11
|
|
|
|
|
18
|
|
|
11
|
|
|
|
|
35
|
|
|
11
|
|
|
|
|
20
|
|
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
# first, we look for sections which are common (disk and internal), then we search for exclusive ones |
306
|
11
|
|
|
|
|
44
|
my @may_conflict; |
307
|
|
|
|
|
|
|
my @exclusive_disk; |
308
|
11
|
|
|
|
|
0
|
my @exclusive_internal; |
309
|
11
|
100
|
|
|
|
39
|
if (defined $ref_on_disk_config) { |
310
|
10
|
|
|
|
|
16
|
for my $identifier_internal (@{$self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'section_order'}}) { |
|
10
|
|
|
|
|
50
|
|
311
|
33
|
100
|
|
|
|
79
|
if (exists $ref_on_disk_config->{$identifier_internal}) { |
312
|
29
|
|
|
|
|
69
|
push @may_conflict, $identifier_internal; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
else { |
315
|
4
|
|
|
|
|
11
|
push @exclusive_internal, $identifier_internal; |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
} |
318
|
10
|
|
|
|
|
18
|
for my $identifier_ondisk (@{$ref_on_disk_config->{INTERNAL_KEY_PREFIX . 'section_order'}}) { |
|
10
|
|
|
|
|
30
|
|
319
|
31
|
100
|
|
|
|
80
|
unless (exists $self->{parsed_config}{$interface}{$identifier_ondisk}) { |
320
|
|
|
|
|
|
|
# if we have the latest data, we can safely assume the peer has been deleted |
321
|
2
|
100
|
|
|
|
11
|
if (!$self->_is_latest_data($interface)) { |
322
|
1
|
|
|
|
|
5
|
push @exclusive_disk, $identifier_ondisk; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
else { |
328
|
|
|
|
|
|
|
# if no on-disk reference is provided all sections are considered as exclusive internal |
329
|
1
|
|
|
|
|
3
|
@exclusive_internal = @{$self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'section_order'}}; |
|
1
|
|
|
|
|
5
|
|
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
11
|
|
|
|
|
32
|
for my $identifier (@may_conflict) { |
333
|
|
|
|
|
|
|
# if the shas differ, the configuration on disk had been changed in the mean time |
334
|
27
|
|
|
|
|
82
|
my $on_disk_sha = _calculate_sha1_from_section($ref_on_disk_config->{$identifier}); |
335
|
27
|
|
|
|
|
76
|
my $internal_sha = _calculate_sha1_from_section($self->{parsed_config}{$interface}{$identifier}); |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
# if the shas differ, it means that the we either have not the most recent data or the on-disk version has been changed in the meantime. |
338
|
27
|
100
|
|
|
|
103
|
if ($on_disk_sha ne $internal_sha) { |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
# we may have a integrity hash from this section which allows us to modify |
341
|
6
|
100
|
100
|
|
|
38
|
if (defined $ref_hash_integrity_keys && exists $ref_hash_integrity_keys->{$identifier}) { |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
# if the on-disk sha differs from our integrity hash, this section has been changed by an other process or user. |
344
|
4
|
100
|
|
|
|
18
|
if ($on_disk_sha ne $ref_hash_integrity_keys->{$identifier}) { |
345
|
1
|
|
|
|
|
78
|
die "your changes for `$identifier` were not applied"; |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
else { |
349
|
|
|
|
|
|
|
# take from disk (we have no integrity key for this section) |
350
|
2
|
|
|
|
|
11
|
$self->{parsed_config}{$interface}{$identifier} = $ref_on_disk_config->{$identifier}; |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
else { |
354
|
|
|
|
|
|
|
# take from disk |
355
|
|
|
|
|
|
|
#$self->{parsed_config}{$identifier} = $ref_on_disk_config->{$identifier}; |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
# exclusive mode |
360
|
10
|
|
|
|
|
29
|
for my $key (@exclusive_disk) { |
361
|
1
|
|
|
|
|
3
|
$self->{parsed_config}{$interface}{$key} = $ref_on_disk_config->{$key}; |
362
|
1
|
|
|
|
|
2
|
push @{$self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'section_order'}}, $key; |
|
1
|
|
|
|
|
7
|
|
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
=head3 may_reload_from_disk([$interface = undef]) |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
This method is called before any data is returned from one of the C methods. It behaves as follows: |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
=over 1 |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=item * |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
If the interface is not defined, it loops through the known interfaces and reloads them individually (if needed). |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=item * |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
If the interface is defined (and known), the modify timestamps are compared an if the on-disk version is newer, a reload is triggered. |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
=item * |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
If the interface is defined (but not known -> this could be the case if a new interface has been added), first we check if there is |
384
|
|
|
|
|
|
|
actually a matching config file on disk and if yes, its loaded and parsed from disk. |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=back |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
Remark: This method is not meant for public access, there is just this extensive documentation block since its behaviour |
389
|
|
|
|
|
|
|
is crucial to the function of this class. |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
B |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
=over 1 |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=item |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
C<$interface> A (possibly) invalid (or new) interface name |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=back |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
B |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
None |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
=cut |
406
|
|
|
|
|
|
|
# sub may_reload_from_disk($self, $interface = undef) { |
407
|
|
|
|
|
|
|
# unless (defined $interface) { |
408
|
|
|
|
|
|
|
# for my $known_interface (keys %{$self->{parsed_config}}) { |
409
|
|
|
|
|
|
|
# # my $s = $self->_get_my_mtime($known_interface); |
410
|
|
|
|
|
|
|
# # my $t = get_mtime($self->{parsed_config}{$known_interface}{config_path}); |
411
|
|
|
|
|
|
|
# if ($self->_get_my_mtime($known_interface) < get_mtime($self->{parsed_config}{$known_interface}{config_path})) { |
412
|
|
|
|
|
|
|
# $self->may_reload_from_disk($known_interface); |
413
|
|
|
|
|
|
|
# } |
414
|
|
|
|
|
|
|
# } |
415
|
|
|
|
|
|
|
# } |
416
|
|
|
|
|
|
|
# elsif (exists $self->{parsed_config}{$interface}) { |
417
|
|
|
|
|
|
|
# # my $s = $self->_get_my_mtime($interface); |
418
|
|
|
|
|
|
|
# # my $t = get_mtime($self->{parsed_config}{$interface}{config_path}); |
419
|
|
|
|
|
|
|
# if ($self->_get_my_mtime($interface) < get_mtime($self->{parsed_config}{$interface}{config_path})) { |
420
|
|
|
|
|
|
|
# $self->may_reload_from_disk($interface); |
421
|
|
|
|
|
|
|
# } |
422
|
|
|
|
|
|
|
# } |
423
|
|
|
|
|
|
|
# else { |
424
|
|
|
|
|
|
|
# # we may have a new interface added in the meantime so we probe if there is actually a config file first |
425
|
|
|
|
|
|
|
# if (-e $self->{wireguard_home} . $interface . '.conf') { |
426
|
|
|
|
|
|
|
# $self->may_reload_from_disk($interface, TRUE); |
427
|
|
|
|
|
|
|
# } |
428
|
|
|
|
|
|
|
# } |
429
|
|
|
|
|
|
|
# |
430
|
|
|
|
|
|
|
# } |
431
|
|
|
|
|
|
|
|
432
|
2
|
|
|
2
|
|
4
|
sub _get_my_mtime($self, $interface) { |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
5
|
|
433
|
2
|
50
|
|
|
|
8
|
if (exists $self->{parsed_config}{$interface}) { |
434
|
2
|
|
|
|
|
15
|
return $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX. 'mtime'}; |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
else { |
437
|
0
|
|
|
|
|
0
|
return 0; |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
} |
440
|
|
|
|
|
|
|
|
441
|
2
|
|
|
2
|
|
5
|
sub _is_latest_data($self, $interface) { |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
3
|
|
442
|
2
|
|
|
|
|
7
|
my $hot_path = $self->{wireguard_home} . $interface . ".conf"; |
443
|
2
|
|
|
|
|
8
|
my $safe_path = $self->{wireguard_home} . $interface . $self->{not_applied_suffix}; |
444
|
2
|
50
|
|
|
|
35
|
if (-e $safe_path) { |
445
|
0
|
|
0
|
|
|
0
|
return $self->_get_my_mtime($interface) ge get_mtime($hot_path) || $self->_get_my_mtime($interface) ge get_mtime($safe_path); |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
# my $t = $self->_get_my_mtime($interface); |
448
|
|
|
|
|
|
|
# my $s = get_mtime($conf_path); |
449
|
2
|
|
|
|
|
12
|
return $self->_get_my_mtime($interface) ge get_mtime($hot_path); |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
27
|
|
|
27
|
|
42
|
sub _sync_interfaces($self) { |
|
27
|
|
|
|
|
36
|
|
|
27
|
|
|
|
|
38
|
|
453
|
|
|
|
|
|
|
# check if there's maybe a new interface by comparing the file counts |
454
|
27
|
|
|
|
|
96
|
my ($conf_files, $count) = _get_all_conf_files($self->{wireguard_home}); |
455
|
27
|
50
|
|
|
|
92
|
if ($self->{n_conf_files} != $count) { |
456
|
27
|
|
|
|
|
45
|
for my $conf_path (@{$conf_files}) { |
|
27
|
|
|
|
|
75
|
|
457
|
|
|
|
|
|
|
# read interface name |
458
|
117
|
|
|
|
|
3900
|
my $i_name = basename($conf_path); |
459
|
117
|
|
|
|
|
504
|
$i_name =~ s/\.conf$//; |
460
|
117
|
100
|
|
|
|
393
|
unless (exists $self->{parsed_config}{$i_name}) { |
461
|
55
|
|
|
|
|
197
|
$self->may_reload_from_disk($i_name, TRUE); |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
# scan for deleted interfaces |
466
|
27
|
|
|
|
|
46
|
for my $internal_interface (keys %{$self->{parsed_config}}) { |
|
27
|
|
|
|
|
100
|
|
467
|
64
|
100
|
|
|
|
1077
|
if (not -e $self->{parsed_config}{$internal_interface}{INTERNAL_KEY_PREFIX. 'config_path'}) { |
468
|
1
|
50
|
|
|
|
7
|
warn "Interface `$internal_interface` has been deleted in the meantime" if $self->_has_changed($internal_interface); |
469
|
1
|
|
|
|
|
9
|
delete $self->{parsed_config}{$internal_interface}; |
470
|
|
|
|
|
|
|
} |
471
|
|
|
|
|
|
|
} |
472
|
|
|
|
|
|
|
} |
473
|
58
|
|
|
58
|
|
87
|
sub _calculate_sha1_from_section($ref_to_hash) { |
|
58
|
|
|
|
|
88
|
|
|
58
|
|
|
|
|
88
|
|
474
|
58
|
|
|
|
|
85
|
my %h = %{$ref_to_hash}; |
|
58
|
|
|
|
|
331
|
|
475
|
58
|
|
|
|
|
119
|
return sha1_hex INTEGRITY_HASH_SALT . join '', map {$h{$_}} @{$ref_to_hash->{INTERNAL_KEY_PREFIX . 'order'}}; |
|
251
|
|
|
|
|
925
|
|
|
58
|
|
|
|
|
116
|
|
476
|
|
|
|
|
|
|
} |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=head3 calculate_sha_from_internal($interface, $identifier) |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
Calculates the sha1 from a section (already parsed). |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
B |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
It is possible that this method does not return the most recent, on-disk version of this section! It returns your current |
485
|
|
|
|
|
|
|
parsed state! This method does NOT trigger a C! |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
B |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
=over 1 |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
=item |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
C<$interface> A valid interface name |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
=item |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
C<$identifier> A valid identifier for this interface |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
=back |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
B |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
The sha1 (in HEX) the requested section |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=cut |
506
|
4
|
|
|
4
|
1
|
946
|
sub calculate_sha_from_internal($self, $interface, $identifier) { |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
11
|
|
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
7
|
|
507
|
4
|
50
|
33
|
|
|
32
|
if (exists $self->{parsed_config}{$interface} && exists $self->{parsed_config}{$interface}{$identifier}) { |
508
|
4
|
|
|
|
|
16
|
return _calculate_sha1_from_section($self->{parsed_config}{$interface}{$identifier}); |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
else { |
511
|
0
|
|
|
|
|
|
die "Invalid interface `$interface` or section `$identifier`"; |
512
|
|
|
|
|
|
|
} |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
} |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
1; |