From 71c7ab4a9d87126a8de0434c08857f76f06addf5 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 1 Nov 2019 15:53:56 -0700 Subject: [PATCH] wireguard: Only use network manager for connections to servers - Don't create network link. This don't persist across reboots and it is the job of Network Manager. - Move NM settings code to regular plinth process instead of superuser. Permission for managing NM connections from the service daemon is granted by PolKit. - Use interface name to identify the connection as it seems to be simply to do so than the public key. Public key is not easy to retrieve from NM connection. - Merge code for adding and editing the connection to avoid repetition. - Add icon to the edit button. - Throw 404 error when incorrect client is specified. - Fix issue with storing preshared key. - Show formatting date in case of last connected time. - Show formatted sizes for data transmitted. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- actions/wireguard | 137 ------------------ plinth/modules/wireguard/__init__.py | 76 +++++++++- plinth/modules/wireguard/forms.py | 43 ++++-- .../wireguard/templates/wireguard.html | 12 +- .../templates/wireguard_delete_server.html | 15 +- .../templates/wireguard_show_client.html | 4 +- .../templates/wireguard_show_server.html | 40 +++-- plinth/modules/wireguard/urls.py | 6 +- plinth/modules/wireguard/utils.py | 45 ++++++ plinth/modules/wireguard/views.py | 119 ++++++--------- plinth/network.py | 14 +- 11 files changed, 258 insertions(+), 253 deletions(-) create mode 100644 plinth/modules/wireguard/utils.py diff --git a/actions/wireguard b/actions/wireguard index 7221ae907..3856f795e 100755 --- a/actions/wireguard +++ b/actions/wireguard @@ -24,9 +24,6 @@ import json import os import pathlib import subprocess -import sys - -from plinth import network PUBLIC_KEY_HELP = 'Public key for the client' @@ -57,17 +54,6 @@ def parse_arguments(): help='Remove a client') remove_client.add_argument('publickey', help=PUBLIC_KEY_HELP) - add_server = subparsers.add_parser('add-server', help='Add a server') - add_server.add_argument('--endpoint', required=True, - help='Server endpoint') - add_server.add_argument('--client-ip', required=True, - help='Client IP address provided by server') - add_server.add_argument('--public-key', required=True, - help='Public key of the server') - add_server.add_argument( - '--all-outgoing', action='store_true', - help='Use this connection to send all outgoing traffic') - modify_server = subparsers.add_parser('modify-server', help='Modify a server') modify_server.add_argument('--endpoint', required=True, @@ -185,129 +171,6 @@ def subcommand_remove_client(arguments): check=True) -def _find_next_interface(): - """Find next unused wireguard interface name.""" - output = subprocess.check_output(['wg', 'show', - 'interfaces']).decode().strip() - interfaces = output.split() - interface_num = 1 - new_interface_name = 'wg1' - while new_interface_name in interfaces: - interface_num += 1 - new_interface_name = 'wg' + str(interface_num) - - return new_interface_name - - -def _get_connection_settings(name, interface, endpoint, client_ip, public_key, - client_private_key, pre_shared_key): - """Return settings for Network Manager connection.""" - if not client_private_key: - with PRIVATE_KEY_PATH.open() as private_key_file: - client_private_key = private_key_file.read().strip() - - return { - 'common': { - 'name': name, - 'type': 'wireguard', - 'interface': interface, - 'zone': 'internal', - }, - 'ipv4': { - 'method': 'manual', - 'address': client_ip, - 'netmask': '', - 'gateway': '', - 'dns': '', - 'second_dns': '', - }, - 'wireguard': { - 'private_key': client_private_key, - 'peer_endpoint': endpoint, - 'peer_public_key': public_key, - 'preshared_key': pre_shared_key, - }, - } - - -def subcommand_add_server(arguments): - """Add a server.""" - secret_args = json.loads(sys.stdin.read() or '{}') - new_interface_name = _find_next_interface() - - subprocess.run( - ['ip', 'link', 'add', 'dev', new_interface_name, 'type', 'wireguard'], - check=True) - - connection_name = 'WireGuard-' + new_interface_name - settings = _get_connection_settings(connection_name, new_interface_name, - arguments.endpoint, - arguments.client_ip, - arguments.public_key, - secret_args.get('client_private_key'), - secret_args.get('pre_shared_key')) - network.add_connection(settings) - - -def subcommand_modify_server(arguments): - """Modify a server.""" - secret_args = json.loads(sys.stdin.read() or '{}') - - interfaces = _get_info() - interfaces.pop(SERVER_INTERFACE, None) - interface_to_modify = None - for interface in interfaces.values(): - if interface['peers']: - peer = interface['peers'][0] - if peer['public_key'] == arguments.public_key: - interface_to_modify = interface['interface_name'] - - if interface_to_modify: - connection = network.get_connection_by_interface_name( - interface_to_modify) - settings = _get_connection_settings( - 'WireGuard-' + interface_to_modify, interface_to_modify, - arguments.endpoint, arguments.client_ip, arguments.public_key, - secret_args.get('client_private_key'), - secret_args.get('pre_shared_key')) - - if connection: - network.edit_connection(connection, settings) - - else: - # XXX: raise error? - network.add_connection(settings) - - else: - raise InterfaceNotFoundError( - 'Interface with peer %s not found' % arguments.public_key) - - -def subcommand_remove_server(arguments): - """Remove a server.""" - interfaces = _get_info() - interfaces.pop(SERVER_INTERFACE, None) - interface_to_remove = None - for interface in interfaces.values(): - if interface['peers']: - peer = interface['peers'][0] - if peer['public_key'] == arguments.publickey: - interface_to_remove = interface['interface_name'] - - if interface_to_remove: - connection = network.get_connection_by_interface_name( - interface_to_remove) - if connection: - network.delete_connection(connection.get_uuid()) - - subprocess.run(['ip', 'link', 'delete', interface_to_remove], - check=True) - - else: - raise InterfaceNotFoundError( - 'Interface with peer %s not found' % arguments.publickey) - - def main(): """Parse arguments and perform all duties.""" arguments = parse_arguments() diff --git a/plinth/modules/wireguard/__init__.py b/plinth/modules/wireguard/__init__.py index 21f24aa80..7cdb630e0 100644 --- a/plinth/modules/wireguard/__init__.py +++ b/plinth/modules/wireguard/__init__.py @@ -18,6 +18,7 @@ FreedomBox app for wireguard. """ +import datetime import json from django.urls import reverse_lazy @@ -27,10 +28,12 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.modules.firewall.components import Firewall -from plinth.utils import format_lazy +from plinth.utils import format_lazy, import_from_gi from .manifest import clients # noqa, pylint: disable=unused-import +nm = import_from_gi('NM', '1.0') + version = 1 managed_packages = ['wireguard'] @@ -116,15 +119,74 @@ def get_public_key(): return public_key +def get_nm_info(): + """Get information from network manager.""" + client = nm.Client.new(None) + + connections = {} + for connection in client.get_connections(): + if connection.get_connection_type() != 'wireguard': + continue + + settings = connection.get_setting_by_name('wireguard') + secrets = connection.get_secrets('wireguard') + connection.update_secrets('wireguard', secrets) + + info = {} + info['interface'] = connection.get_interface_name() + info['private_key'] = settings.get_private_key() + info['listen_port'] = settings.get_listen_port() + info['fwmark'] = settings.get_fwmark() + info['mtu'] = settings.get_mtu() + info['default_route'] = settings.get_ip4_auto_default_route() + info['peers'] = [] + for peer_index in range(settings.get_peers_len()): + peer = settings.get_peer(peer_index) + peer_info = { + 'endpoint': peer.get_endpoint(), + 'public_key': peer.get_public_key(), + 'preshared_key': peer.get_preshared_key(), + 'persistent_keepalive': peer.get_persistent_keepalive(), + 'allowed_ips': [] + } + for index in range(peer.get_allowed_ips_len()): + allowed_ip = peer.get_allowed_ip(index, None) + peer_info['allowed_ips'].append(allowed_ip) + + info['peers'].append(peer_info) + + settings_ipv4 = connection.get_setting_ip4_config() + if settings_ipv4 and settings_ipv4.get_num_addresses(): + info['ip_address'] = settings_ipv4.get_address(0).get_address() + + connections[info['interface']] = info + + return connections + + def get_info(): """Return server and clients info.""" output = actions.superuser_run('wireguard', ['get-info']) - info = json.loads(output) - my_server_info = info.pop(SERVER_INTERFACE, {}) - my_client_servers = [] - for interface in info.values(): - if interface['peers']: - my_client_servers.append(interface['peers'][0]) + status = json.loads(output) + + nm_info = get_nm_info() + + my_server_info = status.pop(SERVER_INTERFACE, {}) + my_client_servers = {} + + for interface, info in nm_info.items(): + my_client_servers[interface] = info + + if interface not in status: + continue + + for info_peer in info['peers']: + for status_peer in status[interface]['peers']: + if info_peer['public_key'] == status_peer['public_key']: + info_peer['status'] = status_peer + status_peer['latest_handshake'] = \ + datetime.datetime.fromtimestamp( + int(status_peer['latest_handshake'])) return { 'my_server': { diff --git a/plinth/modules/wireguard/forms.py b/plinth/modules/wireguard/forms.py index 1c4059c1a..9dc7ef977 100644 --- a/plinth/modules/wireguard/forms.py +++ b/plinth/modules/wireguard/forms.py @@ -31,30 +31,55 @@ class AddClientForm(forms.Form): class AddServerForm(forms.Form): """Form to add server.""" - endpoint = forms.CharField( + peer_endpoint = forms.CharField( label=_('Endpoint'), strip=True, help_text=_('Server endpoint with the form "ip:port".')) - client_ip_address = forms.CharField( + peer_public_key = forms.CharField( + label=_('Public key of the server'), strip=True, + help_text=_('Public key of the server.')) + + ip_address = forms.CharField( label=_('Client IP address provided by server'), strip=True, help_text=_('IP address assigned to the client on the VPN after ' 'connecting to the endpoint.')) - public_key = forms.CharField( - label=_('Public key of the server'), strip=True, - help_text=_('Public key of the server.')) - - client_private_key = forms.CharField( + private_key = forms.CharField( label=_('Private key of the client'), strip=True, help_text=_('Optional. A new key is generated if left blank.'), required=False) - pre_shared_key = forms.CharField( + preshared_key = forms.CharField( label=_('Pre-shared key'), strip=True, required=False, help_text=_('Optional. A shared secret key provided by the server to ' 'add an additional layer of encryption.')) - all_outgoing_traffic = forms.BooleanField( + default_route = forms.BooleanField( label=_('Use this connection to send all outgoing traffic'), required=False, help_text=_('Use this connection to send all outgoing traffic.')) + + def get_settings(self): + """Return NM settings dict from cleaned data.""" + settings = { + 'common': { + 'type': 'wireguard', + 'zone': 'internal', + }, + 'ipv4': { + 'method': 'manual', + 'address': self.cleaned_data['ip_address'], + 'netmask': '', + 'gateway': '', + 'dns': '', + 'second_dns': '', + }, + 'wireguard': { + 'peer_endpoint': self.cleaned_data['peer_endpoint'], + 'peer_public_key': self.cleaned_data['peer_public_key'], + 'private_key': self.cleaned_data['private_key'], + 'preshared_key': self.cleaned_data['preshared_key'], + 'default_route': self.cleaned_data['default_route'], + } + } + return settings diff --git a/plinth/modules/wireguard/templates/wireguard.html b/plinth/modules/wireguard/templates/wireguard.html index 7734d7f47..753836e44 100644 --- a/plinth/modules/wireguard/templates/wireguard.html +++ b/plinth/modules/wireguard/templates/wireguard.html @@ -43,7 +43,6 @@ {{ peer.latest_handshake }} {% endfor %} - {% else %} @@ -72,18 +71,17 @@ {% trans "Last Connected Time" %} {% if client_peers %} - {% for peer in client_peers %} + {% for interface, server in client_peers.items %} - {{ peer.endpoint }} + {{ server.peers.0.endpoint }} - - {{ peer.public_key }} + + {{ server.peers.0.public_key }} - {{ peer.latest_handshake }} + {{ server.peers.0.status.latest_handshake }} {% endfor %} - {% else %} diff --git a/plinth/modules/wireguard/templates/wireguard_delete_server.html b/plinth/modules/wireguard/templates/wireguard_delete_server.html index 1fb708300..b36cdf205 100644 --- a/plinth/modules/wireguard/templates/wireguard_delete_server.html +++ b/plinth/modules/wireguard/templates/wireguard_delete_server.html @@ -28,9 +28,18 @@

