Blame cloudinit/sources/helpers/netlink.py

Packit Service a04d08
# Author: Tamilmani Manoharan <tamanoha@microsoft.com>
Packit Service a04d08
#
Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
Packit Service a04d08
from cloudinit import log as logging
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
from collections import namedtuple
Packit Service a04d08
Packit Service a04d08
import os
Packit Service a04d08
import select
Packit Service a04d08
import socket
Packit Service a04d08
import struct
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
Packit Service a04d08
# http://man7.org/linux/man-pages/man7/netlink.7.html
Packit Service a04d08
RTMGRP_LINK = 1
Packit Service a04d08
NLMSG_NOOP = 1
Packit Service a04d08
NLMSG_ERROR = 2
Packit Service a04d08
NLMSG_DONE = 3
Packit Service a04d08
RTM_NEWLINK = 16
Packit Service a04d08
RTM_DELLINK = 17
Packit Service a04d08
RTM_GETLINK = 18
Packit Service a04d08
RTM_SETLINK = 19
Packit Service a04d08
MAX_SIZE = 65535
Packit Service a04d08
RTA_DATA_OFFSET = 32
Packit Service a04d08
MSG_TYPE_OFFSET = 16
Packit Service a04d08
SELECT_TIMEOUT = 60
Packit Service a04d08
Packit Service a04d08
NLMSGHDR_FMT = "IHHII"
Packit Service a04d08
IFINFOMSG_FMT = "BHiII"
Packit Service a04d08
NLMSGHDR_SIZE = struct.calcsize(NLMSGHDR_FMT)
Packit Service a04d08
IFINFOMSG_SIZE = struct.calcsize(IFINFOMSG_FMT)
Packit Service a04d08
RTATTR_START_OFFSET = NLMSGHDR_SIZE + IFINFOMSG_SIZE
Packit Service a04d08
RTA_DATA_START_OFFSET = 4
Packit Service a04d08
PAD_ALIGNMENT = 4
Packit Service a04d08
Packit Service a04d08
IFLA_IFNAME = 3
Packit Service a04d08
IFLA_OPERSTATE = 16
Packit Service a04d08
Packit Service a04d08
# https://www.kernel.org/doc/Documentation/networking/operstates.txt
Packit Service a04d08
OPER_UNKNOWN = 0
Packit Service a04d08
OPER_NOTPRESENT = 1
Packit Service a04d08
OPER_DOWN = 2
Packit Service a04d08
OPER_LOWERLAYERDOWN = 3
Packit Service a04d08
OPER_TESTING = 4
Packit Service a04d08
OPER_DORMANT = 5
Packit Service a04d08
OPER_UP = 6
Packit Service a04d08
Packit Service a04d08
RTAAttr = namedtuple('RTAAttr', ['length', 'rta_type', 'data'])
Packit Service a04d08
InterfaceOperstate = namedtuple('InterfaceOperstate', ['ifname', 'operstate'])
Packit Service a04d08
NetlinkHeader = namedtuple('NetlinkHeader', ['length', 'type', 'flags', 'seq',
Packit Service a04d08
                                             'pid'])
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class NetlinkCreateSocketError(RuntimeError):
Packit Service a04d08
    '''Raised if netlink socket fails during create or bind.'''
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def create_bound_netlink_socket():
Packit Service a04d08
    '''Creates netlink socket and bind on netlink group to catch interface
Packit Service a04d08
    down/up events. The socket will bound only on RTMGRP_LINK (which only
Packit Service a04d08
    includes RTM_NEWLINK/RTM_DELLINK/RTM_GETLINK events). The socket is set to
Packit Service a04d08
    non-blocking mode since we're only receiving messages.
Packit Service a04d08
Packit Service a04d08
    :returns: netlink socket in non-blocking mode
Packit Service a04d08
    :raises: NetlinkCreateSocketError
Packit Service a04d08
    '''
Packit Service a04d08
    try:
Packit Service a04d08
        netlink_socket = socket.socket(socket.AF_NETLINK,
Packit Service a04d08
                                       socket.SOCK_RAW,
Packit Service a04d08
                                       socket.NETLINK_ROUTE)
Packit Service a04d08
        netlink_socket.bind((os.getpid(), RTMGRP_LINK))
Packit Service a04d08
        netlink_socket.setblocking(0)
Packit Service a04d08
    except socket.error as e:
Packit Service a04d08
        msg = "Exception during netlink socket create: %s" % e
Packit Service 9bfd13
        raise NetlinkCreateSocketError(msg) from e
Packit Service a04d08
    LOG.debug("Created netlink socket")
Packit Service a04d08
    return netlink_socket
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def get_netlink_msg_header(data):
Packit Service a04d08
    '''Gets netlink message type and length
Packit Service a04d08
Packit Service a04d08
    :param: data read from netlink socket
Packit Service a04d08
    :returns: netlink message type
Packit Service a04d08
    :raises: AssertionError if data is None or data is not >= NLMSGHDR_SIZE
Packit Service a04d08
    struct nlmsghdr {
Packit Service a04d08
               __u32 nlmsg_len;    /* Length of message including header */
Packit Service a04d08
               __u16 nlmsg_type;   /* Type of message content */
Packit Service a04d08
               __u16 nlmsg_flags;  /* Additional flags */
Packit Service a04d08
               __u32 nlmsg_seq;    /* Sequence number */
Packit Service a04d08
               __u32 nlmsg_pid;    /* Sender port ID */
Packit Service a04d08
    };
Packit Service a04d08
    '''
