diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/vyos/configdict.py | 41 | ||||
| -rw-r--r-- | python/vyos/configverify.py | 6 | ||||
| -rw-r--r-- | python/vyos/firewall.py | 12 | ||||
| -rw-r--r-- | python/vyos/ifconfig/ethernet.py | 19 | ||||
| -rw-r--r-- | python/vyos/ifconfig/wireguard.py | 103 | ||||
| -rw-r--r-- | python/vyos/nat.py | 188 | ||||
| -rw-r--r-- | python/vyos/template.py | 30 | ||||
| -rw-r--r-- | python/vyos/version.py | 39 | 
8 files changed, 356 insertions, 82 deletions
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 912bc94f2..53decfbf5 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -643,7 +643,9 @@ def get_accel_dict(config, base, chap_secrets):      from vyos.util import get_half_cpus      from vyos.template import is_ipv4 -    dict = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) +    dict = config.get_config_dict(base, key_mangling=('-', '_'), +                                  get_first_key=True, +                                  no_tag_node_value_mangle=True)      # We have gathered the dict representation of the CLI, but there are default      # options which we need to update into the dictionary retrived. @@ -663,6 +665,18 @@ def get_accel_dict(config, base, chap_secrets):      # added to individual local users instead - so we can simply delete them      if dict_search('client_ipv6_pool.prefix.mask', default_values):          del default_values['client_ipv6_pool']['prefix']['mask'] +        # delete empty dicts +        if len (default_values['client_ipv6_pool']['prefix']) == 0: +            del default_values['client_ipv6_pool']['prefix'] +        if len (default_values['client_ipv6_pool']) == 0: +            del default_values['client_ipv6_pool'] + +    # T2665: IPoE only - it has an interface tag node +    # added to individual local users instead - so we can simply delete them +    if dict_search('authentication.interface', default_values): +        del default_values['authentication']['interface'] +    if dict_search('interface', default_values): +        del default_values['interface']      dict = dict_merge(default_values, dict) @@ -684,11 +698,9 @@ def get_accel_dict(config, base, chap_secrets):          dict.update({'name_server_ipv4' : ns_v4, 'name_server_ipv6' : ns_v6})          del dict['name_server'] -    # Add individual RADIUS server default values +    # T2665: Add individual RADIUS server default values      if dict_search('authentication.radius.server', dict): -        # T2665          default_values = defaults(base + ['authentication', 'radius', 'server']) -          for server in dict_search('authentication.radius.server', dict):              dict['authentication']['radius']['server'][server] = dict_merge(                  default_values, dict['authentication']['radius']['server'][server]) @@ -698,22 +710,31 @@ def get_accel_dict(config, base, chap_secrets):              if 'disable_accounting' in dict['authentication']['radius']['server'][server]:                  dict['authentication']['radius']['server'][server]['acct_port'] = '0' -    # Add individual local-user default values +    # T2665: Add individual local-user default values      if dict_search('authentication.local_users.username', dict): -        # T2665          default_values = defaults(base + ['authentication', 'local-users', 'username']) -          for username in dict_search('authentication.local_users.username', dict):              dict['authentication']['local_users']['username'][username] = dict_merge(                  default_values, dict['authentication']['local_users']['username'][username]) -    # Add individual IPv6 client-pool default mask if required +    # T2665: Add individual IPv6 client-pool default mask if required      if dict_search('client_ipv6_pool.prefix', dict): -        # T2665          default_values = defaults(base + ['client-ipv6-pool', 'prefix']) -          for prefix in dict_search('client_ipv6_pool.prefix', dict):              dict['client_ipv6_pool']['prefix'][prefix] = dict_merge(                  default_values, dict['client_ipv6_pool']['prefix'][prefix]) +    # T2665: IPoE only - add individual local-user default values +    if dict_search('authentication.interface', dict): +        default_values = defaults(base + ['authentication', 'interface']) +        for interface in dict_search('authentication.interface', dict): +            dict['authentication']['interface'][interface] = dict_merge( +                default_values, dict['authentication']['interface'][interface]) + +    if dict_search('interface', dict): +        default_values = defaults(base + ['interface']) +        for interface in dict_search('interface', dict): +            dict['interface'][interface] = dict_merge(default_values, +                                                      dict['interface'][interface]) +      return dict diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 447ec795c..afa0c5b33 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -381,14 +381,14 @@ def verify_vlan_config(config):              verify_mtu_parent(c_vlan, config)              verify_mtu_parent(c_vlan, s_vlan) -def verify_accel_ppp_base_service(config): +def verify_accel_ppp_base_service(config, local_users=True):      """      Common helper function which must be used by all Accel-PPP services based      on get_config_dict()      """      # vertify auth settings -    if dict_search('authentication.mode', config) == 'local': -        if not dict_search('authentication.local_users', config): +    if local_users and dict_search('authentication.mode', config) == 'local': +        if dict_search(f'authentication.local_users', config) == None:              raise ConfigError('Authentication mode local requires local users to be configured!')          for user in dict_search('authentication.local_users.username', config): diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index b56caef71..4075e55b0 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -248,6 +248,14 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):                  value = rule_conf['hop_limit'][op]                  output.append(f'ip6 hoplimit {operator} {value}') +    if 'inbound_interface' in rule_conf: +        iiface = rule_conf['inbound_interface'] +        output.append(f'iifname {iiface}') + +    if 'outbound_interface' in rule_conf: +        oiface = rule_conf['outbound_interface'] +        output.append(f'oifname {oiface}') +      if 'ttl' in rule_conf:          operators = {'eq': '==', 'gt': '>', 'lt': '<'}          for op, operator in operators.items(): @@ -326,6 +334,10 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):      if 'action' in rule_conf:          output.append(nft_action(rule_conf['action'])) +        if 'jump' in rule_conf['action']: +            target = rule_conf['jump_target'] +            output.append(f'NAME{def_suffix}_{target}') +      else:          output.append('return') diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 32e667038..519cfc58c 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -70,17 +70,6 @@ class EthernetIf(Interface):          },      }} -    _sysfs_set = {**Interface._sysfs_set, **{ -        'rps': { -            'convert': lambda cpus: cpus if cpus else '0', -            'location': '/sys/class/net/{ifname}/queues/rx-0/rps_cpus', -        }, -        'rfs': { -            'convert': lambda num: num if num else '0', -            'location': '/proc/sys/net/core/rps_sock_flow_entries', -        }, -    }} -      def __init__(self, ifname, **kargs):          super().__init__(ifname, **kargs)          self.ethtool = Ethtool(ifname) @@ -251,6 +240,7 @@ class EthernetIf(Interface):              raise ValueError('Value out of range')          rps_cpus = '0' +        queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*'))          if state:              # Enable RPS on all available CPUs except CPU0 which we will not              # utilize so the system has one spare core when it's under high @@ -260,18 +250,19 @@ class EthernetIf(Interface):              # Linux will clip that internally!              rps_cpus = 'ffffffff,ffffffff,ffffffff,fffffffe' +        for i in range(0, queues): +            self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', rps_cpus) +          # send bitmask representation as hex string without leading '0x' -        return self.set_interface('rps', rps_cpus) +        return True      def set_rfs(self, state):          rfs_flow = 0 -        global_rfs_flow = 0          queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*'))          if state:              global_rfs_flow = 32768              rfs_flow = int(global_rfs_flow/queues) -        self.set_interface('rfs', global_rfs_flow)          for i in range(0, queues):              self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_flow_cnt', rfs_flow) diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index 28b5e2991..fe5e9c519 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>  #  # This library is free software; you can redistribute it and/or  # modify it under the terms of the GNU Lesser General Public @@ -17,11 +17,11 @@ import os  import time  from datetime import timedelta +from tempfile import NamedTemporaryFile  from hurry.filesize import size  from hurry.filesize import alternative -from vyos.config import Config  from vyos.ifconfig import Interface  from vyos.ifconfig import Operational  from vyos.template import is_ipv6 @@ -71,10 +71,11 @@ class WireGuardOperational(Operational):          return output      def show_interface(self): -        wgdump = self._dump().get(self.config['ifname'], None) - +        from vyos.config import Config          c = Config() +        wgdump = self._dump().get(self.config['ifname'], None) +          c.set_level(["interfaces", "wireguard", self.config['ifname']])          description = c.return_effective_value(["description"])          ips = c.return_effective_values(["address"]) @@ -167,64 +168,66 @@ class WireGuardIf(Interface):          # remove no longer associated peers first          if 'peer_remove' in config: -            for tmp in config['peer_remove']: -                peer = config['peer_remove'][tmp] -                peer['ifname'] = config['ifname'] +            for peer, public_key in config['peer_remove'].items(): +                self._cmd(f'wg set {self.ifname} peer {public_key} remove') -                cmd = 'wg set {ifname} peer {public_key} remove' -                self._cmd(cmd.format(**peer)) - -        config['private_key_file'] = '/tmp/tmp.wireguard.key' -        with open(config['private_key_file'], 'w') as f: -            f.write(config['private_key']) +        tmp_file = NamedTemporaryFile('w') +        tmp_file.write(config['private_key']) +        tmp_file.flush()          # Wireguard base command is identical for every peer -        base_cmd  = 'wg set {ifname} private-key {private_key_file}' +        base_cmd  = 'wg set {ifname}'          if 'port' in config:              base_cmd += ' listen-port {port}'          if 'fwmark' in config:              base_cmd += ' fwmark {fwmark}' +        base_cmd += f' private-key {tmp_file.name}'          base_cmd = base_cmd.format(**config) -        for tmp in config['peer']: -            peer = config['peer'][tmp] - -            # start of with a fresh 'wg' command -            cmd = base_cmd + ' peer {public_key}' - -            # If no PSK is given remove it by using /dev/null - passing keys via -            # the shell (usually bash) is considered insecure, thus we use a file -            no_psk_file = '/dev/null' -            psk_file = no_psk_file -            if 'preshared_key' in peer: -                psk_file = '/tmp/tmp.wireguard.psk' -                with open(psk_file, 'w') as f: -                    f.write(peer['preshared_key']) -            cmd += f' preshared-key {psk_file}' - -            # Persistent keepalive is optional -            if 'persistent_keepalive'in peer: -                cmd += ' persistent-keepalive {persistent_keepalive}' - -            # Multiple allowed-ip ranges can be defined - ensure we are always -            # dealing with a list -            if isinstance(peer['allowed_ips'], str): -                peer['allowed_ips'] = [peer['allowed_ips']] -            cmd += ' allowed-ips ' + ','.join(peer['allowed_ips']) - -            # Endpoint configuration is optional -            if {'address', 'port'} <= set(peer): -                if is_ipv6(peer['address']): -                    cmd += ' endpoint [{address}]:{port}' -                else: -                    cmd += ' endpoint {address}:{port}' +        if 'peer' in config: +            for peer, peer_config in config['peer'].items(): +                # T4702: No need to configure this peer when it was explicitly +                # marked as disabled - also active sessions are terminated as +                # the public key was already removed when entering this method! +                if 'disable' in peer_config: +                    continue + +                # start of with a fresh 'wg' command +                cmd = base_cmd + ' peer {public_key}' + +                # If no PSK is given remove it by using /dev/null - passing keys via +                # the shell (usually bash) is considered insecure, thus we use a file +                no_psk_file = '/dev/null' +                psk_file = no_psk_file +                if 'preshared_key' in peer_config: +                    psk_file = '/tmp/tmp.wireguard.psk' +                    with open(psk_file, 'w') as f: +                        f.write(peer_config['preshared_key']) +                cmd += f' preshared-key {psk_file}' + +                # Persistent keepalive is optional +                if 'persistent_keepalive'in peer_config: +                    cmd += ' persistent-keepalive {persistent_keepalive}' + +                # Multiple allowed-ip ranges can be defined - ensure we are always +                # dealing with a list +                if isinstance(peer_config['allowed_ips'], str): +                    peer_config['allowed_ips'] = [peer_config['allowed_ips']] +                cmd += ' allowed-ips ' + ','.join(peer_config['allowed_ips']) + +                # Endpoint configuration is optional +                if {'address', 'port'} <= set(peer_config): +                    if is_ipv6(peer_config['address']): +                        cmd += ' endpoint [{address}]:{port}' +                    else: +                        cmd += ' endpoint {address}:{port}' -            self._cmd(cmd.format(**peer)) +                self._cmd(cmd.format(**peer_config)) -            # PSK key file is not required to be stored persistently as its backed by CLI -            if psk_file != no_psk_file and os.path.exists(psk_file): -                os.remove(psk_file) +                # PSK key file is not required to be stored persistently as its backed by CLI +                if psk_file != no_psk_file and os.path.exists(psk_file): +                    os.remove(psk_file)          # call base class          super().update(config) diff --git a/python/vyos/nat.py b/python/vyos/nat.py new file mode 100644 index 000000000..31bbdc386 --- /dev/null +++ b/python/vyos/nat.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 vyos.template import is_ip_network +from vyos.util import dict_search_args + +def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): +    output = [] +    ip_prefix = 'ip6' if ipv6 else 'ip' +    log_prefix = ('DST' if nat_type == 'destination' else 'SRC') + f'-NAT-{rule_id}' +    log_suffix = '' + +    if ipv6: +        log_prefix = log_prefix.replace("NAT-", "NAT66-") + +    ignore_type_addr = False +    translation_str = '' + +    if 'inbound_interface' in rule_conf: +        ifname = rule_conf['inbound_interface'] +        if ifname != 'any': +            output.append(f'iifname "{ifname}"') + +    if 'outbound_interface' in rule_conf: +        ifname = rule_conf['outbound_interface'] +        if ifname != 'any': +            output.append(f'oifname "{ifname}"') + +    if 'protocol' in rule_conf and rule_conf['protocol'] != 'all': +        protocol = rule_conf['protocol'] +        if protocol == 'tcp_udp': +            protocol = '{ tcp, udp }' +        output.append(f'meta l4proto {protocol}') + +    if 'exclude' in rule_conf: +        translation_str = 'return' +        log_suffix = '-EXCL' +    elif 'translation' in rule_conf: +        translation_prefix = nat_type[:1] +        translation_output = [f'{translation_prefix}nat'] +        addr = dict_search_args(rule_conf, 'translation', 'address') +        port = dict_search_args(rule_conf, 'translation', 'port') + +        if addr and is_ip_network(addr): +            if not ipv6: +                map_addr =  dict_search_args(rule_conf, nat_type, 'address') +                translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}') +                ignore_type_addr = True +            else: +                translation_output.append(f'prefix to {addr}') +        elif addr == 'masquerade': +            if port: +                addr = f'{addr} to ' +            translation_output = [addr] +            log_suffix = '-MASQ' +        else: +            translation_output.append('to') +            if addr: +                translation_output.append(addr) + +        options = [] +        addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping') +        port_mapping = dict_search_args(rule_conf, 'translation', 'options', 'port_mapping') +        if addr_mapping == 'persistent': +            options.append('persistent') +        if port_mapping and port_mapping != 'none': +            options.append(port_mapping) + +        translation_str = " ".join(translation_output) + (f':{port}' if port else '') + +        if options: +            translation_str += f' {",".join(options)}' + +    for target in ['source', 'destination']: +        prefix = target[:1] +        addr = dict_search_args(rule_conf, target, 'address') +        if addr and not (ignore_type_addr and target == nat_type): +            operator = '' +            if addr[:1] == '!': +                operator = '!=' +                addr = addr[1:] +            output.append(f'{ip_prefix} {prefix}addr {operator} {addr}') + +        addr_prefix = dict_search_args(rule_conf, target, 'prefix') +        if addr_prefix and ipv6: +            operator = '' +            if addr_prefix[:1] == '!': +                operator = '!=' +                addr_prefix = addr[1:] +            output.append(f'ip6 {prefix}addr {operator} {addr_prefix}') + +        port = dict_search_args(rule_conf, target, 'port') +        if port: +            protocol = rule_conf['protocol'] +            if protocol == 'tcp_udp': +                protocol = 'th' +            operator = '' +            if port[:1] == '!': +                operator = '!=' +                port = port[1:] +            output.append(f'{protocol} {prefix}port {operator} {{ {port} }}') + +    output.append('counter') + +    if 'log' in rule_conf: +        output.append(f'log prefix "[{log_prefix}{log_suffix}]"') + +    if translation_str: +        output.append(translation_str) + +    output.append(f'comment "{log_prefix}"') + +    return " ".join(output) + +def parse_nat_static_rule(rule_conf, rule_id, nat_type): +    output = [] +    log_prefix = ('STATIC-DST' if nat_type == 'destination' else 'STATIC-SRC') + f'-NAT-{rule_id}' +    log_suffix = '' + +    ignore_type_addr = False +    translation_str = '' + +    if 'inbound_interface' in rule_conf: +        ifname = rule_conf['inbound_interface'] +        ifprefix = 'i' if nat_type == 'destination' else 'o' +        if ifname != 'any': +            output.append(f'{ifprefix}ifname "{ifname}"') + +    if 'exclude' in rule_conf: +        translation_str = 'return' +        log_suffix = '-EXCL' +    elif 'translation' in rule_conf: +        translation_prefix = nat_type[:1] +        translation_output = [f'{translation_prefix}nat'] +        addr = dict_search_args(rule_conf, 'translation', 'address') +        map_addr =  dict_search_args(rule_conf, 'destination', 'address') + +        if nat_type == 'source': +            addr, map_addr = map_addr, addr # Swap + +        if addr and is_ip_network(addr): +            translation_output.append(f'ip prefix to ip {translation_prefix}addr map {{ {map_addr} : {addr} }}') +            ignore_type_addr = True +        elif addr: +            translation_output.append(f'to {addr}') + +        options = [] +        addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping') +        port_mapping = dict_search_args(rule_conf, 'translation', 'options', 'port_mapping') +        if addr_mapping == 'persistent': +            options.append('persistent') +        if port_mapping and port_mapping != 'none': +            options.append(port_mapping) + +        if options: +            translation_output.append(",".join(options)) + +        translation_str = " ".join(translation_output) + +    prefix = nat_type[:1] +    addr = dict_search_args(rule_conf, 'translation' if nat_type == 'source' else nat_type, 'address') +    if addr and not ignore_type_addr: +        output.append(f'ip {prefix}addr {addr}') + +    output.append('counter') + +    if translation_str: +        output.append(translation_str) + +    if 'log' in rule_conf: +        output.append(f'log prefix "[{log_prefix}{log_suffix}]"') + +    output.append(f'comment "{log_prefix}"') + +    return " ".join(output) diff --git a/python/vyos/template.py b/python/vyos/template.py index 4281fb34f..2a4135f9e 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -548,7 +548,7 @@ def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'):      return parse_rule(rule_conf, fw_name, rule_id, ip_name)  @register_filter('nft_default_rule') -def nft_default_rule(fw_conf, fw_name): +def nft_default_rule(fw_conf, fw_name, ipv6=False):      output = ['counter']      default_action = fw_conf['default_action'] @@ -557,16 +557,26 @@ def nft_default_rule(fw_conf, fw_name):          output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}]"')      output.append(nft_action(default_action)) +    if 'default_jump_target' in fw_conf: +        target = fw_conf['default_jump_target'] +        def_suffix = '6' if ipv6 else '' +        output.append(f'NAME{def_suffix}_{target}') +      output.append(f'comment "{fw_name} default-action {default_action}"')      return " ".join(output)  @register_filter('nft_state_policy') -def nft_state_policy(conf, state, ipv6=False): +def nft_state_policy(conf, state):      out = [f'ct state {state}'] -    if 'log' in conf: -        log_level = conf['log'] -        out.append(f'log level {log_level}') +    if 'log' in conf and 'enable' in conf['log']: +        log_state = state[:3].upper() +        log_action = (conf['action'] if 'action' in conf else 'accept')[:1].upper() +        out.append(f'log prefix "[STATE-POLICY-{log_state}-{log_action}]"') + +        if 'log_level' in conf: +            log_level = conf['log_level'] +            out.append(f'level {log_level}')      out.append('counter') @@ -611,6 +621,16 @@ def nft_nested_group(out_list, includes, groups, key):          add_includes(name)      return out_list +@register_filter('nat_rule') +def nat_rule(rule_conf, rule_id, nat_type, ipv6=False): +    from vyos.nat import parse_nat_rule +    return parse_nat_rule(rule_conf, rule_id, nat_type, ipv6) + +@register_filter('nat_static_rule') +def nat_static_rule(rule_conf, rule_id, nat_type): +    from vyos.nat import parse_nat_static_rule +    return parse_nat_static_rule(rule_conf, rule_id, nat_type) +  @register_filter('range_to_regex')  def range_to_regex(num_range):      from vyos.range_regex import range_to_regex diff --git a/python/vyos/version.py b/python/vyos/version.py index 871bb0f1b..fb706ad44 100644 --- a/python/vyos/version.py +++ b/python/vyos/version.py @@ -31,6 +31,7 @@ Example of the version data dict::  import os  import json +import requests  import vyos.defaults  from vyos.util import read_file @@ -105,3 +106,41 @@ def get_full_version_data(fname=version_file):      version_data['hardware_uuid'] = read_file(subsystem + '/product_uuid', 'Unknown')      return version_data + +def get_remote_version(url): +    """ +    Get remote available JSON file from remote URL +    An example of the image-version.json + +    [ +       { +          "arch":"amd64", +          "flavors":[ +           "generic" +        ], +        "image":"vyos-rolling-latest.iso", +        "latest":true, +        "lts":false, +        "release_date":"2022-09-06", +        "release_train":"sagitta", +        "url":"http://xxx/rolling/current/vyos-rolling-latest.iso", +        "version":"vyos-1.4-rolling-202209060217" +      } +    ] +    """ +    headers = {} +    try: +        remote_data = requests.get(url=url, headers=headers) +        remote_data.raise_for_status() +        if remote_data.status_code != 200: +            return False +        return remote_data.json() +    except requests.exceptions.HTTPError as errh: +        print ("HTTP Error:", errh) +    except requests.exceptions.ConnectionError as errc: +        print ("Connecting error:", errc) +    except requests.exceptions.Timeout as errt: +        print ("Timeout error:", errt) +    except requests.exceptions.RequestException as err: +        print ("Unable to get remote data", err) +    return False  | 
