# -*- coding: utf-8 -*-
'''
The networking module for NI Linux Real-Time distro

'''

# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import logging
import time
import os
import re

# Import salt libs
import salt.exceptions
import salt.utils.files
import salt.utils.validate.net

# Import 3rd-party libs
# pylint: disable=import-error,redefined-builtin,no-name-in-module
from salt.ext.six.moves import map, range, configparser
from salt.ext import six
# pylint: enable=import-error,redefined-builtin,no-name-in-module

try:
    import pyconnman
except ImportError:
    pyconnman = None

try:
    import dbus
except ImportError:
    dbus = None

try:
    import pyiface
except ImportError:
    pyiface = None

try:
    from requests.structures import CaseInsensitiveDict
except ImportError:
    CaseInsensitiveDict = None

log = logging.getLogger(__name__)

# Define the module's virtual name
__virtualname__ = 'ip'

SERVICE_PATH = '/net/connman/service/'
INTERFACES_CONFIG = '/var/lib/connman/interfaces.config'
NIRTCFG_PATH = '/usr/local/natinst/bin/nirtcfg'
INI_FILE = '/etc/natinst/share/ni-rt.ini'
_CONFIG_TRUE = ['yes', 'on', 'true', '1', True]
IFF_LOOPBACK = 0x8
IFF_RUNNING = 0x40
NIRTCFG_ETHERCAT = 'EtherCAT'


def _assume_condition(condition, err):
    '''
    Raise an exception if the condition is false
    '''
    if not condition:
        raise RuntimeError(err)


def __virtual__():
    '''
    Confine this module to NI Linux Real-Time based distros
    '''
    try:
        msg = 'The nilrt_ip module could not be loaded: unsupported OS family'
        _assume_condition(__grains__['os_family'] == 'NILinuxRT', msg)
        _assume_condition(CaseInsensitiveDict, 'The python package request is not installed')
        _assume_condition(pyiface, 'The python pyiface package is not installed')
        if __grains__['lsb_distrib_id'] != 'nilrt':
            _assume_condition(pyconnman, 'The python package pyconnman is not installed')
            _assume_condition(dbus, 'The python DBus package is not installed')
            _assume_condition(_get_state() != 'offline', 'Connman is not running')
    except RuntimeError as exc:
        return False, str(exc)
    return __virtualname__


def _get_state():
    '''
    Returns the state of connman
    '''
    try:
        return pyconnman.ConnManager().get_property('State')
    except KeyError:
        return 'offline'
    except dbus.DBusException as exc:
        raise salt.exceptions.CommandExecutionError('Connman daemon error: {0}'.format(exc))


def _get_technologies():
    '''
    Returns the technologies of connman
    '''
    tech = ''
    technologies = pyconnman.ConnManager().get_technologies()
    for path, params in technologies:
        tech += '{0}\n\tName = {1}\n\tType = {2}\n\tPowered = {3}\n\tConnected = {4}\n'.format(
            path, params['Name'], params['Type'], params['Powered'] == 1, params['Connected'] == 1)
    return tech


def _get_services():
    '''
    Returns a list with all connman services
    '''
    serv = []
    services = pyconnman.ConnManager().get_services()
    for path, _ in services:
        serv.append(six.text_type(path[len(SERVICE_PATH):]))
    return serv


def _connected(service):
    '''
    Verify if a connman service is connected
    '''
    state = pyconnman.ConnService(os.path.join(SERVICE_PATH, service)).get_property('State')
    return state == 'online' or state == 'ready'


def _space_delimited_list(value):
    '''
    validate that a value contains one or more space-delimited values
    '''
    if isinstance(value, str):
        items = value.split(' ')
        valid = items and all(items)
    else:
        valid = hasattr(value, '__iter__') and (value != [])

    if valid:
        return (True, 'space-delimited string')
    else:
        return (False, '{0} is not a valid list.\n'.format(value))


