diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/policy-route.py | 26 | ||||
-rwxr-xr-x | src/conf_mode/service_upnp.py | 155 | ||||
-rw-r--r-- | src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 16 | ||||
-rw-r--r-- | src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup | 2 | ||||
-rwxr-xr-x | src/migration-scripts/firewall/6-to-7 | 168 | ||||
-rw-r--r-- | src/systemd/miniupnpd.service | 13 |
6 files changed, 283 insertions, 97 deletions
diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index ee5197af0..7dcab4b58 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -205,6 +205,7 @@ def generate(policy): def apply_table_marks(policy): for route in ['route', 'route6']: if route in policy: + cmd_str = 'ip' if route == 'route' else 'ip -6' for name, pol_conf in policy[route].items(): if 'rule' in pol_conf: for rule_id, rule_conf in pol_conf['rule'].items(): @@ -213,20 +214,21 @@ def apply_table_marks(policy): if set_table == 'main': set_table = '254' table_mark = mark_offset - int(set_table) - cmd(f'ip rule add fwmark {table_mark} table {set_table}') + cmd(f'{cmd_str} rule add pref {set_table} fwmark {table_mark} table {set_table}') def cleanup_table_marks(): - json_rules = cmd('ip -j -N rule list') - rules = loads(json_rules) - for rule in rules: - if 'fwmark' not in rule or 'table' not in rule: - continue - fwmark = rule['fwmark'] - table = int(rule['table']) - if fwmark[:2] == '0x': - fwmark = int(fwmark, 16) - if (int(fwmark) == (mark_offset - table)): - cmd(f'ip rule del fwmark {fwmark} table {table}') + for cmd_str in ['ip', 'ip -6']: + json_rules = cmd(f'{cmd_str} -j -N rule list') + rules = loads(json_rules) + for rule in rules: + if 'fwmark' not in rule or 'table' not in rule: + continue + fwmark = rule['fwmark'] + table = int(rule['table']) + if fwmark[:2] == '0x': + fwmark = int(fwmark, 16) + if (int(fwmark) == (mark_offset - table)): + cmd(f'{cmd_str} rule del fwmark {fwmark} table {table}') def apply(policy): install_result = run(f'nft -f {nftables_conf}') diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py new file mode 100755 index 000000000..638296f45 --- /dev/null +++ b/src/conf_mode/service_upnp.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit +import uuid +import netifaces +from ipaddress import IPv4Network +from ipaddress import IPv6Network + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import dict_search +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_vrf +from vyos.util import call +from vyos.template import render +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.xml import defaults +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/upnp/miniupnp.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'upnp'] + upnpd = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + if not upnpd: + return None + + if dict_search('rule', upnpd): + default_member_values = defaults(base + ['rule']) + for rule,rule_config in upnpd['rule'].items(): + upnpd['rule'][rule] = dict_merge(default_member_values, upnpd['rule'][rule]) + + uuidgen = uuid.uuid1() + upnpd.update({'uuid': uuidgen}) + + return upnpd + +def get_all_interface_addr(prefix, filter_dev, filter_family): + list_addr = [] + interfaces = netifaces.interfaces() + + for interface in interfaces: + if filter_dev and interface in filter_dev: + continue + addrs = netifaces.ifaddresses(interface) + if netifaces.AF_INET in addrs.keys(): + if netifaces.AF_INET in filter_family: + for addr in addrs[netifaces.AF_INET]: + if prefix: + # we need to manually assemble a list of IPv4 address/prefix + prefix = '/' + \ + str(IPv4Network('0.0.0.0/' + addr['netmask']).prefixlen) + list_addr.append(addr['addr'] + prefix) + else: + list_addr.append(addr['addr']) + if netifaces.AF_INET6 in addrs.keys(): + if netifaces.AF_INET6 in filter_family: + for addr in addrs[netifaces.AF_INET6]: + if prefix: + # we need to manually assemble a list of IPv4 address/prefix + bits = bin(int(addr['netmask'].replace(':', '').split('/')[0], 16)).count('1') + prefix = '/' + str(bits) + list_addr.append(addr['addr'] + prefix) + else: + list_addr.append(addr['addr']) + + return list_addr + +def verify(upnpd): + if not upnpd: + return None + + if 'wan_interface' not in upnpd: + raise ConfigError('To enable UPNP, you must have the "wan-interface" option!') + + if dict_search('rules', upnpd): + for rule,rule_config in upnpd['rule'].items(): + for option in ['external_port_range', 'internal_port_range', 'ip', 'action']: + if option not in rule_config: + raise ConfigError(f'A UPNP rule must have an "{option}" option!') + + if dict_search('stun', upnpd): + for option in ['host', 'port']: + if option not in upnpd['stun']: + raise ConfigError(f'A UPNP stun support must have an "{option}" option!') + + # Check the validity of the IP address + listen_dev = [] + system_addrs_cidr = get_all_interface_addr(True, [], [netifaces.AF_INET, netifaces.AF_INET6]) + system_addrs = get_all_interface_addr(False, [], [netifaces.AF_INET, netifaces.AF_INET6]) + for listen_if_or_addr in upnpd['listen']: + if listen_if_or_addr not in netifaces.interfaces(): + listen_dev.append(listen_if_or_addr) + if (listen_if_or_addr not in system_addrs) and (listen_if_or_addr not in system_addrs_cidr) and (listen_if_or_addr not in netifaces.interfaces()): + if is_ipv4(listen_if_or_addr) and IPv4Network(listen_if_or_addr).is_multicast: + raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') + if is_ipv6(listen_if_or_addr) and IPv6Network(listen_if_or_addr).is_multicast: + raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') + + system_listening_dev_addrs_cidr = get_all_interface_addr(True, listen_dev, [netifaces.AF_INET6]) + system_listening_dev_addrs = get_all_interface_addr(False, listen_dev, [netifaces.AF_INET6]) + for listen_if_or_addr in upnpd['listen']: + if listen_if_or_addr not in netifaces.interfaces() and (listen_if_or_addr not in system_listening_dev_addrs_cidr) and (listen_if_or_addr not in system_listening_dev_addrs) and is_ipv6(listen_if_or_addr) and (not IPv6Network(listen_if_or_addr).is_multicast): + raise ConfigError(f'{listen_if_or_addr} must listen on the interface of the network card') + +def generate(upnpd): + if not upnpd: + return None + + if os.path.isfile(config_file): + os.unlink(config_file) + + render(config_file, 'firewall/upnpd.conf.tmpl', upnpd) + +def apply(upnpd): + if not upnpd: + # Stop the UPNP service + call('systemctl stop miniupnpd.service') + else: + # Start the UPNP service + call('systemctl restart miniupnpd.service') + +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/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper index 74a7e83bf..9d5505758 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -4,7 +4,7 @@ IF_METRIC=${IF_METRIC:-210} # Check if interface is inside a VRF -VRF_OPTION=$(/usr/sbin/ip -j -d link show ${interface} | awk '{if(match($0, /.*"master":"(\w+)".*"info_slave_kind":"vrf"/, IFACE_DETAILS)) printf("vrf %s", IFACE_DETAILS[1])}') +VRF_OPTION=$(ip -j -d link show ${interface} | awk '{if(match($0, /.*"master":"(\w+)".*"info_slave_kind":"vrf"/, IFACE_DETAILS)) printf("vrf %s", IFACE_DETAILS[1])}') # get status of FRR function frr_alive () { @@ -66,9 +66,9 @@ function iptovtysh () { # delete the same route from kernel before adding new one function delroute () { logmsg info "Checking if the route presented in kernel: $@ $VRF_OPTION" - if /usr/sbin/ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then - logmsg info "Deleting IP route: \"/usr/sbin/ip route del $@ $VRF_OPTION\"" - /usr/sbin/ip route del $@ $VRF_OPTION + if ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then + logmsg info "Deleting IP route: \"ip route del $@ $VRF_OPTION\"" + ip route del $@ $VRF_OPTION fi } @@ -76,8 +76,8 @@ function delroute () { function ip () { # pass comand to system `ip` if this is not related to routes change if [ "$2" != "route" ] ; then - logmsg info "Passing command to /usr/sbin/ip: \"$@\"" - /usr/sbin/ip $@ + logmsg info "Passing command to iproute2: \"$@\"" + ip $@ else # if we want to work with routes, try to use FRR first if frr_alive ; then @@ -87,8 +87,8 @@ function ip () { vtysh -c "conf t" -c "$VTYSH_CMD" else # add ip route to kernel - logmsg info "Modifying routes in kernel: \"/usr/sbin/ip $@\"" - /usr/sbin/ip $@ $VRF_OPTION + logmsg info "Modifying routes in kernel: \"ip $@\"" + ip $@ $VRF_OPTION fi fi } diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup index ad6a1d5eb..a6989441b 100644 --- a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup +++ b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup @@ -1,7 +1,7 @@ ## ## VyOS cleanup ## -# NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via /usr/sbin/ip or vtysh, according to the system state +# NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via ip or vtysh, according to the system state hostsd_client="/usr/bin/vyos-hostsd-client" hostsd_changes= # check vyos-hostsd status diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7 index cc3a9b559..efc901530 100755 --- a/src/migration-scripts/firewall/6-to-7 +++ b/src/migration-scripts/firewall/6-to-7 @@ -100,87 +100,103 @@ icmpv6_translations = { if config.exists(base + ['name']): for name in config.list_nodes(base + ['name']): - if config.exists(base + ['name', name, 'rule']): - for rule in config.list_nodes(base + ['name', name, 'rule']): - rule_time = base + ['name', name, 'rule', rule, 'time'] - rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags'] - rule_icmp = base + ['name', name, 'rule', rule, 'icmp'] - - if config.exists(rule_time + ['monthdays']): - config.delete(rule_time + ['monthdays']) - - if config.exists(rule_time + ['utc']): - config.delete(rule_time + ['utc']) - - if config.exists(rule_tcp_flags): - tmp = config.return_value(rule_tcp_flags) - config.delete(rule_tcp_flags) - for flag in tmp.split(","): - if flag[0] == '!': - config.set(rule_tcp_flags + ['not', flag[1:].lower()]) - else: - config.set(rule_tcp_flags + [flag.lower()]) - - if config.exists(rule_icmp + ['type-name']): - tmp = config.return_value(rule_icmp + ['type-name']) - if tmp in icmp_remove: + if not config.exists(base + ['name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['name', name, 'rule']): + rule_time = base + ['name', name, 'rule', rule, 'time'] + rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags'] + rule_icmp = base + ['name', name, 'rule', rule, 'icmp'] + + if config.exists(rule_time + ['monthdays']): + config.delete(rule_time + ['monthdays']) + + if config.exists(rule_time + ['utc']): + config.delete(rule_time + ['utc']) + + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) + else: + config.set(rule_tcp_flags + [flag.lower()]) + + if config.exists(rule_icmp + ['type-name']): + tmp = config.return_value(rule_icmp + ['type-name']) + if tmp in icmp_remove: + config.delete(rule_icmp + ['type-name']) + elif tmp in icmp_translations: + translate = icmp_translations[tmp] + if isinstance(translate, str): + config.set(rule_icmp + ['type-name'], value=translate) + elif isinstance(translate, list): config.delete(rule_icmp + ['type-name']) - elif tmp in icmp_translations: - translate = icmp_translations[tmp] - if isinstance(translate, str): - config.set(rule_icmp + ['type-name'], value=translate) - elif isinstance(translate, list): - config.delete(rule_icmp + ['type-name']) - config.set(rule_icmp + ['type'], value=translate[0]) - config.set(rule_icmp + ['code'], value=translate[1]) + config.set(rule_icmp + ['type'], value=translate[0]) + config.set(rule_icmp + ['code'], value=translate[1]) + + for src_dst in ['destination', 'source']: + pg_base = base + ['name', name, 'rule', rule, src_dst, 'group', 'port-group'] + proto_base = base + ['name', name, 'rule', rule, 'protocol'] + if config.exists(pg_base) and not config.exists(proto_base): + config.set(proto_base, value='tcp_udp') if config.exists(base + ['ipv6-name']): for name in config.list_nodes(base + ['ipv6-name']): - if config.exists(base + ['ipv6-name', name, 'rule']): - for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): - rule_time = base + ['ipv6-name', name, 'rule', rule, 'time'] - rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags'] - rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6'] - - if config.exists(rule_time + ['monthdays']): - config.delete(rule_time + ['monthdays']) - - if config.exists(rule_time + ['utc']): - config.delete(rule_time + ['utc']) - - if config.exists(rule_tcp_flags): - tmp = config.return_value(rule_tcp_flags) - config.delete(rule_tcp_flags) - for flag in tmp.split(","): - if flag[0] == '!': - config.set(rule_tcp_flags + ['not', flag[1:].lower()]) - else: - config.set(rule_tcp_flags + [flag.lower()]) - - if config.exists(base + ['ipv6-name', name, 'rule', rule, 'protocol']): - tmp = config.return_value(base + ['ipv6-name', name, 'rule', rule, 'protocol']) - if tmp == 'icmpv6': - config.set(base + ['ipv6-name', name, 'rule', rule, 'protocol'], value='ipv6-icmp') - - if config.exists(rule_icmp + ['type']): - tmp = config.return_value(rule_icmp + ['type']) - type_code_match = re.match(r'^(\d+)/(\d+)$', tmp) - - if type_code_match: - config.set(rule_icmp + ['type'], value=type_code_match[1]) - config.set(rule_icmp + ['code'], value=type_code_match[2]) - elif tmp in icmpv6_remove: - config.delete(rule_icmp + ['type']) - elif tmp in icmpv6_translations: - translate = icmpv6_translations[tmp] - if isinstance(translate, str): - config.delete(rule_icmp + ['type']) - config.set(rule_icmp + ['type-name'], value=translate) - elif isinstance(translate, list): - config.set(rule_icmp + ['type'], value=translate[0]) - config.set(rule_icmp + ['code'], value=translate[1]) + if not config.exists(base + ['ipv6-name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): + rule_time = base + ['ipv6-name', name, 'rule', rule, 'time'] + rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags'] + rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6'] + + if config.exists(rule_time + ['monthdays']): + config.delete(rule_time + ['monthdays']) + + if config.exists(rule_time + ['utc']): + config.delete(rule_time + ['utc']) + + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) else: - config.rename(rule_icmp + ['type'], 'type-name') + config.set(rule_tcp_flags + [flag.lower()]) + + if config.exists(base + ['ipv6-name', name, 'rule', rule, 'protocol']): + tmp = config.return_value(base + ['ipv6-name', name, 'rule', rule, 'protocol']) + if tmp == 'icmpv6': + config.set(base + ['ipv6-name', name, 'rule', rule, 'protocol'], value='ipv6-icmp') + + if config.exists(rule_icmp + ['type']): + tmp = config.return_value(rule_icmp + ['type']) + type_code_match = re.match(r'^(\d+)/(\d+)$', tmp) + + if type_code_match: + config.set(rule_icmp + ['type'], value=type_code_match[1]) + config.set(rule_icmp + ['code'], value=type_code_match[2]) + elif tmp in icmpv6_remove: + config.delete(rule_icmp + ['type']) + elif tmp in icmpv6_translations: + translate = icmpv6_translations[tmp] + if isinstance(translate, str): + config.delete(rule_icmp + ['type']) + config.set(rule_icmp + ['type-name'], value=translate) + elif isinstance(translate, list): + config.set(rule_icmp + ['type'], value=translate[0]) + config.set(rule_icmp + ['code'], value=translate[1]) + else: + config.rename(rule_icmp + ['type'], 'type-name') + + for src_dst in ['destination', 'source']: + pg_base = base + ['ipv6-name', name, 'rule', rule, src_dst, 'group', 'port-group'] + proto_base = base + ['ipv6-name', name, 'rule', rule, 'protocol'] + if config.exists(pg_base) and not config.exists(proto_base): + config.set(proto_base, value='tcp_udp') try: with open(file_name, 'w') as f: diff --git a/src/systemd/miniupnpd.service b/src/systemd/miniupnpd.service new file mode 100644 index 000000000..51cb2eed8 --- /dev/null +++ b/src/systemd/miniupnpd.service @@ -0,0 +1,13 @@ +[Unit] +Description=UPnP service +ConditionPathExists=/run/upnp/miniupnp.conf +After=vyos-router.service +StartLimitIntervalSec=0 + +[Service] +WorkingDirectory=/run/upnp +Type=simple +ExecStart=/usr/sbin/miniupnpd -d -f /run/upnp/miniupnp.conf +PrivateTmp=yes +PIDFile=/run/miniupnpd.pid +Restart=on-failure |