{% trans "Are you sure that you want to delete this server?" %}

-

- {{ public_key }} -

+ + + + + + + + + + + +
{% trans "Endpoint" %}{{ peer_endpoint }}
{% trans "Public Key" %}{{ peer_public_key }}
{% csrf_token %} diff --git a/plinth/modules/wireguard/templates/wireguard_show_client.html b/plinth/modules/wireguard/templates/wireguard_show_client.html index 4d2abfe5f..416a87c6c 100644 --- a/plinth/modules/wireguard/templates/wireguard_show_client.html +++ b/plinth/modules/wireguard/templates/wireguard_show_client.html @@ -32,8 +32,8 @@

{% trans "Status" %}

{% trans "Client Public Key:" %} {{ client.public_key }}

-

{% trans "Data transmitted:" %} {{ client.transfer_tx }}

-

{% trans "Data received:" %} {{ client.transfer_rx }}

+

{% trans "Data transmitted:" %} {{ client.transfer_tx|filesizeformat }}

+

{% trans "Data received:" %} {{ client.transfer_rx|filesizeformat }}

{% trans "Latest handshake:" %} {{ client.latest_handshake }}

diff --git a/plinth/modules/wireguard/templates/wireguard_show_server.html b/plinth/modules/wireguard/templates/wireguard_show_server.html index 66f7a80b1..92a52d04e 100644 --- a/plinth/modules/wireguard/templates/wireguard_show_server.html +++ b/plinth/modules/wireguard/templates/wireguard_show_server.html @@ -24,21 +24,43 @@