def _validate_ipv4(value):
    '''
    validate ipv4 values
    '''
    if len(value) == 3:
        if not salt.utils.validate.net.ipv4_addr(value[0].strip()):
            return False, 'Invalid ip address: {0} for ipv4 option'.format(value[0])
        if not salt.utils.validate.net.netmask(value[1].strip()):
            return False, 'Invalid netmask: {0} for ipv4 option'.format(value[1])
        if not salt.utils.validate.net.ipv4_addr(value[2].strip()):
            return False, 'Invalid gateway: {0} for ipv4 option'.format(value[2])
    else:
        return False, 'Invalid value: {0} for ipv4 option'.format(value)
    return True, ''


def _interface_to_service(iface):
    '''
    returns the coresponding service to given interface if exists, otherwise return None
    '''
    for _service in _get_services():
        service_info = pyconnman.ConnService(os.path.join(SERVICE_PATH, _service))
        if service_info.get_property('Ethernet')['Interface'] == iface:
            return _service
    return None


def _get_service_info(service):
    '''
    return details about given connman service
    '''
    service_info = pyconnman.ConnService(os.path.join(SERVICE_PATH, service))
    data = {
        'label': service,
        'wireless': service_info.get_property('Type') == 'wifi',
        'connectionid': six.text_type(service_info.get_property('Ethernet')['Interface']),
        'hwaddr': six.text_type(service_info.get_property('Ethernet')['Address'])
    }

    state = service_info.get_property('State')
    if state == 'ready' or state == 'online':
        data['up'] = True
        data['ipv4'] = {
            'gateway': '0.0.0.0'
        }
        ipv4 = 'IPv4'
        if service_info.get_property('IPv4')['Method'] == 'manual':
            ipv4 += '.Configuration'
        ipv4_info = service_info.get_property(ipv4)
        for info in ['Method', 'Address', 'Netmask', 'Gateway']:
            value = ipv4_info.get(info)
            if value is None:
                log.warning('Unable to get IPv4 %s for service %s\n', info, service)
                continue
            if info == 'Method':
                info = 'requestmode'
                if value == 'dhcp':
                    value = 'dhcp_linklocal'
                elif value in ('manual', 'fixed'):
                    value = 'static'
            data['ipv4'][info.lower()] = six.text_type(value)

        ipv6_info = service_info.get_property('IPv6')
        for info in ['Address', 'Prefix', 'Gateway']:
            value = ipv6_info.get(info)
            if value is None:
                log.warning('Unable to get IPv6 %s for service %s\n', info, service)
                continue
            data['ipv6'][info.lower()] = [six.text_type(value)]

        nameservers = []
        for nameserver_prop in service_info.get_property('Nameservers'):
            nameservers.append(six.text_type(nameserver_prop))
        data['ipv4']['dns'] = nameservers
    else:
        data['up'] = False

    if 'ipv4' in data:
        data['ipv4']['supportedrequestmodes'] = [
            'static',
            'dhcp_linklocal'
        ]
    return data


def _get_dns_info():
    '''
    return dns list
    '''
    dns_list = []
    try:
        with salt.utils.files.fopen('/etc/resolv.conf', 'r+') as dns_info:
            lines = dns_info.readlines()
            for line in lines:
                if 'nameserver' in line:
                    dns = line.split()[1].strip()
                    if dns not in dns_list:
                        dns_list.append(dns)
    except IOError:
        log.warning('Could not get domain\n')
    return dns_list


def _remove_quotes(value):
    '''
    Remove leading and trailing double quotes if they exist.
    '''
    # nirtcfg writes values with quotes
    if len(value) > 1 and value[0] == value[-1] == '\"':
        value = value[1:-1]
    return value


def _load_config(section, options, default_value='', filename=INI_FILE):
    '''
    Get values for some options and a given section from a config file.

    :param section: Section Name
    :param options: List of options
    :param default_value: Default value if an option doesn't have a value. Default is empty string.
    :param filename: config file. Default is INI_FILE.
    :return:
    '''
    results = {}
    if not options:
        return results
    with salt.utils.files.fopen(filename, 'r') as config_file:
        config_parser = configparser.RawConfigParser(dict_type=CaseInsensitiveDict)
        config_parser.readfp(config_file)
        for option in options:
            if six.PY2:
                results[option] = _remove_quotes(config_parser.get(section, option)) \
                    if config_parser.has_option(section, option) else default_value
            else:
                results[option] = _remove_quotes(config_parser.get(section, option, fallback=default_value))

    return results


