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.rtnl import RTM_VALUES
from pyroute2.netlink.rtnl.iw_event import iw_event
from pyroute2.netlink.exceptions import NetlinkError


# 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

##
#
# vlan filter flags
#
BRIDGE_VLAN_INFO_MASTER = 0x1       # operate on bridge device
BRIDGE_VLAN_INFO_PVID = 0x2         # ingress untagged
BRIDGE_VLAN_INFO_UNTAGGED = 0x4     # egress untagged
BRIDGE_VLAN_INFO_RANGE_BEGIN = 0x8  # range start
BRIDGE_VLAN_INFO_RANGE_END = 0x10   # range end
BRIDGE_VLAN_INFO_BRENTRY = 0x20     # global bridge vlan entry
(BRIDGE_VLAN_NAMES, BRIDGE_VLAN_VALUES) = \
    map_namespace('BRIDGE_VLAN_INFO', globals())


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 macvx_data(nla):
    nla_map = (('IFLA_MACVLAN_UNSPEC', 'none'),
               ('IFLA_MACVLAN_MODE', 'mode'),
               ('IFLA_MACVLAN_FLAGS', 'flags'),
               ('IFLA_MACVLAN_MACADDR_MODE', 'macaddr_mode'),
               ('IFLA_MACVLAN_MACADDR', 'l2addr'),
               ('IFLA_MACVLAN_MACADDR_DATA', 'macaddr_data'),
               ('IFLA_MACVLAN_MACADDR_COUNT', 'uint32'))

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

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

    class macaddr_mode(nlmsg_atoms.uint32):
        value_map = {0: 'add',
                     1: 'del',
                     2: 'flush',
                     3: 'set'}

    class macaddr_data(nla):
        nla_map = ((4, 'IFLA_MACVLAN_MACADDR', 'l2addr'), )


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', 'x'),
              ('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'),
               ('IFLA_PROTO_DOWN', 'uint8'),
               ('IFLA_GSO_MAX_SEGS', 'uint32'),
               ('IFLA_GSO_MAX_SIZE', 'uint32'))

    @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'))

    @staticmethod
    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'))

        @staticmethod
        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'))

        @staticmethod
        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,
                        'vrf': self.vrf_data,
                        'gtp': self.gtp_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'))

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

        class vxlan_data(nla):
            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):
            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 macvlan_data(macvx_data):
            pass

        class macvtap_data(macvx_data):
            nla_map = [(x[0].replace('MACVLAN', 'MACVTAP'), x[1])
                       for x in macvx_data.nla_map]

        class ipvlan_data(nla):
            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', 'qos'),
                       ('IFLA_VLAN_INGRESS_QOS', 'qos'),
                       ('IFLA_VLAN_PROTOCOL', 'be16'))

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

            class qos(nla):
                nla_map = (('IFLA_VLAN_QOS_UNSPEC', 'none'),
                           ('IFLA_VLAN_QOS_MAPPING', 'qos_mapping'))

                class qos_mapping(nla):
                    fields = (('from', 'I'),
                              ('to', 'I'))

        class gtp_data(nla):
            nla_map = (('IFLA_GTP_UNSPEC', 'none'),
                       ('IFLA_GTP_FD0', 'uint32'),
                       ('IFLA_GTP_FD1', 'uint32'),
                       ('IFLA_GTP_PDP_HASHSIZE', 'uint32'))

        class bridge_data(nla):
            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):
            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'),
                       ('IFLA_BOND_AD_ACTOR_SYS_PRIO', 'uint16'),
                       ('IFLA_BOND_AD_USER_PORT_KEY', 'uint16'),
                       ('IFLA_BOND_AD_ACTOR_SYSTEM', 'hex'),
                       ('IFLA_BOND_TLB_DYNAMIC_LB', 'uint8'))

            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'), )

        class vrf_data(nla):
            nla_map = (('IFLA_VRF_UNSPEC', 'none'),
                       ('IFLA_VRF_TABLE', 'uint32'))

    @staticmethod
    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'))

            @staticmethod
            def flags2names(flags):
                ret = []
                for flag in BRIDGE_VLAN_VALUES:
                    if (flag & flags) == flag:
                        ret.append(BRIDGE_VLAN_VALUES[flag])
                return ret

            @staticmethod
            def names2flags(flags):
                ret = 0
                for flag in flags:
                    ret |= BRIDGE_VLAN_NAMES['BRIDGE_VLAN_INFO_' +
                                             flag.upper()]
                return ret

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

    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 proxy_setlink(msg, 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}

    forward = True

    # 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'

        ifname = msg.get_attr('IFLA_IFNAME') or \
            get_interface(msg['index'])['ifname']

        # 2. manage the port
        forward_map = {'team': manage_team_port}
        if master['kind'] in forward_map:
            func = forward_map[master['kind']]
            forward = func(cmd, master['ifname'], ifname, nl)

    if forward is not None:
        return {'verdict': 'forward',
                'data': msg.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_newlink(msg, nl):
    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)

    return {'verdict': 'forward',
            'data': msg.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)
