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
|
|
77468
|
use strict; |
|
1
|
|
|
|
|
11
|
|
|
1
|
|
|
|
|
31
|
|
101
|
1
|
|
|
1
|
|
5
|
use warnings FATAL => 'all'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
33
|
|
102
|
1
|
|
|
1
|
|
568
|
use Digest::SHA qw(sha1_hex); |
|
1
|
|
|
|
|
3191
|
|
|
1
|
|
|
|
|
85
|
|
103
|
1
|
|
|
1
|
|
7
|
use Fcntl qw(:flock); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
92
|
|
104
|
1
|
|
|
1
|
|
6
|
use File::Basename; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
62
|
|
105
|
1
|
|
|
1
|
|
6
|
use experimental 'signatures'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
6
|
|
106
|
|
|
|
|
|
|
|
107
|
1
|
|
|
1
|
|
674
|
use Wireguard::WGmeta::Wrapper::Config; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
32
|
|
108
|
1
|
|
|
1
|
|
7
|
use Wireguard::WGmeta::Parser::Middleware; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
50
|
|
109
|
1
|
|
|
1
|
|
6
|
use Wireguard::WGmeta::Parser::Conf qw(INTERNAL_KEY_PREFIX); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
34
|
|
110
|
1
|
|
|
1
|
|
6
|
use Wireguard::WGmeta::ValidAttributes; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
51
|
|
111
|
1
|
|
|
1
|
|
5
|
use Wireguard::WGmeta::Utils; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
51
|
|
112
|
|
|
|
|
|
|
|
113
|
1
|
|
|
1
|
|
415
|
use parent 'Wireguard::WGmeta::Wrapper::Config'; |
|
1
|
|
|
|
|
272
|
|
|
1
|
|
|
|
|
5
|
|
114
|
|
|
|
|
|
|
|
115
|
1
|
|
|
1
|
|
66
|
use constant FALSE => 0; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
49
|
|
116
|
1
|
|
|
1
|
|
5
|
use constant TRUE => 1; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
41
|
|
117
|
1
|
|
|
1
|
|
6
|
use constant INTEGRITY_HASH_SALT => 'wefnwioefh9032ur3'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2022
|
|
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
our $VERSION = "0.3.4"; # 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
|
38
|
sub is_valid_interface($self, $interface) { |
|
25
|
|
|
|
|
36
|
|
|
25
|
|
|
|
|
38
|
|
|
25
|
|
|
|
|
29
|
|
127
|
25
|
|
|
|
|
67
|
$self->_sync_interfaces(); |
128
|
25
|
|
|
|
|
129
|
return $self->SUPER::is_valid_interface($interface); |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
|
132
|
1
|
|
|
1
|
1
|
2
|
sub is_valid_alias($self, $interface, $alias) { |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
133
|
1
|
|
|
|
|
4
|
$self->may_reload_from_disk($interface); |
134
|
1
|
|
|
|
|
10
|
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
|
44
|
sub is_valid_identifier($self, $interface, $identifier) { |
|
24
|
|
|
|
|
38
|
|
|
24
|
|
|
|
|
33
|
|
|
24
|
|
|
|
|
33
|
|
|
24
|
|
|
|
|
31
|
|
143
|
24
|
|
|
|
|
64
|
$self->may_reload_from_disk($interface); |
144
|
24
|
|
|
|
|
77
|
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
|
31
|
sub try_translate_alias($self, $interface, $may_alias) { |
|
21
|
|
|
|
|
31
|
|
|
21
|
|
|
|
|
34
|
|
|
21
|
|
|
|
|
30
|
|
|
21
|
|
|
|
|
28
|
|
153
|
21
|
|
|
|
|
62
|
$self->may_reload_from_disk($interface); |
154
|
21
|
|
|
|
|
72
|
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
|
13
|
sub get_interface_section($self, $interface, $identifier) { |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
3
|
|
163
|
2
|
|
|
|
|
8
|
$self->may_reload_from_disk($interface); |
164
|
2
|
50
|
|
|
|
8
|
if (exists $self->{parsed_config}{$interface}{$identifier}) { |
165
|
2
|
|
|
|
|
4
|
my %r = %{$self->{parsed_config}{$interface}{$identifier}}; |
|
2
|
|
|
|
|
15
|
|
166
|
2
|
|
|
|
|
18
|
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
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4
|
|
179
|
2
|
|
|
|
|
8
|
$self->may_reload_from_disk($interface); |
180
|
2
|
|
|
|
|
13
|
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
|
|
72
|
sub _get_all_conf_files($wireguard_home) { |
|
27
|
|
|
|
|
45
|
|
|
27
|
|
|
|
|
32
|
|
194
|
27
|
|
|
|
|
154
|
my @config_files = read_dir($wireguard_home, qr/.*\.conf$/); |
195
|
27
|
50
|
|
|
|
94
|
if (@config_files == 0) { |
196
|
0
|
|
|
|
|
0
|
die "No matching interface configuration(s) in " . $wireguard_home; |
197
|
|
|
|
|
|
|
} |
198
|
27
|
|
|
|
|
48
|
my $count = @config_files; |
199
|
27
|
|
|
|
|
87
|
return \@config_files, $count; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=head3 get_interface_list() |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
L |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=cut |
207
|
2
|
|
|
2
|
1
|
8
|
sub get_interface_list($self) { |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
3
|
|
208
|
2
|
|
|
|
|
6
|
$self->_sync_interfaces(); |
209
|
|
|
|
|
|
|
# $self->may_reload_from_disk(); |
210
|
2
|
|
|
|
|
7
|
return sort keys %{$self->{parsed_config}}; |
|
2
|
|
|
|
|
54
|
|
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
|
30894
|
sub commit($self, $is_hot_config = FALSE, $plain = FALSE, $ref_hash_integrity_keys = undef) { |
|
11
|
|
|
|
|
20
|
|
|
11
|
|
|
|
|
20
|
|
|
11
|
|
|
|
|
17
|
|
|
11
|
|
|
|
|
18
|
|
|
11
|
|
|
|
|
16
|
|
253
|
11
|
|
|
|
|
15
|
for my $interface_name (keys %{$self->{parsed_config}}) { |
|
11
|
|
|
|
|
54
|
|
254
|
25
|
100
|
|
|
|
79
|
if ($self->_has_changed($interface_name)) { |
255
|
11
|
|
|
|
|
17
|
my $file_name; |
256
|
11
|
50
|
|
|
|
27
|
if ($is_hot_config == TRUE) { |
257
|
11
|
|
|
|
|
32
|
$file_name = $self->{wireguard_home} . $interface_name . '.conf'; |
258
|
11
|
|
|
|
|
24
|
$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
|
|
|
|
|
18
|
my $on_disk_config = undef; |
265
|
11
|
|
|
|
|
18
|
my $is_new = undef; |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# --- From here we lock the affected configuration file exclusively ---- |
268
|
11
|
|
|
|
|
16
|
my $fh; |
269
|
|
|
|
|
|
|
# check if interface exists - if not, we have a new interface |
270
|
11
|
100
|
|
|
|
255
|
if (-e $file_name) { |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# in this case open the file for RW |
273
|
10
|
50
|
|
|
|
442
|
open $fh, '+<', $file_name or die "Could not open $file_name: $!"; |
274
|
10
|
|
|
|
|
97
|
flock $fh, LOCK_EX; |
275
|
10
|
|
|
|
|
53
|
my $config_contents = read_file($fh, TRUE); |
276
|
10
|
|
|
|
|
44
|
$on_disk_config = parse_wg_config2($config_contents, $interface_name, $self->{wg_meta_prefix}, $self->{wg_meta_disabled_prefix}); |
277
|
10
|
|
|
|
|
113
|
seek $fh, 0, 0; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
else { |
280
|
1
|
|
|
|
|
95
|
open $fh, '>', $file_name; |
281
|
1
|
|
|
|
|
37
|
flock $fh, LOCK_EX; |
282
|
1
|
|
|
|
|
5
|
$is_new = 1; |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
11
|
|
|
|
|
46
|
$self->_sync_changes( |
286
|
|
|
|
|
|
|
$interface_name, |
287
|
|
|
|
|
|
|
$on_disk_config, |
288
|
|
|
|
|
|
|
$ref_hash_integrity_keys |
289
|
|
|
|
|
|
|
); |
290
|
|
|
|
|
|
|
# write down to file |
291
|
10
|
|
|
|
|
449
|
truncate $fh, 0; |
292
|
10
|
|
|
|
|
63
|
print $fh create_wg_config2($self->{parsed_config}{$interface_name}); |
293
|
10
|
|
|
|
|
28
|
$self->{parsed_config}{$interface_name}{mtime} = get_mtime($file_name); |
294
|
10
|
100
|
|
|
|
31
|
$self->{n_conf_files}++ if (defined $is_new); |
295
|
10
|
|
|
|
|
47
|
$self->_reset_changed($interface_name); |
296
|
|
|
|
|
|
|
# Close file handle before calling reload callbacks, otherwise the exclusive lock is kept! |
297
|
10
|
|
|
|
|
867
|
close $fh; |
298
|
|
|
|
|
|
|
# Notify listeners about a file change |
299
|
10
|
|
|
|
|
70
|
$self->_call_reload_listeners($interface_name); |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
} |
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
|
304
|
11
|
|
|
11
|
|
17
|
sub _sync_changes($self, $interface, $ref_on_disk_config = undef, $ref_hash_integrity_keys = undef) { |
|
11
|
|
|
|
|
18
|
|
|
11
|
|
|
|
|
19
|
|
|
11
|
|
|
|
|
16
|
|
|
11
|
|
|
|
|
15
|
|
|
11
|
|
|
|
|
16
|
|
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# first, we look for sections which are common (disk and internal), then we search for exclusive ones |
307
|
11
|
|
|
|
|
25
|
my @may_conflict; |
308
|
|
|
|
|
|
|
my @exclusive_disk; |
309
|
11
|
|
|
|
|
0
|
my @exclusive_internal; |
310
|
11
|
100
|
|
|
|
23
|
if (defined $ref_on_disk_config) { |
311
|
10
|
|
|
|
|
16
|
for my $identifier_internal (@{$self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'section_order'}}) { |
|
10
|
|
|
|
|
36
|
|
312
|
33
|
100
|
|
|
|
63
|
if (exists $ref_on_disk_config->{$identifier_internal}) { |
313
|
29
|
|
|
|
|
52
|
push @may_conflict, $identifier_internal; |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
else { |
316
|
4
|
|
|
|
|
10
|
push @exclusive_internal, $identifier_internal; |
317
|
|
|
|
|
|
|
} |
318
|
|
|
|
|
|
|
} |
319
|
10
|
|
|
|
|
19
|
for my $identifier_ondisk (@{$ref_on_disk_config->{INTERNAL_KEY_PREFIX . 'section_order'}}) { |
|
10
|
|
|
|
|
17
|
|
320
|
31
|
100
|
|
|
|
86
|
unless (exists $self->{parsed_config}{$interface}{$identifier_ondisk}) { |
321
|
|
|
|
|
|
|
# if we have the latest data, we can safely assume the peer has been deleted |
322
|
2
|
100
|
|
|
|
6
|
if (!$self->_is_latest_data($interface)) { |
323
|
1
|
|
|
|
|
5
|
push @exclusive_disk, $identifier_ondisk; |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
else { |
329
|
|
|
|
|
|
|
# if no on-disk reference is provided all sections are considered as exclusive internal |
330
|
1
|
|
|
|
|
2
|
@exclusive_internal = @{$self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'section_order'}}; |
|
1
|
|
|
|
|
4
|
|
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
|
333
|
11
|
|
|
|
|
21
|
for my $identifier (@may_conflict) { |
334
|
|
|
|
|
|
|
# if the shas differ, the configuration on disk had been changed in the mean time |
335
|
27
|
|
|
|
|
60
|
my $on_disk_sha = _calculate_sha1_from_section($ref_on_disk_config->{$identifier}); |
336
|
27
|
|
|
|
|
73
|
my $internal_sha = _calculate_sha1_from_section($self->{parsed_config}{$interface}{$identifier}); |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
# 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. |
339
|
27
|
100
|
|
|
|
85
|
if ($on_disk_sha ne $internal_sha) { |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# we may have a integrity hash from this section which allows us to modify |
342
|
6
|
100
|
100
|
|
|
26
|
if (defined $ref_hash_integrity_keys && exists $ref_hash_integrity_keys->{$identifier}) { |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
# if the on-disk sha differs from our integrity hash, this section has been changed by an other process or user. |
345
|
4
|
100
|
|
|
|
13
|
if ($on_disk_sha ne $ref_hash_integrity_keys->{$identifier}) { |
346
|
1
|
|
|
|
|
43
|
die "your changes for `$identifier` were not applied"; |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
else { |
350
|
|
|
|
|
|
|
# take from disk (we have no integrity key for this section) |
351
|
2
|
|
|
|
|
9
|
$self->{parsed_config}{$interface}{$identifier} = $ref_on_disk_config->{$identifier}; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
else { |
355
|
|
|
|
|
|
|
# take from disk |
356
|
|
|
|
|
|
|
#$self->{parsed_config}{$identifier} = $ref_on_disk_config->{$identifier}; |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
# exclusive mode |
361
|
10
|
|
|
|
|
31
|
for my $key (@exclusive_disk) { |
362
|
1
|
|
|
|
|
3
|
$self->{parsed_config}{$interface}{$key} = $ref_on_disk_config->{$key}; |
363
|
1
|
|
|
|
|
4
|
push @{$self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX . 'section_order'}}, $key; |
|
1
|
|
|
|
|
4
|
|
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
=head3 may_reload_from_disk([$interface = undef]) |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
This method is called before any data is returned from one of the C methods. It behaves as follows: |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
=over 1 |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=item * |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
If the interface is not defined, it loops through the known interfaces and reloads them individually (if needed). |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=item * |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
If the interface is defined (and known), the modify timestamps are compared an if the on-disk version is newer, a reload is triggered. |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=item * |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
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 |
385
|
|
|
|
|
|
|
actually a matching config file on disk and if yes, its loaded and parsed from disk. |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
=back |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
Remark: This method is not meant for public access, there is just this extensive documentation block since its behaviour |
390
|
|
|
|
|
|
|
is crucial to the function of this class. |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
B |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=over 1 |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
=item |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
C<$interface> A (possibly) invalid (or new) interface name |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
=back |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
B |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
None |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=cut |
407
|
|
|
|
|
|
|
# sub may_reload_from_disk($self, $interface = undef) { |
408
|
|
|
|
|
|
|
# unless (defined $interface) { |
409
|
|
|
|
|
|
|
# for my $known_interface (keys %{$self->{parsed_config}}) { |
410
|
|
|
|
|
|
|
# # my $s = $self->_get_my_mtime($known_interface); |
411
|
|
|
|
|
|
|
# # my $t = get_mtime($self->{parsed_config}{$known_interface}{config_path}); |
412
|
|
|
|
|
|
|
# if ($self->_get_my_mtime($known_interface) < get_mtime($self->{parsed_config}{$known_interface}{config_path})) { |
413
|
|
|
|
|
|
|
# $self->may_reload_from_disk($known_interface); |
414
|
|
|
|
|
|
|
# } |
415
|
|
|
|
|
|
|
# } |
416
|
|
|
|
|
|
|
# } |
417
|
|
|
|
|
|
|
# elsif (exists $self->{parsed_config}{$interface}) { |
418
|
|
|
|
|
|
|
# # my $s = $self->_get_my_mtime($interface); |
419
|
|
|
|
|
|
|
# # my $t = get_mtime($self->{parsed_config}{$interface}{config_path}); |
420
|
|
|
|
|
|
|
# if ($self->_get_my_mtime($interface) < get_mtime($self->{parsed_config}{$interface}{config_path})) { |
421
|
|
|
|
|
|
|
# $self->may_reload_from_disk($interface); |
422
|
|
|
|
|
|
|
# } |
423
|
|
|
|
|
|
|
# } |
424
|
|
|
|
|
|
|
# else { |
425
|
|
|
|
|
|
|
# # we may have a new interface added in the meantime so we probe if there is actually a config file first |
426
|
|
|
|
|
|
|
# if (-e $self->{wireguard_home} . $interface . '.conf') { |
427
|
|
|
|
|
|
|
# $self->may_reload_from_disk($interface, TRUE); |
428
|
|
|
|
|
|
|
# } |
429
|
|
|
|
|
|
|
# } |
430
|
|
|
|
|
|
|
# |
431
|
|
|
|
|
|
|
# } |
432
|
|
|
|
|
|
|
|
433
|
2
|
|
|
2
|
|
5
|
sub _get_my_mtime($self, $interface) { |
|
2
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
2
|
|
434
|
2
|
50
|
|
|
|
11
|
if (exists $self->{parsed_config}{$interface}) { |
435
|
2
|
|
|
|
|
12
|
return $self->{parsed_config}{$interface}{INTERNAL_KEY_PREFIX. 'mtime'}; |
436
|
|
|
|
|
|
|
} |
437
|
|
|
|
|
|
|
else { |
438
|
0
|
|
|
|
|
0
|
return 0; |
439
|
|
|
|
|
|
|
} |
440
|
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
|
442
|
2
|
|
|
2
|
|
4
|
sub _is_latest_data($self, $interface) { |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
4
|
|
443
|
2
|
|
|
|
|
9
|
my $hot_path = $self->{wireguard_home} . $interface . ".conf"; |
444
|
2
|
|
|
|
|
6
|
my $safe_path = $self->{wireguard_home} . $interface . $self->{not_applied_suffix}; |
445
|
2
|
50
|
|
|
|
31
|
if (-e $safe_path) { |
446
|
0
|
|
0
|
|
|
0
|
return $self->_get_my_mtime($interface) ge get_mtime($hot_path) || $self->_get_my_mtime($interface) ge get_mtime($safe_path); |
447
|
|
|
|
|
|
|
} |
448
|
|
|
|
|
|
|
# my $t = $self->_get_my_mtime($interface); |
449
|
|
|
|
|
|
|
# my $s = get_mtime($conf_path); |
450
|
2
|
|
|
|
|
8
|
return $self->_get_my_mtime($interface) ge get_mtime($hot_path); |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
|
453
|
27
|
|
|
27
|
|
36
|
sub _sync_interfaces($self) { |
|
27
|
|
|
|
|
36
|
|
|
27
|
|
|
|
|
43
|
|
454
|
|
|
|
|
|
|
# check if there's maybe a new interface by comparing the file counts |
455
|
27
|
|
|
|
|
63
|
my ($conf_files, $count) = _get_all_conf_files($self->{wireguard_home}); |
456
|
27
|
50
|
|
|
|
82
|
if ($self->{n_conf_files} != $count) { |
457
|
27
|
|
|
|
|
40
|
for my $conf_path (@{$conf_files}) { |
|
27
|
|
|
|
|
58
|
|
458
|
|
|
|
|
|
|
# read interface name |
459
|
117
|
|
|
|
|
3600
|
my $i_name = basename($conf_path); |
460
|
117
|
|
|
|
|
460
|
$i_name =~ s/\.conf$//; |
461
|
117
|
100
|
|
|
|
353
|
unless (exists $self->{parsed_config}{$i_name}) { |
462
|
55
|
|
|
|
|
176
|
$self->may_reload_from_disk($i_name, TRUE); |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
} |
466
|
|
|
|
|
|
|
# scan for deleted interfaces |
467
|
27
|
|
|
|
|
48
|
for my $internal_interface (keys %{$self->{parsed_config}}) { |
|
27
|
|
|
|
|
89
|
|
468
|
64
|
100
|
|
|
|
981
|
if (not -e $self->{parsed_config}{$internal_interface}{INTERNAL_KEY_PREFIX. 'config_path'}) { |
469
|
1
|
50
|
|
|
|
7
|
warn "Interface `$internal_interface` has been deleted in the meantime" if $self->_has_changed($internal_interface); |
470
|
1
|
|
|
|
|
10
|
delete $self->{parsed_config}{$internal_interface}; |
471
|
|
|
|
|
|
|
} |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
} |
474
|
58
|
|
|
58
|
|
114
|
sub _calculate_sha1_from_section($ref_to_hash) { |
|
58
|
|
|
|
|
80
|
|
|
58
|
|
|
|
|
82
|
|
475
|
58
|
|
|
|
|
79
|
my %h = %{$ref_to_hash}; |
|
58
|
|
|
|
|
315
|
|
476
|
58
|
|
|
|
|
128
|
return sha1_hex INTEGRITY_HASH_SALT . join '', map {$h{$_}} @{$ref_to_hash->{INTERNAL_KEY_PREFIX . 'order'}}; |
|
255
|
|
|
|
|
747
|
|
|
58
|
|
|
|
|
105
|
|
477
|
|
|
|
|
|
|
} |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
=head3 calculate_sha_from_internal($interface, $identifier) |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
Calculates the sha1 from a section (already parsed). |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
B |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
It is possible that this method does not return the most recent, on-disk version of this section! It returns your current |
486
|
|
|
|
|
|
|
parsed state! This method does NOT trigger a C! |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
B |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
=over 1 |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
=item |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
C<$interface> A valid interface name |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=item |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
C<$identifier> A valid identifier for this interface |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
=back |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
B |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
The sha1 (in HEX) the requested section |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=cut |
507
|
4
|
|
|
4
|
1
|
675
|
sub calculate_sha_from_internal($self, $interface, $identifier) { |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
7
|
|
508
|
4
|
50
|
33
|
|
|
22
|
if (exists $self->{parsed_config}{$interface} && exists $self->{parsed_config}{$interface}{$identifier}) { |
509
|
4
|
|
|
|
|
11
|
return _calculate_sha1_from_section($self->{parsed_config}{$interface}{$identifier}); |
510
|
|
|
|
|
|
|
} |
511
|
|
|
|
|
|
|
else { |
512
|
0
|
|
|
|
|
|
die "Invalid interface `$interface` or section `$identifier`"; |
513
|
|
|
|
|
|
|
} |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
} |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
1; |