Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar_network/toolkit/netlink.py
blob: 9ad83708d0172d1ae10b867bab5010dd94bda82c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# 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 <http://www.gnu.org/licenses/>.

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

    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 '<netlink.Message type=%r flags=0x%x seqno=%r pid=%r>' % \
                (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()