summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py85
-rwxr-xr-xsrc/conf_mode/host_name.py196
-rwxr-xr-xsrc/conf_mode/http-api.py3
-rwxr-xr-xsrc/conf_mode/https.py59
-rwxr-xr-xsrc/conf_mode/interface-bonding.py469
-rwxr-xr-xsrc/conf_mode/interface-bridge.py234
-rwxr-xr-xsrc/conf_mode/interface-dummy.py115
-rwxr-xr-xsrc/conf_mode/interface-loopback.py101
-rwxr-xr-xsrc/conf_mode/interface-openvpn.py88
-rwxr-xr-xsrc/conf_mode/interface-vxlan.py208
-rwxr-xr-xsrc/conf_mode/interface-wireguard.py541
-rwxr-xr-xsrc/conf_mode/ipsec-settings.py2
-rwxr-xr-xsrc/conf_mode/syslog.py418
13 files changed, 1584 insertions, 935 deletions
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 3ca77adee..38f3cb4de 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -24,6 +24,7 @@ import jinja2
import netifaces
import vyos.util
+import vyos.hostsd_client
from vyos.config import Config
from vyos import ConfigError
@@ -44,7 +45,7 @@ config_tmpl = """
# Non-configurable defaults
daemon=yes
threads=1
-allow-from=0.0.0.0/0, ::/0
+allow-from={{ allow_from | join(',') }}
log-common-errors=yes
non-local-bind=yes
query-local-address=0.0.0.0
@@ -83,10 +84,10 @@ dnssec={{ dnssec }}
"""
default_config_data = {
+ 'allow_from': [],
'cache_size': 10000,
'export_hosts_file': 'yes',
'listen_on': [],
- 'interfaces': [],
'name_servers': [],
'negative_ttl': 3600,
'domains': [],
@@ -94,19 +95,6 @@ default_config_data = {
}
-# borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX!
-def get_resolvers(file):
- try:
- with open(file, 'r') as resolvconf:
- lines = [line.split('#', 1)[0].rstrip()
- for line in resolvconf.readlines()]
- resolvers = [line.split()[1]
- for line in lines if 'nameserver' in line]
- return resolvers
- except IOError:
- return []
-
-
def get_config(arguments):
dns = default_config_data
conf = Config()
@@ -121,6 +109,9 @@ def get_config(arguments):
conf.set_level('service dns forwarding')
+ if conf.exists('allow-from'):
+ dns['allow_from'] = conf.return_values('allow-from')
+
if conf.exists('cache-size'):
cache_size = conf.return_value('cache-size')
dns['cache_size'] = cache_size
@@ -164,64 +155,27 @@ def get_config(arguments):
if conf.exists('dnssec'):
dns['dnssec'] = conf.return_value('dnssec')
- ## Hacks and tricks
-
- # The old VyOS syntax that comes from dnsmasq was "listen-on $interface".
- # pdns wants addresses instead, so we emulate it by looking up all addresses
- # of a given interface and writing them to the config
- if conf.exists('listen-on'):
- print("WARNING: since VyOS 1.2.0, \"service dns forwarding listen-on\" is a limited compatibility option.")
- print("It will only make DNS forwarder listen on addresses assigned to the interface at the time of commit")
- print("which means it will NOT work properly with VRRP/clustering or addresses received from DHCP.")
- print("Please reconfigure your system with \"service dns forwarding listen-address\" instead.")
-
- interfaces = conf.return_values('listen-on')
-
- listen4 = []
- listen6 = []
- for interface in interfaces:
- try:
- addrs = netifaces.ifaddresses(interface)
- except ValueError:
- print(
- "WARNING: interface {0} does not exist".format(interface))
- continue
-
- if netifaces.AF_INET in addrs.keys():
- for ip4 in addrs[netifaces.AF_INET]:
- listen4.append(ip4['addr'])
-
- if netifaces.AF_INET6 in addrs.keys():
- for ip6 in addrs[netifaces.AF_INET6]:
- listen6.append(ip6['addr'])
-
- if (not listen4) and (not (listen6)):
- print(
- "WARNING: interface {0} has no configured addresses".format(interface))
-
- dns['listen_on'] = dns['listen_on'] + listen4 + listen6
-
- # Save interfaces in the dict for the reference
- dns['interfaces'] = interfaces
-
# Add name servers received from DHCP
if conf.exists('dhcp'):
interfaces = []
interfaces = conf.return_values('dhcp')
+ hc = vyos.hostsd_client.Client()
+
for interface in interfaces:
- dhcp_resolvers = get_resolvers(
- "/etc/resolv.conf.dhclient-new-{0}".format(interface))
+ dhcp_resolvers = hc.get_name_servers("dhcp-{0}".format(interface))
+ dhcpv6_resolvers = hc.get_name_servers("dhcpv6-{0}".format(interface))
+
if dhcp_resolvers:
dns['name_servers'] = dns['name_servers'] + dhcp_resolvers
+ if dhcpv6_resolvers:
+ dns['name_servers'] = dns['name_servers'] + dhcpv6_resolvers
return dns
-
def bracketize_ipv6_addrs(addrs):
"""Wraps each IPv6 addr in addrs in [], leaving IPv4 addrs untouched."""
return ['[{0}]'.format(a) if a.count(':') > 1 else a for a in addrs]
-
def verify(dns):
# bail out early - looks like removal from running config
if dns is None:
@@ -231,6 +185,10 @@ def verify(dns):
raise ConfigError(
"Error: DNS forwarding requires either a listen-address (preferred) or a listen-on option")
+ if not dns['allow_from']:
+ raise ConfigError(
+ "Error: DNS forwarding requires an allow-from network")
+
if dns['domains']:
for domain in dns['domains']:
if not domain['servers']:
@@ -239,7 +197,6 @@ def verify(dns):
return None
-
def generate(dns):
# bail out early - looks like removal from running config
if dns is None:
@@ -251,16 +208,14 @@ def generate(dns):
f.write(config_text)
return None
-
def apply(dns):
- if dns is not None:
- os.system("systemctl restart pdns-recursor")
- else:
+ if dns is None:
# DNS forwarding is removed in the commit
os.system("systemctl stop pdns-recursor")
if os.path.isfile(config_file):
os.unlink(config_file)
-
+ else:
+ os.system("systemctl restart pdns-recursor")
if __name__ == '__main__':
args = parser.parse_args()
diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py
index 2fad57db6..bb1ec9597 100755
--- a/src/conf_mode/host_name.py
+++ b/src/conf_mode/host_name.py
@@ -30,57 +30,12 @@ import argparse
import jinja2
import vyos.util
+import vyos.hostsd_client
from vyos.config import Config
from vyos import ConfigError
-parser = argparse.ArgumentParser()
-parser.add_argument("--dhclient", action="store_true",
- help="Started from dhclient-script")
-
-config_file_hosts = '/etc/hosts'
-config_file_resolv = '/etc/resolv.conf'
-
-config_tmpl_hosts = """
-### Autogenerated by host_name.py ###
-127.0.0.1 localhost
-127.0.1.1 {{ hostname }}{% if domain_name %}.{{ domain_name }} {{ hostname }}{% endif %}
-
-# The following lines are desirable for IPv6 capable hosts
-::1 localhost ip6-localhost ip6-loopback
-fe00::0 ip6-localnet
-ff00::0 ip6-mcastprefix
-ff02::1 ip6-allnodes
-ff02::2 ip6-allrouters
-
-# static hostname mappings
-{%- if static_host_mapping['hostnames'] %}
-{% for hn in static_host_mapping['hostnames'] -%}
-{{static_host_mapping['hostnames'][hn]['ipaddr']}}\t{{static_host_mapping['hostnames'][hn]['alias']}}\t{{hn}}
-{% endfor -%}
-{%- endif %}
-
-### modifications from other scripts should be added below
-
-"""
-
-config_tmpl_resolv = """
-### Autogenerated by host_name.py ###
-{% for ns in nameserver -%}
-nameserver {{ ns }}
-{% endfor -%}
-
-{%- if domain_name %}
-domain {{ domain_name }}
-{%- endif %}
-
-{%- if domain_search %}
-search {{ domain_search | join(" ") }}
-{%- endif %}
-
-"""
-
default_config_data = {
'hostname': 'vyos',
'domain_name': '',
@@ -89,32 +44,10 @@ default_config_data = {
'no_dhcp_ns': False
}
-# borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX!
-def get_resolvers(file):
- resolv = {}
- try:
- with open(file, 'r') as resolvconf:
- lines = [line.split('#', 1)[0].rstrip()
- for line in resolvconf.readlines()]
- resolvers = [line.split()[1]
- for line in lines if 'nameserver' in line]
- domains = [line.split()[1] for line in lines if 'search' in line]
- resolv['resolvers'] = resolvers
- resolv['domains'] = domains
- return resolv
- except IOError:
- return []
-
-
-def get_config(arguments):
+def get_config():
conf = Config()
hosts = copy.deepcopy(default_config_data)
- if arguments.dhclient:
- conf.exists = conf.exists_effective
- conf.return_value = conf.return_effective_value
- conf.return_values = conf.return_effective_values
-
if conf.exists("system host-name"):
hosts['hostname'] = conf.return_value("system host-name")
# This may happen if the config is not loaded yet,
@@ -136,19 +69,15 @@ def get_config(arguments):
hosts['no_dhcp_ns'] = conf.exists('system disable-dhcp-nameservers')
# system static-host-mapping
- hosts['static_host_mapping'] = {'hostnames': {}}
+ hosts['static_host_mapping'] = []
if conf.exists('system static-host-mapping host-name'):
for hn in conf.list_nodes('system static-host-mapping host-name'):
- hosts['static_host_mapping']['hostnames'][hn] = {
- 'ipaddr': conf.return_value('system static-host-mapping host-name ' + hn + ' inet'),
- 'alias': ''
- }
-
- if conf.exists('system static-host-mapping host-name ' + hn + ' alias'):
- a = conf.return_values(
- 'system static-host-mapping host-name ' + hn + ' alias')
- hosts['static_host_mapping']['hostnames'][hn]['alias'] = " ".join(a)
+ mapping = {}
+ mapping['host'] = hn
+ mapping['address'] = conf.return_value('system static-host-mapping host-name {0} inet'.format(hn))
+ mapping['aliases'] = conf.return_values('system static-host-mapping host-name {0} alias'.format(hn))
+ hosts['static_host_mapping'].append(mapping)
return hosts
@@ -180,83 +109,43 @@ def verify(config):
'The search list is currently limited to 256 characters')
# static mappings alias hostname
- if config['static_host_mapping']['hostnames']:
- for hn in config['static_host_mapping']['hostnames']:
- if not config['static_host_mapping']['hostnames'][hn]['ipaddr']:
- raise ConfigError('IP address required for ' + hn)
- for hn_alias in config['static_host_mapping']['hostnames'][hn]['alias'].split(' '):
- if not hostname_regex.match(hn_alias) and len(hn_alias) != 0:
- raise ConfigError('Invalid hostname alias ' + hn_alias)
+ if config['static_host_mapping']:
+ for m in config['static_host_mapping']:
+ if not m['address']:
+ raise ConfigError('IP address required for ' + m['host'])
+ for a in m['aliases']:
+ if not hostname_regex.match(a) and len(a) != 0:
+ raise ConfigError('Invalid alias \'{0}\' in mapping {1}'.format(a, m['host']))
return None
def generate(config):
+ pass
+
+def apply(config):
if config is None:
return None
- # If "system disable-dhcp-nameservers" is __configured__ all DNS resolvers
- # received via dhclient should not be added into the final 'resolv.conf'.
- #
- # We iterate over every resolver file and retrieve the received nameservers
- # for later adjustment of the system nameservers
- dhcp_ns = []
- dhcp_sd = []
- for file in glob.glob('/etc/resolv.conf.dhclient-new*'):
- for key, value in get_resolvers(file).items():
- ns = [r for r in value if key == 'resolvers']
- dhcp_ns.extend(ns)
- sd = [d for d in value if key == 'domains']
- dhcp_sd.extend(sd)
-
- if not config['no_dhcp_ns']:
- config['nameserver'] += dhcp_ns
- config['domain_search'] += dhcp_sd
-
- # Prune duplicate values
- # Not order preserving, but then when multiple DHCP clients are used,
- # there can't be guarantees about the order anyway
- dhcp_ns = list(set(dhcp_ns))
- dhcp_sd = list(set(dhcp_sd))
-
- # We have third party scripts altering /etc/hosts, too.
- # One example are the DHCP hostname update scripts thus we need to cache in
- # every modification first - so changing domain-name, domain-search or hostname
- # during runtime works
- old_hosts = ""
- with open(config_file_hosts, 'r') as f:
- # Skips text before the beginning of our marker.
- # NOTE: Marker __MUST__ match the one specified in config_tmpl_hosts
- for line in f:
- if line.strip() == '### modifications from other scripts should be added below':
- break
-
- for line in f:
- # This additional line.strip() filters empty lines
- if line.strip():
- old_hosts += line
-
- # Add an additional newline
- old_hosts += '\n'
-
- tmpl = jinja2.Template(config_tmpl_hosts)
- config_text = tmpl.render(config)
-
- with open(config_file_hosts, 'w') as f:
- f.write(config_text)
- f.write(old_hosts)
-
- tmpl = jinja2.Template(config_tmpl_resolv)
- config_text = tmpl.render(config)
- with open(config_file_resolv, 'w') as f:
- f.write(config_text)
+ ## Send the updated data to vyos-hostsd
- return None
+ # vyos-hostsd uses "tags" to identify data sources
+ tag = "static"
+ try:
+ client = vyos.hostsd_client.Client()
-def apply(config):
- if config is None:
- return None
+ client.set_host_name(config['hostname'], config['domain_name'], config['domain_search'])
+
+ client.delete_name_servers(tag)
+ client.add_name_servers(tag, config['nameserver'])
+
+ client.delete_hosts(tag)
+ client.add_hosts(tag, config['static_host_mapping'])
+ except vyos.hostsd_client.VyOSHostsdError as e:
+ raise ConfigError(str(e))
+
+ ## Actually update the hostname -- vyos-hostsd doesn't do that
# No domain name -- the Debian way.
hostname_new = config['hostname']
@@ -283,22 +172,9 @@ def apply(config):
if __name__ == '__main__':
- args = parser.parse_args()
-
- if args.dhclient:
- # There's a big chance it was triggered by a commit still in progress
- # so we need to wait until the new values are in the running config
- vyos.util.wait_for_commit_lock()
-
-
try:
- c = get_config(args)
- # If it's called from dhclient, then either:
- # a) verification was already done at commit time
- # b) it's run on an unconfigured system, e.g. by cloud-init
- # Therefore, verification is either redundant or useless
- if not args.dhclient:
- verify(c)
+ c = get_config()
+ verify(c)
generate(c)
apply(c)
except ConfigError as e:
diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py
index 1f91ac582..9c062f0aa 100755
--- a/src/conf_mode/http-api.py
+++ b/src/conf_mode/http-api.py
@@ -69,6 +69,9 @@ def generate(http_api):
if http_api is None:
return None
+ if not os.path.exists('/etc/vyos'):
+ os.mkdir('/etc/vyos')
+
with open(config_file, 'w') as f:
json.dump(http_api, f, indent=2)
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index 289eacf69..f948063e9 100755
--- a/src/conf_mode/https.py
+++ b/src/conf_mode/https.py
@@ -40,12 +40,21 @@ server {
return 302 https://$server_name$request_uri;
}
+{% for addr, names in listen_addresses.items() %}
server {
# SSL configuration
#
+{% if addr == '*' %}
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
+{% else %}
+ listen {{ addr }}:443 ssl;
+{% endif %}
+
+{% for name in names %}
+ server_name {{ name }};
+{% endfor %}
{% if vyos_cert %}
include {{ vyos_cert.conf }};
@@ -57,9 +66,42 @@ server {
include snippets/snakeoil.conf;
{% endif %}
-{% for l_addr in listen_address %}
- server_name {{ l_addr }};
-{% endfor %}
+ # proxy settings for HTTP API, if enabled; 503, if not
+ location ~ /(retrieve|configure) {
+{% if api %}
+ proxy_pass http://localhost:{{ api.port }};
+ proxy_buffering off;
+{% else %}
+ return 503;
+{% endif %}
+ }
+
+ error_page 501 502 503 =200 @50*_json;
+
+ location @50*_json {
+ default_type application/json;
+ return 200 '{"error": "Start service in configuration mode: set service https api"}';
+ }
+
+}
+{% else %}
+server {
+ # SSL configuration
+ #
+ listen 443 ssl default_server;
+ listen [::]:443 ssl default_server;
+
+ server_name _;
+
+{% if vyos_cert %}
+ include {{ vyos_cert.conf }};
+{% else %}
+ #
+ # Self signed certs generated by the ssl-cert package
+ # Don't use them in a production server!
+ #
+ include snippets/snakeoil.conf;
+{% endif %}
# proxy settings for HTTP API, if enabled; 503, if not
location ~ /(retrieve|configure) {
@@ -79,6 +121,8 @@ server {
}
}
+
+{% endfor %}
"""
def get_config():
@@ -90,8 +134,13 @@ def get_config():
conf.set_level('service https')
if conf.exists('listen-address'):
- addrs = conf.return_values('listen-address')
- https['listen_address'] = addrs[:]
+ addrs = {}
+ for addr in conf.list_nodes('listen-address'):
+ addrs[addr] = ['_']
+ if conf.exists('listen-address {0} server-name'.format(addr)):
+ names = conf.return_values('listen-address {0} server-name'.format(addr))
+ addrs[addr] = names[:]
+ https['listen_addresses'] = addrs
if conf.exists('certificates'):
if conf.exists('certificates system-generated-certificate'):
diff --git a/src/conf_mode/interface-bonding.py b/src/conf_mode/interface-bonding.py
new file mode 100755
index 000000000..dc0363fb7
--- /dev/null
+++ b/src/conf_mode/interface-bonding.py
@@ -0,0 +1,469 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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 copy import deepcopy
+from sys import exit
+from netifaces import interfaces
+
+from vyos.ifconfig import BondIf, EthernetIf
+from vyos.configdict import list_diff, vlan_to_dict
+from vyos.config import Config
+from vyos import ConfigError
+
+default_config_data = {
+ 'address': [],
+ 'address_remove': [],
+ 'arp_mon_intvl': 0,
+ 'arp_mon_tgt': [],
+ 'description': '',
+ 'deleted': False,
+ 'dhcp_client_id': '',
+ 'dhcp_hostname': '',
+ 'dhcpv6_prm_only': False,
+ 'dhcpv6_temporary': False,
+ 'disable': False,
+ 'disable_link_detect': 1,
+ 'hash_policy': 'layer2',
+ 'ip_arp_cache_tmo': 30,
+ 'ip_proxy_arp': 0,
+ 'ip_proxy_arp_pvlan': 0,
+ 'intf': '',
+ 'mac': '',
+ 'mode': '802.3ad',
+ 'member': [],
+ 'mtu': 1500,
+ 'primary': '',
+ 'vif_s': [],
+ 'vif_s_remove': [],
+ 'vif': [],
+ 'vif_remove': []
+}
+
+
+def get_bond_mode(mode):
+ if mode == 'round-robin':
+ return 'balance-rr'
+ elif mode == 'active-backup':
+ return 'active-backup'
+ elif mode == 'xor-hash':
+ return 'balance-xor'
+ elif mode == 'broadcast':
+ return 'broadcast'
+ elif mode == '802.3ad':
+ return '802.3ad'
+ elif mode == 'transmit-load-balance':
+ return 'balance-tlb'
+ elif mode == 'adaptive-load-balance':
+ return 'balance-alb'
+ else:
+ raise ConfigError('invalid bond mode "{}"'.format(mode))
+
+
+def apply_vlan_config(vlan, config):
+ """
+ Generic function to apply a VLAN configuration from a dictionary
+ to a VLAN interface
+ """
+
+ if type(vlan) != type(EthernetIf("lo")):
+ raise TypeError()
+
+ # update interface description used e.g. within SNMP
+ vlan.ifalias = config['description']
+ # ignore link state changes
+ vlan.link_detect = config['disable_link_detect']
+ # Maximum Transmission Unit (MTU)
+ vlan.mtu = config['mtu']
+ # Change VLAN interface MAC address
+ if config['mac']:
+ vlan.mac = config['mac']
+
+ # enable/disable VLAN interface
+ if config['disable']:
+ vlan.state = 'down'
+ else:
+ vlan.state = 'up'
+
+ # Configure interface address(es)
+ # - not longer required addresses get removed first
+ # - newly addresses will be added second
+ for addr in config['address_remove']:
+ vlan.del_addr(addr)
+ for addr in config['address']:
+ vlan.add_addr(addr)
+
+
+def get_config():
+ # initialize kernel module if not loaded
+ if not os.path.isfile('/sys/class/net/bonding_masters'):
+ import syslog
+ syslog.syslog(syslog.LOG_NOTICE, "loading bonding kernel module")
+ if os.system('modprobe bonding max_bonds=0 miimon=250') != 0:
+ syslog.syslog(syslog.LOG_NOTICE, "failed loading bonding kernel module")
+ raise ConfigError("failed loading bonding kernel module")
+
+ bond = deepcopy(default_config_data)
+ conf = Config()
+
+ # determine tagNode instance
+ try:
+ bond['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ except KeyError as E:
+ print("Interface not specified")
+
+ # check if bond has been removed
+ cfg_base = 'interfaces bonding ' + bond['intf']
+ if not conf.exists(cfg_base):
+ bond['deleted'] = True
+ return bond
+
+ # set new configuration level
+ conf.set_level(cfg_base)
+
+ # retrieve configured interface addresses
+ if conf.exists('address'):
+ bond['address'] = conf.return_values('address')
+
+ # get interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed
+ eff_addr = conf.return_effective_values('address')
+ bond['address_remove'] = list_diff(eff_addr, bond['address'])
+
+ # ARP link monitoring frequency in milliseconds
+ if conf.exists('arp-monitor interval'):
+ bond['arp_mon_intvl'] = int(conf.return_value('arp-monitor interval'))
+
+ # IP address to use for ARP monitoring
+ if conf.exists('arp-monitor target'):
+ bond['arp_mon_tgt'] = conf.return_values('arp-monitor target')
+
+ # retrieve interface description
+ if conf.exists('description'):
+ bond['description'] = conf.return_value('description')
+ else:
+ bond['description'] = bond['intf']
+
+ # get DHCP client identifier
+ if conf.exists('dhcp-options client-id'):
+ bond['dhcp_client_id'] = conf.return_value('dhcp-options client-id')
+
+ # DHCP client host name (overrides the system host name)
+ if conf.exists('dhcp-options host-name'):
+ bond['dhcp_hostname'] = conf.return_value('dhcp-options host-name')
+
+ # DHCPv6 only acquire config parameters, no address
+ if conf.exists('dhcpv6-options parameters-only'):
+ bond['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only')
+
+ # DHCPv6 temporary IPv6 address
+ if conf.exists('dhcpv6-options temporary'):
+ bond['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary')
+
+ # ignore link state changes
+ if conf.exists('disable-link-detect'):
+ bond['disable_link_detect'] = 2
+
+ # disable bond interface
+ if conf.exists('disable'):
+ bond['disable'] = True
+
+ # Bonding transmit hash policy
+ if conf.exists('hash-policy'):
+ bond['hash_policy'] = conf.return_value('hash-policy')
+
+ # ARP cache entry timeout in seconds
+ if conf.exists('ip arp-cache-timeout'):
+ bond['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout'))
+
+ # Enable proxy-arp on this interface
+ if conf.exists('ip enable-proxy-arp'):
+ bond['ip_proxy_arp'] = 1
+
+ # Enable private VLAN proxy ARP on this interface
+ if conf.exists('ip proxy-arp-pvlan'):
+ bond['ip_proxy_arp_pvlan'] = 1
+
+ # Media Access Control (MAC) address
+ if conf.exists('mac'):
+ bond['mac'] = conf.return_value('mac')
+
+ # Bonding mode
+ if conf.exists('mode'):
+ bond['mode'] = get_bond_mode(conf.return_value('mode'))
+
+ # Maximum Transmission Unit (MTU)
+ if conf.exists('mtu'):
+ bond['mtu'] = int(conf.return_value('mtu'))
+
+ # determine bond member interfaces (currently configured)
+ if conf.exists('member interface'):
+ bond['member'] = conf.return_values('member interface')
+
+ # Primary device interface
+ if conf.exists('primary'):
+ bond['primary'] = conf.return_value('primary')
+
+ # re-set configuration level and retrieve vif-s interfaces
+ conf.set_level(cfg_base)
+ # get vif-s interfaces (currently effective) - to determine which vif-s
+ # interface is no longer present and needs to be removed
+ eff_intf = conf.list_effective_nodes('vif-s')
+ act_intf = conf.list_nodes('vif-s')
+ bond['vif_s_remove'] = list_diff(eff_intf, act_intf)
+
+ if conf.exists('vif-s'):
+ for vif_s in conf.list_nodes('vif-s'):
+ # set config level to vif-s interface
+ conf.set_level(cfg_base + ' vif-s ' + vif_s)
+ bond['vif_s'].append(vlan_to_dict(conf))
+
+ # re-set configuration level and retrieve vif-s interfaces
+ conf.set_level(cfg_base)
+ # Determine vif interfaces (currently effective) - to determine which
+ # vif interface is no longer present and needs to be removed
+ eff_intf = conf.list_effective_nodes('vif')
+ act_intf = conf.list_nodes('vif')
+ bond['vif_remove'] = list_diff(eff_intf, act_intf)
+
+ if conf.exists('vif'):
+ for vif in conf.list_nodes('vif'):
+ # set config level to vif interface
+ conf.set_level(cfg_base + ' vif ' + vif)
+ bond['vif'].append(vlan_to_dict(conf))
+
+ return bond
+
+
+def verify(bond):
+ if len (bond['arp_mon_tgt']) > 16:
+ raise ConfigError('The maximum number of targets that can be specified is 16')
+
+ if bond['primary']:
+ if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']:
+ raise ConfigError('Mode dependency failed, primary not supported ' \
+ 'in this mode.'.format())
+
+ if bond['primary'] not in bond['member']:
+ raise ConfigError('Interface "{}" is not part of the bond' \
+ .format(bond['primary']))
+
+ for vif_s in bond['vif_s']:
+ for vif in bond['vif']:
+ if vif['id'] == vif_s['id']:
+ raise ConfigError('Can not use identical ID on vif and vif-s interface')
+
+
+ conf = Config()
+ for intf in bond['member']:
+ # a bonding member interface is only allowed to be assigned to one bond!
+ all_bonds = conf.list_nodes('interfaces bonding')
+ # We do not need to check our own bond
+ all_bonds.remove(bond['intf'])
+ for tmp in all_bonds:
+ if conf.exists('interfaces bonding ' + tmp + ' member interface ' + intf):
+ raise ConfigError('can not enslave interface {} which already ' \
+ 'belongs to {}'.format(intf, tmp))
+
+ # we can not add disabled slave interfaces to our bond
+ if conf.exists('interfaces ethernet ' + intf + ' disable'):
+ raise ConfigError('can not enslave disabled interface {}' \
+ .format(intf))
+
+ # can not add interfaces with an assigned address to a bond
+ if conf.exists('interfaces ethernet ' + intf + ' address'):
+ raise ConfigError('can not enslave interface {} which has an address ' \
+ 'assigned'.format(intf))
+
+ # bond members are not allowed to be bridge members, too
+ for tmp in conf.list_nodes('interfaces bridge'):
+ if conf.exists('interfaces bridge ' + tmp + ' member interface ' + intf):
+ raise ConfigError('can not enslave interface {} which belongs to ' \
+ 'bridge {}'.format(intf, tmp))
+
+ # bond members are not allowed to be vrrp members, too
+ for tmp in conf.list_nodes('high-availability vrrp group'):
+ if conf.exists('high-availability vrrp group ' + tmp + ' interface ' + intf):
+ raise ConfigError('can not enslave interface {} which belongs to ' \
+ 'VRRP group {}'.format(intf, tmp))
+
+ # bond members are not allowed to be underlaying psuedo-ethernet devices
+ for tmp in conf.list_nodes('interfaces pseudo-ethernet'):
+ if conf.exists('interfaces pseudo-ethernet ' + tmp + ' link ' + intf):
+ raise ConfigError('can not enslave interface {} which belongs to ' \
+ 'pseudo-ethernet {}'.format(intf, tmp))
+
+ # bond members are not allowed to be underlaying vxlan devices
+ for tmp in conf.list_nodes('interfaces vxlan'):
+ if conf.exists('interfaces vxlan ' + tmp + ' link ' + intf):
+ raise ConfigError('can not enslave interface {} which belongs to ' \
+ 'vxlan {}'.format(intf, tmp))
+
+
+ if bond['primary']:
+ if bond['primary'] not in bond['member']:
+ raise ConfigError('primary interface must be a member interface of {}' \
+ .format(bond['intf']))
+
+ if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']:
+ raise ConfigError('primary interface only works for mode active-backup, ' \
+ 'transmit-load-balance or adaptive-load-balance')
+
+ if bond['arp_mon_intvl'] > 0:
+ if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']:
+ raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \
+ 'transmit-load-balance or adaptive-load-balance')
+
+ return None
+
+
+def generate(bond):
+ return None
+
+
+def apply(bond):
+ b = BondIf(bond['intf'])
+
+ if bond['deleted']:
+ #
+ # delete bonding interface
+ b.remove()
+ else:
+ # Some parameters can not be changed when the bond is up.
+ # Always disable the bond prior changing anything
+ b.state = 'down'
+
+ # The bonding mode can not be changed when there are interfaces enslaved
+ # to this bond, thus we will free all interfaces from the bond first!
+ for intf in b.get_slaves():
+ b.del_port(intf)
+
+ # ARP link monitoring frequency
+ b.arp_interval = bond['arp_mon_intvl']
+ # reset miimon on arp-montior deletion
+ if bond['arp_mon_intvl'] == 0:
+ # reset miimon to default
+ b.bond_miimon = 250
+
+ # ARP monitor targets need to be synchronized between sysfs and CLI.
+ # Unfortunately an address can't be send twice to sysfs as this will
+ # result in the following exception: OSError: [Errno 22] Invalid argument.
+ #
+ # We remove ALL adresses prior adding new ones, this will remove addresses
+ # added manually by the user too - but as we are limited to 16 adresses
+ # from the kernel side this looks valid to me. We won't run into an error
+ # when a user added manual adresses which would result in having more
+ # then 16 adresses in total.
+ arp_tgt_addr = list(map(str, b.arp_ip_target.split()))
+ for addr in arp_tgt_addr:
+ b.arp_ip_target = '-' + addr
+
+ # Add configured ARP target addresses
+ for addr in bond['arp_mon_tgt']:
+ b.arp_ip_target = '+' + addr
+
+ # update interface description used e.g. within SNMP
+ b.ifalias = bond['description']
+
+ #
+ # missing DHCP/DHCPv6 options go here
+ #
+
+ # ignore link state changes
+ b.link_detect = bond['disable_link_detect']
+ # Bonding transmit hash policy
+ b.xmit_hash_policy = bond['hash_policy']
+ # configure ARP cache timeout in milliseconds
+ b.arp_cache_tmp = bond['ip_arp_cache_tmo']
+ # Enable proxy-arp on this interface
+ b.proxy_arp = bond['ip_proxy_arp']
+ # Enable private VLAN proxy ARP on this interface
+ b.proxy_arp_pvlan = bond['ip_proxy_arp_pvlan']
+
+ # Change interface MAC address
+ if bond['mac']:
+ b.mac = bond['mac']
+
+ # Bonding policy
+ b.mode = bond['mode']
+ # Maximum Transmission Unit (MTU)
+ b.mtu = bond['mtu']
+
+ # Primary device interface
+ if bond['primary']:
+ b.primary = bond['primary']
+
+ # Add (enslave) interfaces to bond
+ for intf in bond['member']:
+ b.add_port(intf)
+
+ # As the bond interface is always disabled first when changing
+ # parameters we will only re-enable the interface if it is not
+ # administratively disabled
+ if not bond['disable']:
+ b.state = 'up'
+
+ # Configure interface address(es)
+ # - not longer required addresses get removed first
+ # - newly addresses will be added second
+ for addr in bond['address_remove']:
+ b.del_addr(addr)
+ for addr in bond['address']:
+ b.add_addr(addr)
+
+ # remove no longer required service VLAN interfaces (vif-s)
+ for vif_s in bond['vif_s_remove']:
+ b.del_vlan(vif_s)
+
+ # create service VLAN interfaces (vif-s)
+ for vif_s in bond['vif_s']:
+ s_vlan = b.add_vlan(vif_s['id'], ethertype=vif_s['ethertype'])
+ apply_vlan_config(s_vlan, vif_s)
+
+ # remove no longer required client VLAN interfaces (vif-c)
+ # on lower service VLAN interface
+ for vif_c in vif_s['vif_c_remove']:
+ s_vlan.del_vlan(vif_c)
+
+ # create client VLAN interfaces (vif-c)
+ # on lower service VLAN interface
+ for vif_c in vif_s['vif_c']:
+ c_vlan = s_vlan.add_vlan(vif_c['id'])
+ apply_vlan_config(c_vlan, vif_c)
+
+ # remove no longer required VLAN interfaces (vif)
+ for vif in bond['vif_remove']:
+ b.del_vlan(vif)
+
+ # create VLAN interfaces (vif)
+ for vif in bond['vif']:
+ vlan = b.add_vlan(vif['id'])
+ apply_vlan_config(vlan, vif)
+
+ 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/interface-bridge.py b/src/conf_mode/interface-bridge.py
index 543349e7b..401182a0d 100755
--- a/src/conf_mode/interface-bridge.py
+++ b/src/conf_mode/interface-bridge.py
@@ -17,51 +17,39 @@
#
import os
-import sys
-import copy
-import subprocess
-import vyos.configinterface as VyIfconfig
+from copy import deepcopy
+from sys import exit
+from netifaces import interfaces
+from vyos.ifconfig import BridgeIf, Interface
+from vyos.configdict import list_diff
from vyos.config import Config
from vyos import ConfigError
default_config_data = {
'address': [],
'address_remove': [],
- 'aging': '300',
- 'arp_cache_timeout_ms': '30000',
+ 'aging': 300,
+ 'arp_cache_tmo': 30,
'description': '',
'deleted': False,
- 'dhcp_client_id': '',
- 'dhcp_hostname': '',
- 'dhcpv6_parameters_only': False,
- 'dhcpv6_temporary': False,
'disable': False,
- 'disable_link_detect': False,
- 'forwarding_delay': '15',
- 'hello_time': '2',
+ 'disable_link_detect': 1,
+ 'forwarding_delay': 14,
+ 'hello_time': 2,
'igmp_querier': 0,
'intf': '',
'mac' : '',
- 'max_age': '20',
+ 'max_age': 20,
'member': [],
'member_remove': [],
- 'priority': '32768',
- 'stp': 'off'
+ 'priority': 32768,
+ 'stp': 0
}
-def subprocess_cmd(command):
- process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True)
- proc_stdout = process.communicate()[0].strip()
- pass
-
-def diff(first, second):
- second = set(second)
- return [item for item in first if item not in second]
-
def get_config():
- bridge = copy.deepcopy(default_config_data)
+ bridge = deepcopy(default_config_data)
conf = Config()
# determine tagNode instance
@@ -82,45 +70,34 @@ def get_config():
if conf.exists('address'):
bridge['address'] = conf.return_values('address')
+ # Determine interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed
+ eff_addr = conf.return_effective_values('address')
+ bridge['address_remove'] = list_diff(eff_addr, bridge['address'])
+
# retrieve aging - how long addresses are retained
if conf.exists('aging'):
- bridge['aging'] = conf.return_value('aging')
+ bridge['aging'] = int(conf.return_value('aging'))
# retrieve interface description
if conf.exists('description'):
bridge['description'] = conf.return_value('description')
- # DHCP client identifier
- if conf.exists('dhcp-options client-id'):
- bridge['dhcp_client_id'] = conf.return_value('dhcp-options client-id')
-
- # DHCP client hostname
- if conf.exists('dhcp-options host-name'):
- bridge['dhcp_hostname'] = conf.return_value('dhcp-options host-name')
-
- # DHCPv6 acquire only config parameters, no address
- if conf.exists('dhcpv6-options parameters-only'):
- bridge['dhcpv6_parameters_only'] = True
-
- # DHCPv6 IPv6 "temporary" address
- if conf.exists('dhcpv6-options temporary'):
- bridge['dhcpv6_temporary'] = True
-
# Disable this bridge interface
if conf.exists('disable'):
bridge['disable'] = True
# Ignore link state changes
if conf.exists('disable-link-detect'):
- bridge['disable_link_detect'] = True
+ bridge['disable_link_detect'] = 2
# Forwarding delay
if conf.exists('forwarding-delay'):
- bridge['forwarding_delay'] = conf.return_value('forwarding-delay')
+ bridge['forwarding_delay'] = int(conf.return_value('forwarding-delay'))
# Hello packet advertisment interval
if conf.exists('hello-time'):
- bridge['hello_time'] = conf.return_value('hello-time')
+ bridge['hello_time'] = int(conf.return_value('hello-time'))
# Enable Internet Group Management Protocol (IGMP) querier
if conf.exists('igmp querier'):
@@ -128,8 +105,7 @@ def get_config():
# ARP cache entry timeout in seconds
if conf.exists('ip arp-cache-timeout'):
- tmp = 1000 * int(conf.return_value('ip arp-cache-timeout'))
- bridge['arp_cache_timeout_ms'] = str(tmp)
+ bridge['arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout'))
# Media Access Control (MAC) address
if conf.exists('mac'):
@@ -137,21 +113,24 @@ def get_config():
# Interval at which neighbor bridges are removed
if conf.exists('max-age'):
- bridge['max_age'] = conf.return_value('max-age')
+ bridge['max_age'] = int(conf.return_value('max-age'))
# Determine bridge member interface (currently configured)
for intf in conf.list_nodes('member interface'):
+ # cost and priority initialized with linux defaults
+ # by reading /sys/devices/virtual/net/br0/brif/eth2/{path_cost,priority}
+ # after adding interface to bridge after reboot
iface = {
'name': intf,
- 'cost': '',
- 'priority': ''
+ 'cost': 100,
+ 'priority': 32
}
if conf.exists('member interface {} cost'.format(intf)):
- iface['cost'] = conf.return_value('member interface {} cost'.format(intf))
+ iface['cost'] = int(conf.return_value('member interface {} cost'.format(intf)))
if conf.exists('member interface {} priority'.format(intf)):
- iface['priority'] = conf.return_value('member interface {} priority'.format(intf))
+ iface['priority'] = int(conf.return_value('member interface {} priority'.format(intf)))
bridge['member'].append(iface)
@@ -159,28 +138,19 @@ def get_config():
# interfaces is no longer assigend to the bridge and thus can be removed
eff_intf = conf.list_effective_nodes('member interface')
act_intf = conf.list_nodes('member interface')
- bridge['member_remove'] = diff(eff_intf, act_intf)
-
- # Determine interface addresses (currently effective) - to determine which
- # address is no longer valid and needs to be removed from the bridge
- eff_addr = conf.return_effective_values('address')
- act_addr = conf.return_values('address')
- bridge['address_remove'] = diff(eff_addr, act_addr)
+ bridge['member_remove'] = list_diff(eff_intf, act_intf)
# Priority for this bridge
if conf.exists('priority'):
- bridge['priority'] = conf.return_value('priority')
+ bridge['priority'] = int(conf.return_value('priority'))
# Enable spanning tree protocol
if conf.exists('stp'):
- bridge['stp'] = 'on'
+ bridge['stp'] = 1
return bridge
def verify(bridge):
- if bridge is None:
- return None
-
conf = Config()
for br in conf.list_nodes('interfaces bridge'):
# it makes no sense to verify ourself in this case
@@ -190,108 +160,88 @@ def verify(bridge):
for intf in bridge['member']:
tmp = conf.list_nodes('interfaces bridge {} member interface'.format(br))
if intf['name'] in tmp:
- raise ConfigError('{} can be assigned to any one bridge only'.format(intf['name']))
+ raise ConfigError('Interface "{}" belongs to bridge "{}" and can not be enslaved.'.format(intf['name'], bridge['intf']))
+
+ # the interface must exist prior adding it to a bridge
+ for intf in bridge['member']:
+ if intf['name'] not in interfaces():
+ raise ConfigError('Can not add non existing interface "{}" to bridge "{}"'.format(intf['name'], bridge['intf']))
+
+ # bridge members are not allowed to be bond members, too
+ for intf in bridge['member']:
+ for bond in conf.list_nodes('interfaces bonding'):
+ if conf.exists('interfaces bonding ' + bond + ' member interface'):
+ if intf['name'] in conf.return_values('interfaces bonding ' + bond + ' member interface'):
+ raise ConfigError('Interface {} belongs to bond {}, can not add it to {}'.format(intf['name'], bond, bridge['intf']))
return None
def generate(bridge):
- if bridge is None:
- return None
-
return None
def apply(bridge):
- if bridge is None:
- return None
+ br = BridgeIf(bridge['intf'])
- cmd = ''
if bridge['deleted']:
- # bridges need to be shutdown first
- cmd += 'ip link set dev "{}" down'.format(bridge['intf'])
- cmd += ' && '
- # delete bridge
- cmd += 'brctl delbr "{}"'.format(bridge['intf'])
- subprocess_cmd(cmd)
-
+ # delete bridge interface
+ # DHCP is stopped inside remove()
+ br.remove()
else:
- # create bridge if it does not exist
- if not os.path.exists("/sys/class/net/" + bridge['intf']):
- # create bridge interface
- cmd += 'brctl addbr "{}"'.format(bridge['intf'])
- cmd += ' && '
- # activate "UP" the interface
- cmd += 'ip link set dev "{}" up'.format(bridge['intf'])
- cmd += ' && '
-
+ # enable interface
+ br.state = 'up'
# set ageing time
- cmd += 'brctl setageing "{}" "{}"'.format(bridge['intf'], bridge['aging'])
- cmd += ' && '
-
+ br.ageing_time = bridge['aging']
# set bridge forward delay
- cmd += 'brctl setfd "{}" "{}"'.format(bridge['intf'], bridge['forwarding_delay'])
- cmd += ' && '
-
+ br.forward_delay = bridge['forwarding_delay']
# set hello time
- cmd += 'brctl sethello "{}" "{}"'.format(bridge['intf'], bridge['hello_time'])
- cmd += ' && '
-
+ br.hello_time = bridge['hello_time']
# set max message age
- cmd += 'brctl setmaxage "{}" "{}"'.format(bridge['intf'], bridge['max_age'])
- cmd += ' && '
-
+ br.max_age = bridge['max_age']
# set bridge priority
- cmd += 'brctl setbridgeprio "{}" "{}"'.format(bridge['intf'], bridge['priority'])
- cmd += ' && '
-
+ br.priority = bridge['priority']
# turn stp on/off
- cmd += 'brctl stp "{}" "{}"'.format(bridge['intf'], bridge['stp'])
-
- for intf in bridge['member_remove']:
- # remove interface from bridge
- cmd += ' && '
- cmd += 'brctl delif "{}" "{}"'.format(bridge['intf'], intf)
-
- for intf in bridge['member']:
- # add interface to bridge
- # but only if it is not yet member of this bridge
- if not os.path.exists('/sys/devices/virtual/net/' + bridge['intf'] + '/brif/' + intf['name']):
- cmd += ' && '
- cmd += 'brctl addif "{}" "{}"'.format(bridge['intf'], intf['name'])
-
- # set bridge port cost
- if intf['cost']:
- cmd += ' && '
- cmd += 'brctl setpathcost "{}" "{}" "{}"'.format(bridge['intf'], intf['name'], intf['cost'])
-
- # set bridge port priority
- if intf['priority']:
- cmd += ' && '
- cmd += 'brctl setportprio "{}" "{}" "{}"'.format(bridge['intf'], intf['name'], intf['priority'])
-
- subprocess_cmd(cmd)
+ br.stp_state = bridge['stp']
+ # enable or disable IGMP querier
+ br.multicast_querier = bridge['igmp_querier']
+ # update interface description used e.g. within SNMP
+ br.ifalias = bridge['description']
# Change interface MAC address
if bridge['mac']:
- VyIfconfig.set_mac_address(bridge['intf'], bridge['mac'])
-
- # update interface description used e.g. within SNMP
- VyIfconfig.set_description(bridge['intf'], bridge['description'])
+ br.mac = bridge['mac']
- # Ignore link state changes?
- VyIfconfig.set_link_detect(bridge['intf'], bridge['disable_link_detect'])
+ # remove interface from bridge
+ for intf in bridge['member_remove']:
+ br.del_port( intf['name'] )
- # enable or disable IGMP querier
- VyIfconfig.set_multicast_querier(bridge['intf'], bridge['igmp_querier'])
+ # add interfaces to bridge
+ for member in bridge['member']:
+ br.add_port(member['name'])
- # ARP cache entry timeout in seconds
- VyIfconfig.set_arp_cache_timeout(bridge['intf'], bridge['arp_cache_timeout_ms'])
+ # up/down interface
+ if bridge['disable']:
+ br.state = 'down'
# Configure interface address(es)
+ # - not longer required addresses get removed first
+ # - newly addresses will be added second
for addr in bridge['address_remove']:
- VyIfconfig.remove_interface_address(bridge['intf'], addr)
-
+ br.del_addr(addr)
for addr in bridge['address']:
- VyIfconfig.add_interface_address(bridge['intf'], addr)
+ br.add_addr(addr)
+
+ # configure additional bridge member options
+ for member in bridge['member']:
+ # set bridge port cost
+ br.set_cost(member['name'], member['cost'])
+ # set bridge port priority
+ br.set_priority(member['name'], member['priority'])
+
+ i = Interface(member['name'])
+ # configure ARP cache timeout
+ i.arp_cache_tmo = bridge['arp_cache_tmo']
+ # ignore link state changes
+ i.link_detect = bridge['disable_link_detect']
return None
@@ -303,4 +253,4 @@ if __name__ == '__main__':
apply(c)
except ConfigError as e:
print(e)
- sys.exit(1)
+ exit(1)
diff --git a/src/conf_mode/interface-dummy.py b/src/conf_mode/interface-dummy.py
new file mode 100755
index 000000000..614fe08db
--- /dev/null
+++ b/src/conf_mode/interface-dummy.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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/>.
+#
+#
+
+from os import environ
+from copy import deepcopy
+from sys import exit
+
+from vyos.ifconfig import DummyIf
+from vyos.configdict import list_diff
+from vyos.config import Config
+from vyos import ConfigError
+
+default_config_data = {
+ 'address': [],
+ 'address_remove': [],
+ 'deleted': False,
+ 'description': '',
+ 'disable': False,
+ 'intf': ''
+}
+
+def get_config():
+ dummy = deepcopy(default_config_data)
+ conf = Config()
+
+ # determine tagNode instance
+ try:
+ dummy['intf'] = environ['VYOS_TAGNODE_VALUE']
+ except KeyError as E:
+ print("Interface not specified")
+
+ # Check if interface has been removed
+ if not conf.exists('interfaces dummy ' + dummy['intf']):
+ dummy['deleted'] = True
+ return dummy
+
+ # set new configuration level
+ conf.set_level('interfaces dummy ' + dummy['intf'])
+
+ # retrieve configured interface addresses
+ if conf.exists('address'):
+ dummy['address'] = conf.return_values('address')
+
+ # retrieve interface description
+ if conf.exists('description'):
+ dummy['description'] = conf.return_value('description')
+
+ # Disable this interface
+ if conf.exists('disable'):
+ dummy['disable'] = True
+
+ # Determine interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed from the interface
+ eff_addr = conf.return_effective_values('address')
+ act_addr = conf.return_values('address')
+ dummy['address_remove'] = list_diff(eff_addr, act_addr)
+
+ return dummy
+
+def verify(dummy):
+ return None
+
+def generate(dummy):
+ return None
+
+def apply(dummy):
+ du = DummyIf(dummy['intf'])
+
+ # Remove dummy interface
+ if dummy['deleted']:
+ du.remove()
+ else:
+ # enable interface
+ du.state = 'up'
+ # update interface description used e.g. within SNMP
+ du.ifalias = dummy['description']
+
+ # Configure interface address(es)
+ # - not longer required addresses get removed first
+ # - newly addresses will be added second
+ for addr in dummy['address_remove']:
+ du.del_addr(addr)
+ for addr in dummy['address']:
+ du.add_addr(addr)
+
+ # disable interface on demand
+ if dummy['disable']:
+ du.state = 'down'
+
+ 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/interface-loopback.py b/src/conf_mode/interface-loopback.py
new file mode 100755
index 000000000..a1a807868
--- /dev/null
+++ b/src/conf_mode/interface-loopback.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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/>.
+#
+
+from os import environ
+from sys import exit
+from copy import deepcopy
+
+from vyos.ifconfig import LoopbackIf
+from vyos.configdict import list_diff
+from vyos.config import Config
+from vyos import ConfigError
+
+default_config_data = {
+ 'address': [],
+ 'address_remove': [],
+ 'deleted': False,
+ 'description': '',
+}
+
+
+def get_config():
+ loopback = deepcopy(default_config_data)
+ conf = Config()
+
+ # determine tagNode instance
+ try:
+ loopback['intf'] = environ['VYOS_TAGNODE_VALUE']
+ except KeyError as E:
+ print("Interface not specified")
+
+ # Check if interface has been removed
+ if not conf.exists('interfaces loopback ' + loopback['intf']):
+ loopback['deleted'] = True
+
+ # set new configuration level
+ conf.set_level('interfaces loopback ' + loopback['intf'])
+
+ # retrieve configured interface addresses
+ if conf.exists('address'):
+ loopback['address'] = conf.return_values('address')
+
+ # retrieve interface description
+ if conf.exists('description'):
+ loopback['description'] = conf.return_value('description')
+
+ # Determine interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed from the interface
+ eff_addr = conf.return_effective_values('address')
+ act_addr = conf.return_values('address')
+ loopback['address_remove'] = list_diff(eff_addr, act_addr)
+
+ return loopback
+
+def verify(loopback):
+ return None
+
+def generate(loopback):
+ return None
+
+def apply(loopback):
+ lo = LoopbackIf(loopback['intf'])
+ if not loopback['deleted']:
+ # update interface description used e.g. within SNMP
+ # update interface description used e.g. within SNMP
+ lo.ifalias = loopback['description']
+
+ # Configure interface address(es)
+ # - not longer required addresses get removed first
+ # - newly addresses will be added second
+ for addr in loopback['address']:
+ lo.add_addr(addr)
+
+ # remove interface address(es)
+ for addr in loopback['address_remove']:
+ lo.del_addr(addr)
+
+ 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/interface-openvpn.py b/src/conf_mode/interface-openvpn.py
index 4e5915d4e..548c78535 100755
--- a/src/conf_mode/interface-openvpn.py
+++ b/src/conf_mode/interface-openvpn.py
@@ -18,24 +18,25 @@
import os
import re
-import pwd
-import grp
import sys
import stat
-import copy
import jinja2
-import psutil
-from ipaddress import ip_address,ip_network,IPv4Interface
-from signal import SIGUSR1
+from copy import deepcopy
+from grp import getgrnam
+from ipaddress import ip_address,ip_network,IPv4Interface
+from netifaces import interfaces
+from psutil import pid_exists
+from pwd import getpwnam
from subprocess import Popen, PIPE
+from time import sleep
from vyos.config import Config
from vyos import ConfigError
from vyos.validate import is_addr_assigned
-user = 'nobody'
-group = 'nogroup'
+user = 'openvpn'
+group = 'openvpn'
# Please be careful if you edit the template.
config_tmpl = """
@@ -58,6 +59,7 @@ dev {{ intf }}
user {{ uid }}
group {{ gid }}
persist-key
+iproute /usr/libexec/vyos/system/unpriv-ip
proto {% if 'tcp-active' in protocol -%}tcp-client{% elif 'tcp-passive' in protocol -%}tcp-server{% else %}udp{% endif %}
@@ -301,8 +303,8 @@ def openvpn_mkdir(directory):
# fix permissions - corresponds to mode 755
os.chmod(directory, stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH)
- uid = pwd.getpwnam(user).pw_uid
- gid = grp.getgrnam(group).gr_gid
+ uid = getpwnam(user).pw_uid
+ gid = getgrnam(group).gr_gid
os.chown(directory, uid, gid)
def fixup_permission(filename, permission=stat.S_IRUSR):
@@ -314,8 +316,8 @@ def fixup_permission(filename, permission=stat.S_IRUSR):
os.chmod(filename, permission)
# make file owned by root / vyattacfg
- uid = pwd.getpwnam('root').pw_uid
- gid = grp.getgrnam('vyattacfg').gr_gid
+ uid = getpwnam('root').pw_uid
+ gid = getgrnam('vyattacfg').gr_gid
os.chown(filename, uid, gid)
def checkCertHeader(header, filename):
@@ -334,7 +336,7 @@ def checkCertHeader(header, filename):
return False
def get_config():
- openvpn = copy.deepcopy(default_config_data)
+ openvpn = deepcopy(default_config_data)
conf = Config()
# determine tagNode instance
@@ -792,8 +794,8 @@ def generate(openvpn):
fixup_permission(auth_file)
# get numeric uid/gid
- uid = pwd.getpwnam(user).pw_uid
- gid = grp.getgrnam(group).gr_gid
+ uid = getpwnam(user).pw_uid
+ gid = getgrnam(group).gr_gid
# Generate client specific configuration
for client in openvpn['client']:
@@ -806,6 +808,11 @@ def generate(openvpn):
tmpl = jinja2.Template(config_tmpl)
config_text = tmpl.render(openvpn)
+
+ # we need to support quoting of raw parameters from OpenVPN CLI
+ # see https://phabricator.vyos.net/T1632
+ config_text = config_text.replace("&quot;",'"')
+
with open(get_config_name(interface), 'w') as f:
f.write(config_text)
os.chown(get_config_name(interface), uid, gid)
@@ -813,47 +820,46 @@ def generate(openvpn):
return None
def apply(openvpn):
- interface = openvpn['intf']
-
pid = 0
- pidfile = '/var/run/openvpn/{}.pid'.format(interface)
+ pidfile = '/var/run/openvpn/{}.pid'.format(openvpn['intf'])
if os.path.isfile(pidfile):
pid = 0
with open(pidfile, 'r') as f:
pid = int(f.read())
- # If tunnel interface has been deleted - stop service
- if openvpn['deleted'] or openvpn['disable']:
- directory = os.path.dirname(get_config_name(interface))
+ # Always stop OpenVPN service. We can not send a SIGUSR1 for restart of the
+ # service as the configuration is not re-read. Stop daemon only if it's
+ # running - it could have died or killed by someone evil
+ if pid_exists(pid):
+ cmd = 'start-stop-daemon --stop --quiet'
+ cmd += ' --pidfile ' + pidfile
+ subprocess_cmd(cmd)
- # we only need to stop the demon if it's running
- # daemon could have died or killed by someone
- if psutil.pid_exists(pid):
- cmd = 'start-stop-daemon --stop --quiet'
- cmd += ' --pidfile ' + pidfile
- subprocess_cmd(cmd)
-
- # cleanup old PID file
- if os.path.isfile(pidfile):
- os.remove(pidfile)
+ # cleanup old PID file
+ if os.path.isfile(pidfile):
+ os.remove(pidfile)
+ # Do some cleanup when OpenVPN is disabled/deleted
+ if openvpn['deleted'] or openvpn['disable']:
# cleanup old configuration file
- if os.path.isfile(get_config_name(interface)):
- os.remove(get_config_name(interface))
+ if os.path.isfile(get_config_name(openvpn['intf'])):
+ os.remove(get_config_name(openvpn['intf']))
# cleanup client config dir
- if os.path.isdir(directory + '/ccd/' + interface):
+ directory = os.path.dirname(get_config_name(openvpn['intf']))
+ if os.path.isdir(directory + '/ccd/' + openvpn['intf']):
try:
- os.remove(directory + '/ccd/' + interface + '/*')
+ os.remove(directory + '/ccd/' + openvpn['intf'] + '/*')
except:
pass
return None
- # Send SIGUSR1 to the process instead of creating a new process
- if psutil.pid_exists(pid):
- os.kill(pid, SIGUSR1)
- return None
+ # On configuration change we need to wait for the 'old' interface to
+ # vanish from the Kernel, if it is not gone, OpenVPN will report:
+ # ERROR: Cannot ioctl TUNSETIFF vtun10: Device or resource busy (errno=16)
+ while openvpn['intf'] in interfaces():
+ sleep(0.250) # 250ms
# No matching OpenVPN process running - maybe it got killed or none
# existed - nevertheless, spawn new OpenVPN process
@@ -862,13 +868,13 @@ def apply(openvpn):
cmd += ' --exec /usr/sbin/openvpn'
# now pass arguments to openvpn binary
cmd += ' --'
- cmd += ' --config ' + get_config_name(interface)
+ cmd += ' --config ' + get_config_name(openvpn['intf'])
# execute assembled command
subprocess_cmd(cmd)
-
return None
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/interface-vxlan.py b/src/conf_mode/interface-vxlan.py
new file mode 100755
index 000000000..59022238e
--- /dev/null
+++ b/src/conf_mode/interface-vxlan.py
@@ -0,0 +1,208 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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/>.
+#
+
+from os import environ
+from sys import exit
+from copy import deepcopy
+
+from vyos.configdict import list_diff
+from vyos.config import Config
+from vyos.ifconfig import VXLANIf, Interface
+from vyos.interfaces import get_type_of_interface
+from vyos import ConfigError
+from netifaces import interfaces
+
+default_config_data = {
+ 'address': [],
+ 'address_remove': [],
+ 'deleted': False,
+ 'description': '',
+ 'disable': False,
+ 'group': '',
+ 'intf': '',
+ 'ip_arp_cache_tmo': 30,
+ 'ip_proxy_arp': 0,
+ 'link': '',
+ 'mtu': 1450,
+ 'remote': '',
+ 'remote_port': 8472 # The Linux implementation of VXLAN pre-dates
+ # the IANA's selection of a standard destination port
+}
+
+
+def get_config():
+ vxlan = deepcopy(default_config_data)
+ conf = Config()
+
+ # determine tagNode instance
+ try:
+ vxlan['intf'] = environ['VYOS_TAGNODE_VALUE']
+ except KeyError as E:
+ print("Interface not specified")
+
+ # Check if interface has been removed
+ if not conf.exists('interfaces vxlan ' + vxlan['intf']):
+ vxlan['deleted'] = True
+ return vxlan
+
+ # set new configuration level
+ conf.set_level('interfaces vxlan ' + vxlan['intf'])
+
+ # retrieve configured interface addresses
+ if conf.exists('address'):
+ vxlan['address'] = conf.return_values('address')
+
+ # Determine interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed from the interface
+ eff_addr = conf.return_effective_values('address')
+ act_addr = conf.return_values('address')
+ vxlan['address_remove'] = list_diff(eff_addr, act_addr)
+
+ # retrieve interface description
+ if conf.exists('description'):
+ vxlan['description'] = conf.return_value('description')
+
+ # Disable this interface
+ if conf.exists('disable'):
+ vxlan['disable'] = True
+
+ # VXLAN multicast grou
+ if conf.exists('group'):
+ vxlan['group'] = conf.return_value('group')
+
+ # ARP cache entry timeout in seconds
+ if conf.exists('ip arp-cache-timeout'):
+ vxlan['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout'))
+
+ # Enable proxy-arp on this interface
+ if conf.exists('ip enable-proxy-arp'):
+ vxlan['ip_proxy_arp'] = 1
+
+ # VXLAN underlay interface
+ if conf.exists('link'):
+ vxlan['link'] = conf.return_value('link')
+
+ # Maximum Transmission Unit (MTU)
+ if conf.exists('mtu'):
+ vxlan['mtu'] = int(conf.return_value('mtu'))
+
+ # Remote address of VXLAN tunnel
+ if conf.exists('remote'):
+ vxlan['remote'] = conf.return_value('remote')
+
+ # Remote port of VXLAN tunnel
+ if conf.exists('port'):
+ vxlan['remote_port'] = int(conf.return_value('port'))
+
+ # Virtual Network Identifier
+ if conf.exists('vni'):
+ vxlan['vni'] = conf.return_value('vni')
+
+ return vxlan
+
+
+def verify(vxlan):
+ if vxlan['deleted']:
+ # bail out early
+ return None
+
+ if vxlan['mtu'] < 1500:
+ print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU')
+
+ if vxlan['group'] and not vxlan['link']:
+ raise ConfigError('Multicast VXLAN requires an underlaying interface ')
+
+ if not (vxlan['group'] or vxlan['remote']):
+ raise ConfigError('Group or remote must be configured')
+
+ if not vxlan['vni']:
+ raise ConfigError('Must configure VNI for VXLAN')
+
+ if vxlan['link']:
+ # VXLAN adds a 50 byte overhead - we need to check the underlaying MTU
+ # if our configured MTU is at least 50 bytes less
+ underlay_mtu = int(Interface(vxlan['link']).mtu)
+ if underlay_mtu < (vxlan['mtu'] + 50):
+ raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \
+ 'MTU is to small ({})'.format(underlay_mtu))
+
+ return None
+
+
+def generate(vxlan):
+ return None
+
+
+def apply(vxlan):
+ # Check if the VXLAN interface already exists
+ if vxlan['intf'] in interfaces():
+ v = VXLANIf(vxlan['intf'])
+ # VXLAN is super picky and the tunnel always needs to be recreated,
+ # thus we can simply always delete it first.
+ v.remove()
+
+ if not vxlan['deleted']:
+ # VXLAN interface needs to be created on-block
+ # instead of passing a ton of arguments, I just use a dict
+ # that is managed by vyos.ifconfig
+ conf = deepcopy(VXLANIf.get_config())
+
+ # Assign VXLAN instance configuration parameters to config dict
+ conf['vni'] = vxlan['vni']
+ conf['group'] = vxlan['group']
+ conf['dev'] = vxlan['link']
+ conf['remote'] = vxlan['remote']
+ conf['port'] = vxlan['remote_port']
+
+ # Finally create the new interface
+ v = VXLANIf(vxlan['intf'], config=conf)
+ # update interface description used e.g. by SNMP
+ v.ifalias = vxlan['description']
+ # Maximum Transfer Unit (MTU)
+ v.mtu = vxlan['mtu']
+
+ # configure ARP cache timeout in milliseconds
+ v.arp_cache_tmp = vxlan['ip_arp_cache_tmo']
+ # Enable proxy-arp on this interface
+ v.proxy_arp = vxlan['ip_proxy_arp']
+
+ # Configure interface address(es)
+ # - not longer required addresses get removed first
+ # - newly addresses will be added second
+ for addr in vxlan['address_remove']:
+ v.del_addr(addr)
+ for addr in vxlan['address']:
+ v.add_addr(addr)
+
+ # As the bond interface is always disabled first when changing
+ # parameters we will only re-enable the interface if it is not
+ # administratively disabled
+ if not vxlan['disable']:
+ v.state='up'
+
+ 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/interface-wireguard.py b/src/conf_mode/interface-wireguard.py
index 8234fad0b..d51a7a08d 100755
--- a/src/conf_mode/interface-wireguard.py
+++ b/src/conf_mode/interface-wireguard.py
@@ -24,350 +24,243 @@ import subprocess
from vyos.config import Config
from vyos import ConfigError
+from vyos.ifconfig import WireGuardIf
-dir = r'/config/auth/wireguard'
-pk = dir + '/private.key'
-pub = dir + '/public.key'
-psk_file = r'/tmp/psk'
+ifname = str(os.environ['VYOS_TAGNODE_VALUE'])
+intfc = WireGuardIf(ifname)
+
+kdir = r'/config/auth/wireguard'
def check_kmod():
- if not os.path.exists('/sys/module/wireguard'):
- sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod")
- if os.system('sudo modprobe wireguard') != 0:
- sl.syslog(sl.LOG_NOTICE, "modprobe wireguard failed")
- raise ConfigError("modprobe wireguard failed")
+ if not os.path.exists('/sys/module/wireguard'):
+ sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod")
+ if os.system('sudo modprobe wireguard') != 0:
+ sl.syslog(sl.LOG_NOTICE, "modprobe wireguard failed")
+ raise ConfigError("modprobe wireguard failed")
+
def get_config():
- c = Config()
- if not c.exists('interfaces wireguard'):
- return None
-
- c.set_level('interfaces')
- intfcs = c.list_nodes('wireguard')
- intfcs_eff = c.list_effective_nodes('wireguard')
- new_lst = list(set(intfcs) - set(intfcs_eff))
- del_lst = list(set(intfcs_eff) - set(intfcs))
-
- config_data = {
- 'interfaces' : {}
- }
- ### setting defaults and determine status of the config
- for intfc in intfcs:
- cnf = 'wireguard ' + intfc
- # default data struct
- config_data['interfaces'].update(
- {
- intfc : {
- 'addr' : '',
- 'descr' : intfc, ## snmp ifAlias
- 'lport' : '',
- 'status' : 'exists',
- 'state' : 'enabled',
- 'fwmark' : 0x00,
- 'mtu' : '1420',
- 'peer' : {}
- }
+ c = Config()
+ if not c.exists('interfaces wireguard'):
+ return None
+
+ config_data = {
+ ifname: {
+ 'addr': '',
+ 'descr': ifname,
+ 'lport': None,
+ 'status': 'exists',
+ 'state': 'enabled',
+ 'fwmark': 0x00,
+ 'mtu': 1420,
+ 'peer': {},
+ 'pk' : '{}/default/private.key'.format(kdir)
}
- )
-
- ### determine status either delete or create
- for i in new_lst:
- config_data['interfaces'][i]['status'] = 'create'
-
- for i in del_lst:
- config_data['interfaces'].update(
- {
- i : {
- 'status': 'delete'
- }
- }
- )
-
- ### based on the status, setup conf values
- for intfc in intfcs:
- cnf = 'wireguard ' + intfc
- if config_data['interfaces'][intfc]['status'] != 'delete':
- ### addresses
- if c.exists(cnf + ' address'):
- config_data['interfaces'][intfc]['addr'] = c.return_values(cnf + ' address')
- ### interface up/down
- if c.exists(cnf + ' disable'):
- config_data['interfaces'][intfc]['state'] = 'disable'
- ### listen port
- if c.exists(cnf + ' port'):
- config_data['interfaces'][intfc]['lport'] = c.return_value(cnf + ' port')
- ### fwmark
- if c.exists(cnf + ' fwmark'):
- config_data['interfaces'][intfc]['fwmark'] = c.return_value(cnf + ' fwmark')
- ### description
- if c.exists(cnf + ' description'):
- config_data['interfaces'][intfc]['descr'] = c.return_value(cnf + ' description')
- ### mtu
- if c.exists(cnf + ' mtu'):
- config_data['interfaces'][intfc]['mtu'] = c.return_value(cnf + ' mtu')
- ### peers
- if c.exists(cnf + ' peer'):
- for p in c.list_nodes(cnf + ' peer'):
- if not c.exists(cnf + ' peer ' + p + ' disable'):
- config_data['interfaces'][intfc]['peer'].update(
- {
- p : {
- 'allowed-ips' : [],
- 'endpoint' : '',
- 'pubkey' : ''
- }
- }
- )
- if c.exists(cnf + ' peer ' + p + ' pubkey'):
- config_data['interfaces'][intfc]['peer'][p]['pubkey'] = c.return_value(cnf + ' peer ' + p + ' pubkey')
- if c.exists(cnf + ' peer ' + p + ' allowed-ips'):
- config_data['interfaces'][intfc]['peer'][p]['allowed-ips'] = c.return_values(cnf + ' peer ' + p + ' allowed-ips')
- if c.exists(cnf + ' peer ' + p + ' endpoint'):
- config_data['interfaces'][intfc]['peer'][p]['endpoint'] = c.return_value(cnf + ' peer ' + p + ' endpoint')
- if c.exists(cnf + ' peer ' + p + ' persistent-keepalive'):
- config_data['interfaces'][intfc]['peer'][p]['persistent-keepalive'] = c.return_value(cnf + ' peer ' + p + ' persistent-keepalive')
- if c.exists(cnf + ' peer ' + p + ' preshared-key'):
- config_data['interfaces'][intfc]['peer'][p]['psk'] = c.return_value(cnf + ' peer ' + p + ' preshared-key')
-
- return config_data
+ }
+
+ c.set_level('interfaces wireguard')
+ if not c.exists_effective(ifname):
+ config_data[ifname]['status'] = 'create'
+
+ if not c.exists(ifname) and c.exists_effective(ifname):
+ config_data[ifname]['status'] = 'delete'
+
+ if config_data[ifname]['status'] != 'delete':
+ if c.exists(ifname + ' address'):
+ config_data[ifname]['addr'] = c.return_values(ifname + ' address')
+ if c.exists(ifname + ' disable'):
+ config_data[ifname]['state'] = 'disable'
+ if c.exists(ifname + ' port'):
+ config_data[ifname]['lport'] = c.return_value(ifname + ' port')
+ if c.exists(ifname + ' fwmark'):
+ config_data[ifname]['fwmark'] = c.return_value(ifname + ' fwmark')
+ if c.exists(ifname + ' description'):
+ config_data[ifname]['descr'] = c.return_value(
+ ifname + ' description')
+ if c.exists(ifname + ' mtu'):
+ config_data[ifname]['mtu'] = c.return_value(ifname + ' mtu')
+ if c.exists(ifname + ' private-key'):
+ config_data[ifname]['pk'] = "{0}/{1}/private.key".format(kdir,c.return_value(ifname + ' private-key'))
+ if c.exists(ifname + ' peer'):
+ for p in c.list_nodes(ifname + ' peer'):
+ if not c.exists(ifname + ' peer ' + p + ' disable'):
+ config_data[ifname]['peer'].update(
+ {
+ p: {
+ 'allowed-ips': [],
+ 'endpoint': '',
+ 'pubkey': ''
+ }
+ }
+ )
+ if c.exists(ifname + ' peer ' + p + ' pubkey'):
+ config_data[ifname]['peer'][p]['pubkey'] = c.return_value(
+ ifname + ' peer ' + p + ' pubkey')
+ if c.exists(ifname + ' peer ' + p + ' allowed-ips'):
+ config_data[ifname]['peer'][p]['allowed-ips'] = c.return_values(
+ ifname + ' peer ' + p + ' allowed-ips')
+ if c.exists(ifname + ' peer ' + p + ' endpoint'):
+ config_data[ifname]['peer'][p]['endpoint'] = c.return_value(
+ ifname + ' peer ' + p + ' endpoint')
+ if c.exists(ifname + ' peer ' + p + ' persistent-keepalive'):
+ config_data[ifname]['peer'][p]['persistent-keepalive'] = c.return_value(
+ ifname + ' peer ' + p + ' persistent-keepalive')
+ if c.exists(ifname + ' peer ' + p + ' preshared-key'):
+ config_data[ifname]['peer'][p]['psk'] = c.return_value(
+ ifname + ' peer ' + p + ' preshared-key')
+
+ return config_data
def verify(c):
- if not c:
- return None
+ if not c:
+ return None
- for i in c['interfaces']:
- if c['interfaces'][i]['status'] != 'delete':
- if not c['interfaces'][i]['addr']:
- raise ConfigError("address required for interface " + i)
- if not c['interfaces'][i]['peer']:
- raise ConfigError("peer required on interface " + i)
+ if not os.path.exists(c[ifname]['pk']):
+ raise ConfigError(
+ "No keys found, generate them by executing: \'run generate wireguard [keypair|named-keypairs]\'")
- for p in c['interfaces'][i]['peer']:
- if not c['interfaces'][i]['peer'][p]['allowed-ips']:
- raise ConfigError("allowed-ips required on interface " + i + " for peer " + p)
- if not c['interfaces'][i]['peer'][p]['pubkey']:
- raise ConfigError("pubkey from your peer is mandatory on " + i + " for peer " + p)
+ if c[ifname]['status'] != 'delete':
+ if not c[ifname]['addr']:
+ raise ConfigError("ERROR: IP address required")
+ if not c[ifname]['peer']:
+ raise ConfigError("ERROR: peer required")
+ for p in c[ifname]['peer']:
+ if not c[ifname]['peer'][p]['allowed-ips']:
+ raise ConfigError("ERROR: allowed-ips required for peer " + p)
+ if not c[ifname]['peer'][p]['pubkey']:
+ raise ConfigError("peer pubkey required for peer " + p)
def apply(c):
- ### no wg config left, delete all wireguard devices on the os
- if not c:
- net_devs = os.listdir('/sys/class/net/')
- for dev in net_devs:
- if os.path.isdir('/sys/class/net/' + dev):
- buf = open('/sys/class/net/' + dev + '/uevent', 'r').read()
- if re.search("DEVTYPE=wireguard", buf, re.I|re.M):
- wg_intf = re.sub("INTERFACE=", "", re.search("INTERFACE=.*", buf, re.I|re.M).group(0))
- sl.syslog(sl.LOG_NOTICE, "removing interface " + wg_intf)
- subprocess.call(['ip l d dev ' + wg_intf + ' >/dev/null'], shell=True)
- return None
-
- ###
- ## find the diffs between effective config an new config
- ###
- c_eff = Config()
- c_eff.set_level('interfaces wireguard')
-
- ### link status up/down aka interface disable
-
- for intf in c['interfaces']:
- if not c['interfaces'][intf]['status'] == 'delete':
- if c['interfaces'][intf]['state'] == 'disable':
- sl.syslog(sl.LOG_NOTICE, "disable interface " + intf)
- subprocess.call(['ip l s dev ' + intf + ' down ' + ' &>/dev/null'], shell=True)
- else:
- sl.syslog(sl.LOG_NOTICE, "enable interface " + intf)
- subprocess.call(['ip l s dev ' + intf + ' up ' + ' &>/dev/null'], shell=True)
-
- ### deletion of a specific interface
- for intf in c['interfaces']:
- if c['interfaces'][intf]['status'] == 'delete':
- sl.syslog(sl.LOG_NOTICE, "removing interface " + intf)
- subprocess.call(['ip l d dev ' + intf + ' &>/dev/null'], shell=True)
-
- ### peer deletion
- peer_eff = c_eff.list_effective_nodes( intf + ' peer')
+ # no wg config left, delete all wireguard devices, if any
+ if not c:
+ net_devs = os.listdir('/sys/class/net/')
+ for dev in net_devs:
+ if os.path.isdir('/sys/class/net/' + dev):
+ buf = open('/sys/class/net/' + dev + '/uevent', 'r').read()
+ if re.search("DEVTYPE=wireguard", buf, re.I | re.M):
+ wg_intf = re.sub("INTERFACE=", "", re.search(
+ "INTERFACE=.*", buf, re.I | re.M).group(0))
+ sl.syslog(sl.LOG_NOTICE, "removing interface " + wg_intf)
+ subprocess.call(
+ ['ip l d dev ' + wg_intf + ' >/dev/null'], shell=True)
+ return None
+
+ # interface removal
+ if c[ifname]['status'] == 'delete':
+ sl.syslog(sl.LOG_NOTICE, "removing interface " + ifname)
+ intfc.remove()
+ return None
+
+ c_eff = Config()
+ c_eff.set_level('interfaces wireguard')
+
+ # interface state
+ if c[ifname]['state'] == 'disable':
+ sl.syslog(sl.LOG_NOTICE, "disable interface " + ifname)
+ intfc.state = 'down'
+ else:
+ if not intfc.state == 'up':
+ sl.syslog(sl.LOG_NOTICE, "enable interface " + ifname)
+ intfc.state = 'up'
+
+ # IP address
+ if not c_eff.exists_effective(ifname + ' address'):
+ for ip in c[ifname]['addr']:
+ intfc.add_addr(ip)
+ else:
+ addr_eff = c_eff.return_effective_values(ifname + ' address')
+ addr_rem = list(set(addr_eff) - set(c[ifname]['addr']))
+ addr_add = list(set(c[ifname]['addr']) - set(addr_eff))
+
+ if len(addr_rem) != 0:
+ for ip in addr_rem:
+ sl.syslog(
+ sl.LOG_NOTICE, "remove IP address {0} from {1}".format(ip, ifname))
+ intfc.del_addr(ip)
+
+ if len(addr_add) != 0:
+ for ip in addr_add:
+ sl.syslog(
+ sl.LOG_NOTICE, "add IP address {0} to {1}".format(ip, ifname))
+ intfc.add_addr(ip)
+
+ # interface MTU
+ if c[ifname]['mtu'] != 1420:
+ intfc.mtu = int(c[ifname]['mtu'])
+ else:
+ # default is set to 1420 in config_data
+ intfc.mtu = int(c[ifname]['mtu'])
+
+ # ifalias for snmp from description
+ descr_eff = c_eff.return_effective_value(ifname + ' description')
+ if descr_eff != c[ifname]['descr']:
+ intfc.ifalias = str(c[ifname]['descr'])
+
+ # peer deletion
+ peer_eff = c_eff.list_effective_nodes(ifname + ' peer')
peer_cnf = []
+
try:
- for p in c['interfaces'][intf]['peer']:
- peer_cnf.append(p)
+ for p in c[ifname]['peer']:
+ peer_cnf.append(p)
except KeyError:
- pass
+ pass
peer_rem = list(set(peer_eff) - set(peer_cnf))
for p in peer_rem:
- pkey = c_eff.return_effective_value( intf + ' peer ' + p +' pubkey')
- remove_peer(intf, pkey)
+ pkey = c_eff.return_effective_value(ifname + ' peer ' + p + ' pubkey')
+ intfc.remove_peer(pkey)
- ### peer pubkey update
- ### wg identifies peers by its pubky, so we have to remove the peer first
- ### it will recreated it then below with the new key from the cli config
+ # peer key update
for p in peer_eff:
- if p in peer_cnf:
- ekey = c_eff.return_effective_value( intf + ' peer ' + p +' pubkey')
- nkey = c['interfaces'][intf]['peer'][p]['pubkey']
- if nkey != ekey:
- sl.syslog(sl.LOG_NOTICE, "peer " + p + ' changed pubkey from ' + ekey + 'to key ' + nkey + ' on interface ' + intf)
- remove_peer(intf, ekey)
-
- ### new config
- if c['interfaces'][intf]['status'] == 'create':
- if not os.path.exists(pk):
- raise ConfigError("No keys found, generate them by executing: \'run generate wireguard keypair\'")
-
- subprocess.call(['ip l a dev ' + intf + ' type wireguard 2>/dev/null'], shell=True)
- for addr in c['interfaces'][intf]['addr']:
- add_addr(intf, addr)
-
- subprocess.call(['ip l set up dev ' + intf + ' mtu ' + c['interfaces'][intf]['mtu'] + ' &>/dev/null'], shell=True)
- configure_interface(c, intf)
-
- ### config updates
- if c['interfaces'][intf]['status'] == 'exists':
- ### IP address change
- addr_eff = c_eff.return_effective_values(intf + ' address')
- addr_rem = list(set(addr_eff) - set(c['interfaces'][intf]['addr']))
- addr_add = list(set(c['interfaces'][intf]['addr']) - set(addr_eff))
-
- if len(addr_rem) != 0:
- for addr in addr_rem:
- del_addr(intf, addr)
-
- if len(addr_add) != 0:
- for addr in addr_add:
- add_addr(intf, addr)
-
- ## mtu update
- mtu = c['interfaces'][intf]['mtu']
- if mtu != 1420:
- sl.syslog(sl.LOG_NOTICE, "setting mtu to " + mtu + " on " + intf)
- subprocess.call(['ip l set mtu ' + mtu + ' dev ' + intf + ' &>/dev/null'], shell=True)
-
-
- ### persistent-keepalive
- for p in c['interfaces'][intf]['peer']:
- val_eff = ""
- val = ""
-
- try:
- val = c['interfaces'][intf]['peer'][p]['persistent-keepalive']
- except KeyError:
- pass
-
- if c_eff.exists_effective(intf + ' peer ' + p + ' persistent-keepalive'):
- val_eff = c_eff.return_effective_value(intf + ' peer ' + p + ' persistent-keepalive')
-
- ### disable keepalive
- if val_eff and not val:
- c['interfaces'][intf]['peer'][p]['persistent-keepalive'] = 0
-
- ### set new keepalive value
- if not val_eff and val:
- c['interfaces'][intf]['peer'][p]['persistent-keepalive'] = val
-
- ## wg command call
- configure_interface(c, intf)
-
- ### ifalias for snmp from description
- if c['interfaces'][intf]['status'] != 'delete':
- descr_eff = c_eff.return_effective_value(intf + ' description')
- cnf_descr = c['interfaces'][intf]['descr']
- if descr_eff != cnf_descr:
- with open('/sys/class/net/' + str(intf) + '/ifalias', 'w') as fh:
- fh.write(str(cnf_descr))
-
-def configure_interface(c, intf):
- for p in c['interfaces'][intf]['peer']:
- ## config init for wg call
- wg_config = {
- 'interface' : intf,
- 'port' : 0,
- 'private-key' : pk,
- 'pubkey' : '',
- 'psk' : '/dev/null',
- 'allowed-ips' : [],
- 'fwmark' : 0x00,
- 'endpoint' : None,
- 'keepalive' : 0
- }
-
- ## mandatory settings
- wg_config['pubkey'] = c['interfaces'][intf]['peer'][p]['pubkey']
- wg_config['allowed-ips'] = c['interfaces'][intf]['peer'][p]['allowed-ips']
-
- ## optional settings
- # listen-port
- if c['interfaces'][intf]['lport']:
- wg_config['port'] = c['interfaces'][intf]['lport']
-
- ## fwmark
- if c['interfaces'][intf]['fwmark']:
- wg_config['fwmark'] = c['interfaces'][intf]['fwmark']
-
- ## endpoint
- if c['interfaces'][intf]['peer'][p]['endpoint']:
- wg_config['endpoint'] = c['interfaces'][intf]['peer'][p]['endpoint']
-
- ## persistent-keepalive
- if 'persistent-keepalive' in c['interfaces'][intf]['peer'][p]:
- wg_config['keepalive'] = c['interfaces'][intf]['peer'][p]['persistent-keepalive']
-
- ## preshared-key - is only read from a file, it's called via sudo redirection doesn't work either
- if 'psk' in c['interfaces'][intf]['peer'][p]:
- old_umask = os.umask(0o077)
- open(psk_file, 'w').write(str(c['interfaces'][intf]['peer'][p]['psk']))
- os.umask(old_umask)
- wg_config['psk'] = psk_file
-
- ### assemble wg command
- cmd = "sudo wg set " + intf
- cmd += " listen-port " + str(wg_config['port'])
- cmd += " fwmark " + str(wg_config['fwmark'])
- cmd += " private-key " + wg_config['private-key']
- cmd += " peer " + wg_config['pubkey']
- cmd += " preshared-key " + wg_config['psk']
- cmd += " allowed-ips "
- for ap in wg_config['allowed-ips']:
- if ap != wg_config['allowed-ips'][-1]:
- cmd += ap + ","
- else:
- cmd += ap
-
- if wg_config['endpoint']:
- cmd += " endpoint " + wg_config['endpoint']
-
- if wg_config['keepalive'] != 0:
- cmd += " persistent-keepalive " + wg_config['keepalive']
- else:
- cmd += " persistent-keepalive 0"
-
- sl.syslog(sl.LOG_NOTICE, cmd)
- #print (cmd)
- subprocess.call([cmd], shell=True)
- """ remove psk_file """
- if os.path.exists(psk_file):
- os.remove(psk_file)
-
-def add_addr(intf, addr):
- # see https://phabricator.vyos.net/T949
- ret = subprocess.call(['ip a a dev ' + intf + ' ' + addr + ' &>/dev/null'], shell=True)
- sl.syslog(sl.LOG_NOTICE, "ip a a dev " + intf + " " + addr)
-
-def del_addr(intf, addr):
- ret = subprocess.call(['ip a d dev ' + intf + ' ' + addr + ' &>/dev/null'], shell=True)
- sl.syslog(sl.LOG_NOTICE, "ip a d dev " + intf + " " + addr)
-
-def remove_peer(intf, peer_key):
- cmd = 'sudo wg set ' + str(intf) + ' peer ' + peer_key + ' remove &>/dev/null'
- ret = subprocess.call([cmd], shell=True)
- sl.syslog(sl.LOG_NOTICE, "peer " + peer_key + " removed from " + intf)
+ if p in peer_cnf:
+ ekey = c_eff.return_effective_value(
+ ifname + ' peer ' + p + ' pubkey')
+ nkey = c[ifname]['peer'][p]['pubkey']
+ if nkey != ekey:
+ sl.syslog(
+ sl.LOG_NOTICE, "peer {0} pubkey changed from {1} to {2} on interface {3}".format(p, ekey, nkey, ifname))
+ intfc.remove_peer(ekey)
+
+ intfc.config['private-key'] = c[ifname]['pk']
+ for p in c[ifname]['peer']:
+ intfc.config['pubkey'] = str(c[ifname]['peer'][p]['pubkey'])
+ intfc.config['allowed-ips'] = (c[ifname]['peer'][p]['allowed-ips'])
+
+ # listen-port
+ if c[ifname]['lport']:
+ intfc.config['port'] = c[ifname]['lport']
+
+ # fwmark
+ if c[ifname]['fwmark']:
+ intfc.config['fwmark'] = c[ifname]['fwmark']
+
+ # endpoint
+ if c[ifname]['peer'][p]['endpoint']:
+ intfc.config['endpoint'] = c[ifname]['peer'][p]['endpoint']
+
+ # persistent-keepalive
+ if 'persistent-keepalive' in c[ifname]['peer'][p]:
+ intfc.config['keepalive'] = c[ifname][
+ 'peer'][p]['persistent-keepalive']
+
+ # preshared-key - needs to be read from a file
+ if 'psk' in c[ifname]['peer'][p]:
+ psk_file = '/config/auth/wireguard/psk'
+ old_umask = os.umask(0o077)
+ open(psk_file, 'w').write(str(c[ifname]['peer'][p]['psk']))
+ os.umask(old_umask)
+ intfc.config['psk'] = psk_file
+
+ intfc.update()
if __name__ == '__main__':
- try:
- check_kmod()
- c = get_config()
- verify(c)
- apply(c)
- except ConfigError as e:
- print(e)
- sys.exit(1)
+ try:
+ check_kmod()
+ c = get_config()
+ verify(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py
index 8d25e7abd..156bb2edd 100755
--- a/src/conf_mode/ipsec-settings.py
+++ b/src/conf_mode/ipsec-settings.py
@@ -62,7 +62,7 @@ conn {{ra_conn_name}}
left={{outside_addr}}
leftsubnet=%dynamic[/1701]
rightsubnet=%dynamic
- mark=%unique
+ mark_in=%unique
auto=add
ike=aes256-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1024!
dpddelay=15
diff --git a/src/conf_mode/syslog.py b/src/conf_mode/syslog.py
index 7b79c701b..c4f3d2c9c 100755
--- a/src/conf_mode/syslog.py
+++ b/src/conf_mode/syslog.py
@@ -24,16 +24,16 @@ import jinja2
from vyos.config import Config
from vyos import ConfigError
-########### config templates
+# config templates
-#### /etc/rsyslog.d/vyos-rsyslog.conf ###
+# /etc/rsyslog.d/vyos-rsyslog.conf ###
configs = '''
## generated by syslog.py ##
## file based logging
{% if files['global']['marker'] -%}
$ModLoad immark
{% if files['global']['marker-interval'] %}
-$MarkMessagePeriod {{files['global']['marker-interval']}}
+$MarkMessagePeriod {{files['global']['marker-interval']}}
{% endif %}
{% endif -%}
{% if files['global']['preserver_fqdn'] -%}
@@ -80,217 +80,241 @@ logrotate_configs = '''
}
{% endfor %}
'''
-############# config templates end
+# config templates end
+
def get_config():
- c = Config()
- if not c.exists('system syslog'):
- return None
- c.set_level('system syslog')
-
- config_data = {
- 'files' : {},
- 'console' : {},
- 'hosts' : {},
- 'user' : {}
- }
-
- #####
- # /etc/rsyslog.d/vyos-rsyslog.conf
- # 'set system syslog global'
- #####
- config_data['files'].update(
- {
- 'global' : {
- 'log-file' : '/var/log/messages',
- 'max-size' : 262144,
- 'action-on-max-size' : '/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog',
- 'selectors' : '*.notice;local7.debug',
- 'max-files' : '5',
- 'preserver_fqdn' : False
- }
- }
- )
-
- if c.exists('global marker'):
- config_data['files']['global']['marker'] = True
- if c.exists('global marker interval'):
- config_data['files']['global']['marker-interval'] = c.return_value('global marker interval')
- if c.exists('global facility'):
- config_data['files']['global']['selectors'] = generate_selectors(c, 'global facility')
- if c.exists('global archive size'):
- config_data['files']['global']['max-size'] = int(c.return_value('global archive size'))* 1024
- if c.exists('global archive file'):
- config_data['files']['global']['max-files'] = c.return_value('global archive file')
- if c.exists('global preserve-fqdn'):
- config_data['files']['global']['preserver_fqdn'] = True
-
- ###
- # set system syslog file
- ###
-
- if c.exists('file'):
- filenames = c.list_nodes('file')
- for filename in filenames:
- config_data['files'].update(
- {
- filename : {
- 'log-file' : '/var/log/user/' + filename,
- 'max-files' : '5',
- 'action-on-max-size' : '/usr/sbin/logrotate /etc/logrotate.d/' + filename,
- 'selectors' : '*.err',
- 'max-size' : 262144
- }
- }
- )
-
- if c.exists('file ' + filename + ' facility'):
- config_data['files'][filename]['selectors'] = generate_selectors(c, 'file ' + filename + ' facility')
- if c.exists('file ' + filename + ' archive size'):
- config_data['files'][filename]['max-size'] = int(c.return_value('file ' + filename + ' archive size'))* 1024
- if c.exists('file ' + filename + ' archive files'):
- config_data['files'][filename]['max-files'] = c.return_value('file ' + filename + ' archive files')
-
- ## set system syslog console
- if c.exists('console'):
- config_data['console'] = {
- '/dev/console' : {
- 'selectors' : '*.err'
- }
+ c = Config()
+ if not c.exists('system syslog'):
+ return None
+ c.set_level('system syslog')
+
+ config_data = {
+ 'files': {},
+ 'console': {},
+ 'hosts': {},
+ 'user': {}
}
-
- for f in c.list_nodes('console facility'):
- if c.exists('console facility ' + f + ' level'):
- config_data['console'] = {
- '/dev/console' : {
- 'selectors' : generate_selectors(c, 'console facility')
- }
- }
-
- ## set system syslog host
- if c.exists('host'):
- proto = 'udp'
- rhosts = c.list_nodes('host')
- for rhost in rhosts:
- for fac in c.list_nodes('host ' + rhost + ' facility'):
- if c.exists('host ' + rhost + ' facility ' + fac + ' protocol'):
- proto = c.return_value('host ' + rhost + ' facility ' + fac + ' protocol')
-
- config_data['hosts'].update(
+
+ #
+ # /etc/rsyslog.d/vyos-rsyslog.conf
+ # 'set system syslog global'
+ #
+ config_data['files'].update(
{
- rhost : {
- 'selectors' : generate_selectors(c, 'host ' + rhost + ' facility'),
- 'proto' : proto
- }
+ 'global': {
+ 'log-file': '/var/log/messages',
+ 'max-size': 262144,
+ 'action-on-max-size': '/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog',
+ 'selectors': '*.notice;local7.debug',
+ 'max-files': '5',
+ 'preserver_fqdn': False
+ }
}
- )
+ )
- ## set system syslog user
- if c.exists('user'):
- usrs = c.list_nodes('user')
- for usr in usrs:
- config_data['user'].update(
- {
- usr : {
- 'selectors' : generate_selectors(c, 'user ' + usr + ' facility')
- }
+ if c.exists('global marker'):
+ config_data['files']['global']['marker'] = True
+ if c.exists('global marker interval'):
+ config_data['files']['global'][
+ 'marker-interval'] = c.return_value('global marker interval')
+ if c.exists('global facility'):
+ config_data['files']['global'][
+ 'selectors'] = generate_selectors(c, 'global facility')
+ if c.exists('global archive size'):
+ config_data['files']['global']['max-size'] = int(
+ c.return_value('global archive size')) * 1024
+ if c.exists('global archive file'):
+ config_data['files']['global'][
+ 'max-files'] = c.return_value('global archive file')
+ if c.exists('global preserve-fqdn'):
+ config_data['files']['global']['preserver_fqdn'] = True
+
+ #
+ # set system syslog file
+ #
+
+ if c.exists('file'):
+ filenames = c.list_nodes('file')
+ for filename in filenames:
+ config_data['files'].update(
+ {
+ filename: {
+ 'log-file': '/var/log/user/' + filename,
+ 'max-files': '5',
+ 'action-on-max-size': '/usr/sbin/logrotate /etc/logrotate.d/' + filename,
+ 'selectors': '*.err',
+ 'max-size': 262144
+ }
+ }
+ )
+
+ if c.exists('file ' + filename + ' facility'):
+ config_data['files'][filename]['selectors'] = generate_selectors(
+ c, 'file ' + filename + ' facility')
+ if c.exists('file ' + filename + ' archive size'):
+ config_data['files'][filename]['max-size'] = int(
+ c.return_value('file ' + filename + ' archive size')) * 1024
+ if c.exists('file ' + filename + ' archive files'):
+ config_data['files'][filename]['max-files'] = c.return_value(
+ 'file ' + filename + ' archive files')
+
+ # set system syslog console
+ if c.exists('console'):
+ config_data['console'] = {
+ '/dev/console': {
+ 'selectors': '*.err'
+ }
}
- )
-
- return config_data
+
+ for f in c.list_nodes('console facility'):
+ if c.exists('console facility ' + f + ' level'):
+ config_data['console'] = {
+ '/dev/console': {
+ 'selectors': generate_selectors(c, 'console facility')
+ }
+ }
+
+ # set system syslog host
+ if c.exists('host'):
+ proto = 'udp'
+ rhosts = c.list_nodes('host')
+ for rhost in rhosts:
+ for fac in c.list_nodes('host ' + rhost + ' facility'):
+ if c.exists('host ' + rhost + ' facility ' + fac + ' protocol'):
+ proto = c.return_value(
+ 'host ' + rhost + ' facility ' + fac + ' protocol')
+
+ config_data['hosts'].update(
+ {
+ rhost: {
+ 'selectors': generate_selectors(c, 'host ' + rhost + ' facility'),
+ 'proto': proto
+ }
+ }
+ )
+
+ # set system syslog user
+ if c.exists('user'):
+ usrs = c.list_nodes('user')
+ for usr in usrs:
+ config_data['user'].update(
+ {
+ usr: {
+ 'selectors': generate_selectors(c, 'user ' + usr + ' facility')
+ }
+ }
+ )
+
+ return config_data
+
def generate_selectors(c, config_node):
-## protocols and security are being mapped here
-## for backward compatibility with old configs
-## security and protocol mappings can be removed later
- if c.is_tag(config_node):
- nodes = c.list_nodes(config_node)
- selectors = ""
- for node in nodes:
- lvl = c.return_value( config_node + ' ' + node + ' level')
- if lvl == None:
- lvl = "err"
- if lvl == 'all':
- lvl = '*'
- if node == 'all' and node != nodes[-1]:
- selectors += "*." + lvl + ";"
- elif node == 'all':
- selectors += "*." + lvl
- elif node != nodes[-1]:
- if node == 'protocols':
- node = 'local7'
- if node == 'security':
- node = 'auth'
- selectors += node + "." + lvl + ";"
- else:
- if node == 'protocols':
- node = 'local7'
- if node == 'security':
- node = 'auth'
- selectors += node + "." + lvl
- return selectors
+# protocols and security are being mapped here
+# for backward compatibility with old configs
+# security and protocol mappings can be removed later
+ if c.is_tag(config_node):
+ nodes = c.list_nodes(config_node)
+ selectors = ""
+ for node in nodes:
+ lvl = c.return_value(config_node + ' ' + node + ' level')
+ if lvl == None:
+ lvl = "err"
+ if lvl == 'all':
+ lvl = '*'
+ if node == 'all' and node != nodes[-1]:
+ selectors += "*." + lvl + ";"
+ elif node == 'all':
+ selectors += "*." + lvl
+ elif node != nodes[-1]:
+ if node == 'protocols':
+ node = 'local7'
+ if node == 'security':
+ node = 'auth'
+ selectors += node + "." + lvl + ";"
+ else:
+ if node == 'protocols':
+ node = 'local7'
+ if node == 'security':
+ node = 'auth'
+ selectors += node + "." + lvl
+ return selectors
+
def generate(c):
- if c == None:
- return None
+ if c == None:
+ return None
- tmpl = jinja2.Template(configs, trim_blocks=True)
- config_text = tmpl.render(c)
- with open('/etc/rsyslog.d/vyos-rsyslog.conf', 'w') as f:
- f.write(config_text)
+ tmpl = jinja2.Template(configs, trim_blocks=True)
+ config_text = tmpl.render(c)
+ with open('/etc/rsyslog.d/vyos-rsyslog.conf', 'w') as f:
+ f.write(config_text)
+
+ # eventually write for each file its own logrotate file, since size is
+ # defined it shouldn't matter
+ tmpl = jinja2.Template(logrotate_configs, trim_blocks=True)
+ config_text = tmpl.render(c)
+ with open('/etc/logrotate.d/vyos-rsyslog', 'w') as f:
+ f.write(config_text)
- ## eventually write for each file its own logrotate file, since size is defined it shouldn't matter
- tmpl = jinja2.Template(logrotate_configs, trim_blocks=True)
- config_text = tmpl.render(c)
- with open('/etc/logrotate.d/vyos-rsyslog', 'w') as f:
- f.write(config_text)
def verify(c):
- if c == None:
- return None
- #
- # /etc/rsyslog.conf is generated somewhere and copied over the original (exists in /opt/vyatta/etc/rsyslog.conf)
- # it interferes with the global logging, to make sure we are using a single base, template is enforced here
- #
- if not os.path.islink('/etc/rsyslog.conf'):
- os.remove('/etc/rsyslog.conf')
- os.symlink('/usr/share/vyos/templates/rsyslog/rsyslog.conf', '/etc/rsyslog.conf')
-
- # /var/log/vyos-rsyslog were the old files, we may want to clean those up, but currently there
- # is a chance that someone still needs it, so I don't automatically remove them
-
- if c == None:
- return None
-
- fac = ['*','auth','authpriv','cron','daemon','kern','lpr','mail','mark','news','protocols','security',\
- 'syslog','user','uucp','local0','local1','local2','local3','local4','local5','local6','local7']
- lvl = ['emerg','alert','crit','err','warning','notice','info','debug','*']
-
- for conf in c:
- if c[conf]:
- for item in c[conf]:
- for s in c[conf][item]['selectors'].split(";"):
- f = re.sub("\..*$","",s)
- if f not in fac:
- print (c[conf])
- raise ConfigError('Invalid facility ' + s + ' set in '+ conf + ' ' + item)
- l = re.sub("^.+\.","",s)
- if l not in lvl:
- raise ConfigError('Invalid logging level ' + s + ' set in '+ conf + ' ' + item)
+ if c == None:
+ return None
+ #
+ # /etc/rsyslog.conf is generated somewhere and copied over the original (exists in /opt/vyatta/etc/rsyslog.conf)
+ # it interferes with the global logging, to make sure we are using a single base, template is enforced here
+ #
+ if not os.path.islink('/etc/rsyslog.conf'):
+ os.remove('/etc/rsyslog.conf')
+ os.symlink(
+ '/usr/share/vyos/templates/rsyslog/rsyslog.conf', '/etc/rsyslog.conf')
+
+ # /var/log/vyos-rsyslog were the old files, we may want to clean those up, but currently there
+ # is a chance that someone still needs it, so I don't automatically remove
+ # them
+
+ if c == None:
+ return None
+
+ fac = [
+ '*', 'auth', 'authpriv', 'cron', 'daemon', 'kern', 'lpr', 'mail', 'mark', 'news', 'protocols', 'security',
+ 'syslog', 'user', 'uucp', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7']
+ lvl = ['emerg', 'alert', 'crit', 'err',
+ 'warning', 'notice', 'info', 'debug', '*']
+
+ for conf in c:
+ if c[conf]:
+ for item in c[conf]:
+ for s in c[conf][item]['selectors'].split(";"):
+ f = re.sub("\..*$", "", s)
+ if f not in fac:
+ print (c[conf])
+ raise ConfigError(
+ 'Invalid facility ' + s + ' set in ' + conf + ' ' + item)
+ l = re.sub("^.+\.", "", s)
+ if l not in lvl:
+ raise ConfigError(
+ 'Invalid logging level ' + s + ' set in ' + conf + ' ' + item)
+
def apply(c):
- if not os.path.exists('/var/run/rsyslogd.pid'):
- os.system("sudo systemctl start rsyslog >/dev/null")
- else:
- os.system("sudo systemctl restart rsyslog >/dev/null")
+ if not c and os.path.exists('/var/run/rsyslogd.pid'):
+ os.system("sudo systemctl stop syslog.socket")
+ os.system("sudo systemctl stop rsyslog")
+ else:
+ if not os.path.exists('/var/run/rsyslogd.pid'):
+ os.system("sudo systemctl start rsyslog >/dev/null")
+ else:
+ os.system("sudo systemctl restart rsyslog >/dev/null")
if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- sys.exit(1)
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)