| 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; |