def _get_request_mode_info(interface):
    '''
    return requestmode for given interface
    '''
    settings = _load_config(interface, ['linklocalenabled', 'dhcpenabled'], -1)
    link_local_enabled = int(settings['linklocalenabled'])
    dhcp_enabled = int(settings['dhcpenabled'])

    if dhcp_enabled == 1:
        return 'dhcp_linklocal' if link_local_enabled == 1 else 'dhcp_only'
    else:
        if link_local_enabled == 1:
            return 'linklocal_only'
        if link_local_enabled == 0:
            return 'static'

    # some versions of nirtcfg don't set the dhcpenabled/linklocalenabled variables
    # when selecting "DHCP or Link Local" from MAX, so return it by default to avoid
    # having the requestmode "None" because none of the conditions above matched.
    return 'dhcp_linklocal'


def _get_adapter_mode_info(interface):
    '''
    return adaptermode for given interface
    '''
    mode = _load_config(interface, ['mode'])['mode'].lower()
    return mode if mode in ['disabled', 'ethercat'] else 'tcpip'


def _get_possible_adapter_modes(interface, blacklist):
    '''
    Return possible adapter modes for a given interface using a blacklist.

    :param interface: interface name
    :param blacklist: given blacklist
    :return: list of possible adapter modes
    '''
    adapter_modes = []
    protocols = _load_config('lvrt', ['AdditionalNetworkProtocols'])['AdditionalNetworkProtocols'].lower()
    sys_interface_path = os.readlink('/sys/class/net/{0}'.format(interface))
    with salt.utils.files.fopen('/sys/class/net/{0}/uevent'.format(interface)) as uevent_file:
        uevent_lines = uevent_file.readlines()
    uevent_devtype = ""
    for line in uevent_lines:
        if line.startswith("DEVTYPE="):
            uevent_devtype = line.split('=')[1].strip()
            break

    for adapter_mode in blacklist:
        if adapter_mode == '_':
            continue
        value = blacklist.get(adapter_mode, {})
        if value.get('additional_protocol') and adapter_mode not in protocols:
            continue

        if interface not in value['name'] \
            and not any((blacklist['_'][iface_type] == 'sys' and iface_type in sys_interface_path) or
                        (blacklist['_'][iface_type] == 'uevent' and iface_type == uevent_devtype)
                        for iface_type in value['type']):
            adapter_modes += [adapter_mode]
    return adapter_modes


def _get_static_info(interface):
    '''
    Return information about an interface from config file.

    :param interface: interface label
    '''
    data = {
        'connectionid': interface.name,
        'label': interface.name,
        'hwaddr': interface.hwaddr[:-1],
        'up': False,
        'ipv4': {
            'supportedrequestmodes': ['static', 'dhcp_linklocal'],
            'requestmode': 'static'
        },
        'wireless': False
    }
    hwaddr_section_number = ''.join(data['hwaddr'].split(':'))
    if os.path.exists(INTERFACES_CONFIG):
        information = _load_config(hwaddr_section_number, ['IPv4', 'Nameservers'], filename=INTERFACES_CONFIG)
        if information['IPv4'] != '':
            ipv4_information = information['IPv4'].split('/')
            data['ipv4']['address'] = ipv4_information[0]
            data['ipv4']['dns'] = information['Nameservers'].split(',')
            data['ipv4']['netmask'] = ipv4_information[1]
            data['ipv4']['gateway'] = ipv4_information[2]
    return data


