summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/containers.py2
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py2
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py2
-rwxr-xr-xsrc/conf_mode/interfaces-wirelessmodem.py132
-rwxr-xr-xsrc/conf_mode/interfaces-wwan.py105
-rwxr-xr-xsrc/conf_mode/service_mdns-repeater.py38
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py78
-rwxr-xr-xsrc/conf_mode/vpn_rsa-keys.py6
-rwxr-xr-xsrc/conf_mode/vrf.py2
9 files changed, 213 insertions, 154 deletions
diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py
index 5efdb6a2f..21b47f42a 100755
--- a/src/conf_mode/containers.py
+++ b/src/conf_mode/containers.py
@@ -75,7 +75,7 @@ def get_config(config=None):
base = ['container']
container = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True)
+ get_first_key=True, no_tag_node_value_mangle=True)
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
default_values = defaults(base)
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index 3675db73b..6c4c6c95b 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -22,6 +22,7 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
+from vyos.configverify import verify_authentication
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_vrf
from vyos.configverify import verify_mtu_ipv6
@@ -51,6 +52,7 @@ def verify(pppoe):
return None
verify_source_interface(pppoe)
+ verify_authentication(pppoe)
verify_vrf(pppoe)
verify_mtu_ipv6(pppoe)
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index 1575c83ef..294da8ef9 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -114,7 +114,7 @@ def verify(tunnel):
raise ConfigError('Option ignore-df can only be used on GRETAP tunnels!')
if dict_search('parameters.ip.no_pmtu_discovery', tunnel) == None:
- raise ConfigError('Option ignore-df path MTU discovery to be disabled!')
+ raise ConfigError('Option ignore-df requires path MTU discovery to be disabled!')
def generate(tunnel):
diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py
deleted file mode 100755
index 976953b31..000000000
--- a/src/conf_mode/interfaces-wirelessmodem.py
+++ /dev/null
@@ -1,132 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2020 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-
-from sys import exit
-
-from vyos.config import Config
-from vyos.configdict import get_interface_dict
-from vyos.configverify import verify_vrf
-from vyos.template import render
-from vyos.util import call
-from vyos.util import check_kmod
-from vyos.util import find_device_file
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-k_mod = ['option', 'usb_wwan', 'usbserial']
-
-def get_config(config=None):
- """
- Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
- interface name will be added or a deleted flag
- """
- if config:
- conf = config
- else:
- conf = Config()
- base = ['interfaces', 'wirelessmodem']
- wwan = get_interface_dict(conf, base)
-
- return wwan
-
-def verify(wwan):
- if 'deleted' in wwan:
- return None
-
- if not 'apn' in wwan:
- raise ConfigError('No APN configured for "{ifname}"'.format(**wwan))
-
- if not 'device' in wwan:
- raise ConfigError('Physical "device" must be configured')
-
- # we can not use isfile() here as Linux device files are no regular files
- # thus the check will return False
- dev_path = find_device_file(wwan['device'])
- if dev_path is None or not os.path.exists(dev_path):
- raise ConfigError('Device "{device}" does not exist'.format(**wwan))
-
- verify_vrf(wwan)
-
- return None
-
-def generate(wwan):
- # set up configuration file path variables where our templates will be
- # rendered into
- ifname = wwan['ifname']
- config_wwan = f'/etc/ppp/peers/{ifname}'
- config_wwan_chat = f'/etc/ppp/peers/chat.{ifname}'
- script_wwan_pre_up = f'/etc/ppp/ip-pre-up.d/1010-vyos-wwan-{ifname}'
- script_wwan_ip_up = f'/etc/ppp/ip-up.d/1010-vyos-wwan-{ifname}'
- script_wwan_ip_down = f'/etc/ppp/ip-down.d/1010-vyos-wwan-{ifname}'
-
- config_files = [config_wwan, config_wwan_chat, script_wwan_pre_up,
- script_wwan_ip_up, script_wwan_ip_down]
-
- # Always hang-up WWAN connection prior generating new configuration file
- call(f'systemctl stop ppp@{ifname}.service')
-
- if 'deleted' in wwan:
- # Delete PPP configuration files
- for file in config_files:
- if os.path.exists(file):
- os.unlink(file)
-
- else:
- wwan['device'] = find_device_file(wwan['device'])
-
- # Create PPP configuration files
- render(config_wwan, 'wwan/peer.tmpl', wwan)
- # Create PPP chat script
- render(config_wwan_chat, 'wwan/chat.tmpl', wwan)
-
- # generated script file must be executable
-
- # Create script for ip-pre-up.d
- render(script_wwan_pre_up, 'wwan/ip-pre-up.script.tmpl',
- wwan, permission=0o755)
- # Create script for ip-up.d
- render(script_wwan_ip_up, 'wwan/ip-up.script.tmpl',
- wwan, permission=0o755)
- # Create script for ip-down.d
- render(script_wwan_ip_down, 'wwan/ip-down.script.tmpl',
- wwan, permission=0o755)
-
- return None
-
-def apply(wwan):
- if 'deleted' in wwan:
- # bail out early
- return None
-
- if not 'disable' in wwan:
- # "dial" WWAN connection
- call('systemctl start ppp@{ifname}.service'.format(**wwan))
-
- return None
-
-if __name__ == '__main__':
- try:
- check_kmod(k_mod)
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py
new file mode 100755
index 000000000..31c599145
--- /dev/null
+++ b/src/conf_mode/interfaces-wwan.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020-2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import get_interface_dict
+from vyos.configverify import verify_authentication
+from vyos.configverify import verify_interface_exists
+from vyos.configverify import verify_vrf
+from vyos.ifconfig import WWANIf
+from vyos.util import cmd
+from vyos.util import dict_search
+from vyos.template import render
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+def get_config(config=None):
+ """
+ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
+ interface name will be added or a deleted flag
+ """
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['interfaces', 'wwan']
+ wwan = get_interface_dict(conf, base)
+
+ return wwan
+
+def verify(wwan):
+ if 'deleted' in wwan:
+ return None
+
+ ifname = wwan['ifname']
+ if not 'apn' in wwan:
+ raise ConfigError(f'No APN configured for "{ifname}"!')
+
+ verify_interface_exists(ifname)
+ verify_authentication(wwan)
+ verify_vrf(wwan)
+
+ return None
+
+def generate(wwan):
+ return None
+
+def apply(wwan):
+ # we only need the modem number. wwan0 -> 0, wwan1 -> 1
+ modem = wwan['ifname'].lstrip('wwan')
+ base_cmd = f'mmcli --modem {modem}'
+ # Number of bearers is limited - always disconnect first
+ cmd(f'{base_cmd} --simple-disconnect')
+
+ w = WWANIf(wwan['ifname'])
+ if 'deleted' in wwan or 'disable' in wwan:
+ w.remove()
+ return None
+
+ ip_type = 'ipv4'
+ slaac = dict_search('ipv6.address.autoconf', wwan) != None
+ if 'address' in wwan:
+ if 'dhcp' in wwan['address'] and ('dhcpv6' in wwan['address'] or slaac):
+ ip_type = 'ipv4v6'
+ elif 'dhcpv6' in wwan['address'] or slaac:
+ ip_type = 'ipv6'
+ elif 'dhcp' in wwan['address']:
+ ip_type = 'ipv4'
+
+ options = f'ip-type={ip_type},apn=' + wwan['apn']
+ if 'authentication' in wwan:
+ options += ',user={user},password={password}'.format(**wwan['authentication'])
+
+ command = f'{base_cmd} --simple-connect="{options}"'
+ cmd(command)
+ w.update(wwan)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/service_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py
index 729518c96..c920920ed 100755
--- a/src/conf_mode/service_mdns-repeater.py
+++ b/src/conf_mode/service_mdns-repeater.py
@@ -16,10 +16,12 @@
import os
+from json import loads
from sys import exit
from netifaces import ifaddresses, interfaces, AF_INET
from vyos.config import Config
+from vyos.ifconfig.vrrp import VRRP
from vyos.template import render
from vyos.util import call
from vyos import ConfigError
@@ -27,6 +29,7 @@ from vyos import airbag
airbag.enable()
config_file = r'/etc/default/mdns-repeater'
+vrrp_running_file = '/run/mdns_vrrp_active'
def get_config(config=None):
if config:
@@ -35,6 +38,9 @@ def get_config(config=None):
conf = Config()
base = ['service', 'mdns', 'repeater']
mdns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ if mdns:
+ mdns['vrrp_exists'] = conf.exists('high-availability vrrp')
return mdns
def verify(mdns):
@@ -60,6 +66,18 @@ def verify(mdns):
return None
+# Get VRRP states from interfaces, returns only interfaces where state is MASTER
+def get_vrrp_master(interfaces):
+ json_data = loads(VRRP.collect('json'))
+ for group in json_data:
+ if 'data' in group:
+ if 'ifp_ifname' in group['data']:
+ iface = group['data']['ifp_ifname']
+ state = group['data']['state'] # 2 = Master
+ if iface in interfaces and state != 2:
+ interfaces.remove(iface)
+ return interfaces
+
def generate(mdns):
if not mdns:
return None
@@ -68,6 +86,12 @@ def generate(mdns):
print('Warning: mDNS repeater will be deactivated because it is disabled')
return None
+ if mdns['vrrp_exists'] and 'vrrp_disable' in mdns:
+ mdns['interface'] = get_vrrp_master(mdns['interface'])
+
+ if len(mdns['interface']) < 2:
+ return None
+
render(config_file, 'mdns-repeater/mdns-repeater.tmpl', mdns)
return None
@@ -76,7 +100,21 @@ def apply(mdns):
call('systemctl stop mdns-repeater.service')
if os.path.exists(config_file):
os.unlink(config_file)
+
+ if os.path.exists(vrrp_running_file):
+ os.unlink(vrrp_running_file)
else:
+ if 'vrrp_disable' not in mdns and os.path.exists(vrrp_running_file):
+ os.unlink(vrrp_running_file)
+
+ if mdns['vrrp_exists'] and 'vrrp_disable' in mdns:
+ if not os.path.exists(vrrp_running_file):
+ os.mknod(vrrp_running_file) # vrrp script looks for this file to update mdns repeater
+
+ if len(mdns['interface']) < 2:
+ call('systemctl stop mdns-repeater.service')
+ return None
+
call('systemctl restart mdns-repeater.service')
return None
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 4efedd995..433c51e7e 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -23,10 +23,11 @@ from vyos.config import Config
from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_interface_exists
from vyos.ifconfig import Interface
+from vyos.template import ip_from_cidr
from vyos.template import render
+from vyos.validate import is_ipv6_link_local
from vyos.util import call
from vyos.util import dict_search
-from vyos.util import get_interface_address
from vyos.util import process_named_running
from vyos.util import run
from vyos.util import cidr_fit
@@ -35,9 +36,9 @@ from vyos import airbag
airbag.enable()
authby_translate = {
- 'pre-shared-secret': 'secret',
- 'rsa': 'rsasig',
- 'x509': 'rsasig'
+ 'pre-shared-secret': 'psk',
+ 'rsa': 'pubkey',
+ 'x509': 'pubkey'
}
default_pfs = 'dh-group2'
pfs_translate = {
@@ -73,12 +74,18 @@ any_log_modes = [
ike_ciphers = {}
esp_ciphers = {}
+dhcp_wait_attempts = 2
+dhcp_wait_sleep = 1
+
mark_base = 0x900000
-CA_PATH = "/etc/ipsec.d/cacerts/"
-CRL_PATH = "/etc/ipsec.d/crls/"
+CERT_PATH="/etc/swanctl/x509/"
+KEY_PATH="/etc/swanctl/private/"
+CA_PATH = "/etc/swanctl/x509ca/"
+CRL_PATH = "/etc/swanctl/x509crl/"
DHCP_BASE = "/var/lib/dhcp/dhclient"
+DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_waiting"
LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/']
X509_PATH = '/config/auth/'
@@ -96,6 +103,7 @@ def get_config(config=None):
ipsec = conf.get_config_dict(base, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
+ ipsec['dhcp_no_address'] = {}
ipsec['interface_change'] = leaf_node_changed(conf, base + ['ipsec-interfaces', 'interface'])
ipsec['l2tp_exists'] = conf.exists('vpn l2tp remote-access ipsec-settings ')
ipsec['nhrp_exists'] = conf.exists('protocols nhrp tunnel')
@@ -119,7 +127,7 @@ def get_config(config=None):
if enc and hash:
ciphers.append(f"{enc}-{hash}-{pfs_translate[pfs]}" if pfs else f"{enc}-{hash}")
- ike_ciphers[group] = ','.join(ciphers) + '!'
+ ike_ciphers[group] = ','.join(ciphers)
if 'esp_group' in ipsec:
for group, esp_conf in ipsec['esp_group'].items():
@@ -139,7 +147,7 @@ def get_config(config=None):
hash = proposal['hash'] if 'hash' in proposal else None
if enc and hash:
ciphers.append(f"{enc}-{hash}-{pfs_translate[pfs]}" if pfs else f"{enc}-{hash}")
- esp_ciphers[group] = ','.join(ciphers) + '!'
+ esp_ciphers[group] = ','.join(ciphers)
return ipsec
@@ -162,6 +170,15 @@ def verify_rsa_local_key(ipsec):
def verify_rsa_key(ipsec, key_name):
return dict_search(f'rsa_key_name.{key_name}.rsa_key', ipsec['rsa_keys'])
+def get_dhcp_address(iface):
+ addresses = Interface(iface).get_addr()
+ if not addresses:
+ return None
+ for address in addresses:
+ if not is_ipv6_link_local(address):
+ return ip_from_cidr(address)
+ return None
+
def verify(ipsec):
if not ipsec:
return None
@@ -252,9 +269,17 @@ def verify(ipsec):
if not os.path.exists(f'{DHCP_BASE}_{dhcp_interface}.conf'):
raise ConfigError(f"Invalid dhcp-interface on site-to-site peer {peer}")
- address = Interface(dhcp_interface).get_addr()
+ address = get_dhcp_address(dhcp_interface)
+ count = 0
+ while not address and count < dhcp_wait_attempts:
+ address = get_dhcp_address(dhcp_interface)
+ count += 1
+ sleep(dhcp_wait_sleep)
+
if not address:
- raise ConfigError(f"Failed to get address from dhcp-interface on site-to-site peer {peer}")
+ ipsec['dhcp_no_address'][peer] = dhcp_interface
+ print(f"Failed to get address from dhcp-interface on site-to-site peer {peer} -- skipped")
+ continue
if 'vti' in peer_conf:
if 'local_address' in peer_conf and 'dhcp_interface' in peer_conf:
@@ -291,16 +316,28 @@ def generate(ipsec):
data = {}
if ipsec:
+ if ipsec['dhcp_no_address']:
+ with open(DHCP_HOOK_IFLIST, 'w') as f:
+ f.write(" ".join(ipsec['dhcp_no_address'].values()))
+
data = ipsec
data['authby'] = authby_translate
data['ciphers'] = {'ike': ike_ciphers, 'esp': esp_ciphers}
data['marks'] = {}
data['rsa_local_key'] = verify_rsa_local_key(ipsec)
- data['x509_path'] = X509_PATH
if 'site_to_site' in data and 'peer' in data['site_to_site']:
for peer, peer_conf in ipsec['site_to_site']['peer'].items():
+ if peer in ipsec['dhcp_no_address']:
+ continue
+
if peer_conf['authentication']['mode'] == 'x509':
+ cert_file = os.path.join(X509_PATH, peer_conf['authentication']['x509']['cert_file'])
+ call(f'cp -f {cert_file} {CERT_PATH}')
+
+ key_file = os.path.join(X509_PATH, peer_conf['authentication']['x509']['key']['file'])
+ call(f'cp -f {key_file} {KEY_PATH}')
+
ca_cert_file = os.path.join(X509_PATH, peer_conf['authentication']['x509']['ca_cert_file'])
call(f'cp -f {ca_cert_file} {CA_PATH}')
@@ -312,22 +349,29 @@ def generate(ipsec):
if 'local_address' in peer_conf:
local_ip = peer_conf['local_address']
elif 'dhcp_interface' in peer_conf:
- local_ip = Interface(peer_conf['dhcp_interface']).get_addr()
+ local_ip = get_dhcp_address(peer_conf['dhcp_interface'])
data['site_to_site']['peer'][peer]['local_address'] = local_ip
if 'vti' in peer_conf and 'bind' in peer_conf['vti']:
vti_interface = peer_conf['vti']['bind']
data['marks'][vti_interface] = get_mark(vti_interface)
- else:
+
+ if 'tunnel' in peer_conf:
for tunnel, tunnel_conf in peer_conf['tunnel'].items():
- local_prefix = dict_search('local.prefix', tunnel_conf)
- remote_prefix = dict_search('remote.prefix', tunnel_conf)
+ local_prefixes = dict_search('local.prefix', tunnel_conf)
+ remote_prefixes = dict_search('remote.prefix', tunnel_conf)
- if not local_prefix or not remote_prefix:
+ if not local_prefixes or not remote_prefixes:
continue
- passthrough = cidr_fit(local_prefix, remote_prefix)
+ passthrough = []
+
+ for local_prefix in local_prefixes:
+ for remote_prefix in remote_prefixes:
+ if cidr_fit(local_prefix, remote_prefix):
+ passthrough.append(local_prefix)
+
data['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough
if 'logging' in ipsec and 'log_modes' in ipsec['logging']:
diff --git a/src/conf_mode/vpn_rsa-keys.py b/src/conf_mode/vpn_rsa-keys.py
index 6cf7eba6e..c6ff369ad 100755
--- a/src/conf_mode/vpn_rsa-keys.py
+++ b/src/conf_mode/vpn_rsa-keys.py
@@ -29,7 +29,8 @@ from Crypto.PublicKey.RSA import construct
airbag.enable()
LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/']
-LOCAL_OUTPUT = '/etc/ipsec.d/certs/localhost.pub'
+LOCAL_OUTPUT = '/etc/swanctl/pubkey/localhost.pub'
+LOCAL_KEY_OUTPUT = '/etc/swanctl/private/localhost.key'
def get_config(config=None):
if config:
@@ -68,6 +69,7 @@ def generate(conf):
if 'local_key' in conf and 'file' in conf['local_key']:
local_key = conf['local_key']['file']
local_key_path = get_local_key(local_key)
+ call(f'sudo cp -f {local_key_path} {LOCAL_KEY_OUTPUT}')
call(f'sudo /usr/bin/openssl rsa -in {local_key_path} -pubout -out {LOCAL_OUTPUT}')
if 'rsa_key_name' in conf:
@@ -82,7 +84,7 @@ def generate(conf):
else:
remote_key = bytes('-----BEGIN PUBLIC KEY-----\n' + remote_key + '\n-----END PUBLIC KEY-----\n', 'utf-8')
- with open(f'/etc/ipsec.d/certs/{key_name}.pub', 'wb') as f:
+ with open(f'/etc/swanctl/pubkey/{key_name}.pub', 'wb') as f:
f.write(remote_key)
def migrate_from_vyatta_key(data):
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index a39da8991..936561edc 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -141,7 +141,7 @@ def apply(vrf):
# set the default VRF global behaviour
bind_all = '0'
- if 'bind_to_all' in vrf:
+ if 'bind-to-all' in vrf:
bind_all = '1'
call(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}')
call(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}')