VPN: WireGuard: Peer generator - Add "Address" field which auto calculates the next available address in the pool (defined by the instance) for both ipv4 and ipv6.

Extend Firewall/Util to iterate over a cidr range, so we can fetch the first available here.
The address fiels should be the "Allowed IPs" in the peer on this instance end. When using allowed IPs ::/0, 0.0.0.0/0 on both ends, the tunnel should work as well, but may people confuse about the routing part.

When the remote end sends traffic from addresses other than the ones configured on the tunnel, one needs to update the peer manually.
This commit is contained in:
Ad Schellevis 2024-04-09 18:32:21 +02:00
parent 93e114db93
commit 231a4d48de
4 changed files with 133 additions and 9 deletions

View File

@ -33,6 +33,7 @@ use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Core\Config;
use OPNsense\Wireguard\Server;
use OPNsense\Core\Backend;
use OPNsense\Firewall\Util;
class ClientController extends ApiMutableModelControllerBase
{
@ -172,4 +173,52 @@ class ClientController extends ApiMutableModelControllerBase
return $this->setBase('configbuilder', 'clients.client', $uuid);
}
public function getServerInfoAction($uuid=null)
{
$result = ['status' => 'failed'];
if ($this->request->isGet()) {
$peers = [];
$subnets = [];
$used_addresses = []; /* We cleanse addresses before storing here, to allow string matching */
foreach ((new Server())->servers->server->iterateItems() as $key => $node) {
if ($key == $uuid) {
$peers = array_filter(explode(',', (string)$node->peers));
$result['endpoint'] = (string)$node->endpoint;
$result['peer_dns'] = (string)$node->peer_dns;
$result['pubkey'] = (string)$node->pubkey;
foreach (array_filter(explode(',', (string)$node->tunneladdress)) as $addr) {
$proto = str_contains($addr, ':') ? 'inet6' : 'inet';
if (!isset($subnets[$proto])) {
$subnets[$proto] = $addr;
}
$used_addresses[] = inet_ntop(inet_pton(explode('/', $addr)[0]));
}
foreach ($peers as $peer) {
$this_peer = $this->getModel()->getNodeByReference('clients.client.'.$peer);
if ($this_peer != null) {
foreach (array_filter(explode(',', (string)$this_peer->tunneladdress)) as $addr) {
$used_addresses[] = inet_ntop(inet_pton(explode('/', $addr)[0]));
}
}
}
$tunneladdress = [];
foreach ($subnets as $cidr) {
foreach (Util::cidrRangeIterator($cidr) as $addr) {
if (!in_array($addr, $used_addresses)) {
$netmask = str_contains($addr, ':') ? '128' : '32';
$tunneladdress[] = $addr . '/' . $netmask;
break;
}
}
}
$result['address'] = implode(',', $tunneladdress);
$result['status'] = 'ok';
break;
}
}
}
return $result;
}
}

View File

@ -29,6 +29,12 @@
<type>text</type>
<help>Private key of this peer, not stored on this host, only used for the configuration below.</help>
</field>
<field>
<id>configbuilder.address</id>
<label>Adress</label>
<type>text</type>
<help>List of addresses to use on the remote peer, these will be allowed (and optionally routed) from this instance.</help>
</field>
<field>
<id>configbuilder.psk</id>
<label>Pre-shared key</label>
@ -39,7 +45,7 @@
<id>configbuilder.tunneladdress</id>
<label>Allowed IPs</label>
<type>text</type>
<help>List of networks allowed to pass trough the tunnel adapter. Use CIDR notation like 10.0.0.0/24.</help>
<help>List of networks allowed to pass through the tunnel adapter. Use CIDR notation like 10.0.0.0/24.</help>
</field>
<field>
<id>configbuilder.keepalive</id>

View File

@ -471,4 +471,69 @@ class Util
return 32 - $bits;
}
}
/**
* convert cidr to array containing a start and end address
* @param array $cidr ipv4/6 cidr range definition
* @return array|bool address range or false when not a valid cidr
*/
public static function cidrToRange($cidr)
{
if (!self::isSubnet($cidr)) {
return false;
}
list ($ipaddr, $bits) = explode('/', $cidr);
$inet_ip = inet_pton($ipaddr);
if (str_contains($ipaddr, ':')) {
/* IPv6 */
$size = 128 - (int)$bits;
$mask = [
1 => (0xffffffff << max($size - 96, 0)) & 0xffffffff,
2 => (0xffffffff << max($size - 64, 0)) & 0xffffffff,
3 => (0xffffffff << max($size - 32, 0)) & 0xffffffff,
4 => (0xffffffff << $size) & 0xffffffff,
];
$netmask_parts = [];
for ($pos = 1; $pos <= 4; $pos += 1) {
$netmask_parts = array_merge($netmask_parts, str_split(sprintf('%08x', $mask[$pos]), 4));
}
$inet_mask = inet_pton(implode(':', $netmask_parts));
} else {
/* IPv4 */
$size = 32 - (int)$bits;
$inet_mask = inet_pton(long2ip((0xffffffff << $size) & 0xffffffff));
}
$inet_start = $inet_ip & $inet_mask;
$inet_end = $inet_ip | ~$inet_mask;
return [inet_ntop($inet_start), inet_ntop($inet_end)];
}
/**
* @param array $cidr ipv4/6 cidr range definition
* @return Generator yielding usable addresses in cidr range (max size for ipv6 is 32 bits)
*/
public static function cidrRangeIterator($cidr)
{
$range = self::cidrToRange($cidr);
if ($range) {
$bits = explode('/', $cidr)[1];
$inet_start = inet_pton($range[0]);
if (str_contains($range[0], ':')) {
for ($i=0; $i < pow(2, min(128 - (int)$bits, 32)); ++$i) {
yield inet_ntop($inet_start | inet_pton('0000::'. dechex($i)));
}
} else {
/**
* For ipv4, skip network and broadcast addresses,
* unless size is >= 31 in which case these may be omitted acording to RFC3021
*/
$range_start = (int)$bits >= 31 ? 0 : 1;
$range_stop = (int)$bits >= 31 ? pow(2, 32 - (int)$bits) : pow(2, 32 - (int)$bits) - 1 ;
for ($i = $range_start; $i < $range_stop; ++$i) {
yield inet_ntop($inet_start | inet_pton(long2ip($i)));
}
}
}
}
}

View File

@ -133,18 +133,19 @@
});
$("#configbuilder\\.servers").change(function(){
ajaxGet('/api/wireguard/server/getServer/' + $(this).val(), {}, function(data, status) {
if (data.server) {
ajaxGet('/api/wireguard/client/get_server_info/' + $(this).val(), {}, function(data, status) {
if (data.status === 'ok') {
let endpoint = $("#configbuilder\\.endpoint");
let peer_dns = $("#configbuilder\\.peer_dns");
$("#configbuilder\\.address").val(data.address);
peer_dns
.val(data.server.peer_dns)
.data('org-value', data.server.peer_dns);
.val(data.peer_dns)
.data('org-value', data.peer_dns);
endpoint
.val(data.server.endpoint)
.data('org-value', data.server.endpoint)
.data('pubkey', data.server.pubkey)
.val(data.endpoint)
.data('org-value', data.endpoint)
.data('pubkey', data.pubkey)
.change();
}
});
@ -162,7 +163,7 @@
name: $("#configbuilder\\.name").val(),
pubkey: $("#configbuilder\\.pubkey").val(),
psk: $("#configbuilder\\.psk").val(),
tunneladdress: $("#configbuilder\\.tunneladdress").val(),
tunneladdress: $("#configbuilder\\.address").val(),
keepalive: $("#configbuilder\\.keepalive").val(),
server: instance_id,
}
@ -211,6 +212,9 @@
let rows = [];
rows.push('[Interface]');
rows.push('PrivateKey = ' + $("#configbuilder\\.privkey").val());
if ($("#configbuilder\\.address").val()) {
rows.push('Address = ' + $("#configbuilder\\.address").val());
}
if ($("#configbuilder\\.peer_dns").val()) {
rows.push('DNS = ' + $("#configbuilder\\.peer_dns").val());
}