The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

WGmeta::Wrapper::ConfigT - Class for interfacing the wireguard configuration supporting concurrent access

DESCRIPTION

Specialized child class of Wireguard::WGmeta::Wrapper::Config which is capable of handling concurrent access.

SYNOPSIS

The interface is almost identical with the exception of "commit([$is_hot_config = FALSE, $plain = FALSE, $ref_hash_integrity_keys = undef])"

 use Wireguard::WGmeta::Wrapper::ConfigT;
 my $wg_metaT = Wireguard::WGmeta::Wrapper::ConfigT->new('<path to wireguard configuration>');

CONCURRENCY

To ensure that no inconsistent config files are generated, calls to a get_*() may result in a reload from disk - namely when the config file on disk is newer than the current (parsed) one. So keep in mind to commit() as soon as possible (this is obviously only true for environments where such situations are possible to occur)

 # thread/process A
 $wg_metaT->set('wg0', 'WG_0_PEER_A_PUBLIC_KEY', 'alias', 'A');

 # thread/process B
 $wg_metaT->set('wg0', 'WG_0_PEER_A_PUBLIC_KEY', 'alias', 'B');
 $wg_metaT->commit(1);

 # thread/process A (alias 'A' is overwritten by 'B')
 wg_metaT->disable_by_alias('wg0', 'A'); # throws exception `invalid alias`!

For more details about the reloading behaviour please refer to "may_reload_from_disk([$interface = undef])".

Commit behaviour

        FUNCTION commit($integrity_hashes)
                FOR $interface IN $known_interfaces
                    IF has_changed($interface) THEN
                lock_exclusive($interface)
                UNLESS my_config_is_latest THEN
                    $on_disk <- read_from_disk($interface)
                $contents <- create_wg_config($interface, $on_disk,$integrity_hashes)
                write($contents)

        FUNCTION create_wg_config($interface, $on_disk, $integrity_hashes);
                $may_conflicting <- search_for_common_data($interface, $on_disk)
                FOR $section IN $may_conflicting
                        $sha_internal <- calculate_sha_from_internal()
                        $sha_disk <- calculate_sha_from_disk()
                        IF $sha_internal NE $sha_disk
                                IF $sha_disk EQ $integrity_hashes[$section]
                                        $section_data <- take_from_internal()
                                ELSE
                                        $section_data <- take_from_disk()
                        ELSE
                                $section_data <- take_from_disk()
                        $config_content .= create_section($section_data)
                $config_content .= create_non_conflicting()
                return $config_content

EXAMPLES

 use Wireguard::WGmeta::Wrapper::ConfigT;

 # thread A
 my $wg_metaT = Wireguard::WGmeta::Wrapper::ConfigT->new('<path to wireguard configuration>');
 $wg_metaT->set('wg0', 'WG_0_PEER_A_PUBLIC_KEY', 'name', 'set_in_thread_A');
 # Assumption: Our internal version is equal with the on-disk version at this point
 my $integrity_hash = $wg_metaT->calculate_sha_from_internal();

 # thread B
 my $wg_metaT = Wireguard::WGmeta::Wrapper::ConfigT->new('<path to wireguard configuration>');
 $wg_metaT->set('wg0', 'AN_OTHER_PUBLIC_KEY', 'name', 'set_in_thread_B');
 $wg_metaT->commit(1); # works fine (internal & on_disk have same version)

 # thread A (non conflicting changes -> same file, different section)
 $wg_metaT->commit(1); # "Your changes for `WG_0_PEER_A_PUBLIC_KEY` were not applied"
 $wg_metaT->commit(1, 0, {'WG_0_PEER_A_PUBLIC_KEY' => $integrity_hash}); # works fine -> non conflicting changes

 # Reload callbacks
 sub my_reload_callback($interface, $ref_list_args){
    my @args = @{$ref_list_args};
    print "$interface, reloaded and $args[0]!";
 }

 # register our callback handler
 $wg_metaT->register_on_reload_listener(\&my_reload_callback, 'handler_id', [ 'hello from listener' ]);

 # Everytime an interface is reloaded, our handler is called until we uninstall our handler
 $wg_metaT->remove_on_reload_listener('handler_id');

METHODS

is_valid_interface($interface)

"is_valid_interface($interface)" in Wireguard::WGmeta::Wrapper::Config

is_valid_identifier($interface, $identifier)

"is_valid_identifier($interface, $identifier)" in Wireguard::WGmeta::Wrapper::Config

try_translate_alias($interface, $may_alias)

"try_translate_alias($interface, $may_alias)" in Wireguard::WGmeta::Wrapper::Config

get_interface_section($interface, $identifier)

"get_interface_section($interface, $identifier)" in Wireguard::WGmeta::Wrapper::Config

get_section_list($interface)

"get_section_list($interface)" in Wireguard::WGmeta::Wrapper::Config

get_peer_count([$interface])

"get_peer_count([$interface = undef])" in Wireguard::WGmeta::Wrapper::Config

get_interface_list()

"get_interface_list()" in Wireguard::WGmeta::Wrapper::Config

commit([$is_hot_config = FALSE, $plain = FALSE, $ref_hash_integrity_keys = undef])

Writes down the parsed config to the wireguard configuration folder.

Parameters

  • [$is_hot_config = FALSE]) If set to TRUE, the existing configuration is overwritten. Otherwise, the suffix '_not_applied' is appended to the filename

  • [$plain = FALSE]) If set to TRUE, no header is generated

  • [$ref_hash_integrity_keys = undef]) Reference to a hash of integrity keys. Expected structure:

        {
            <identifier1> => 'integrity_hash_of_corresponding_section',
            <identifier2> => 'integrity_hash_of_corresponding_section'
        }

    For a more detailed explanation when this information is needed please refer to "CONCURRENCY".

Raises

Exception if: Folder or file is not writeable

Returns

None

may_reload_from_disk([$interface = undef])

This method is called before any data is returned from one of the get_*() methods. It behaves as follows:

  • If the interface is not defined, it loops through the known interfaces and reloads them individually (if needed).

  • If the interface is defined (and known), the modify timestamps are compared an if the on-disk version is newer, a reload is triggered.

  • 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 actually a matching config file on disk and if yes, its loaded and parsed from disk.

Remark: This method is not meant for public access, there is just this extensive documentation block since its behaviour is crucial to the function of this class.

Parameters

  • $interface A (possibly) invalid (or new) interface name

Returns

None

calculate_sha_from_internal($interface, $identifier)

Calculates the sha1 from a section (already parsed).

Caveat

It is possible that this method does not return the most recent, on-disk version of this section! It returns your current parsed state! This method does NOT trigger a may_reload_from_disk()!

Parameters

  • $interface A valid interface name

  • $identifier A valid identifier for this interface

Returns

The sha1 (in HEX) the requested section