Packit Service a04d08
    assert (data is not None), ("data is none")
Packit Service a04d08
    assert (len(data) >= NLMSGHDR_SIZE), (
Packit Service a04d08
        "data is smaller than netlink message header")
Packit Service a04d08
    msg_len, msg_type, flags, seq, pid = struct.unpack(NLMSGHDR_FMT,
Packit Service a04d08
                                                       data[:MSG_TYPE_OFFSET])
Packit Service a04d08
    LOG.debug("Got netlink msg of type %d", msg_type)
Packit Service a04d08
    return NetlinkHeader(msg_len, msg_type, flags, seq, pid)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def read_netlink_socket(netlink_socket, timeout=None):
Packit Service a04d08
    '''Select and read from the netlink socket if ready.
Packit Service a04d08
Packit Service a04d08
    :param: netlink_socket: specify which socket object to read from
Packit Service a04d08
    :param: timeout: specify a timeout value (integer) to wait while reading,
Packit Service a04d08
            if none, it will block indefinitely until socket ready for read
Packit Service a04d08
    :returns: string of data read (max length = <MAX_SIZE>) from socket,
Packit Service a04d08
              if no data read, returns None
Packit Service a04d08
    :raises: AssertionError if netlink_socket is None
Packit Service a04d08
    '''
Packit Service a04d08
    assert (netlink_socket is not None), ("netlink socket is none")
Packit Service a04d08
    read_set, _, _ = select.select([netlink_socket], [], [], timeout)
Packit Service a04d08
    # Incase of timeout,read_set doesn't contain netlink socket.
Packit Service a04d08
    # just return from this function
Packit Service a04d08
    if netlink_socket not in read_set:
Packit Service a04d08
        return None
Packit Service a04d08
    LOG.debug("netlink socket ready for read")
Packit Service a04d08
    data = netlink_socket.recv(MAX_SIZE)
Packit Service a04d08
    if data is None:
Packit Service a04d08
        LOG.error("Reading from Netlink socket returned no data")
Packit Service a04d08
    return data
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def unpack_rta_attr(data, offset):
Packit Service a04d08
    '''Unpack a single rta attribute.
Packit Service a04d08
Packit Service a04d08
    :param: data: string of data read from netlink socket
Packit Service a04d08
    :param: offset: starting offset of RTA Attribute
Packit Service a04d08
    :return: RTAAttr object with length, type and data. On error, return None.
Packit Service a04d08
    :raises: AssertionError if data is None or offset is not integer.
Packit Service a04d08
    '''
Packit Service a04d08
    assert (data is not None), ("data is none")
Packit Service a04d08
    assert (type(offset) == int), ("offset is not integer")
Packit Service a04d08
    assert (offset >= RTATTR_START_OFFSET), (
Packit Service a04d08
        "rta offset is less than expected length")
Packit Service a04d08
    length = rta_type = 0
Packit Service a04d08
    attr_data = None
Packit Service a04d08
    try:
Packit Service a04d08
        length = struct.unpack_from("H", data, offset=offset)[0]
Packit Service a04d08
        rta_type = struct.unpack_from("H", data, offset=offset+2)[0]
Packit Service a04d08
    except struct.error:
Packit Service a04d08
        return None  # Should mean our offset is >= remaining data
Packit Service a04d08
Packit Service a04d08
    # Unpack just the attribute's data. Offset by 4 to skip length/type header
Packit Service a04d08
    attr_data = data[offset+RTA_DATA_START_OFFSET:offset+length]
Packit Service a04d08
    return RTAAttr(length, rta_type, attr_data)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def read_rta_oper_state(data):
Packit Service a04d08
    '''Reads Interface name and operational state from RTA Data.
Packit Service a04d08
Packit Service a04d08
    :param: data: string of data read from netlink socket
Packit Service a04d08
    :returns: InterfaceOperstate object containing if_name and oper_state.
Packit Service a04d08
              None if data does not contain valid IFLA_OPERSTATE and
Packit Service a04d08
              IFLA_IFNAME messages.
Packit Service a04d08
    :raises: AssertionError if data is None or length of data is
Packit Service a04d08
             smaller than RTATTR_START_OFFSET.
Packit Service a04d08
    '''
Packit Service a04d08
    assert (data is not None), ("data is none")
Packit Service a04d08
    assert (len(data) > RTATTR_START_OFFSET), (
Packit Service a04d08
        "length of data is smaller than RTATTR_START_OFFSET")
Packit Service a04d08
    ifname = operstate = None
Packit Service a04d08
    offset = RTATTR_START_OFFSET
Packit Service a04d08
    while offset <= len(data):
Packit Service a04d08
        attr = unpack_rta_attr(data, offset)
Packit Service a04d08
        if not attr or attr.length == 0:
Packit Service a04d08
            break
Packit Service a04d08
        # Each attribute is 4-byte aligned. Determine pad length.
Packit Service a04d08
        padlen = (PAD_ALIGNMENT -
Packit Service a04d08
                  (attr.length % PAD_ALIGNMENT)) % PAD_ALIGNMENT
Packit Service a04d08
        offset += attr.length + padlen
Packit Service a04d08
Packit Service a04d08
        if attr.rta_type == IFLA_OPERSTATE:
Packit Service a04d08
            operstate = ord(attr.data)
Packit Service a04d08
        elif attr.rta_type == IFLA_IFNAME:
Packit Service a04d08
            interface_name = util.decode_binary(attr.data, 'utf-8')
Packit Service a04d08
            ifname = interface_name.strip('\0')
Packit Service a04d08
    if not ifname or operstate is None:
Packit Service a04d08
        return None
Packit Service a04d08
    LOG.debug("rta attrs: ifname %s operstate %d", ifname, operstate)
Packit Service a04d08
    return InterfaceOperstate(ifname, operstate)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def wait_for_media_disconnect_connect(netlink_socket, ifname):
Packit Service a04d08
    '''Block until media disconnect and connect has happened on an interface.
Packit Service a04d08
    Listens on netlink socket to receive netlink events and when the carrier
Packit Service a04d08
    changes from 0 to 1, it considers event has happened and
Packit Service a04d08
    return from this function
Packit Service a04d08
Packit Service a04d08
    :param: netlink_socket: netlink_socket to receive events
Packit Service a04d08
    :param: ifname: Interface name to lookout for netlink events
Packit Service a04d08
    :raises: AssertionError if netlink_socket is None or ifname is None.
Packit Service a04d08
    '''
Packit Service a04d08
    assert (netlink_socket is not None), ("netlink socket is none")
Packit Service a04d08
    assert (ifname is not None), ("interface name is none")
Packit Service a04d08
    assert (len(ifname) > 0), ("interface name cannot be empty")
Packit Service a04d08
    carrier = OPER_UP
Packit Service a04d08
    prevCarrier = OPER_UP
Packit Service a04d08
    data = bytes()
Packit Service a04d08
    LOG.debug("Wait for media disconnect and reconnect to happen")
Packit Service a04d08
    while True:
Packit Service a04d08
        recv_data = read_netlink_socket(netlink_socket, SELECT_TIMEOUT)
Packit Service a04d08
        if recv_data is None:
Packit Service a04d08
            continue
Packit Service a04d08
        LOG.debug('read %d bytes from socket', len(recv_data))
Packit Service a04d08
        data += recv_data
Packit Service a04d08
        LOG.debug('Length of data after concat %d', len(data))
Packit Service a04d08
        offset = 0
Packit Service a04d08
        datalen = len(data)
Packit Service a04d08
        while offset < datalen:
Packit Service a04d08
            nl_msg = data[offset:]
Packit Service a04d08
            if len(nl_msg) < NLMSGHDR_SIZE:
Packit Service a04d08
                LOG.debug("Data is smaller than netlink header")
Packit Service a04d08
                break
Packit Service a04d08
            nlheader = get_netlink_msg_header(nl_msg)
Packit Service a04d08
            if len(nl_msg) < nlheader.length:
Packit Service a04d08
                LOG.debug("Partial data. Smaller than netlink message")
Packit Service a04d08
                break
Packit Service a04d08
            padlen = (nlheader.length+PAD_ALIGNMENT-1) & ~(PAD_ALIGNMENT-1)
Packit Service a04d08
            offset = offset + padlen
Packit Service a04d08
            LOG.debug('offset to next netlink message: %d', offset)
Packit Service a04d08
            # Ignore any messages not new link or del link
Packit Service a04d08
            if nlheader.type not in [RTM_NEWLINK, RTM_DELLINK]:
Packit Service a04d08
                continue
Packit Service a04d08
            interface_state = read_rta_oper_state(nl_msg)
Packit Service a04d08
            if interface_state is None:
Packit Service a04d08
                LOG.debug('Failed to read rta attributes: %s', interface_state)
Packit Service a04d08
                continue
Packit Service a04d08
            if interface_state.ifname != ifname:
Packit Service a04d08
                LOG.debug(
Packit Service a04d08
                    "Ignored netlink event on interface %s. Waiting for %s.",
Packit Service a04d08
                    interface_state.ifname, ifname)
Packit Service a04d08
                continue
Packit Service a04d08
            if interface_state.operstate not in [OPER_UP, OPER_DOWN]:
Packit Service a04d08
                continue
Packit Service a04d08
            prevCarrier = carrier
Packit Service a04d08
            carrier = interface_state.operstate
Packit Service a04d08
            # check for carrier down, up sequence
Packit Service a04d08
            isVnetSwitch = (prevCarrier == OPER_DOWN) and (carrier == OPER_UP)
Packit Service a04d08
            if isVnetSwitch:
Packit Service a04d08
                LOG.debug("Media switch happened on %s.", ifname)
Packit Service a04d08
                return
Packit Service a04d08
        data = data[offset:]
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab