mirror of
https://github.com/opnsense/plugins.git
synced 2026-05-28 04:34:15 -04:00
net/wireguard - offer CARP vhid tracking support, closes https://github.com/opnsense/plugins/issues/3579
When the the selected vhid is in BACKUP or INIT mode, the wireguard interface in question will be set to "down", in which case communication stops and the new master may take over. The advantage of this strategy is that switching is relatively quick as only the interface flag need to be changed.
This commit is contained in:
parent
0ce2b67e52
commit
9787d50806
8 changed files with 81 additions and 7 deletions
|
|
@ -1,5 +1,5 @@
|
|||
PLUGIN_NAME= wireguard
|
||||
PLUGIN_VERSION= 2.1
|
||||
PLUGIN_VERSION= 2.2
|
||||
PLUGIN_COMMENT= WireGuard VPN service kernel implementation
|
||||
PLUGIN_DEPENDS= wireguard-kmod
|
||||
PLUGIN_CONFLICTS= wireguard-go
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ WWW: https://www.wireguard.com/
|
|||
Changelog
|
||||
---------
|
||||
|
||||
2.2
|
||||
|
||||
* add vhid (carp) tracking support
|
||||
|
||||
2.1
|
||||
|
||||
* Only reload when interface configuration did not change
|
||||
|
|
|
|||
3
net/wireguard/src/etc/rc.syshook.d/carp/20-wireguard
Executable file
3
net/wireguard/src/etc/rc.syshook.d/carp/20-wireguard
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
configctl -dq wireguard configure
|
||||
|
|
@ -59,6 +59,13 @@
|
|||
<allownew>true</allownew>
|
||||
<help>List of addresses to configure on the tunnel adapter. Please use CIDR notation like 10.0.0.1/24.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>server.carp_depend_on</id>
|
||||
<label>Depend on (carp)</label>
|
||||
<type>dropdown</type>
|
||||
<help>The carp VHID to depend on, when this virtual address is not in master state,
|
||||
the instance will be shutdown.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>server.peers</id>
|
||||
<label>Peers</label>
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@
|
|||
<gateway type="NetworkField">
|
||||
<Required>N</Required>
|
||||
</gateway>
|
||||
<carp_depend_on type="VirtualIPField">
|
||||
<type>carp</type>
|
||||
<Required>N</Required>
|
||||
<key>mvc</key>
|
||||
</carp_depend_on>
|
||||
<peers type="ModelRelationField">
|
||||
<Model>
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@
|
|||
<tr>
|
||||
<th data-column-id="if" data-type="string" data-width="8em">{{ lang._('Interface') }}</th>
|
||||
<th data-column-id="type" data-type="string" data-width="8em" data-visible="false">{{ lang._('Type') }}</th>
|
||||
<th data-column-id="status" data-type="string" data-width="8em" >{{ lang._('Status') }}</th>
|
||||
<th data-column-id="public-key" data-type="string" data-identifier="true">{{ lang._('Public key') }}</th>
|
||||
<th data-column-id="name" data-type="string">{{ lang._('Name') }}</th>
|
||||
<th data-column-id="endpoint" data-type="string">{{ lang._('Port / Endpoint') }}</th>
|
||||
|
|
|
|||
|
|
@ -31,10 +31,35 @@ require_once('script/load_phalcon.php');
|
|||
require_once('util.inc');
|
||||
require_once('interfaces.inc');
|
||||
|
||||
/**
|
||||
* collect carp status per vhid
|
||||
*/
|
||||
function get_vhid_status()
|
||||
{
|
||||
$vhids = [];
|
||||
$uuids = [];
|
||||
foreach ((new OPNsense\Interfaces\Vip())->vip->iterateItems() as $id => $item) {
|
||||
if ($item->mode == 'carp') {
|
||||
$uuids[(string)$item->vhid] = $id;
|
||||
}
|
||||
}
|
||||
foreach (legacy_interfaces_details() as $ifdata) {
|
||||
if (!empty($ifdata['carp'])) {
|
||||
foreach ($ifdata['carp'] as $data) {
|
||||
if (isset($uuids[$data['vhid']])) {
|
||||
$vhids[$uuids[$data['vhid']]] = $data['status'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $vhids;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* mimic wg-quick behaviour, but bound to our config
|
||||
*/
|
||||
function wg_start($server, $fhandle)
|
||||
function wg_start($server, $fhandle, $ifcfgflag='up')
|
||||
{
|
||||
if (!does_interface_exist($server->interface)) {
|
||||
mwexecf('/sbin/ifconfig wg create name %s', [$server->interface]);
|
||||
|
|
@ -49,7 +74,7 @@ function wg_start($server, $fhandle)
|
|||
if (!empty((string)$server->mtu)) {
|
||||
mwexecf('/sbin/ifconfig %s mtu %s', [$server->interface, $server->mtu]);
|
||||
}
|
||||
mwexecf('/sbin/ifconfig %s up', [$server->interface]);
|
||||
mwexecf('/sbin/ifconfig %s %s', [$server->interface, $ifcfgflag]);
|
||||
|
||||
if (empty((string)$server->disableroutes)) {
|
||||
/**
|
||||
|
|
@ -161,6 +186,8 @@ if (isset($opts['h']) || empty($args) || !in_array($args[0], ['start', 'stop', '
|
|||
|
||||
$server_devs = [];
|
||||
if (!empty((string)(new OPNsense\Wireguard\General())->enabled)) {
|
||||
$ifdetails = legacy_interfaces_details();
|
||||
$vhids = get_vhid_status();
|
||||
foreach ((new OPNsense\Wireguard\Server())->servers->server->iterateItems() as $key => $node) {
|
||||
if (empty((string)$node->enabled)) {
|
||||
continue;
|
||||
|
|
@ -168,6 +195,19 @@ if (isset($opts['h']) || empty($args) || !in_array($args[0], ['start', 'stop', '
|
|||
if ($server_id != null && $key != $server_id) {
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* CARP may influence the interface status (up or down).
|
||||
* In order to fluently switch between roles, one should only have to change the interface flag in this
|
||||
* case, which means we can still reconfigure an interface in the usual way and just omit sending traffic
|
||||
* when in BACKUP or INIT mode.
|
||||
*/
|
||||
$carp_if_flag = 'up';
|
||||
if (
|
||||
!empty($vhids[(string)$node->carp_depend_on]) &&
|
||||
$vhids[(string)$node->carp_depend_on] != 'MASTER'
|
||||
) {
|
||||
$carp_if_flag = 'down';
|
||||
}
|
||||
$server_devs[] = (string)$node->interface;
|
||||
$statHandle = fopen($node->statFilename, "a+");
|
||||
if (flock($statHandle, LOCK_EX)) {
|
||||
|
|
@ -176,16 +216,16 @@ if (isset($opts['h']) || empty($args) || !in_array($args[0], ['start', 'stop', '
|
|||
wg_stop($node);
|
||||
break;
|
||||
case 'start':
|
||||
wg_start($node, $statHandle);
|
||||
wg_start($node, $statHandle, $carp_if_flag);
|
||||
break;
|
||||
case 'restart':
|
||||
wg_stop($node);
|
||||
wg_start($node, $statHandle);
|
||||
wg_start($node, $statHandle, $carp_if_flag);
|
||||
break;
|
||||
case 'configure':
|
||||
if (
|
||||
@md5_file($node->cnfFilename) != get_stat_hash($statHandle)['file'] ||
|
||||
!does_interface_exist((string)$node->interface)
|
||||
!isset($ifdetails[(string)$node->interface])
|
||||
) {
|
||||
if (get_stat_hash($statHandle)['interface'] != wg_reconfigure_hash($node)) {
|
||||
// Fluent reloading not supported for this instance, make sure the user is informed
|
||||
|
|
@ -196,7 +236,13 @@ if (isset($opts['h']) || empty($args) || !in_array($args[0], ['start', 'stop', '
|
|||
);
|
||||
wg_stop($node);
|
||||
}
|
||||
wg_start($node, $statHandle);
|
||||
wg_start($node, $statHandle, $carp_if_flag);
|
||||
} else {
|
||||
// when triggered via a CARP event, check our interface status [UP|DOWN]
|
||||
$tmp = in_array('up', $ifdetails[(string)$node->interface]['flags']) ? 'up' : 'down';
|
||||
if ($tmp != $carp_if_flag) {
|
||||
mwexecf('/sbin/ifconfig %s %s', [$node->interface, $carp_if_flag]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@
|
|||
import subprocess
|
||||
import ujson
|
||||
|
||||
|
||||
interfaces = {}
|
||||
for line in subprocess.run(['/sbin/ifconfig'], capture_output=True, text=True).stdout.split("\n"):
|
||||
if not line.startswith('\t') and line.find('<') > -1:
|
||||
ifname = line.split(':')[0]
|
||||
interfaces[ifname] = 'up' if 'UP' in line.split('<')[1].split('>')[0].split(',') else 'down'
|
||||
|
||||
sp = subprocess.run(['/usr/bin/wg', 'show', 'all', 'dump'], capture_output=True, text=True)
|
||||
result = {'records': []}
|
||||
if sp.returncode == 0:
|
||||
|
|
@ -44,6 +51,7 @@ if sp.returncode == 0:
|
|||
record['fwmark'] = parts[4]
|
||||
# convenience, copy listen-port to endpoint
|
||||
record['endpoint'] = parts[3]
|
||||
record['status'] = interfaces.get(record['if'], 'down')
|
||||
elif len(parts) == 9:
|
||||
record['type'] = 'peer'
|
||||
record['public-key'] = parts[1]
|
||||
|
|
|
|||
Loading…
Reference in a new issue