# Copyright (C) 2012 Aleksey Lim # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """Linux Netlink integration.""" import os import socket import struct import logging from sugar_network.toolkit import coroutine # RTnetlink multicast groups - backwards compatibility for userspace RTMGRP_LINK = 1 RTMGRP_NOTIFY = 2 RTMGRP_NEIGH = 4 RTMGRP_TC = 8 RTMGRP_IPV4_IFADDR = 0x10 RTMGRP_IPV4_MROUTE = 0x20 RTMGRP_IPV4_ROUTE = 0x40 RTMGRP_IPV4_RULE = 0x80 RTMGRP_IPV6_IFADDR = 0x100 RTMGRP_IPV6_MROUTE = 0x200 RTMGRP_IPV6_ROUTE = 0x400 RTMGRP_IPV6_IFINFO = 0x800 RTMGRP_DECnet_IFADDR = 0x1000 RTMGRP_DECnet_ROUTE = 0x4000 RTMGRP_IPV6_PREFIX = 0x20000 #: Message type, Nothing NLMSG_NOOP = 0x1 #: Message type, Error NLMSG_ERROR = 0x2 #: Message type, End of a dump NLMSG_DONE = 0x3 #: Message type, Data lost NLMSG_OVERRUN = 0x4 _MESSAGE_MAX_SIZE = 16384 _logger = logging.getLogger('netlink') def wait_for_route(): def get_route(): with file('/proc/self/net/route') as f: # Skip header f.readline() while True: line = f.readline() if not line: break dst, gw = line.split('\t', 3)[1:3] if int(dst, 16) in (0, 224): return gw coroutine.reset_resolver() old_route = get_route() if old_route: yield old_route with Netlink(socket.NETLINK_ROUTE, RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE) as monitor: while True: coroutine.select([monitor.fileno()], [], []) while coroutine.select([monitor.fileno()], [], [], 1)[0]: monitor.read() new_route = get_route() if new_route != old_route: coroutine.reset_resolver() yield new_route old_route = new_route class Netlink(object): def __init__(self, proto, groups): _logger.info('Start reading Netlink messages') self._socket = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, proto) self._socket.bind((0, groups)) def fileno(self): if self._socket is not None: return self._socket.fileno() @property def closed(self): return self._socket is None def close(self): if self._socket is None: return self._socket.close() self._socket = None _logger.info('Stop reading Netlink messages') def read(self): if self.closed: raise RuntimeError('Netlink is closed') data = self._socket.recv(_MESSAGE_MAX_SIZE) if not data: self.close() return msg = Message() __, msg.type, msg.flags, msg.seqno, msg.pid = \ struct.unpack('IHHII', data[:16]) msg.payload = data[16:] _logger.debug('Got message: %r', msg) if msg.type == NLMSG_ERROR: errno = - struct.unpack('i', msg.payload[:4])[0] if errno: error = OSError('Netlink error, %s(%d)' % (os.strerror(errno), errno)) error.errno = errno raise error return msg def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() class Message(object): type = None flags = None seqno = 0 pid = -1 payload = None def __repr__(self): return '' % \ (self.type, self.flags, self.seqno, self.pid) if __name__ == '__main__': import select logging.basicConfig(level=logging.DEBUG) with Netlink(socket.NETLINK_ROUTE, RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE | RTMGRP_NOTIFY) as netlink: poll = select.poll() poll.register(netlink.fileno(), select.POLLIN) while poll.poll(): netlink.read()