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