import os
import json
import errno
import select
import struct
import threading
import subprocess
from fcntl import ioctl
from socket import AF_INET
from socket import AF_INET6
from socket import AF_BRIDGE
from pyroute2 import RawIPRoute
from pyroute2 import config
from pyroute2.common import map_namespace
from pyroute2.common import map_enoent
from pyroute2.netlink import nla
from pyroute2.netlink import nlmsg
from pyroute2.netlink import nlmsg_atoms
from pyroute2.netlink import NetlinkError
from pyroute2.netlink.rtnl import RTM_VALUES
from pyroute2.netlink.rtnl.iw_event import iw_event


# it's simpler to double constants here, than to change all the
# module layout; but it is a subject of the future refactoring
RTM_NEWLINK = 16
RTM_DELLINK = 17
#

_BONDING_MASTERS = '/sys/class/net/bonding_masters'
_BONDING_SLAVES = '/sys/class/net/%s/bonding/slaves'
_BRIDGE_MASTER = '/sys/class/net/%s/brport/bridge/ifindex'
_BONDING_MASTER = '/sys/class/net/%s/master/ifindex'
IFNAMSIZ = 16

TUNDEV = '/dev/net/tun'
if config.machine in ('i386', 'i686', 'x86_64'):
    TUNSETIFF = 0x400454ca
    TUNSETPERSIST = 0x400454cb
    TUNSETOWNER = 0x400454cc
    TUNSETGROUP = 0x400454ce
elif config.machine in ('ppc64', 'mips'):
    TUNSETIFF = 0x800454ca
    TUNSETPERSIST = 0x800454cb
    TUNSETOWNER = 0x800454cc
    TUNSETGROUP = 0x800454ce
else:
    TUNSETIFF = None

##
#
# tuntap flags
#
IFT_TUN = 0x0001
IFT_TAP = 0x0002
IFT_NO_PI = 0x1000
IFT_ONE_QUEUE = 0x2000
IFT_VNET_HDR = 0x4000
IFT_TUN_EXCL = 0x8000
IFT_MULTI_QUEUE = 0x0100
IFT_ATTACH_QUEUE = 0x0200
IFT_DETACH_QUEUE = 0x0400
# read-only
IFT_PERSIST = 0x0800
IFT_NOFILTER = 0x1000

##
#
# normal flags
#
IFF_UP = 0x1  # interface is up
IFF_BROADCAST = 0x2  # broadcast address valid
IFF_DEBUG = 0x4  # turn on debugging
IFF_LOOPBACK = 0x8  # is a loopback net
IFF_POINTOPOINT = 0x10  # interface is has p-p link
IFF_NOTRAILERS = 0x20  # avoid use of trailers
IFF_RUNNING = 0x40  # interface RFC2863 OPER_UP
IFF_NOARP = 0x80  # no ARP protocol
IFF_PROMISC = 0x100  # receive all packets
IFF_ALLMULTI = 0x200  # receive all multicast packets
IFF_MASTER = 0x400  # master of a load balancer
IFF_SLAVE = 0x800  # slave of a load balancer
IFF_MULTICAST = 0x1000  # Supports multicast
IFF_PORTSEL = 0x2000  # can set media type
IFF_AUTOMEDIA = 0x4000  # auto media select active
IFF_DYNAMIC = 0x8000  # dialup device with changing addresses
IFF_LOWER_UP = 0x10000  # driver signals L1 up
IFF_DORMANT = 0x20000  # driver signals dormant
IFF_ECHO = 0x40000  # echo sent packets

(IFF_NAMES, IFF_VALUES) = map_namespace('IFF', globals())

IFF_MASK = IFF_UP |\
    IFF_DEBUG |\
    IFF_NOTRAILERS |\
    IFF_NOARP |\
    IFF_PROMISC |\
    IFF_ALLMULTI

IFF_VOLATILE = IFF_LOOPBACK |\
    IFF_POINTOPOINT |\
    IFF_BROADCAST |\
    IFF_ECHO |\
    IFF_MASTER |\
    IFF_SLAVE |\
    IFF_RUNNING |\
    IFF_LOWER_UP |\
    IFF_DORMANT

states = ('UNKNOWN',
          'NOTPRESENT',
          'DOWN',
          'LOWERLAYERDOWN',
          'TESTING',
          'DORMANT',
          'UP')
state_by_name = dict(((i[1], i[0]) for i in enumerate(states)))
state_by_code = dict(enumerate(states))
stats_names = ('rx_packets',
               'tx_packets',
               'rx_bytes',
               'tx_bytes',
               'rx_errors',
               'tx_errors',
               'rx_dropped',
               'tx_dropped',
               'multicast',
               'collisions',
               'rx_length_errors',
               'rx_over_errors',
               'rx_crc_errors',
               'rx_frame_errors',
               'rx_fifo_errors',
               'rx_missed_errors',
               'tx_aborted_errors',
               'tx_carrier_errors',
               'tx_fifo_errors',
               'tx_heartbeat_errors',
               'tx_window_errors',
               'rx_compressed',
               'tx_compressed')


class ifla_bridge_id(nla):
    fields = [('value', '=8s')]

    def encode(self):
        r_prio = struct.pack('H', self['prio'])
        r_addr = struct.pack('BBBBBB',
                             *[int(i, 16) for i in
                               self['addr'].split(':')])
        self['value'] = r_prio + r_addr
        nla.encode(self)

    def decode(self):
        nla.decode(self)
        r_prio = self['value'][:2]
        r_addr = self['value'][2:]
        self.value = {'prio': struct.unpack('H', r_prio)[0],
                      'addr': ':'.join('%02x' % (i) for i in
                                       struct.unpack('BBBBBB',
                                                     r_addr))}


class protinfo_bridge(nla):
    nla_map = (('IFLA_BRPORT_UNSPEC', 'none'),
               ('IFLA_BRPORT_STATE', 'uint8'),
               ('IFLA_BRPORT_PRIORITY', 'uint16'),
               ('IFLA_BRPORT_COST', 'uint32'),
               ('IFLA_BRPORT_MODE', 'uint8'),
               ('IFLA_BRPORT_GUARD', 'uint8'),
               ('IFLA_BRPORT_PROTECT', 'uint8'),
               ('IFLA_BRPORT_FAST_LEAVE', 'uint8'),
               ('IFLA_BRPORT_LEARNING', 'uint8'),
               ('IFLA_BRPORT_UNICAST_FLOOD', 'uint8'),
               ('IFLA_BRPORT_PROXYARP', 'uint8'),
               ('IFLA_BRPORT_LEARNING_SYNC', 'uint8'),
               ('IFLA_BRPORT_PROXYARP_WIFI', 'uint8'),
               ('IFLA_BRPORT_ROOT_ID', 'br_id'),
               ('IFLA_BRPORT_BRIDGE_ID', 'br_id'),
               ('IFLA_BRPORT_DESIGNATED_PORT', 'uint16'),
               ('IFLA_BRPORT_DESIGNATED_COST', 'uint16'),
               ('IFLA_BRPORT_ID', 'uint16'),
               ('IFLA_BRPORT_NO', 'uint16'),
               ('IFLA_BRPORT_TOPOLOGY_CHANGE_ACK', 'uint8'),
               ('IFLA_BRPORT_CONFIG_PENDING', 'uint8'),
               ('IFLA_BRPORT_MESSAGE_AGE_TIMER', 'uint64'),
               ('IFLA_BRPORT_FORWARD_DELAY_TIMER', 'uint64'),
               ('IFLA_BRPORT_HOLD_TIMER', 'uint64'),
               ('IFLA_BRPORT_FLUSH', 'flag'),
               ('IFLA_BRPORT_MULTICAST_ROUTER', 'uint8'))

    class br_id(ifla_bridge_id):
        pass


class ifinfbase(object):
    '''
    Network interface message.

    C structure::

        struct ifinfomsg {
            unsigned char  ifi_family; /* AF_UNSPEC */
            unsigned short ifi_type;   /* Device type */
            int            ifi_index;  /* Interface index */
            unsigned int   ifi_flags;  /* Device flags  */
            unsigned int   ifi_change; /* change mask */
        };
    '''
    prefix = 'IFLA_'

    fields = (('family', 'B'),
              ('__align', 'B'),
              ('ifi_type', 'H'),
              ('index', 'i'),
              ('flags', 'I'),
              ('change', 'I'))

    nla_map = (('IFLA_UNSPEC', 'none'),
               ('IFLA_ADDRESS', 'l2addr'),
               ('IFLA_BROADCAST', 'l2addr'),
               ('IFLA_IFNAME', 'asciiz'),
               ('IFLA_MTU', 'uint32'),
               ('IFLA_LINK', 'uint32'),
               ('IFLA_QDISC', 'asciiz'),
               ('IFLA_STATS', 'ifstats'),
               ('IFLA_COST', 'hex'),
               ('IFLA_PRIORITY', 'hex'),
               ('IFLA_MASTER', 'uint32'),
               ('IFLA_WIRELESS', 'wireless'),
               ('IFLA_PROTINFO', 'protinfo'),
               ('IFLA_TXQLEN', 'uint32'),
               ('IFLA_MAP', 'ifmap'),
               ('IFLA_WEIGHT', 'hex'),
               ('IFLA_OPERSTATE', 'state'),
               ('IFLA_LINKMODE', 'uint8'),
               ('IFLA_LINKINFO', 'ifinfo'),
               ('IFLA_NET_NS_PID', 'uint32'),
               ('IFLA_IFALIAS', 'asciiz'),
               ('IFLA_NUM_VF', 'uint32'),
               ('IFLA_VFINFO_LIST', 'hex'),
               ('IFLA_STATS64', 'ifstats64'),
               ('IFLA_VF_PORTS', 'hex'),
               ('IFLA_PORT_SELF', 'hex'),
               ('IFLA_AF_SPEC', 'af_spec'),
               ('IFLA_GROUP', 'uint32'),
               ('IFLA_NET_NS_FD', 'netns_fd'),
               ('IFLA_EXT_MASK', 'uint32'),
               ('IFLA_PROMISCUITY', 'uint32'),
               ('IFLA_NUM_TX_QUEUES', 'uint32'),
               ('IFLA_NUM_RX_QUEUES', 'uint32'),
               ('IFLA_CARRIER', 'uint8'),
               ('IFLA_PHYS_PORT_ID', 'hex'),
               ('IFLA_CARRIER_CHANGES', 'uint32'),
               ('IFLA_PHYS_SWITCH_ID', 'hex'),
               ('IFLA_LINK_NETNSID', 'int32'),
               ('IFLA_PHYS_PORT_NAME', 'asciiz'))

    @staticmethod
    def flags2names(flags, mask=0xffffffff):
        ret = []
        for flag in IFF_VALUES:
            if (flag & mask & flags) == flag:
                ret.append(IFF_VALUES[flag])
        return ret

    @staticmethod
    def names2flags(flags):
        ret = 0
        mask = 0
        for flag in flags:
            if flag[0] == '!':
                flag = flag[1:]
            else:
                ret |= IFF_NAMES[flag]
            mask |= IFF_NAMES[flag]
        return (ret, mask)

    def encode(self):
        # convert flags
        if isinstance(self['flags'], (set, tuple, list)):
            self['flags'], self['change'] = self.names2flags(self['flags'])
        return super(ifinfbase, self).encode()

    class netns_fd(nla):
        fields = [('value', 'I')]
        netns_run_dir = '/var/run/netns'
        netns_fd = None

        def encode(self):
            #
            # There are two ways to specify netns
            #
            # 1. provide fd to an open file
            # 2. provide a file name
            #
            # In the first case, the value is passed to the kernel
            # as is. In the second case, the object opens appropriate
            # file from `self.netns_run_dir` and closes it upon
            # `__del__(self)`
            if isinstance(self.value, int):
                self['value'] = self.value
            else:
                self.netns_fd = os.open('%s/%s' % (self.netns_run_dir,
                                                   self.value), os.O_RDONLY)
                self['value'] = self.netns_fd
                self.register_clean_cb(self.close)
            nla.encode(self)

        def close(self):
            if self.netns_fd is not None:
                os.close(self.netns_fd)

    class wireless(iw_event):
        pass

    class state(nla):
        fields = (('value', 'B'), )

        def encode(self):
            self['value'] = state_by_name[self.value]
            nla.encode(self)

        def decode(self):
            nla.decode(self)
            self.value = state_by_code[self['value']]

    class ifstats(nla):
        fields = [(i, 'I') for i in stats_names]

    class ifstats64(nla):
        fields = [(i, 'Q') for i in stats_names]

    class ifmap(nla):
        fields = (('mem_start', 'Q'),
                  ('mem_end', 'Q'),
                  ('base_addr', 'Q'),
                  ('irq', 'H'),
                  ('dma', 'B'),
                  ('port', 'B'))

    def protinfo(self, *argv, **kwarg):
        proto_map = {AF_BRIDGE: protinfo_bridge}
        return proto_map.get(self['family'], self.hex)

    class ifinfo(nla):
        nla_map = (('IFLA_INFO_UNSPEC', 'none'),
                   ('IFLA_INFO_KIND', 'asciiz'),
                   ('IFLA_INFO_DATA', 'info_data'),
                   ('IFLA_INFO_XSTATS', 'hex'),
                   ('IFLA_INFO_SLAVE_KIND', 'asciiz'),
                   ('IFLA_INFO_SLAVE_DATA', 'info_slave_data'))

        def info_slave_data(self, *argv, **kwarg):
            '''
            Return IFLA_INFO_SLAVE_DATA type based on
            IFLA_INFO_SLAVE_KIND.
            '''
            kind = self.get_attr('IFLA_INFO_SLAVE_KIND')
            data_map = {'bridge': self.bridge_slave_data,
                        'bond': self.bond_slave_data}
            return data_map.get(kind, self.hex)

        class bridge_slave_data(protinfo_bridge):
            pass

        class bond_slave_data(nla):
            nla_map = (('IFLA_BOND_SLAVE_UNSPEC', 'none'),
                       ('IFLA_BOND_SLAVE_STATE', 'uint8'),
                       ('IFLA_BOND_SLAVE_MII_STATUS', 'uint8'),
                       ('IFLA_BOND_SLAVE_LINK_FAILURE_COUNT', 'uint32'),
                       ('IFLA_BOND_SLAVE_PERM_HWADDR', 'l2addr'),
                       ('IFLA_BOND_SLAVE_QUEUE_ID', 'uint16'),
                       ('IFLA_BOND_SLAVE_AD_AGGREGATOR_ID', 'uint16'))

        def info_data(self, *argv, **kwarg):
            '''
            The function returns appropriate IFLA_INFO_DATA
            type according to IFLA_INFO_KIND info. Return
            'hex' type for all unknown kind's and when the
            kind is not known.
            '''
            kind = self.get_attr('IFLA_INFO_KIND')
            data_map = {'vlan': self.vlan_data,
                        'vxlan': self.vxlan_data,
                        'macvlan': self.macvlan_data,
                        'macvtap': self.macvtap_data,
                        'gre': self.gre_data,
                        'gretap': self.gre_data,
                        'bond': self.bond_data,
                        'veth': self.veth_data,
                        'tuntap': self.tuntap_data,
                        'bridge': self.bridge_data,
                        'ipvlan': self.ipvlan_data}
            return data_map.get(kind, self.hex)

        class tuntap_data(nla):
            '''
            Fake data type
            '''
            prefix = 'IFTUN_'

            nla_map = (('IFTUN_UNSPEC', 'none'),
                       ('IFTUN_MODE', 'asciiz'),
                       ('IFTUN_UID', 'uint32'),
                       ('IFTUN_GID', 'uint32'),
                       ('IFTUN_IFR', 'flags'))

            class flags(nla):
                fields = (('no_pi', 'B'),
                          ('one_queue', 'B'),
                          ('vnet_hdr', 'B'),
                          ('tun_excl', 'B'),
                          ('multi_queue', 'B'),
                          ('persist', 'B'),
                          ('nofilter', 'B'))

        class veth_data(nla):
            nla_map = (('VETH_INFO_UNSPEC', 'none'),
                       ('VETH_INFO_PEER', 'info_peer'))

            def info_peer(self, *argv, **kwarg):
                return ifinfveth

        class vxlan_data(nla):
            prefix = 'IFLA_'
            nla_map = (('IFLA_VXLAN_UNSPEC', 'none'),
                       ('IFLA_VXLAN_ID', 'uint32'),
                       ('IFLA_VXLAN_GROUP', 'ip4addr'),
                       ('IFLA_VXLAN_LINK', 'uint32'),
                       ('IFLA_VXLAN_LOCAL', 'ip4addr'),
                       ('IFLA_VXLAN_TTL', 'uint8'),
                       ('IFLA_VXLAN_TOS', 'uint8'),
                       ('IFLA_VXLAN_LEARNING', 'uint8'),
                       ('IFLA_VXLAN_AGEING', 'uint32'),
                       ('IFLA_VXLAN_LIMIT', 'uint32'),
                       ('IFLA_VXLAN_PORT_RANGE', 'port_range'),
                       ('IFLA_VXLAN_PROXY', 'uint8'),
                       ('IFLA_VXLAN_RSC', 'uint8'),
                       ('IFLA_VXLAN_L2MISS', 'uint8'),
                       ('IFLA_VXLAN_L3MISS', 'uint8'),
                       ('IFLA_VXLAN_PORT', 'be16'),
                       ('IFLA_VXLAN_GROUP6', 'ip6addr'),
                       ('IFLA_VXLAN_LOCAL6', 'ip6addr'),
                       ('IFLA_VXLAN_UDP_CSUM', 'uint8'),
                       ('IFLA_VXLAN_UDP_ZERO_CSUM6_TX', 'uint8'),
                       ('IFLA_VXLAN_UDP_ZERO_CSUM6_RX', 'uint8'),
                       ('IFLA_VXLAN_REMCSUM_TX', 'uint8'),
                       ('IFLA_VXLAN_REMCSUM_RX', 'uint8'),
                       ('IFLA_VXLAN_GBP', 'flag'),
                       ('IFLA_VXLAN_REMCSUM_NOPARTIAL', 'flag'),
                       ('IFLA_VXLAN_COLLECT_METADATA', 'uint8'))

            class port_range(nla):
                fields = (('low', '>H'),
                          ('high', '>H'))

        class gre_data(nla):
            prefix = 'IFLA_'

            nla_map = (('IFLA_GRE_UNSPEC', 'none'),
                       ('IFLA_GRE_LINK', 'uint32'),
                       ('IFLA_GRE_IFLAGS', 'uint16'),
                       ('IFLA_GRE_OFLAGS', 'uint16'),
                       ('IFLA_GRE_IKEY', 'be32'),
                       ('IFLA_GRE_OKEY', 'be32'),
                       ('IFLA_GRE_LOCAL', 'ip4addr'),
                       ('IFLA_GRE_REMOTE', 'ip4addr'),
                       ('IFLA_GRE_TTL', 'uint8'),
                       ('IFLA_GRE_TOS', 'uint8'),
                       ('IFLA_GRE_PMTUDISC', 'uint8'),
                       ('IFLA_GRE_ENCAP_LIMIT', 'uint8'),
                       ('IFLA_GRE_FLOWINFO', 'be32'),
                       ('IFLA_GRE_FLAGS', 'uint32'),
                       ('IFLA_GRE_ENCAP_TYPE', 'uint16'),
                       ('IFLA_GRE_ENCAP_FLAGS', 'uint16'),
                       ('IFLA_GRE_ENCAP_SPORT', 'be16'),
                       ('IFLA_GRE_ENCAP_DPORT', 'be16'),
                       ('IFLA_GRE_COLLECT_METADATA', 'flag'))

        class macvx_data(nla):
            prefix = 'IFLA_'

            class mode(nlmsg_atoms.uint32):
                value_map = {0: 'none',
                             1: 'private',
                             2: 'vepa',
                             4: 'bridge',
                             8: 'passthru'}

            class flags(nlmsg_atoms.uint16):
                value_map = {0: 'none',
                             1: 'nopromisc'}

        class macvtap_data(macvx_data):
            nla_map = (('IFLA_MACVTAP_UNSPEC', 'none'),
                       ('IFLA_MACVTAP_MODE', 'mode'),
                       ('IFLA_MACVTAP_FLAGS', 'flags'))

        class macvlan_data(macvx_data):
            nla_map = (('IFLA_MACVLAN_UNSPEC', 'none'),
                       ('IFLA_MACVLAN_MODE', 'mode'),
                       ('IFLA_MACVLAN_FLAGS', 'flags'))

        class ipvlan_data(nla):
            prefix = 'IFLA_'

            nla_map = (('IFLA_IPVLAN_UNSPEC', 'none'),
                       ('IFLA_IPVLAN_MODE', 'uint16'))

            modes = {0: 'IPVLAN_MODE_L2',
                     1: 'IPVLAN_MODE_L3',
                     'IPVLAN_MODE_L2': 0,
                     'IPVLAN_MODE_L3': 1}

        class vlan_data(nla):
            nla_map = (('IFLA_VLAN_UNSPEC', 'none'),
                       ('IFLA_VLAN_ID', 'uint16'),
                       ('IFLA_VLAN_FLAGS', 'vlan_flags'),
                       ('IFLA_VLAN_EGRESS_QOS', 'hex'),
                       ('IFLA_VLAN_INGRESS_QOS', 'hex'))

            class vlan_flags(nla):
                fields = (('flags', 'I'),
                          ('mask', 'I'))

        class bridge_data(nla):
            prefix = 'IFLA_BR_'
            nla_map = (('IFLA_BR_UNSPEC', 'none'),
                       ('IFLA_BR_FORWARD_DELAY', 'uint32'),
                       ('IFLA_BR_HELLO_TIME', 'uint32'),
                       ('IFLA_BR_MAX_AGE', 'uint32'),
                       ('IFLA_BR_AGEING_TIME', 'uint32'),
                       ('IFLA_BR_STP_STATE', 'uint32'),
                       ('IFLA_BR_PRIORITY', 'uint16'),
                       ('IFLA_BR_VLAN_FILTERING', 'uint8'),
                       ('IFLA_BR_VLAN_PROTOCOL', 'be16'),
                       ('IFLA_BR_GROUP_FWD_MASK', 'uint16'),
                       ('IFLA_BR_ROOT_ID', 'br_id'),
                       ('IFLA_BR_BRIDGE_ID', 'br_id'),
                       ('IFLA_BR_ROOT_PORT', 'uint16'),
                       ('IFLA_BR_ROOT_PATH_COST', 'uint32'),
                       ('IFLA_BR_TOPOLOGY_CHANGE', 'uint8'),
                       ('IFLA_BR_TOPOLOGY_CHANGE_DETECTED', 'uint8'),
                       ('IFLA_BR_HELLO_TIMER', 'uint64'),
                       ('IFLA_BR_TCN_TIMER', 'uint64'),
                       ('IFLA_BR_TOPOLOGY_CHANGE_TIMER', 'uint64'),
                       ('IFLA_BR_GC_TIMER', 'uint64'),
                       ('IFLA_BR_GROUP_ADDR', 'l2addr'),
                       ('IFLA_BR_FDB_FLUSH', 'flag'),
                       ('IFLA_BR_MCAST_ROUTER', 'uint8'),
                       ('IFLA_BR_MCAST_SNOOPING', 'uint8'),
                       ('IFLA_BR_MCAST_QUERY_USE_IFADDR', 'uint8'),
                       ('IFLA_BR_MCAST_QUERIER', 'uint8'),
                       ('IFLA_BR_MCAST_HASH_ELASTICITY', 'uint32'),
                       ('IFLA_BR_MCAST_HASH_MAX', 'uint32'),
                       ('IFLA_BR_MCAST_LAST_MEMBER_CNT', 'uint32'),
                       ('IFLA_BR_MCAST_STARTUP_QUERY_CNT', 'uint32'),
                       ('IFLA_BR_MCAST_LAST_MEMBER_INTVL', 'uint64'),
                       ('IFLA_BR_MCAST_MEMBERSHIP_INTVL', 'uint64'),
                       ('IFLA_BR_MCAST_QUERIER_INTVL', 'uint64'),
                       ('IFLA_BR_MCAST_QUERY_INTVL', 'uint64'),
                       ('IFLA_BR_MCAST_QUERY_RESPONSE_INTVL', 'uint64'),
                       ('IFLA_BR_MCAST_STARTUP_QUERY_INTVL', 'uint64'),
                       ('IFLA_BR_NF_CALL_IPTABLES', 'uint8'),
                       ('IFLA_BR_NF_CALL_IP6TABLES', 'uint8'),
                       ('IFLA_BR_NF_CALL_ARPTABLES', 'uint8'),
                       ('IFLA_BR_VLAN_DEFAULT_PVID', 'uint16'))

            class br_id(ifla_bridge_id):
                pass

        class bond_data(nla):
            prefix = 'IFLA_BOND_'
            nla_map = (('IFLA_BOND_UNSPEC', 'none'),
                       ('IFLA_BOND_MODE', 'uint8'),
                       ('IFLA_BOND_ACTIVE_SLAVE', 'uint32'),
                       ('IFLA_BOND_MIIMON', 'uint32'),
                       ('IFLA_BOND_UPDELAY', 'uint32'),
                       ('IFLA_BOND_DOWNDELAY', 'uint32'),
                       ('IFLA_BOND_USE_CARRIER', 'uint8'),
                       ('IFLA_BOND_ARP_INTERVAL', 'uint32'),
                       ('IFLA_BOND_ARP_IP_TARGET', 'arp_ip_target'),
                       ('IFLA_BOND_ARP_VALIDATE', 'uint32'),
                       ('IFLA_BOND_ARP_ALL_TARGETS', 'uint32'),
                       ('IFLA_BOND_PRIMARY', 'uint32'),
                       ('IFLA_BOND_PRIMARY_RESELECT', 'uint8'),
                       ('IFLA_BOND_FAIL_OVER_MAC', 'uint8'),
                       ('IFLA_BOND_XMIT_HASH_POLICY', 'uint8'),
                       ('IFLA_BOND_RESEND_IGMP', 'uint32'),
                       ('IFLA_BOND_NUM_PEER_NOTIF', 'uint8'),
                       ('IFLA_BOND_ALL_SLAVES_ACTIVE', 'uint8'),
                       ('IFLA_BOND_MIN_LINKS', 'uint32'),
                       ('IFLA_BOND_LP_INTERVAL', 'uint32'),
                       ('IFLA_BOND_PACKETS_PER_SLAVE', 'uint32'),
                       ('IFLA_BOND_AD_LACP_RATE', 'uint8'),
                       ('IFLA_BOND_AD_SELECT', 'uint8'),
                       ('IFLA_BOND_AD_INFO', 'ad_info'))

            class ad_info(nla):
                nla_map = (('IFLA_BOND_AD_INFO_UNSPEC', 'none'),
                           ('IFLA_BOND_AD_INFO_AGGREGATOR', 'uint16'),
                           ('IFLA_BOND_AD_INFO_NUM_PORTS', 'uint16'),
                           ('IFLA_BOND_AD_INFO_ACTOR_KEY', 'uint16'),
                           ('IFLA_BOND_AD_INFO_PARTNER_KEY', 'uint16'),
                           ('IFLA_BOND_AD_INFO_PARTNER_MAC', 'l2addr'))

            class arp_ip_target(nla):
                fields = (('targets', '16I'), )

    def af_spec(self, *argv, **kwarg):
        specs = {0: self.af_spec_inet,
                 AF_INET: self.af_spec_inet,
                 AF_INET6: self.af_spec_inet,
                 AF_BRIDGE: self.af_spec_bridge}
        return specs.get(self['family'], self.hex)

    class af_spec_bridge(nla):
        prefix = 'IFLA_BRIDGE_'
        nla_map = (('IFLA_BRIDGE_FLAGS', 'uint16'),
                   ('IFLA_BRIDGE_MODE', 'uint16'),
                   ('IFLA_BRIDGE_VLAN_INFO', 'vlan_info'))

        class vlan_info(nla):
            fields = (('flags', 'H'),
                      ('vid', 'H'))

    class af_spec_inet(nla):
        nla_map = (('AF_UNSPEC', 'none'),
                   ('AF_UNIX', 'hex'),
                   ('AF_INET', 'inet'),
                   ('AF_AX25', 'hex'),
                   ('AF_IPX', 'hex'),
                   ('AF_APPLETALK', 'hex'),
                   ('AF_NETROM', 'hex'),
                   ('AF_BRIDGE', 'hex'),
                   ('AF_ATMPVC', 'hex'),
                   ('AF_X25', 'hex'),
                   ('AF_INET6', 'inet6'))

        class inet(nla):
            #  ./include/linux/inetdevice.h: struct ipv4_devconf
            #  ./include/uapi/linux/ip.h
            field_names = ('dummy',
                           'forwarding',
                           'mc_forwarding',
                           'proxy_arp',
                           'accept_redirects',
                           'secure_redirects',
                           'send_redirects',
                           'shared_media',
                           'rp_filter',
                           'accept_source_route',
                           'bootp_relay',
                           'log_martians',
                           'tag',
                           'arpfilter',
                           'medium_id',
                           'noxfrm',
                           'nopolicy',
                           'force_igmp_version',
                           'arp_announce',
                           'arp_ignore',
                           'promote_secondaries',
                           'arp_accept',
                           'arp_notify',
                           'accept_local',
                           'src_vmark',
                           'proxy_arp_pvlan',
                           'route_localnet',
                           'igmpv2_unsolicited_report_interval',
                           'igmpv3_unsolicited_report_interval')
            fields = [(i, 'I') for i in field_names]

        class inet6(nla):
            nla_map = (('IFLA_INET6_UNSPEC', 'none'),
                       ('IFLA_INET6_FLAGS', 'uint32'),
                       ('IFLA_INET6_CONF', 'ipv6_devconf'),
                       ('IFLA_INET6_STATS', 'ipv6_stats'),
                       ('IFLA_INET6_MCAST', 'hex'),
                       ('IFLA_INET6_CACHEINFO', 'ipv6_cache_info'),
                       ('IFLA_INET6_ICMP6STATS', 'icmp6_stats'),
                       ('IFLA_INET6_TOKEN', 'ip6addr'),
                       ('IFLA_INET6_ADDR_GEN_MODE', 'uint8'))

            class ipv6_devconf(nla):
                # ./include/uapi/linux/ipv6.h
                # DEVCONF_
                field_names = ('forwarding',
                               'hop_limit',
                               'mtu',
                               'accept_ra',
                               'accept_redirects',
                               'autoconf',
                               'dad_transmits',
                               'router_solicitations',
                               'router_solicitation_interval',
                               'router_solicitation_delay',
                               'use_tempaddr',
                               'temp_valid_lft',
                               'temp_prefered_lft',
                               'regen_max_retry',
                               'max_desync_factor',
                               'max_addresses',
                               'force_mld_version',
                               'accept_ra_defrtr',
                               'accept_ra_pinfo',
                               'accept_ra_rtr_pref',
                               'router_probe_interval',
                               'accept_ra_rt_info_max_plen',
                               'proxy_ndp',
                               'optimistic_dad',
                               'accept_source_route',
                               'mc_forwarding',
                               'disable_ipv6',
                               'accept_dad',
                               'force_tllao',
                               'ndisc_notify')
                fields = [(i, 'I') for i in field_names]

            class ipv6_cache_info(nla):
                # ./include/uapi/linux/if_link.h: struct ifla_cacheinfo
                fields = (('max_reasm_len', 'I'),
                          ('tstamp', 'I'),
                          ('reachable_time', 'I'),
                          ('retrans_time', 'I'))

            class ipv6_stats(nla):
                # ./include/uapi/linux/snmp.h
                field_names = ('num',
                               'inpkts',
                               'inoctets',
                               'indelivers',
                               'outforwdatagrams',
                               'outpkts',
                               'outoctets',
                               'inhdrerrors',
                               'intoobigerrors',
                               'innoroutes',
                               'inaddrerrors',
                               'inunknownprotos',
                               'intruncatedpkts',
                               'indiscards',
                               'outdiscards',
                               'outnoroutes',
                               'reasmtimeout',
                               'reasmreqds',
                               'reasmoks',
                               'reasmfails',
                               'fragoks',
                               'fragfails',
                               'fragcreates',
                               'inmcastpkts',
                               'outmcastpkts',
                               'inbcastpkts',
                               'outbcastpkts',
                               'inmcastoctets',
                               'outmcastoctets',
                               'inbcastoctets',
                               'outbcastoctets',
                               'csumerrors',
                               'noectpkts',
                               'ect1pkts',
                               'ect0pkts',
                               'cepkts')
                fields = [(i, 'Q') for i in field_names]

            class icmp6_stats(nla):
                # ./include/uapi/linux/snmp.h
                field_names = ('num',
                               'inmsgs',
                               'inerrors',
                               'outmsgs',
                               'outerrors',
                               'csumerrors')
                fields = [(i, 'Q') for i in field_names]


class ifinfmsg(ifinfbase, nlmsg):
    pass


class ifinfveth(ifinfbase, nla):
    pass


def compat_fix_attrs(msg, nl):
    kind = None
    ifname = msg.get_attr('IFLA_IFNAME')

    # fix master
    if not nl.capabilities['provide_master']:
        master = compat_get_master(ifname)
        if master is not None:
            msg['attrs'].append(['IFLA_MASTER', master])

    # fix linkinfo & kind
    li = msg.get_attr('IFLA_LINKINFO')
    if li is not None:
        kind = li.get_attr('IFLA_INFO_KIND')
        if kind is None:
            kind = get_interface_type(ifname)
            li['attrs'].append(['IFLA_INFO_KIND', kind])
    else:
        kind = get_interface_type(ifname)
        msg['attrs'].append(['IFLA_LINKINFO',
                             {'attrs': [['IFLA_INFO_KIND', kind]]}])

    li = msg.get_attr('IFLA_LINKINFO')
    # fetch specific interface data

    if (kind in ('bridge', 'bond')) and \
            [x for x in li['attrs'] if x[0] == 'IFLA_INFO_DATA']:
        if kind == 'bridge':
            t = '/sys/class/net/%s/bridge/%s'
            ifdata = ifinfmsg.ifinfo.bridge_data
        elif kind == 'bond':
            t = '/sys/class/net/%s/bonding/%s'
            ifdata = ifinfmsg.ifinfo.bond_data

        commands = []
        for cmd, _ in ifdata.nla_map:
            try:
                with open(t % (ifname, ifdata.nla2name(cmd)), 'r') as f:
                    value = f.read()
                if cmd == 'IFLA_BOND_MODE':
                    value = value.split()[1]
                commands.append([cmd, int(value)])
            except:
                pass
        if commands:
            li['attrs'].append(['IFLA_INFO_DATA', {'attrs': commands}])


def proxy_linkinfo(data, nl):
    if config.kernel > [3, 2, 0]:
        return {'verdict': 'forward',
                'data': data}

    offset = 0
    inbox = []
    while offset < len(data):
        msg = ifinfmsg(data[offset:])
        msg.decode()
        inbox.append(msg)
        offset += msg['header']['length']

    data = b''
    for msg in inbox:
        # Sysfs operations can require root permissions,
        # but the script can be run under a normal user
        # Bug-Url: https://github.com/svinota/pyroute2/issues/113
        try:
            compat_fix_attrs(msg, nl)
        except OSError:
            # We can safely ignore here any OSError.
            # In the worst case, we just return what we have got
            # from the kernel via netlink
            pass

        msg.reset()
        msg.encode()
        data += msg.buf.getvalue()

    return {'verdict': 'forward',
            'data': data}


def proxy_setlink(data, nl):

    def get_interface(index):
        msg = nl.get_links(index)[0]
        try:
            kind = msg.get_attr('IFLA_LINKINFO').get_attr('IFLA_INFO_KIND')
        except AttributeError:
            kind = 'unknown'
        return {'ifname': msg.get_attr('IFLA_IFNAME'),
                'master': msg.get_attr('IFLA_MASTER'),
                'kind': kind}

    msg = ifinfmsg(data)
    msg.decode()
    forward = True

    kind = None
    infodata = None

    ifname = msg.get_attr('IFLA_IFNAME') or \
        get_interface(msg['index'])['ifname']
    linkinfo = msg.get_attr('IFLA_LINKINFO')
    if linkinfo:
        kind = linkinfo.get_attr('IFLA_INFO_KIND')
        infodata = linkinfo.get_attr('IFLA_INFO_DATA')

    if kind in ('bond', 'bridge') and infodata is not None:
        code = 0
        #
        if kind == 'bond':
            func = compat_set_bond
        elif kind == 'bridge':
            func = compat_set_bridge
        #
        for (cmd, value) in infodata.get('attrs', []):
            cmd = infodata.nla2name(cmd)
            code = func(ifname, cmd, value) or code
        #
        if code:
            err = OSError()
            err.errno = code
            raise err

    # is it a port setup?
    master = msg.get_attr('IFLA_MASTER')
    if master is not None:

        if master == 0:
            # port delete
            # 1. get the current master
            iface = get_interface(msg['index'])
            master = get_interface(iface['master'])
            cmd = 'del'
        else:
            # port add
            # 1. get the master
            master = get_interface(master)
            cmd = 'add'

        # 2. manage the port
        forward_map = {'team': manage_team_port,
                       'bridge': compat_bridge_port,
                       'bond': compat_bond_port}
        forward = forward_map[master['kind']](cmd,
                                              master['ifname'],
                                              ifname,
                                              nl)

    if forward is not None:
        return {'verdict': 'forward',
                'data': data}


def sync(f):
    '''
    A decorator to wrap up external utility calls.

    A decorated function receives a netlink message
    as a parameter, and then:

    1. Starts a monitoring thread
    2. Performs the external call
    3. Waits for a netlink event specified by `msg`
    4. Joins the monitoring thread

    If the wrapped function raises an exception, the
    monitoring thread will be forced to stop via the
    control channel pipe. The exception will be then
    forwarded.
    '''
    def monitor(event, ifname, cmd):
        with RawIPRoute() as ipr:
            poll = select.poll()
            poll.register(ipr, select.POLLIN | select.POLLPRI)
            poll.register(cmd, select.POLLIN | select.POLLPRI)
            ipr.bind()
            while True:
                events = poll.poll()
                for (fd, event) in events:
                    if fd == ipr.fileno():
                        msgs = ipr.get()
                        for msg in msgs:
                            if msg.get('event') == event and \
                                    msg.get_attr('IFLA_IFNAME') == ifname:
                                return
                    else:
                        return

    def decorated(msg):
        rcmd, cmd = os.pipe()
        t = threading.Thread(target=monitor,
                             args=(RTM_VALUES[msg['header']['type']],
                                   msg.get_attr('IFLA_IFNAME'),
                                   rcmd))
        t.start()
        ret = None
        try:
            ret = f(msg)
        except Exception:
            raise
        finally:
            os.write(cmd, b'q')
            t.join()
        return ret

    return decorated


def proxy_dellink(data, nl):
    orig_msg = ifinfmsg(data)
    orig_msg.decode()

    # get full interface description
    msg = nl.get_links(orig_msg['index'])[0]
    msg['header']['type'] = orig_msg['header']['type']

    # get the interface kind
    kind = None
    li = msg.get_attr('IFLA_LINKINFO')
    if li is not None:
        kind = li.get_attr('IFLA_INFO_KIND')

    # team interfaces can be stopped by a normal RTM_DELLINK
    if kind == 'bond' and not nl.capabilities['create_bond']:
        return compat_del_bond(msg)
    elif kind == 'bridge' and not nl.capabilities['create_bridge']:
        return compat_del_bridge(msg)

    return {'verdict': 'forward',
            'data': data}


def proxy_newlink(data, nl):
    msg = ifinfmsg(data)
    msg.decode()
    kind = None

    # get the interface kind
    linkinfo = msg.get_attr('IFLA_LINKINFO')
    if linkinfo is not None:
        kind = [x[1] for x in linkinfo['attrs']
                if x[0] == 'IFLA_INFO_KIND']
        if kind:
            kind = kind[0]

    if kind == 'tuntap':
        return manage_tuntap(msg)
    elif kind == 'team':
        return manage_team(msg)
    elif kind == 'bond' and not nl.capabilities['create_bond']:
        return compat_create_bond(msg)
    elif kind == 'bridge' and not nl.capabilities['create_bridge']:
        return compat_create_bridge(msg)

    return {'verdict': 'forward',
            'data': data}


@map_enoent
@sync
def manage_team(msg):

    if msg['header']['type'] != RTM_NEWLINK:
        raise ValueError('wrong command type')

    config = {'device': msg.get_attr('IFLA_IFNAME'),
              'runner': {'name': 'activebackup'},
              'link_watch': {'name': 'ethtool'}}

    with open(os.devnull, 'w') as fnull:
        subprocess.check_call(['teamd', '-d', '-n', '-c', json.dumps(config)],
                              stdout=fnull,
                              stderr=fnull)


@map_enoent
def manage_team_port(cmd, master, ifname, nl):
    with open(os.devnull, 'w') as fnull:
        subprocess.check_call(['teamdctl', master, 'port',
                               'remove' if cmd == 'del' else 'add', ifname],
                              stdout=fnull,
                              stderr=fnull)


@sync
def manage_tuntap(msg):

    if TUNSETIFF is None:
        raise NetlinkError(errno.EOPNOTSUPP, 'Arch not supported')

    if msg['header']['type'] != RTM_NEWLINK:
        raise NetlinkError(errno.EOPNOTSUPP, 'Unsupported event')

    ifru_flags = 0
    linkinfo = msg.get_attr('IFLA_LINKINFO')
    infodata = linkinfo.get_attr('IFLA_INFO_DATA')

    flags = infodata.get_attr('IFTUN_IFR', None)
    if infodata.get_attr('IFTUN_MODE') == 'tun':
        ifru_flags |= IFT_TUN
    elif infodata.get_attr('IFTUN_MODE') == 'tap':
        ifru_flags |= IFT_TAP
    else:
        raise ValueError('invalid mode')
    if flags is not None:
        if flags['no_pi']:
            ifru_flags |= IFT_NO_PI
        if flags['one_queue']:
            ifru_flags |= IFT_ONE_QUEUE
        if flags['vnet_hdr']:
            ifru_flags |= IFT_VNET_HDR
        if flags['multi_queue']:
            ifru_flags |= IFT_MULTI_QUEUE
    ifr = msg.get_attr('IFLA_IFNAME')
    if len(ifr) > IFNAMSIZ:
        raise ValueError('ifname too long')
    ifr += (IFNAMSIZ - len(ifr)) * '\0'
    ifr = ifr.encode('ascii')
    ifr += struct.pack('H', ifru_flags)

    user = infodata.get_attr('IFTUN_UID')
    group = infodata.get_attr('IFTUN_GID')
    #
    fd = os.open(TUNDEV, os.O_RDWR)
    try:
        ioctl(fd, TUNSETIFF, ifr)
        if user is not None:
            ioctl(fd, TUNSETOWNER, user)
        if group is not None:
            ioctl(fd, TUNSETGROUP, group)
        ioctl(fd, TUNSETPERSIST, 1)
    except Exception:
        raise
    finally:
        os.close(fd)


@sync
def compat_create_bridge(msg):
    name = msg.get_attr('IFLA_IFNAME')
    with open(os.devnull, 'w') as fnull:
        subprocess.check_call(['brctl', 'addbr', name],
                              stdout=fnull,
                              stderr=fnull)


@sync
def compat_create_bond(msg):
    name = msg.get_attr('IFLA_IFNAME')
    with open(_BONDING_MASTERS, 'w') as f:
        f.write('+%s' % (name))


def compat_set_bond(name, cmd, value):
    # FIXME: join with bridge
    # FIXME: use internal IO, not bash
    t = 'echo %s >/sys/class/net/%s/bonding/%s'
    with open(os.devnull, 'w') as fnull:
        return subprocess.call(['bash', '-c', t % (value, name, cmd)],
                               stdout=fnull,
                               stderr=fnull)


def compat_set_bridge(name, cmd, value):
    t = 'echo %s >/sys/class/net/%s/bridge/%s'
    with open(os.devnull, 'w') as fnull:
        return subprocess.call(['bash', '-c', t % (value, name, cmd)],
                               stdout=fnull,
                               stderr=fnull)


@sync
def compat_del_bridge(msg):
    name = msg.get_attr('IFLA_IFNAME')
    with open(os.devnull, 'w') as fnull:
        subprocess.check_call(['ip', 'link', 'set',
                               'dev', name, 'down'])
        subprocess.check_call(['brctl', 'delbr', name],
                              stdout=fnull,
                              stderr=fnull)


@sync
def compat_del_bond(msg):
    name = msg.get_attr('IFLA_IFNAME')
    subprocess.check_call(['ip', 'link', 'set',
                           'dev', name, 'down'])
    with open(_BONDING_MASTERS, 'w') as f:
        f.write('-%s' % (name))


def compat_bridge_port(cmd, master, port, nl):
    if nl.capabilities['create_bridge']:
        return True
    with open(os.devnull, 'w') as fnull:
        subprocess.check_call(['brctl', '%sif' % (cmd), master, port],
                              stdout=fnull,
                              stderr=fnull)


def compat_bond_port(cmd, master, port, nl):
    if nl.capabilities['create_bond']:
        return True
    remap = {'add': '+',
             'del': '-'}
    cmd = remap[cmd]
    with open(_BONDING_SLAVES % (master), 'w') as f:
        f.write('%s%s' % (cmd, port))


def compat_get_master(name):
    f = None

    for i in (_BRIDGE_MASTER, _BONDING_MASTER):
        try:
            try:
                f = open(i % (name))
            except UnicodeEncodeError:
                # a special case with python3 on Ubuntu 14
                f = open(i % (name.encode('utf-8')))
            break
        except IOError:
            pass

    if f is not None:
        master = int(f.read())
        f.close()
        return master


def get_interface_type(name):
    '''
    Utility function to get interface type.

    Unfortunately, we can not rely on RTNL or even ioctl().
    RHEL doesn't support interface type in RTNL and doesn't
    provide extended (private) interface flags via ioctl().

    Args:
    * name (str): interface name

    Returns:
    * False -- sysfs info unavailable
    * None -- type not known
    * str -- interface type:
        - 'bond'
        - 'bridge'
    '''
    # FIXME: support all interface types? Right now it is
    # not needed
    try:
        ifattrs = os.listdir('/sys/class/net/%s/' % (name))
    except OSError as e:
        if e.errno == 2:
            return 'unknown'
        else:
            raise

    if 'bonding' in ifattrs:
        return 'bond'
    elif 'bridge' in ifattrs:
        return 'bridge'
    else:
        return 'unknown'