def _get_interface_info(interface):
    '''
    return details about given interface
    '''
    blacklist = {
        'tcpip': {
            'name': [],
            'type': [],
            'additional_protocol': False
        },
        'disabled': {
            'name': ['eth0'],
            'type': ['gadget'],
            'additional_protocol': False
        },
        'ethercat': {
            'name': ['eth0'],
            'type': ['gadget', 'usb', 'wlan'],
            'additional_protocol': True
        },
        '_': {
            'usb': 'sys',
            'gadget': 'uevent',
            'wlan': 'uevent'
        }
    }
    data = {
        'label': interface.name,
        'connectionid': interface.name,
        'supported_adapter_modes': _get_possible_adapter_modes(interface.name, blacklist),
        'adapter_mode': _get_adapter_mode_info(interface.name),
        'up': False,
        'ipv4': {
            'supportedrequestmodes': ['dhcp_linklocal', 'dhcp_only', 'linklocal_only', 'static'],
            'requestmode': _get_request_mode_info(interface.name)
        },
        'hwaddr': interface.hwaddr[:-1]
    }
    needed_settings = []
    if data['ipv4']['requestmode'] == 'static':
        needed_settings += ['IP_Address', 'Subnet_Mask', 'Gateway', 'DNS_Address']
    if data['adapter_mode'] == 'ethercat':
        needed_settings += ['MasterID']
    settings = _load_config(interface.name, needed_settings)
    if interface.flags & IFF_RUNNING != 0:
        data['up'] = True
        data['ipv4']['address'] = interface.sockaddrToStr(interface.addr)
        data['ipv4']['netmask'] = interface.sockaddrToStr(interface.netmask)
        data['ipv4']['gateway'] = '0.0.0.0'
        data['ipv4']['dns'] = _get_dns_info()
    elif data['ipv4']['requestmode'] == 'static':
        data['ipv4']['address'] = settings['IP_Address']
        data['ipv4']['netmask'] = settings['Subnet_Mask']
        data['ipv4']['gateway'] = settings['Gateway']
        data['ipv4']['dns'] = [settings['DNS_Address']]

    with salt.utils.files.fopen('/proc/net/route', 'r') as route_file:
        pattern = re.compile(r'^{interface}\t[0]{{8}}\t([0-9A-Z]{{8}})'.format(interface=interface.name), re.MULTILINE)
        match = pattern.search(route_file.read())
        iface_gateway_hex = None if not match else match.group(1)
    if iface_gateway_hex is not None and len(iface_gateway_hex) == 8:
        data['ipv4']['gateway'] = '.'.join([str(int(iface_gateway_hex[i:i+2], 16)) for i in range(6, -1, -2)])
    if data['adapter_mode'] == 'ethercat':
        data['ethercat'] = {
            'masterid': settings['MasterID']
        }
    return data


def _dict_to_string(dictionary):
    '''
    converts a dictionary object into a list of strings
    '''
    ret = ''
    for key, val in sorted(dictionary.items()):
        if isinstance(val, dict):
            for line in _dict_to_string(val):
                ret += six.text_type(key) + '-' + line + '\n'
        elif isinstance(val, list):
            text = ' '.join([six.text_type(item) for item in val])
            ret += six.text_type(key) + ': ' + text + '\n'
        else:
            ret += six.text_type(key) + ': ' + six.text_type(val) + '\n'
    return ret.splitlines()


def _get_info(interface):
    '''
    Return information about an interface even if it's not associated with a service.

    :param interface: interface label
    '''
    service = _interface_to_service(interface.name)
    if service is not None:
        return _get_service_info(service)
    return _get_static_info(interface)


def get_interfaces_details():
    '''
    Get details about all the interfaces on the minion

    :return: information about all interfaces omitting loopback
    :rtype: dictionary

    CLI Example:

    .. code-block:: bash

        salt '*' ip.get_interfaces_details
    '''
    _interfaces = [interface for interface in pyiface.getIfaces() if interface.flags & IFF_LOOPBACK == 0]
    if __grains__['lsb_distrib_id'] == 'nilrt':
        return {'interfaces': list(map(_get_interface_info, _interfaces))}
    return {'interfaces': list(map(_get_info, _interfaces))}


def _change_state(interface, new_state):
    '''
    Enable or disable an interface

    Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.

    :param interface: interface label
    :param new_state: up or down
    :return: True if the service was enabled, otherwise an exception will be thrown.
    :rtype: bool
    '''
    if __grains__['lsb_distrib_id'] == 'nilrt':
        initial_mode = _get_adapter_mode_info(interface)
        _save_config(interface, 'Mode', 'TCPIP')
        if initial_mode == 'ethercat':
            __salt__['system.set_reboot_required_witnessed']()
        else:
            out = __salt__['cmd.run_all']('ip link set {0} {1}'.format(interface, new_state))
            if out['retcode'] != 0:
                msg = 'Couldn\'t {0} interface {1}. Error: {2}'.format('enable' if new_state == 'up' else 'disable',
                                                                       interface, out['stderr'])
                raise salt.exceptions.CommandExecutionError(msg)
        return True
    service = _interface_to_service(interface)
    if not service:
        raise salt.exceptions.CommandExecutionError('Invalid interface name: {0}'.format(interface))
    if not _connected(service):
        service = pyconnman.ConnService(os.path.join(SERVICE_PATH, service))
        try:
            state = service.connect() if new_state == 'up' else service.disconnect()
            return state is None
        except Exception:
            raise salt.exceptions.CommandExecutionError('Couldn\'t {0} service: {1}\n'
                                                        .format('enable' if new_state == 'up' else 'disable', service))
    return True


def up(interface, iface_type=None):  # pylint: disable=invalid-name,unused-argument
    '''
    Enable the specified interface

    Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.

    :param str interface: interface label
    :return: True if the service was enabled, otherwise an exception will be thrown.
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' ip.up interface-label
    '''
    return _change_state(interface, 'up')


def enable(interface):
    '''
    Enable the specified interface

    Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.

    :param str interface: interface label
    :return: True if the service was enabled, otherwise an exception will be thrown.
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' ip.enable interface-label
    '''
    return up(interface)


def down(interface, iface_type=None):  # pylint: disable=unused-argument
    '''
    Disable the specified interface

    Change adapter mode to Disabled. If previous adapter mode was EtherCAT, the target will need reboot.

    :param str interface: interface label
    :return: True if the service was disabled, otherwise an exception will be thrown.
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' ip.down interface-label
    '''
    return _change_state(interface, 'down')


def disable(interface):
    '''
    Disable the specified interface

    Change adapter mode to Disabled. If previous adapter mode was EtherCAT, the target will need reboot.

    :param str interface: interface label
    :return: True if the service was disabled, otherwise an exception will be thrown.
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' ip.disable interface-label
    '''
    return down(interface)


def _save_config(section, token, value):
    '''
    Helper function to persist a configuration in the ini file
    '''
    cmd = NIRTCFG_PATH
    cmd += ' --set section={0},token=\'{1}\',value=\'{2}\''.format(section, token, value)
    if __salt__['cmd.run_all'](cmd)['retcode'] != 0:
        exc_msg = 'Error: could not set {} to {} for {}\n'.format(token, value, section)
        raise salt.exceptions.CommandExecutionError(exc_msg)


def set_ethercat(interface, master_id):
    '''
    Configure specified adapter to use EtherCAT adapter mode. If successful, the target will need reboot if it doesn't
    already use EtherCAT adapter mode, otherwise will return true.

    :param interface: interface label
    :param master_id: EtherCAT Master ID
    :return: True if the settings were applied, otherwise an exception will be thrown.

    CLI Example:

    .. code-block:: bash

        salt '*' ip.set_ethercat interface-label master-id
    '''
    if __grains__['lsb_distrib_id'] == 'nilrt':
        initial_mode = _get_adapter_mode_info(interface)
        _save_config(interface, 'Mode', NIRTCFG_ETHERCAT)
        _save_config(interface, 'MasterID', master_id)
        if initial_mode != 'ethercat':
            __salt__['system.set_reboot_required_witnessed']()
        return True
    raise salt.exceptions.CommandExecutionError('EtherCAT is not supported')


def _restart(interface):
    '''
    Disable and enable an interface
    '''
    disable(interface)
    enable(interface)


def set_dhcp_linklocal_all(interface):
    '''
    Configure specified adapter to use DHCP with linklocal fallback

    Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.

    :param str interface: interface label
    :return: True if the settings were applied, otherwise an exception will be thrown.
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' ip.set_dhcp_linklocal_all interface-label
    '''
    if __grains__['lsb_distrib_id'] == 'nilrt':
        initial_mode = _get_adapter_mode_info(interface)
        _save_config(interface, 'Mode', 'TCPIP')
        _save_config(interface, 'dhcpenabled', '1')
        _save_config(interface, 'linklocalenabled', '1')
        if initial_mode == 'ethercat':
            __salt__['system.set_reboot_required_witnessed']()
        else:
            _restart(interface)
        return True
    service = _interface_to_service(interface)
    if not service:
        raise salt.exceptions.CommandExecutionError('Invalid interface name: {0}'.format(interface))
    service = pyconnman.ConnService(os.path.join(SERVICE_PATH, service))
    ipv4 = service.get_property('IPv4.Configuration')
    ipv4['Method'] = dbus.String('dhcp', variant_level=1)
    ipv4['Address'] = dbus.String('', variant_level=1)
    ipv4['Netmask'] = dbus.String('', variant_level=1)
    ipv4['Gateway'] = dbus.String('', variant_level=1)
    try:
        service.set_property('IPv4.Configuration', ipv4)
        service.set_property('Nameservers.Configuration', [''])  # reset nameservers list
    except Exception as exc:
        exc_msg = 'Couldn\'t set dhcp linklocal for service: {0}\nError: {1}\n'.format(service, exc)
        raise salt.exceptions.CommandExecutionError(exc_msg)
    return True


def set_dhcp_only_all(interface):
    '''
    Configure specified adapter to use DHCP only

    Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.

    :param str interface: interface label
    :return: True if the settings were applied, otherwise an exception will be thrown.
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' ip.dhcp_only_all interface-label
    '''
    if not __grains__['lsb_distrib_id'] == 'nilrt':
        raise salt.exceptions.CommandExecutionError('Not supported in this version')
    initial_mode = _get_adapter_mode_info(interface)
    _save_config(interface, 'Mode', 'TCPIP')
    _save_config(interface, 'dhcpenabled', '1')
    _save_config(interface, 'linklocalenabled', '0')
    if initial_mode == 'ethercat':
        __salt__['system.set_reboot_required_witnessed']()
    else:
        _restart(interface)
    return True


def set_linklocal_only_all(interface):
    '''
    Configure specified adapter to use linklocal only

    Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.

    :param str interface: interface label
    :return: True if the settings were applied, otherwise an exception will be thrown.
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' ip.linklocal_only_all interface-label
    '''
    if not __grains__['lsb_distrib_id'] == 'nilrt':
        raise salt.exceptions.CommandExecutionError('Not supported in this version')
    initial_mode = _get_adapter_mode_info(interface)
    _save_config(interface, 'Mode', 'TCPIP')
    _save_config(interface, 'dhcpenabled', '0')
    _save_config(interface, 'linklocalenabled', '1')
    if initial_mode == 'ethercat':
        __salt__['system.set_reboot_required_witnessed']()
    else:
        _restart(interface)
    return True


def _configure_static_interface(interface, **settings):
    '''
    Configure an interface that is not detected as a service by Connman (i.e. link is down)

    :param interface: interface label
    :param settings:
            - ip
            - netmask
            - gateway
            - dns
            - name
    :return: True if settings were applied successfully.
    :rtype: bool
    '''
    interface = pyiface.Interface(name=interface)
    parser = configparser.ConfigParser()
    if os.path.exists(INTERFACES_CONFIG):
        try:
            with salt.utils.files.fopen(INTERFACES_CONFIG, 'r') as config_file:
                parser.readfp(config_file)
        except configparser.MissingSectionHeaderError:
            pass
    hwaddr = interface.hwaddr[:-1]
    hwaddr_section_number = ''.join(hwaddr.split(':'))
    if not parser.has_section('interface_{0}'.format(hwaddr_section_number)):
        parser.add_section('interface_{0}'.format(hwaddr_section_number))
    ip_address = settings.get('ip', '0.0.0.0')
    netmask = settings.get('netmask', '0.0.0.0')
    gateway = settings.get('gateway', '0.0.0.0')
    dns_servers = settings.get('dns', '')
    name = settings.get('name', 'ethernet_cable_{0}'.format(hwaddr_section_number))
    parser.set('interface_{0}'.format(hwaddr_section_number), 'IPv4', '{0}/{1}/{2}'.
               format(ip_address, netmask, gateway))
    parser.set('interface_{0}'.format(hwaddr_section_number), 'Nameservers', dns_servers)
    parser.set('interface_{0}'.format(hwaddr_section_number), 'Name', name)
    parser.set('interface_{0}'.format(hwaddr_section_number), 'MAC', hwaddr)
    parser.set('interface_{0}'.format(hwaddr_section_number), 'Type', 'ethernet')
    with salt.utils.files.fopen(INTERFACES_CONFIG, 'w') as config_file:
        parser.write(config_file)
    return True


def set_static_all(interface, address, netmask, gateway, nameservers):
    '''
    Configure specified adapter to use ipv4 manual settings

    Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.

    :param str interface: interface label
    :param str address: ipv4 address
    :param str netmask: ipv4 netmask
    :param str gateway: ipv4 gateway
    :param str nameservers: list of nameservers servers separated by spaces
    :return: True if the settings were applied, otherwise an exception will be thrown.
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' ip.set_static_all interface-label address netmask gateway nameservers
    '''
    validate, msg = _validate_ipv4([address, netmask, gateway])
    if not validate:
        raise salt.exceptions.CommandExecutionError(msg)
    validate, msg = _space_delimited_list(nameservers)
    if not validate:
        raise salt.exceptions.CommandExecutionError(msg)
    if not isinstance(nameservers, list):
        nameservers = nameservers.split(' ')
    if __grains__['lsb_distrib_id'] == 'nilrt':
        initial_mode = _get_adapter_mode_info(interface)
        _save_config(interface, 'Mode', 'TCPIP')
        _save_config(interface, 'dhcpenabled', '0')
        _save_config(interface, 'linklocalenabled', '0')
        _save_config(interface, 'IP_Address', address)
        _save_config(interface, 'Subnet_Mask', netmask)
        _save_config(interface, 'Gateway', gateway)
        if nameservers:
            _save_config(interface, 'DNS_Address', nameservers[0])
        if initial_mode == 'ethercat':
            __salt__['system.set_reboot_required_witnessed']()
        else:
            _restart(interface)
        return True
    service = _interface_to_service(interface)
    if not service:
        if interface in pyiface.getIfaces():
            return _configure_static_interface(interface, **{'ip': address, 'dns': ','.join(nameservers),
                                                             'netmask': netmask, 'gateway': gateway})
        raise salt.exceptions.CommandExecutionError('Invalid interface name: {0}'.format(interface))
    service = pyconnman.ConnService(os.path.join(SERVICE_PATH, service))
    ipv4 = service.get_property('IPv4.Configuration')
    ipv4['Method'] = dbus.String('manual', variant_level=1)
    ipv4['Address'] = dbus.String('{0}'.format(address), variant_level=1)
    ipv4['Netmask'] = dbus.String('{0}'.format(netmask), variant_level=1)
    ipv4['Gateway'] = dbus.String('{0}'.format(gateway), variant_level=1)
    try:
        service.set_property('IPv4.Configuration', ipv4)
        service.set_property('Nameservers.Configuration', [dbus.String('{0}'.format(d)) for d in nameservers])
    except Exception as exc:
        exc_msg = 'Couldn\'t set manual settings for service: {0}\nError: {1}\n'.format(service, exc)
        raise salt.exceptions.CommandExecutionError(exc_msg)
    return True


