]>
Commit | Line | Data |
---|---|---|
f82758bf RP |
1 | #!/usr/bin/env python |
2 | # | |
3 | # Copyright 2014 Cumulus Networks, Inc. All rights reserved. | |
4 | # Author: Scott Feldman, sfeldma@cumulusnetworks.com | |
5 | # | |
6 | ||
7 | from os import strerror | |
8 | import select | |
9 | from time import time | |
10 | import socket | |
11 | from ctypes import * | |
12 | from errno import * | |
13 | import logging | |
14 | ||
15 | logger = logging.getLogger(__name__) | |
16 | ||
17 | # | |
18 | # from /usr/include/linux/netlink.h | |
19 | # | |
20 | ||
21 | NETLINK_ROUTE = 0 # Routing/device hook | |
22 | NETLINK_UNUSED = 1 # Unused number | |
23 | NETLINK_USERSOCK = 2 # Reserved for user mode socket protocols | |
24 | NETLINK_FIREWALL = 3 # Firewalling hook | |
25 | NETLINK_INET_DIAG = 4 # INET socket monitoring | |
26 | NETLINK_NFLOG = 5 # netfilter/iptables ULOG | |
27 | NETLINK_XFRM = 6 # ipsec | |
28 | NETLINK_SELINUX = 7 # SELinux event notifications | |
29 | NETLINK_ISCSI = 8 # Open-iSCSI | |
30 | NETLINK_AUDIT = 9 # auditing | |
31 | NETLINK_FIB_LOOKUP = 10 | |
32 | NETLINK_CONNECTOR = 11 | |
33 | NETLINK_NETFILTER = 12 # netfilter subsystem | |
34 | NETLINK_IP6_FW = 13 | |
35 | NETLINK_DNRTMSG = 14 # DECnet routing messages | |
36 | NETLINK_KOBJECT_UEVENT = 15 # Kernel messages to userspace | |
37 | NETLINK_GENERIC = 16 | |
38 | NETLINK_SCSITRANSPORT = 18 # SCSI Transports | |
39 | NETLINK_ECRYPTFS = 19 | |
40 | NETLINK_RDMA = 20 | |
41 | NETLINK_CRYPTO = 21 # Crypto layer | |
42 | ||
43 | NLMSG_NOOP = 1 # Nothing. | |
44 | NLMSG_ERROR = 2 # Error | |
45 | NLMSG_DONE = 3 # End of a dump | |
46 | NLMSG_OVERRUN = 4 # Data lost | |
47 | ||
48 | NETLINK_NO_ENOBUFS = 5 | |
49 | ||
50 | SOL_NETLINK = 270 | |
51 | ||
52 | class Nlmsghdr(Structure): | |
53 | ||
54 | _fields_ = [ | |
55 | ('nlmsg_len', c_uint32), | |
56 | ('nlmsg_type', c_uint16), | |
57 | ('nlmsg_flags', c_uint16), | |
58 | ('nlmsg_seq', c_uint32), | |
59 | ('nlmsg_pid', c_uint32) | |
60 | ] | |
61 | ||
62 | def dump(self): | |
63 | print 'nlmsg_len', self.nlmsg_len | |
64 | print 'nlmsg_type', self.nlmsg_type | |
65 | print 'nlmsg_flags 0x%04x' % self.nlmsg_flags | |
66 | print 'nlmsg_seq', self.nlmsg_seq | |
67 | print 'nlmsg_pid', self.nlmsg_pid | |
68 | ||
69 | # Flags values | |
70 | ||
71 | NLM_F_REQUEST = 1 # It is request message. | |
72 | NLM_F_MULTI = 2 # Multipart message, terminated by NLMSG_DONE | |
73 | NLM_F_ACK = 4 # Reply with ack, with zero or error code | |
74 | NLM_F_ECHO = 8 # Echo this request | |
75 | NLM_F_DUMP_INTR = 16 # Dump was inconsistent due to sequence change | |
76 | ||
77 | # Modifiers to GET request | |
78 | NLM_F_ROOT = 0x100 # specify tree root | |
79 | NLM_F_MATCH = 0x200 # return all matching | |
80 | NLM_F_ATOMIC = 0x400 # atomic GET | |
81 | NLM_F_DUMP = (NLM_F_ROOT|NLM_F_MATCH) | |
82 | ||
83 | # Modifiers to NEW request | |
84 | NLM_F_REPLACE = 0x100 # Override existing | |
85 | NLM_F_EXCL = 0x200 # Do not touch, if it exists | |
86 | NLM_F_CREATE = 0x400 # Create, if it does not exist | |
87 | NLM_F_APPEND = 0x800 # Add to end of list | |
88 | ||
89 | NLMSG_ALIGNTO = 4 | |
90 | def NLMSG_ALIGN(len): | |
91 | return (len + NLMSG_ALIGNTO - 1) & ~(NLMSG_ALIGNTO - 1) | |
92 | def NLMSG_HDRLEN(): | |
93 | return NLMSG_ALIGN(sizeof(Nlmsghdr)) | |
94 | def NLMSG_LENGTH(len): | |
95 | return len + NLMSG_ALIGN(NLMSG_HDRLEN()) | |
96 | def NLMSG_SPACE(len): | |
97 | return NLMSG_ALIGN(NLMSG_LENGTH(len)) | |
98 | def NLMSG_DATA(nlh): | |
99 | return addressof(nlh) + NLMSG_LENGTH(0) | |
100 | def NLMSG_NEXT(nlh, len): | |
101 | cur = NLMSG_ALIGN(nlh.nlmsg_len) | |
102 | nlh = Nlmsghdr.from_address(addressof(nlh) + cur) | |
103 | return len - cur, nlh | |
104 | def NLMSG_OK(nlh, len): | |
105 | return len >= sizeof(Nlmsghdr) and \ | |
106 | nlh.nlmsg_len >= sizeof(Nlmsghdr) and \ | |
107 | nlh.nlmsg_len <= len | |
108 | ||
109 | class Nlmsgerr(Structure): | |
110 | ||
111 | _fields_ = [ | |
112 | ('error', c_int), | |
113 | ('msg', Nlmsghdr), | |
114 | ] | |
115 | ||
116 | class NetlinkError(Exception): | |
117 | ||
118 | def __init__(self, message): | |
119 | Exception.__init__(self, message) | |
120 | #print(message) | |
121 | ||
122 | class Netlink(socket.socket): | |
123 | ||
124 | def __init__(self, pid, proto): | |
125 | ||
126 | self.pid = pid | |
127 | self.recvbuf = bytearray(8 * 1024) | |
128 | self.sendbuf = bytearray(8 * 1024) | |
129 | self.seq = int(time()) | |
130 | ||
131 | try: | |
132 | ||
133 | socket.socket.__init__(self, socket.AF_NETLINK, \ | |
134 | socket.SOCK_RAW, proto) | |
135 | self.setblocking(0) | |
136 | ||
137 | # Need to turn off ENOBUFS for netlink socket otherwise | |
138 | # in a kernel overrun situation, the socket will return | |
139 | # ENOBUFS on socket recv and be stuck for future recvs. | |
140 | ||
141 | self.setsockopt(SOL_NETLINK, NETLINK_NO_ENOBUFS, 1) | |
142 | ||
143 | except socket.error as (errno, string): | |
144 | raise NetlinkError("open: socket err[%d]: %s" % \ | |
145 | (errno, string)) | |
146 | ||
147 | def bind(self, groups, cb): | |
148 | ||
149 | self._nl_cb = cb | |
150 | ||
151 | try: | |
152 | socket.socket.bind(self, (self.pid, groups)) | |
153 | ||
154 | except socket.error as (errno, string): | |
155 | raise NetlinkError("bind: socket err[%d]: %s" % \ | |
156 | (errno, string)) | |
157 | ||
158 | def sendall(self, string): | |
159 | try: | |
160 | socket.socket.sendall(self, string) | |
161 | except socket.error as (errno, string): | |
162 | raise NetlinkError("send: socket err[%d]: %s" % \ | |
163 | (errno, string)) | |
164 | ||
165 | def _process_nlh(self, recv, nlh): | |
166 | while NLMSG_OK(nlh, recv): | |
167 | yield recv, nlh | |
168 | recv, nlh = NLMSG_NEXT(nlh, recv) | |
169 | ||
170 | def process(self, tokens=[]): | |
171 | ||
172 | found_done = False | |
173 | ||
174 | try: | |
175 | recv, src_addr = self.recvfrom_into(self.recvbuf) | |
176 | if not recv: | |
177 | # EOF | |
178 | print "EOF" | |
179 | return False | |
180 | ||
181 | except socket.error as (errno, string): | |
182 | if errno in [EINTR, EAGAIN]: | |
183 | return False | |
184 | raise NetlinkError("netlink: socket err[%d]: %s" % \ | |
185 | (errno, string)) | |
186 | ||
187 | nlh = Nlmsghdr.from_buffer(self.recvbuf) | |
188 | for recv, nlh in self._process_nlh(recv, nlh): | |
189 | ||
190 | # print "type %u, seq %u, pid %u" % \ | |
191 | # (nlh.nlmsg_type, nlh.nlmsg_seq, nlh.nlmsg_pid) | |
192 | ||
193 | l = nlh.nlmsg_len - sizeof(Nlmsghdr) | |
194 | ||
195 | if l < 0 or nlh.nlmsg_len > recv: | |
196 | raise NetlinkError("netlink: malformed msg: len %d" % \ | |
197 | nlh.nlmsg_len) | |
198 | ||
199 | if tokens: | |
200 | current = (nlh.nlmsg_pid, nlh.nlmsg_seq) | |
201 | if current not in tokens: | |
202 | continue | |
203 | ||
204 | if nlh.nlmsg_type == NLMSG_DONE: | |
205 | found_done = True | |
206 | break | |
207 | ||
208 | if nlh.nlmsg_type == NLMSG_ERROR: | |
209 | err = Nlmsgerr.from_address(NLMSG_DATA(nlh)) | |
210 | if err.error == 0: | |
211 | return False | |
212 | raise NetlinkError("netlink: %s" % strerror(abs(err.error))) | |
213 | ||
214 | if self._nl_cb: | |
215 | self._nl_cb(nlh) | |
216 | ||
217 | if found_done: | |
218 | return False | |
219 | ||
220 | remnant = recv - NLMSG_ALIGN(nlh.nlmsg_len) > 0 | |
221 | if remnant: | |
222 | raise NetlinkError("netlink: remnant of size %d" % \ | |
223 | remnant) | |
224 | ||
225 | return True | |
226 | ||
227 | def process_wait(self, tokens): | |
228 | while self.process(tokens): | |
229 | pass | |
230 | ||
231 | def process_forever(self): | |
232 | epoll = select.epoll() | |
233 | epoll.register(self.fileno(), select.EPOLLIN) | |
234 | while True: | |
235 | events = epoll.poll() | |
236 | for fileno, event in events: | |
237 | if fileno == self.fileno(): | |
238 | self.process() | |
239 | ||
240 | def process_event(self, event): | |
241 | return self.process() |