{{ title }}

-

{% trans "Server Information" %}

-

{% trans "Endpoint:" %} {{ server.endpoint }}

-

{% trans "Public Key:" %} {{ server.public_key }}

-

{% trans "Pre-shared key:" %} {{ server.preshared_key }}

-

{% trans "Data transmitted:" %} {{ server.transfer_tx }}

-

{% trans "Data received:" %} {{ server.transfer_rx }}

-

{% trans "Latest handshake:" %} {{ server.latest_handshake }}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans "Endpoint:" %}{{ server.peers.0.endpoint }}
{% trans "Public Key:" %}{{ server.peers.0.public_key }}
{% trans "Pre-shared key:" %}{{ server.peers.0.preshared_key }}
{% trans "Data transmitted:" %}{{ server.peers.0.status.transfer_tx|filesizeformat }}
{% trans "Data received:" %}{{ server.peers.0.status.transfer_rx|filesizeformat }}
{% trans "Latest handshake:" %}{{ server.peers.0.status.latest_handshake }}

+ href="{% url 'wireguard:edit-server' interface %}"> + {% trans "Edit Server" %} + href="{% url 'wireguard:delete-server' interface %}"> {% trans "Delete Server" %} diff --git a/plinth/modules/wireguard/urls.py b/plinth/modules/wireguard/urls.py index 20c9c11d6..899b32aae 100644 --- a/plinth/modules/wireguard/urls.py +++ b/plinth/modules/wireguard/urls.py @@ -34,10 +34,10 @@ urlpatterns = [ views.DeleteClientView.as_view(), name='delete-client'), url(r'^apps/wireguard/server/add/$', views.AddServerView.as_view(), name='add-server'), - url(r'^apps/wireguard/server/(?P[^/]+)/show/$', + url(r'^apps/wireguard/server/(?Pwg[0-9]+)/show/$', views.ShowServerView.as_view(), name='show-server'), - url(r'^apps/wireguard/server/(?P[^/]+)/edit/$', + url(r'^apps/wireguard/server/(?Pwg[0-9]+)/edit/$', views.EditServerView.as_view(), name='edit-server'), - url(r'^apps/wireguard/server/(?P[^/]+)/delete/$', + url(r'^apps/wireguard/server/(?Pwg[0-9]+)/delete/$', views.DeleteServerView.as_view(), name='delete-server'), ] diff --git a/plinth/modules/wireguard/utils.py b/plinth/modules/wireguard/utils.py new file mode 100644 index 000000000..d41ba042e --- /dev/null +++ b/plinth/modules/wireguard/utils.py @@ -0,0 +1,45 @@ +# +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +Utilities for managing WireGuard. +""" + +import subprocess + +from plinth import network + + +def find_next_interface(): + """Find next unused wireguard interface name.""" + output = subprocess.check_output(['wg', 'show', + 'interfaces']).decode().strip() + interfaces = output.split() + interface_num = 1 + new_interface_name = 'wg1' + while new_interface_name in interfaces: + interface_num += 1 + new_interface_name = 'wg' + str(interface_num) + + return new_interface_name + + +def add_server(settings): + """Add a server.""" + interface_name = find_next_interface() + settings['common']['name'] = 'WireGuard-' + interface_name + settings['common']['interface'] = interface_name + network.add_connection(settings) diff --git a/plinth/modules/wireguard/views.py b/plinth/modules/wireguard/views.py index 824b27a93..1e1f72141 100644 --- a/plinth/modules/wireguard/views.py +++ b/plinth/modules/wireguard/views.py @@ -18,22 +18,21 @@ Views for WireGuard application. """ -import json -import tempfile import urllib.parse from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin +from django.http import Http404 from django.shortcuts import redirect from django.urls import reverse_lazy from django.utils.translation import ugettext as _ from django.views.generic import FormView, TemplateView import plinth.modules.wireguard as wireguard -from plinth import actions +from plinth import actions, network from plinth.views import AppView -from . import forms +from . import forms, utils class WireguardView(AppView): @@ -159,29 +158,7 @@ class AddServerView(SuccessMessageMixin, FormView): def form_valid(self, form): """Add the server.""" - endpoint = form.cleaned_data.get('endpoint') - client_ip_address = form.cleaned_data.get('client_ip_address') - public_key = form.cleaned_data.get('public_key') - client_private_key = form.cleaned_data.get('client_private_key') - pre_shared_key = form.cleaned_data.get('pre_shared_key') - all_outgoing_traffic = form.cleaned_data.get('all_outgoing_traffic') - args = [ - 'add-server', '--endpoint', endpoint, '--client-ip', - client_ip_address, '--public-key', public_key - ] - secret_args = {} - if client_private_key: - secret_args['client_private_key'] = client_private_key - - if pre_shared_key: - secret_args['pre_shared_key'] = pre_shared_key - - if all_outgoing_traffic: - args.append('--all-outgoing') - - actions.superuser_run('wireguard', args, - input=json.dumps(secret_args).encode()) - + utils.add_server(form.get_settings()) return super().form_valid(form) @@ -192,15 +169,16 @@ class ShowServerView(SuccessMessageMixin, TemplateView): def get_context_data(self, **kwargs): """Return additional context data for rendering the template.""" context = super().get_context_data(**kwargs) - context['title'] = _('Show Server') + context['title'] = _('Server Information') - public_key = urllib.parse.unquote(self.kwargs['public_key']) + interface = self.kwargs['interface'] info = wireguard.get_info() - context.update(info) - for server in info['my_client']['servers']: - if server['public_key'] == public_key: - context['server'] = server + server = info['my_client']['servers'].get(interface) + if not server: + raise Http404 + context['interface'] = interface + context['server'] = server return context @@ -220,48 +198,31 @@ class EditServerView(SuccessMessageMixin, FormView): def get_initial(self): """Get initial form data.""" initial = super().get_initial() - public_key = urllib.parse.unquote(self.kwargs['public_key']) - info = wireguard.get_info() - for server in info['my_client']['servers']: - if server['public_key'] == public_key: - initial['endpoint'] = server['endpoint'] - initial['client_ip_address'] = '' - initial['public_key'] = server['public_key'] - pre_shared_key = server['preshared_key'] - if pre_shared_key == '(none)': - initial['pre_shared_key'] = '' - else: - initial['pre_shared_key'] = server['preshared_key'] + interface = self.kwargs['interface'] + info = wireguard.get_nm_info() + server = info.get(interface) + if not server: + raise Http404 - initial['all_outgoing_traffic'] = False + initial['ip_address'] = server.get('ip_address') + if server['peers']: + peer = server['peers'][0] + initial['peer_endpoint'] = peer['endpoint'] + initial['peer_public_key'] = peer['public_key'] + initial['private_key'] = server['private_key'] + initial['preshared_key'] = peer['preshared_key'] + initial['default_route'] = server['default_route'] return initial def form_valid(self, form): """Update the server.""" - endpoint = form.cleaned_data.get('endpoint') - client_ip_address = form.cleaned_data.get('client_ip_address') - public_key = form.cleaned_data.get('public_key') - client_private_key = form.client_data.get('client_private_key') - pre_shared_key = form.cleaned_data.get('pre_shared_key') - all_outgoing_traffic = form.cleaned_data.get('all_outgoing_traffic') - args = [ - 'modify-server', '--endpoint', endpoint, '--client-ip', - client_ip_address, '--public-key', public_key - ] - secret_args = {} - if client_private_key: - secret_args['client_private_key'] = client_private_key - - if pre_shared_key: - secret_args['pre_shared_key'] = pre_shared_key - - if all_outgoing_traffic: - args.append('--all-outgoing') - - actions.superuser_run('wireguard', args, - input=json.dumps(secret_args).encode()) - + settings = form.get_settings() + interface = self.kwargs['interface'] + settings['common']['interface'] = interface + settings['common']['name'] = 'WireGuard-' + interface + connection = network.get_connection_by_interface_name(interface) + network.edit_connection(connection, settings) return super().form_valid(form) @@ -273,12 +234,24 @@ class DeleteServerView(SuccessMessageMixin, TemplateView): """Return additional context data for rendering the template.""" context = super().get_context_data(**kwargs) context['title'] = _('Delete Server') - context['public_key'] = urllib.parse.unquote(self.kwargs['public_key']) + + interface = self.kwargs['interface'] + info = wireguard.get_nm_info() + server = info.get(interface) + if not server: + raise Http404 + + context['interface'] = interface + if server['peers']: + peer = server['peers'][0] + context['peer_endpoint'] = peer['endpoint'] + context['peer_public_key'] = peer['public_key'] + return context - def post(self, request, public_key): + def post(self, request, interface): """Delete the server.""" - public_key = urllib.parse.unquote(public_key) - actions.superuser_run('wireguard', ['remove-server', public_key]) + connection = network.get_connection_by_interface_name(interface) + network.delete_connection(connection.get_uuid()) messages.success(request, _('Server deleted.')) return redirect('wireguard:index') diff --git a/plinth/network.py b/plinth/network.py index 0a954f8e9..d491e3083 100644 --- a/plinth/network.py +++ b/plinth/network.py @@ -470,17 +470,25 @@ def _update_wireless_settings(connection, wireless): def _update_wireguard_settings(connection, wireguard): """Create/edit WireGuard settings for network manager connections.""" - settings = nm.SettingWireGuard.new() - connection.add_setting(settings) + settings = connection.get_setting_by_name('wireguard') + if not settings: + settings = nm.SettingWireGuard.new() + connection.add_setting(settings) settings.set_property(nm.SETTING_WIREGUARD_PRIVATE_KEY, wireguard['private_key']) peer = nm.WireGuardPeer.new() peer.set_endpoint(wireguard['peer_endpoint'], False) peer.set_public_key(wireguard['peer_public_key'], False) - peer.set_preshared_key(wireguard['preshared_key'], False) + if wireguard['preshared_key']: + # Flag NONE means that NM should store and retain the secret. + # Default seems to be NOT_REQUIRED in this case. + peer.set_preshared_key_flags(nm.SettingSecretFlags.NONE) + peer.set_preshared_key(wireguard['preshared_key'], False) + peer.append_allowed_ip('0.0.0.0/0', False) peer.append_allowed_ip('::/0', False) + settings.clear_peers() settings.append_peer(peer)