def get_interface(iface):
    '''
    Returns details about given interface.

    CLI Example:

    .. code-block:: bash

        salt '*' ip.get_interface eth0
    '''
    _interfaces = get_interfaces_details()
    for _interface in _interfaces['interfaces']:
        if _interface['connectionid'] == iface:
            return _dict_to_string(_interface)
    return None


def build_interface(iface, iface_type, enabled, **settings):
    '''
    Build an interface script for a network interface.

    CLI Example:

    .. code-block:: bash

        salt '*' ip.build_interface eth0 eth <settings>
    '''
    if __grains__['lsb_distrib_id'] == 'nilrt':
        raise salt.exceptions.CommandExecutionError('Not supported in this version.')
    if iface_type != 'eth':
        raise salt.exceptions.CommandExecutionError('Interface type not supported: {0}:'.format(iface_type))

    if 'proto' not in settings or settings['proto'] == 'dhcp':  # default protocol type used is dhcp
        set_dhcp_linklocal_all(iface)
    elif settings['proto'] != 'static':
        exc_msg = 'Protocol type: {0} is not supported'.format(settings['proto'])
        raise salt.exceptions.CommandExecutionError(exc_msg)
    else:
        address = settings['ipaddr']
        netmask = settings['netmask']
        gateway = settings['gateway']
        dns = []
        for key, val in six.iteritems(settings):
            if 'dns' in key or 'domain' in key:
                dns += val
        set_static_all(iface, address, netmask, gateway, dns)

    if enabled:
        up(iface)

    return get_interface(iface)


def build_network_settings(**settings):
    '''
    Build the global network script.

    CLI Example:

    .. code-block:: bash

        salt '*' ip.build_network_settings <settings>
    '''
    if __grains__['lsb_distrib_id'] == 'nilrt':
        raise salt.exceptions.CommandExecutionError('Not supported in this version.')
    changes = []
    if 'networking' in settings:
        if settings['networking'] in _CONFIG_TRUE:
            __salt__['service.enable']('connman')
        else:
            __salt__['service.disable']('connman')

    if 'hostname' in settings:
        new_hostname = settings['hostname'].split('.', 1)[0]
        settings['hostname'] = new_hostname
        old_hostname = __salt__['network.get_hostname']
        if new_hostname != old_hostname:
            __salt__['network.mod_hostname'](new_hostname)
            changes.append('hostname={0}'.format(new_hostname))

    return changes


def get_network_settings():
    '''
    Return the contents of the global network script.

    CLI Example:

    .. code-block:: bash

        salt '*' ip.get_network_settings
    '''
    if __grains__['lsb_distrib_id'] == 'nilrt':
        raise salt.exceptions.CommandExecutionError('Not supported in this version.')
    settings = []
    networking = 'no' if _get_state() == 'offline' else 'yes'
    settings.append('networking={0}'.format(networking))
    hostname = __salt__['network.get_hostname']
    settings.append('hostname={0}'.format(hostname))
    return settings


def apply_network_settings(**settings):
    '''
    Apply global network configuration.

    CLI Example:

    .. code-block:: bash

        salt '*' ip.apply_network_settings
    '''
    if __grains__['lsb_distrib_id'] == 'nilrt':
        raise salt.exceptions.CommandExecutionError('Not supported in this version.')
    if 'require_reboot' not in settings:
        settings['require_reboot'] = False

    if 'apply_hostname' not in settings:
        settings['apply_hostname'] = False

    hostname_res = True
    if settings['apply_hostname'] in _CONFIG_TRUE:
        if 'hostname' in settings:
            hostname_res = __salt__['network.mod_hostname'](settings['hostname'])
        else:
            log.warning(
                'The network state sls is trying to apply hostname '
                'changes but no hostname is defined.'
            )
            hostname_res = False

    res = True
    if settings['require_reboot'] in _CONFIG_TRUE:
        log.warning(
            'The network state sls is requiring a reboot of the system to '
            'properly apply network configuration.'
        )
        res = True
    else:
        stop = __salt__['service.stop']('connman')
        time.sleep(2)
        res = stop and __salt__['service.start']('connman')

    return hostname_res and res
