]>
Commit | Line | Data |
---|---|---|
198ded6a | 1 | #!/usr/bin/env python |
d486dd0d JF |
2 | # |
3 | # Copyright (C) 2015, 2017 Cumulus Networks, Inc. all rights reserved | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or | |
6 | # modify it under the terms of the GNU General Public License as | |
7 | # published by the Free Software Foundation; version 2. | |
8 | # | |
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | # General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
17 | # 02110-1301, USA. | |
18 | # | |
19 | # https://www.gnu.org/licenses/gpl-2.0-standalone.html | |
20 | # | |
21 | # Authors: | |
22 | # Daniel Walton, dwalton@cumulusnetworks.com | |
23 | # Julien Fortin, julien@cumulusnetworks.com | |
24 | # | |
25 | # Netlink Manager -- | |
26 | # | |
198ded6a | 27 | |
26d1e82b | 28 | from collections import OrderedDict |
198ded6a JF |
29 | from ipaddr import IPv4Address, IPv6Address |
30 | from nlpacket import * | |
31 | from select import select | |
32 | from struct import pack, unpack | |
198ded6a JF |
33 | import logging |
34 | import os | |
35 | import socket | |
36 | ||
37 | log = logging.getLogger(__name__) | |
38 | ||
39 | ||
40 | class NetlinkError(Exception): | |
41 | pass | |
42 | ||
43 | ||
cee2e13f JF |
44 | class NetlinkNoAddressError(NetlinkError): |
45 | pass | |
46 | ||
47 | ||
48 | class NetlinkInterruptedSystemCall(NetlinkError): | |
198ded6a JF |
49 | pass |
50 | ||
51 | ||
52 | class InvalidInterfaceNameVlanCombo(Exception): | |
53 | pass | |
54 | ||
55 | ||
56 | class Sequence(object): | |
57 | ||
58 | def __init__(self): | |
59 | self._next = 0 | |
60 | ||
61 | def next(self): | |
62 | self._next += 1 | |
63 | return self._next | |
64 | ||
65 | ||
66 | class NetlinkManager(object): | |
67 | ||
d486dd0d | 68 | def __init__(self, pid_offset=0, use_color=True, log_level=None): |
9f25ff0d SE |
69 | # PID_MAX_LIMIT is 2^22 allowing 1024 sockets per-pid. We default to 0 |
70 | # in the upper space (top 10 bits), which will simply be the PID. Other | |
71 | # NetlinkManager instantiations in the same process can choose other | |
72 | # offsets to avoid conflicts with each other. | |
73 | self.pid = os.getpid() | (pid_offset << 22) | |
198ded6a JF |
74 | self.sequence = Sequence() |
75 | self.shutdown_flag = False | |
76 | self.ifindexmap = {} | |
77 | self.tx_socket = None | |
a61d1777 | 78 | self.use_color = use_color |
198ded6a JF |
79 | |
80 | # debugs | |
81 | self.debug = {} | |
82 | self.debug_link(False) | |
83 | self.debug_address(False) | |
84 | self.debug_neighbor(False) | |
85 | self.debug_route(False) | |
d486dd0d JF |
86 | |
87 | if log_level: | |
88 | log.setLevel(log_level) | |
89 | set_log_level(log_level) | |
198ded6a JF |
90 | |
91 | def __str__(self): | |
92 | return 'NetlinkManager' | |
93 | ||
94 | def signal_term_handler(self, signal, frame): | |
95 | log.info("NetlinkManager: Caught SIGTERM") | |
96 | self.shutdown_flag = True | |
97 | ||
98 | def signal_int_handler(self, signal, frame): | |
99 | log.info("NetlinkManager: Caught SIGINT") | |
100 | self.shutdown_flag = True | |
101 | ||
102 | def shutdown(self): | |
103 | if self.tx_socket: | |
104 | self.tx_socket.close() | |
105 | self.tx_socket = None | |
106 | log.info("NetlinkManager: shutdown complete") | |
107 | ||
108 | def _debug_set_clear(self, msg_types, enabled): | |
109 | """ | |
110 | Enable or disable debugs for all msgs_types messages | |
111 | """ | |
112 | ||
113 | for x in msg_types: | |
114 | if enabled: | |
115 | self.debug[x] = True | |
116 | else: | |
117 | if x in self.debug: | |
118 | del self.debug[x] | |
119 | ||
120 | def debug_link(self, enabled): | |
121 | self._debug_set_clear((RTM_NEWLINK, RTM_DELLINK, RTM_GETLINK, RTM_SETLINK), enabled) | |
122 | ||
123 | def debug_address(self, enabled): | |
124 | self._debug_set_clear((RTM_NEWADDR, RTM_DELADDR, RTM_GETADDR), enabled) | |
125 | ||
126 | def debug_neighbor(self, enabled): | |
127 | self._debug_set_clear((RTM_NEWNEIGH, RTM_DELNEIGH, RTM_GETNEIGH), enabled) | |
128 | ||
129 | def debug_route(self, enabled): | |
130 | self._debug_set_clear((RTM_NEWROUTE, RTM_DELROUTE, RTM_GETROUTE), enabled) | |
131 | ||
3479a0c3 JF |
132 | def debug_netconf(self, enabled): |
133 | self._debug_set_clear((RTM_GETNETCONF, RTM_NEWNETCONF), enabled) | |
134 | ||
198ded6a JF |
135 | def debug_this_packet(self, mtype): |
136 | if mtype in self.debug: | |
137 | return True | |
138 | return False | |
139 | ||
140 | def tx_socket_allocate(self): | |
141 | """ | |
142 | The TX socket is used for install requests, sending RTM_GETXXXX | |
143 | requests, etc | |
144 | """ | |
986965c1 JF |
145 | try: |
146 | self.tx_socket = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, 0) | |
147 | ||
148 | # bind retry mechanism: | |
149 | # in some cases we are running into weird issues... Address already in use | |
150 | # to counter this problem, we will retry up to NLMANAGER_BIND_RETRY times to | |
151 | # bind our socket, every time increasing the address (or pid) that we bind it | |
152 | # to. NLMANAGER_BIND_RETRY default to 4242 | |
153 | for i in xrange(0, int(os.getenv("NLMANAGER_BIND_RETRY", 4242))): | |
154 | try: | |
155 | self.tx_socket.bind((self.pid + i, 0)) | |
156 | # the bind call succeeded, we need to update self.pid | |
157 | # to reflect the correct value we are binded to. If we | |
158 | # couldn't bind to our real pid (os.getpid()) warn user | |
159 | # to avoid confusion (via debug logs). | |
160 | if i != 0: | |
161 | log.debug( | |
162 | "nlmanager: pid %s already in use - binding netlink socket to pid %s" | |
163 | % (self.pid, self.pid + i) | |
164 | ) | |
165 | self.pid = self.pid + i | |
166 | return | |
167 | except: | |
168 | pass | |
169 | # if we reach this code it means all our bind calls failed. We are trying to | |
170 | # bind the socket one last time on the original parameters if not we will not | |
171 | # be catching the exception | |
172 | self.tx_socket.bind((self.pid, 0)) | |
173 | except: | |
174 | if self.tx_socket: | |
175 | self.tx_socket.close() | |
176 | self.tx_socket = None | |
177 | raise | |
198ded6a JF |
178 | |
179 | def tx_nlpacket_raw(self, message): | |
180 | """ | |
181 | TX a bunch of concatenated nlpacket.messages....do NOT wait for an ACK | |
182 | """ | |
183 | if not self.tx_socket: | |
184 | self.tx_socket_allocate() | |
185 | self.tx_socket.sendall(message) | |
186 | ||
187 | def tx_nlpacket(self, nlpacket): | |
188 | """ | |
189 | TX a netlink packet but do NOT wait for an ACK | |
190 | """ | |
191 | if not nlpacket.message: | |
192 | log.error('You must first call build_message() to create the packet') | |
193 | return | |
194 | ||
195 | if not self.tx_socket: | |
196 | self.tx_socket_allocate() | |
197 | self.tx_socket.sendall(nlpacket.message) | |
198 | ||
199 | def tx_nlpacket_get_response(self, nlpacket): | |
200 | ||
201 | if not nlpacket.message: | |
202 | log.error('You must first call build_message() to create the packet') | |
203 | return | |
204 | ||
205 | if not self.tx_socket: | |
206 | self.tx_socket_allocate() | |
207 | self.tx_socket.sendall(nlpacket.message) | |
208 | ||
26d1e82b JF |
209 | # If nlpacket.debug is True we already printed the following in the |
210 | # build_message() call...so avoid printing two messages for one packet. | |
198ded6a | 211 | if not nlpacket.debug: |
cb80e1d3 | 212 | log.debug("TXed %12s, pid %d, seq %d, %d bytes" % |
198ded6a JF |
213 | (nlpacket.get_type_string(), nlpacket.pid, nlpacket.seq, nlpacket.length)) |
214 | ||
215 | header_PACK = NetlinkPacket.header_PACK | |
216 | header_LEN = NetlinkPacket.header_LEN | |
217 | null_read = 0 | |
cee2e13f JF |
218 | nle_intr_count = 0 |
219 | MAX_NULL_READS = 3 | |
220 | MAX_ERROR_NLE_INTR = 3 | |
198ded6a JF |
221 | msgs = [] |
222 | ||
223 | # Now listen to our socket and wait for the reply | |
224 | while True: | |
225 | ||
226 | if self.shutdown_flag: | |
227 | log.info('shutdown flag is True, exiting') | |
228 | return msgs | |
229 | ||
230 | # Only block for 1 second so we can wake up to see if self.shutdown_flag is True | |
cee2e13f JF |
231 | try: |
232 | (readable, writeable, exceptional) = select([self.tx_socket, ], [], [self.tx_socket, ], 1) | |
233 | except Exception as e: | |
234 | # 4 is Interrupted system call | |
235 | if isinstance(e.args, tuple) and e[0] == 4: | |
236 | nle_intr_count += 1 | |
237 | log.info("select() Interrupted system call %d/%d" % (nle_intr_count, MAX_ERROR_NLE_INTR)) | |
238 | ||
239 | if nle_intr_count >= MAX_ERROR_NLE_INTR: | |
240 | raise NetlinkInterruptedSystemCall(error_str) | |
241 | else: | |
242 | continue | |
243 | else: | |
244 | raise | |
198ded6a | 245 | |
cee2e13f JF |
246 | if readable: |
247 | null_read = 0 | |
248 | else: | |
198ded6a JF |
249 | null_read += 1 |
250 | ||
251 | # Safety net to make sure we do not spend too much time in | |
252 | # this while True loop | |
253 | if null_read >= MAX_NULL_READS: | |
cee2e13f | 254 | log.info('Socket was not readable for %d attempts' % null_read) |
198ded6a | 255 | return msgs |
cee2e13f JF |
256 | else: |
257 | continue | |
198ded6a JF |
258 | |
259 | for s in readable: | |
cee2e13f JF |
260 | data = [] |
261 | ||
262 | try: | |
263 | data = s.recv(4096) | |
264 | except Exception as e: | |
265 | # 4 is Interrupted system call | |
266 | if isinstance(e.args, tuple) and e[0] == 4: | |
267 | nle_intr_count += 1 | |
268 | log.info("%s: recv() Interrupted system call %d/%d" % (s, nle_intr_count, MAX_ERROR_NLE_INTR)) | |
269 | ||
270 | if nle_intr_count >= MAX_ERROR_NLE_INTR: | |
271 | raise NetlinkInterruptedSystemCall(error_str) | |
272 | else: | |
273 | continue | |
274 | else: | |
275 | raise | |
198ded6a JF |
276 | |
277 | if not data: | |
278 | log.info('RXed zero length data, the socket is closed') | |
279 | return msgs | |
280 | ||
281 | while data: | |
282 | ||
283 | # Extract the length, etc from the header | |
284 | (length, msgtype, flags, seq, pid) = unpack(header_PACK, data[:header_LEN]) | |
285 | ||
286 | debug_str = "RXed %12s, pid %d, seq %d, %d bytes" % (NetlinkPacket.type_to_string[msgtype], pid, seq, length) | |
287 | ||
288 | # This shouldn't happen but it would be nice to be aware of it if it does | |
289 | if pid != nlpacket.pid: | |
290 | log.debug(debug_str + '...we are not interested in this pid %s since ours is %s' % | |
291 | (pid, nlpacket.pid)) | |
292 | data = data[length:] | |
293 | continue | |
26d1e82b | 294 | |
198ded6a JF |
295 | if seq != nlpacket.seq: |
296 | log.debug(debug_str + '...we are not interested in this seq %s since ours is %s' % | |
297 | (seq, nlpacket.seq)) | |
298 | data = data[length:] | |
299 | continue | |
26d1e82b | 300 | |
198ded6a JF |
301 | # See if we RXed an ACK for our RTM_GETXXXX |
302 | if msgtype == NLMSG_DONE: | |
303 | log.debug(debug_str + '...this is an ACK') | |
304 | return msgs | |
305 | ||
306 | elif msgtype == NLMSG_ERROR: | |
307 | ||
198ded6a JF |
308 | msg = Error(msgtype, nlpacket.debug) |
309 | msg.decode_packet(length, flags, seq, pid, data) | |
310 | ||
d54d014d JF |
311 | # The error code is a signed negative number. |
312 | error_code = abs(msg.negative_errno) | |
313 | ||
198ded6a JF |
314 | # 0 is NLE_SUCCESS...everything else is a true error |
315 | if error_code: | |
19c95699 | 316 | |
d54d014d JF |
317 | if self.debug: |
318 | msg.dump() | |
cee2e13f | 319 | |
d54d014d JF |
320 | try: |
321 | # os.strerror might raise ValueError | |
322 | strerror = os.strerror(error_code) | |
cee2e13f | 323 | |
d54d014d JF |
324 | if strerror: |
325 | error_str = "operation failed with '%s' (%s)" % (strerror, error_code) | |
326 | else: | |
327 | error_str = "operation failed with code %s" % error_code | |
26d1e82b | 328 | |
d54d014d JF |
329 | except ValueError: |
330 | error_str = "operation failed with code %s" % error_code | |
331 | ||
332 | raise NetlinkError(error_str) | |
198ded6a | 333 | else: |
2e04b8c0 | 334 | log.debug('%s code NLE_SUCCESS...this is an ACK' % debug_str) |
198ded6a JF |
335 | return msgs |
336 | ||
337 | # No ACK...create a nlpacket object and append it to msgs | |
338 | else: | |
cee2e13f | 339 | nle_intr_count = 0 |
198ded6a | 340 | |
198ded6a | 341 | if msgtype == RTM_NEWLINK or msgtype == RTM_DELLINK: |
a61d1777 | 342 | msg = Link(msgtype, nlpacket.debug, use_color=self.use_color) |
198ded6a JF |
343 | |
344 | elif msgtype == RTM_NEWADDR or msgtype == RTM_DELADDR: | |
a61d1777 | 345 | msg = Address(msgtype, nlpacket.debug, use_color=self.use_color) |
198ded6a JF |
346 | |
347 | elif msgtype == RTM_NEWNEIGH or msgtype == RTM_DELNEIGH: | |
a61d1777 | 348 | msg = Neighbor(msgtype, nlpacket.debug, use_color=self.use_color) |
198ded6a JF |
349 | |
350 | elif msgtype == RTM_NEWROUTE or msgtype == RTM_DELROUTE: | |
a61d1777 | 351 | msg = Route(msgtype, nlpacket.debug, use_color=self.use_color) |
198ded6a | 352 | |
3479a0c3 JF |
353 | elif msgtype in (RTM_GETNETCONF, RTM_NEWNETCONF): |
354 | msg = Netconf(msgtype, nlpacket.debug, use_color=self.use_color) | |
355 | ||
198ded6a JF |
356 | else: |
357 | raise Exception("RXed unknown netlink message type %s" % msgtype) | |
358 | ||
359 | msg.decode_packet(length, flags, seq, pid, data) | |
360 | msgs.append(msg) | |
361 | ||
26d1e82b JF |
362 | if nlpacket.debug: |
363 | msg.dump() | |
364 | ||
198ded6a JF |
365 | data = data[length:] |
366 | ||
367 | def ip_to_afi(self, ip): | |
368 | type_ip = type(ip) | |
369 | ||
370 | if type_ip == IPv4Address: | |
371 | return socket.AF_INET | |
372 | elif type_ip == IPv6Address: | |
373 | return socket.AF_INET6 | |
374 | else: | |
375 | raise Exception("%s is an invalid IP type" % type_ip) | |
376 | ||
377 | def request_dump(self, rtm_type, family, debug): | |
378 | """ | |
379 | Issue a RTM_GETROUTE, etc with the NLM_F_DUMP flag | |
380 | set and return the results | |
381 | """ | |
382 | ||
383 | if rtm_type == RTM_GETADDR: | |
a61d1777 | 384 | msg = Address(rtm_type, debug, use_color=self.use_color) |
198ded6a JF |
385 | msg.body = pack('Bxxxi', family, 0) |
386 | ||
387 | elif rtm_type == RTM_GETLINK: | |
a61d1777 | 388 | msg = Link(rtm_type, debug, use_color=self.use_color) |
198ded6a JF |
389 | msg.body = pack('Bxxxiii', family, 0, 0, 0) |
390 | ||
391 | elif rtm_type == RTM_GETNEIGH: | |
a61d1777 | 392 | msg = Neighbor(rtm_type, debug, use_color=self.use_color) |
198ded6a JF |
393 | msg.body = pack('Bxxxii', family, 0, 0) |
394 | ||
395 | elif rtm_type == RTM_GETROUTE: | |
a61d1777 | 396 | msg = Route(rtm_type, debug, use_color=self.use_color) |
198ded6a JF |
397 | msg.body = pack('Bxxxii', family, 0, 0) |
398 | ||
399 | else: | |
400 | log.error("request_dump RTM_GET %s is not supported" % rtm_type) | |
401 | return None | |
402 | ||
403 | msg.flags = NLM_F_REQUEST | NLM_F_DUMP | |
404 | msg.attributes = {} | |
405 | msg.build_message(self.sequence.next(), self.pid) | |
406 | return self.tx_nlpacket_get_response(msg) | |
407 | ||
408 | # ====== | |
409 | # Routes | |
410 | # ====== | |
411 | def _routes_add_or_delete(self, add_route, routes, ecmp_routes, table, protocol, route_scope, route_type): | |
412 | ||
413 | def tx_or_concat_message(total_message, route): | |
414 | """ | |
415 | Adding an ipv4 route only takes 60 bytes, if we are adding thousands | |
416 | of them this can add up to a lot of send calls. Concat several of | |
417 | them together before TXing. | |
418 | """ | |
419 | ||
420 | if not total_message: | |
421 | total_message = route.message | |
422 | else: | |
423 | total_message += route.message | |
424 | ||
425 | if len(total_message) >= PACKET_CONCAT_SIZE: | |
426 | self.tx_nlpacket_raw(total_message) | |
427 | total_message = None | |
428 | ||
429 | return total_message | |
430 | ||
431 | if add_route: | |
432 | rtm_command = RTM_NEWROUTE | |
433 | else: | |
434 | rtm_command = RTM_DELROUTE | |
435 | ||
436 | total_message = None | |
437 | PACKET_CONCAT_SIZE = 16384 | |
438 | debug = rtm_command in self.debug | |
439 | ||
440 | if routes: | |
441 | for (afi, ip, mask, nexthop, interface_index) in routes: | |
a61d1777 | 442 | route = Route(rtm_command, debug, use_color=self.use_color) |
198ded6a JF |
443 | route.flags = NLM_F_REQUEST | NLM_F_CREATE |
444 | route.body = pack('BBBBBBBBi', afi, mask, 0, 0, table, protocol, | |
445 | route_scope, route_type, 0) | |
446 | route.family = afi | |
447 | route.add_attribute(Route.RTA_DST, ip) | |
448 | if nexthop: | |
449 | route.add_attribute(Route.RTA_GATEWAY, nexthop) | |
450 | route.add_attribute(Route.RTA_OIF, interface_index) | |
451 | route.build_message(self.sequence.next(), self.pid) | |
452 | total_message = tx_or_concat_message(total_message, route) | |
453 | ||
454 | if total_message: | |
455 | self.tx_nlpacket_raw(total_message) | |
456 | ||
457 | if ecmp_routes: | |
458 | ||
459 | for (route_key, value) in ecmp_routes.iteritems(): | |
460 | (afi, ip, mask) = route_key | |
461 | ||
a61d1777 | 462 | route = Route(rtm_command, debug, use_color=self.use_color) |
198ded6a JF |
463 | route.flags = NLM_F_REQUEST | NLM_F_CREATE |
464 | route.body = pack('BBBBBBBBi', afi, mask, 0, 0, table, protocol, | |
465 | route_scope, route_type, 0) | |
466 | route.family = afi | |
467 | route.add_attribute(Route.RTA_DST, ip) | |
468 | route.add_attribute(Route.RTA_MULTIPATH, value) | |
469 | route.build_message(self.sequence.next(), self.pid) | |
470 | total_message = tx_or_concat_message(total_message, route) | |
471 | ||
472 | if total_message: | |
473 | self.tx_nlpacket_raw(total_message) | |
474 | ||
475 | def routes_add(self, routes, ecmp_routes, | |
476 | table=Route.RT_TABLE_MAIN, | |
477 | protocol=Route.RT_PROT_XORP, | |
478 | route_scope=Route.RT_SCOPE_UNIVERSE, | |
479 | route_type=Route.RTN_UNICAST): | |
480 | self._routes_add_or_delete(True, routes, ecmp_routes, table, protocol, route_scope, route_type) | |
481 | ||
482 | def routes_del(self, routes, ecmp_routes, | |
483 | table=Route.RT_TABLE_MAIN, | |
484 | protocol=Route.RT_PROT_XORP, | |
485 | route_scope=Route.RT_SCOPE_UNIVERSE, | |
486 | route_type=Route.RTN_UNICAST): | |
487 | self._routes_add_or_delete(False, routes, ecmp_routes, table, protocol, route_scope, route_type) | |
488 | ||
489 | def route_get(self, ip, debug=False): | |
490 | """ | |
491 | ip must be one of the following: | |
492 | - IPv4Address | |
493 | - IPv6Address | |
494 | """ | |
495 | # Transmit a RTM_GETROUTE to query for the route we want | |
a61d1777 | 496 | route = Route(RTM_GETROUTE, debug, use_color=self.use_color) |
198ded6a JF |
497 | route.flags = NLM_F_REQUEST | NLM_F_ACK |
498 | ||
499 | # Set everything in the service header as 0 other than the afi | |
500 | afi = self.ip_to_afi(ip) | |
501 | route.body = pack('Bxxxxxxxi', afi, 0) | |
502 | route.family = afi | |
503 | route.add_attribute(Route.RTA_DST, ip) | |
504 | route.build_message(self.sequence.next(), self.pid) | |
505 | return self.tx_nlpacket_get_response(route) | |
506 | ||
507 | def routes_dump(self, family=socket.AF_UNSPEC, debug=True): | |
508 | return self.request_dump(RTM_GETROUTE, family, debug) | |
509 | ||
510 | def routes_print(self, routes): | |
511 | """ | |
d8a846b8 | 512 | Print a table of 'routes' |
198ded6a | 513 | """ |
d8a846b8 | 514 | print "Prefix Nexthop ifindex" |
198ded6a JF |
515 | |
516 | for x in routes: | |
517 | if Route.RTA_DST not in x.attributes: | |
518 | log.warning("Route is missing RTA_DST") | |
519 | continue | |
520 | ||
d8a846b8 JF |
521 | ip = "%s/%d" % (x.attributes[Route.RTA_DST].value, x.src_len) |
522 | print "%-15s %-15s %s" %\ | |
523 | (ip, | |
524 | str(x.attributes[Route.RTA_GATEWAY].value) if Route.RTA_GATEWAY in x.attributes else None, | |
525 | x.attributes[Route.RTA_OIF].value) | |
198ded6a JF |
526 | |
527 | # ===== | |
528 | # Links | |
529 | # ===== | |
530 | def _get_iface_by_name(self, ifname): | |
531 | """ | |
532 | Return a Link object for ifname | |
533 | """ | |
534 | debug = RTM_GETLINK in self.debug | |
535 | ||
a61d1777 | 536 | link = Link(RTM_GETLINK, debug, use_color=self.use_color) |
198ded6a JF |
537 | link.flags = NLM_F_REQUEST | NLM_F_ACK |
538 | link.body = pack('=Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) | |
539 | link.add_attribute(Link.IFLA_IFNAME, ifname) | |
540 | link.build_message(self.sequence.next(), self.pid) | |
541 | ||
542 | try: | |
543 | return self.tx_nlpacket_get_response(link)[0] | |
544 | ||
545 | except NetlinkNoAddressError: | |
546 | log.info("Netlink did not find interface %s" % ifname) | |
547 | return None | |
548 | ||
d486dd0d JF |
549 | def _get_iface_by_index(self, ifindex): |
550 | """ | |
551 | Return a Link object for ifindex | |
552 | """ | |
553 | debug = RTM_GETLINK in self.debug | |
554 | ||
555 | link = Link(RTM_GETLINK, debug, use_color=self.use_color) | |
556 | link.flags = NLM_F_REQUEST | NLM_F_ACK | |
557 | link.body = pack('=Bxxxiii', socket.AF_UNSPEC, ifindex, 0, 0) | |
558 | link.build_message(self.sequence.next(), self.pid) | |
559 | try: | |
560 | return self.tx_nlpacket_get_response(link)[0] | |
561 | except NetlinkNoAddressError: | |
562 | log.info("Netlink did not find interface %s" % ifindex) | |
563 | return None | |
564 | ||
198ded6a JF |
565 | def get_iface_index(self, ifname): |
566 | """ | |
567 | Return the interface index for ifname | |
568 | """ | |
569 | iface = self._get_iface_by_name(ifname) | |
570 | ||
571 | if iface: | |
572 | return iface.ifindex | |
573 | return None | |
574 | ||
d486dd0d JF |
575 | def get_iface_name(self, ifindex): |
576 | iface = self._get_iface_by_index(ifindex) | |
577 | ||
578 | if iface: | |
579 | return iface.attributes[Link.IFLA_IFNAME].get_pretty_value(str) | |
580 | return None | |
581 | ||
582 | def link_dump(self, ifname=None): | |
f8f6549b JF |
583 | debug = RTM_GETLINK in self.debug |
584 | msg = Link(RTM_GETLINK, debug, use_color=self.use_color) | |
d486dd0d JF |
585 | msg.body = pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) |
586 | msg.flags = NLM_F_REQUEST | NLM_F_ACK | |
587 | ||
588 | if ifname: | |
589 | msg.add_attribute(Link.IFLA_IFNAME, ifname) | |
590 | else: | |
591 | msg.flags |= NLM_F_DUMP | |
592 | ||
593 | msg.build_message(self.sequence.next(), self.pid) | |
594 | return self.tx_nlpacket_get_response(msg) | |
595 | ||
596 | def link_set_attrs(self, ifname, kind=None, slave_kind=None, ifindex=0, ifla={}, ifla_info_data={}, ifla_info_slave_data={}): | |
597 | debug = RTM_NEWLINK in self.debug | |
598 | ||
599 | link = Link(RTM_NEWLINK, debug, use_color=self.use_color) | |
600 | link.flags = NLM_F_REQUEST | NLM_F_ACK | |
601 | link.body = pack('Bxxxiii', socket.AF_UNSPEC, ifindex, 0, 0) | |
602 | ||
603 | for nl_attr, value in ifla.items(): | |
604 | link.add_attribute(nl_attr, value) | |
605 | ||
606 | if ifname: | |
607 | link.add_attribute(Link.IFLA_IFNAME, ifname) | |
608 | ||
609 | linkinfo = dict() | |
610 | ||
611 | if kind: | |
612 | linkinfo[Link.IFLA_INFO_KIND] = kind | |
613 | linkinfo[Link.IFLA_INFO_DATA] = ifla_info_data | |
614 | elif slave_kind: | |
615 | linkinfo[Link.IFLA_INFO_SLAVE_KIND] = slave_kind, | |
616 | linkinfo[Link.IFLA_INFO_SLAVE_DATA] = ifla_info_slave_data | |
617 | ||
618 | link.add_attribute(Link.IFLA_LINKINFO, linkinfo) | |
619 | link.build_message(self.sequence.next(), self.pid) | |
620 | return self.tx_nlpacket_get_response(link) | |
621 | ||
622 | def link_add_set(self, kind, | |
623 | ifname=None, | |
624 | ifindex=0, | |
625 | slave_kind=None, | |
626 | ifla={}, | |
627 | ifla_info_data={}, | |
628 | ifla_info_slave_data={}): | |
629 | """ | |
630 | Build and TX a RTM_NEWLINK message to add the desired interface | |
631 | """ | |
632 | debug = RTM_NEWLINK in self.debug | |
633 | ||
634 | link = Link(RTM_NEWLINK, debug, use_color=self.use_color) | |
635 | link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK | |
636 | link.body = pack('Bxxxiii', socket.AF_UNSPEC, ifindex, 0, 0) | |
637 | ||
638 | for nl_attr, value in ifla.items(): | |
639 | link.add_attribute(nl_attr, value) | |
640 | ||
641 | if ifname: | |
642 | link.add_attribute(Link.IFLA_IFNAME, ifname) | |
643 | ||
644 | linkinfo = dict() | |
645 | if kind: | |
646 | linkinfo[Link.IFLA_INFO_KIND] = kind | |
647 | linkinfo[Link.IFLA_INFO_DATA] = ifla_info_data | |
648 | if slave_kind: | |
649 | linkinfo[Link.IFLA_INFO_SLAVE_KIND] = slave_kind | |
650 | linkinfo[Link.IFLA_INFO_SLAVE_DATA] = ifla_info_slave_data | |
651 | link.add_attribute(Link.IFLA_LINKINFO, linkinfo) | |
652 | ||
653 | link.build_message(self.sequence.next(), self.pid) | |
654 | return self.tx_nlpacket_get_response(link) | |
655 | ||
656 | def link_del(self, ifindex=None, ifname=None): | |
657 | if not ifindex and not ifname: | |
658 | raise ValueError('invalid ifindex and/or ifname') | |
659 | ||
660 | if not ifindex: | |
661 | ifindex = self.get_iface_index(ifname) | |
662 | ||
663 | debug = RTM_DELLINK in self.debug | |
664 | ||
665 | link = Link(RTM_DELLINK, debug, use_color=self.use_color) | |
666 | link.flags = NLM_F_REQUEST | NLM_F_ACK | |
667 | link.body = pack('Bxxxiii', socket.AF_UNSPEC, ifindex, 0, 0) | |
668 | link.build_message(self.sequence.next(), self.pid) | |
669 | return self.tx_nlpacket_get_response(link) | |
670 | ||
255ffb1f | 671 | def _link_add(self, ifindex, ifname, kind, ifla_info_data, mtu=None): |
198ded6a JF |
672 | """ |
673 | Build and TX a RTM_NEWLINK message to add the desired interface | |
674 | """ | |
675 | debug = RTM_NEWLINK in self.debug | |
676 | ||
a61d1777 | 677 | link = Link(RTM_NEWLINK, debug, use_color=self.use_color) |
c268fccf | 678 | link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK |
198ded6a JF |
679 | link.body = pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) |
680 | link.add_attribute(Link.IFLA_IFNAME, ifname) | |
d486dd0d JF |
681 | |
682 | if ifindex: | |
683 | link.add_attribute(Link.IFLA_LINK, ifindex) | |
684 | ||
255ffb1f JF |
685 | if mtu: |
686 | link.add_attribute(Link.IFLA_MTU, mtu) | |
687 | ||
198ded6a JF |
688 | link.add_attribute(Link.IFLA_LINKINFO, { |
689 | Link.IFLA_INFO_KIND: kind, | |
690 | Link.IFLA_INFO_DATA: ifla_info_data | |
691 | }) | |
692 | link.build_message(self.sequence.next(), self.pid) | |
c268fccf | 693 | return self.tx_nlpacket_get_response(link) |
198ded6a | 694 | |
255ffb1f JF |
695 | def link_add_bridge(self, ifname, ifla_info_data={}, mtu=None): |
696 | return self._link_add(ifindex=None, ifname=ifname, kind='bridge', ifla_info_data=ifla_info_data, mtu=mtu) | |
d486dd0d JF |
697 | |
698 | def link_add_vlan(self, ifindex, ifname, vlanid, vlan_protocol=None): | |
198ded6a JF |
699 | """ |
700 | ifindex is the index of the parent interface that this sub-interface | |
701 | is being added to | |
702 | """ | |
703 | ||
704 | ''' | |
705 | If you name an interface swp2.17 but assign it to vlan 12, the kernel | |
706 | will return a very misleading NLE_MSG_OVERFLOW error. It only does | |
707 | this check if the ifname uses dot notation. | |
708 | ||
709 | Do this check here so we can provide a more intuitive error | |
710 | ''' | |
711 | if '.' in ifname: | |
712 | ifname_vlanid = int(ifname.split('.')[-1]) | |
713 | ||
714 | if ifname_vlanid != vlanid: | |
715 | raise InvalidInterfaceNameVlanCombo("Interface %s must belong " | |
716 | "to VLAN %d (VLAN %d was requested)" % | |
717 | (ifname, ifname_vlanid, vlanid)) | |
718 | ||
d486dd0d JF |
719 | ifla_info_data = {Link.IFLA_VLAN_ID: vlanid} |
720 | ||
721 | if vlan_protocol: | |
722 | ifla_info_data[Link.IFLA_VLAN_PROTOCOL] = vlan_protocol | |
723 | ||
724 | return self._link_add(ifindex, ifname, 'vlan', ifla_info_data) | |
198ded6a JF |
725 | |
726 | def link_add_macvlan(self, ifindex, ifname): | |
727 | """ | |
728 | ifindex is the index of the parent interface that this sub-interface | |
729 | is being added to | |
730 | """ | |
731 | return self._link_add(ifindex, ifname, 'macvlan', {Link.IFLA_MACVLAN_MODE: Link.MACVLAN_MODE_PRIVATE}) | |
732 | ||
ca42da72 SA |
733 | def link_add_xfrm(self, physdev, xfrm_ifname, xfrm_id): |
734 | """ | |
735 | ifindex is the index of the parent interface that this sub-interface | |
736 | is being added to | |
737 | """ | |
738 | ifla_info_data = { | |
739 | Link.IFLA_XFRM_IF_ID: int(xfrm_id), | |
740 | Link.IFLA_XFRM_LINK: int(physdev) | |
741 | } | |
742 | ||
743 | return self._link_add(ifindex=None, ifname=xfrm_ifname, kind='xfrm', ifla_info_data=ifla_info_data) | |
744 | ||
d486dd0d | 745 | def vlan_get(self, filter_ifindex=None, filter_vlanid=None, compress_vlans=True): |
198ded6a | 746 | """ |
26d1e82b JF |
747 | filter_ifindex should be a tuple if interface indexes, this is a whitelist filter |
748 | filter_vlandid should be a tuple if VLAN IDs, this is a whitelist filter | |
198ded6a | 749 | """ |
26d1e82b | 750 | debug = RTM_GETLINK in self.debug |
198ded6a | 751 | |
a61d1777 | 752 | link = Link(RTM_GETLINK, debug, use_color=self.use_color) |
c4fd4972 | 753 | link.family = AF_BRIDGE |
26d1e82b JF |
754 | link.flags = NLM_F_DUMP | NLM_F_REQUEST |
755 | link.body = pack('Bxxxiii', socket.AF_BRIDGE, 0, 0, 0) | |
198ded6a | 756 | |
26d1e82b JF |
757 | if compress_vlans: |
758 | link.add_attribute(Link.IFLA_EXT_MASK, Link.RTEXT_FILTER_BRVLAN_COMPRESSED) | |
198ded6a | 759 | else: |
26d1e82b JF |
760 | link.add_attribute(Link.IFLA_EXT_MASK, Link.RTEXT_FILTER_BRVLAN) |
761 | ||
762 | link.build_message(self.sequence.next(), self.pid) | |
763 | reply = self.tx_nlpacket_get_response(link) | |
764 | ||
765 | iface_vlans = {} | |
766 | ||
767 | for msg in reply: | |
768 | if msg.family != socket.AF_BRIDGE: | |
769 | continue | |
770 | ||
771 | if filter_ifindex and msg.ifindex not in filter_ifindex: | |
772 | continue | |
773 | ||
774 | ifla_af_spec = msg.get_attribute_value(Link.IFLA_AF_SPEC) | |
775 | ||
776 | if not ifla_af_spec: | |
777 | continue | |
778 | ||
779 | ifname = msg.get_attribute_value(Link.IFLA_IFNAME) | |
780 | ||
781 | ''' | |
782 | Example IFLA_AF_SPEC | |
783 | ||
784 | 20: 0x1c001a00 .... Length 0x001c (28), Type 0x001a (26) IFLA_AF_SPEC | |
785 | 21: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO | |
786 | 22: 0x00000a00 .... | |
787 | 23: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO | |
788 | 24: 0x00001000 .... | |
789 | 25: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO | |
790 | 26: 0x00001400 .... | |
791 | ''' | |
792 | for (x_type, x_value) in ifla_af_spec.iteritems(): | |
793 | if x_type == Link.IFLA_BRIDGE_VLAN_INFO: | |
794 | for (vlan_flag, vlan_id) in x_value: | |
795 | if filter_vlanid is None or vlan_id in filter_vlanid: | |
796 | ||
797 | if ifname not in iface_vlans: | |
798 | iface_vlans[ifname] = [] | |
799 | ||
800 | # We store these in the tuple as (vlan, flag) instead (flag, vlan) | |
801 | # so that we can sort the list of tuples | |
802 | iface_vlans[ifname].append((vlan_id, vlan_flag)) | |
803 | ||
804 | return iface_vlans | |
805 | ||
806 | def vlan_show(self, filter_ifindex=None, filter_vlanid=None, compress_vlans=True): | |
807 | ||
808 | def vlan_flag_to_string(vlan_flag): | |
809 | flag_str = [] | |
810 | if vlan_flag & Link.BRIDGE_VLAN_INFO_PVID: | |
811 | flag_str.append('PVID') | |
812 | ||
813 | if vlan_flag & Link.BRIDGE_VLAN_INFO_UNTAGGED: | |
814 | flag_str.append('Egress Untagged') | |
815 | ||
816 | return ', '.join(flag_str) | |
817 | ||
818 | iface_vlans = self.vlan_get(filter_ifindex, filter_vlanid, compress_vlans) | |
819 | log.debug("iface_vlans:\n%s\n" % pformat(iface_vlans)) | |
820 | range_begin_vlan_id = None | |
821 | range_flag = None | |
822 | ||
823 | print " Interface VLAN Flags" | |
824 | print " ========== ==== =====" | |
825 | ||
826 | for (ifname, vlan_tuples) in sorted(iface_vlans.iteritems()): | |
827 | for (vlan_id, vlan_flag) in sorted(vlan_tuples): | |
828 | ||
829 | if vlan_flag & Link.BRIDGE_VLAN_INFO_RANGE_BEGIN: | |
830 | range_begin_vlan_id = vlan_id | |
831 | range_flag = vlan_flag | |
832 | ||
833 | elif vlan_flag & Link.BRIDGE_VLAN_INFO_RANGE_END: | |
834 | range_flag |= vlan_flag | |
835 | ||
836 | if not range_begin_vlan_id: | |
837 | log.warning("BRIDGE_VLAN_INFO_RANGE_END is %d but we never saw a BRIDGE_VLAN_INFO_RANGE_BEGIN" % vlan_id) | |
838 | range_begin_vlan_id = vlan_id | |
839 | ||
840 | for x in xrange(range_begin_vlan_id, vlan_id + 1): | |
841 | print " %10s %4d %s" % (ifname, x, vlan_flag_to_string(vlan_flag)) | |
842 | ifname = '' | |
843 | ||
844 | range_begin_vlan_id = None | |
845 | range_flag = None | |
846 | ||
847 | else: | |
848 | print " %10s %4d %s" % (ifname, vlan_id, vlan_flag_to_string(vlan_flag)) | |
849 | ifname = '' | |
850 | ||
851 | ||
852 | def vlan_modify(self, msgtype, ifindex, vlanid_start, vlanid_end=None, bridge_self=False, bridge_master=False, pvid=False, untagged=False): | |
853 | """ | |
854 | iproute2 bridge/vlan.c vlan_modify() | |
855 | """ | |
856 | assert msgtype in (RTM_SETLINK, RTM_DELLINK), "Invalid msgtype %s, must be RTM_SETLINK or RTM_DELLINK" % msgtype | |
857 | assert vlanid_start >= 1 and vlanid_start <= 4096, "Invalid VLAN start %s" % vlanid_start | |
858 | ||
859 | if vlanid_end is None: | |
860 | vlanid_end = vlanid_start | |
861 | ||
862 | assert vlanid_end >= 1 and vlanid_end <= 4096, "Invalid VLAN end %s" % vlanid_end | |
863 | assert vlanid_start <= vlanid_end, "Invalid VLAN range %s-%s, start must be <= end" % (vlanid_start, vlanid_end) | |
198ded6a JF |
864 | |
865 | debug = msgtype in self.debug | |
26d1e82b JF |
866 | bridge_flags = 0 |
867 | vlan_info_flags = 0 | |
198ded6a | 868 | |
a61d1777 | 869 | link = Link(msgtype, debug, use_color=self.use_color) |
198ded6a JF |
870 | link.flags = NLM_F_REQUEST | NLM_F_ACK |
871 | link.body = pack('Bxxxiii', socket.AF_BRIDGE, ifindex, 0, 0) | |
26d1e82b JF |
872 | |
873 | if bridge_self: | |
874 | bridge_flags |= Link.BRIDGE_FLAGS_SELF | |
875 | ||
876 | if bridge_master: | |
877 | bridge_flags |= Link.BRIDGE_FLAGS_MASTER | |
878 | ||
879 | if pvid: | |
880 | vlan_info_flags |= Link.BRIDGE_VLAN_INFO_PVID | |
881 | ||
882 | if untagged: | |
883 | vlan_info_flags |= Link.BRIDGE_VLAN_INFO_UNTAGGED | |
884 | ||
885 | ifla_af_spec = OrderedDict() | |
886 | ||
887 | if bridge_flags: | |
888 | ifla_af_spec[Link.IFLA_BRIDGE_FLAGS] = bridge_flags | |
889 | ||
890 | # just one VLAN | |
891 | if vlanid_start == vlanid_end: | |
892 | ifla_af_spec[Link.IFLA_BRIDGE_VLAN_INFO] = [(vlan_info_flags, vlanid_start), ] | |
893 | ||
894 | # a range of VLANs | |
895 | else: | |
896 | ifla_af_spec[Link.IFLA_BRIDGE_VLAN_INFO] = [ | |
897 | (vlan_info_flags | Link.BRIDGE_VLAN_INFO_RANGE_BEGIN, vlanid_start), | |
898 | (vlan_info_flags | Link.BRIDGE_VLAN_INFO_RANGE_END, vlanid_end) | |
899 | ] | |
900 | ||
901 | link.add_attribute(Link.IFLA_AF_SPEC, ifla_af_spec) | |
198ded6a | 902 | link.build_message(self.sequence.next(), self.pid) |
c268fccf | 903 | return self.tx_nlpacket_get_response(link) |
198ded6a | 904 | |
26d1e82b JF |
905 | def link_add_bridge_vlan(self, ifindex, vlanid_start, vlanid_end=None, pvid=False, untagged=False, master=False): |
906 | """ | |
907 | Add VLAN(s) to a bridge interface | |
908 | """ | |
909 | bridge_self = False if master else True | |
910 | self.vlan_modify(RTM_SETLINK, ifindex, vlanid_start, vlanid_end, bridge_self, master, pvid, untagged) | |
198ded6a | 911 | |
26d1e82b JF |
912 | def link_del_bridge_vlan(self, ifindex, vlanid_start, vlanid_end=None, pvid=False, untagged=False, master=False): |
913 | """ | |
914 | Delete VLAN(s) from a bridge interface | |
915 | """ | |
916 | bridge_self = False if master else True | |
917 | self.vlan_modify(RTM_DELLINK, ifindex, vlanid_start, vlanid_end, bridge_self, master, pvid, untagged) | |
198ded6a JF |
918 | |
919 | def link_set_updown(self, ifname, state): | |
920 | """ | |
921 | Either bring ifname up or take it down | |
922 | """ | |
923 | ||
924 | if state == 'up': | |
925 | if_flags = Link.IFF_UP | |
926 | elif state == 'down': | |
927 | if_flags = 0 | |
928 | else: | |
929 | raise Exception('Unsupported state %s, valid options are "up" and "down"' % state) | |
930 | ||
931 | debug = RTM_NEWLINK in self.debug | |
932 | if_change = Link.IFF_UP | |
933 | ||
a61d1777 | 934 | link = Link(RTM_NEWLINK, debug, use_color=self.use_color) |
c268fccf | 935 | link.flags = NLM_F_REQUEST | NLM_F_ACK |
198ded6a JF |
936 | link.body = pack('=BxxxiLL', socket.AF_UNSPEC, 0, if_flags, if_change) |
937 | link.add_attribute(Link.IFLA_IFNAME, ifname) | |
938 | link.build_message(self.sequence.next(), self.pid) | |
c268fccf | 939 | return self.tx_nlpacket_get_response(link) |
198ded6a JF |
940 | |
941 | def link_set_protodown(self, ifname, state): | |
942 | """ | |
943 | Either bring ifname up or take it down by setting IFLA_PROTO_DOWN on or off | |
944 | """ | |
945 | flags = 0 | |
946 | protodown = 1 if state == "on" else 0 | |
947 | ||
948 | debug = RTM_NEWLINK in self.debug | |
949 | ||
a61d1777 | 950 | link = Link(RTM_NEWLINK, debug, use_color=self.use_color) |
c268fccf | 951 | link.flags = NLM_F_REQUEST | NLM_F_ACK |
198ded6a JF |
952 | link.body = pack('=BxxxiLL', socket.AF_UNSPEC, 0, 0, 0) |
953 | link.add_attribute(Link.IFLA_IFNAME, ifname) | |
954 | link.add_attribute(Link.IFLA_PROTO_DOWN, protodown) | |
955 | link.build_message(self.sequence.next(), self.pid) | |
c268fccf | 956 | return self.tx_nlpacket_get_response(link) |
198ded6a | 957 | |
282b84af JF |
958 | def link_set_master(self, ifname, master_ifindex=0, state=None): |
959 | """ | |
960 | ip link set %ifname master %master_ifindex %state | |
961 | use master_ifindex=0 for nomaster | |
962 | """ | |
963 | if state == 'up': | |
964 | if_change = Link.IFF_UP | |
965 | if_flags = Link.IFF_UP | |
966 | elif state == 'down': | |
967 | if_change = Link.IFF_UP | |
968 | if_flags = 0 | |
969 | else: | |
970 | if_change = 0 | |
971 | if_flags = 0 | |
972 | ||
973 | debug = RTM_NEWLINK in self.debug | |
974 | ||
975 | link = Link(RTM_NEWLINK, debug, use_color=self.use_color) | |
976 | link.flags = NLM_F_REQUEST | NLM_F_ACK | |
977 | link.body = pack('=BxxxiLL', socket.AF_UNSPEC, 0, if_flags, if_change) | |
978 | link.add_attribute(Link.IFLA_IFNAME, ifname) | |
979 | link.add_attribute(Link.IFLA_MASTER, master_ifindex) | |
980 | link.build_message(self.sequence.next(), self.pid) | |
981 | return self.tx_nlpacket_get_response(link) | |
982 | ||
198ded6a JF |
983 | # ========= |
984 | # Neighbors | |
985 | # ========= | |
986 | def neighbor_add(self, afi, ifindex, ip, mac): | |
987 | debug = RTM_NEWNEIGH in self.debug | |
988 | service_hdr_flags = 0 | |
989 | ||
a61d1777 | 990 | nbr = Neighbor(RTM_NEWNEIGH, debug, use_color=self.use_color) |
c268fccf | 991 | nbr.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK |
198ded6a JF |
992 | nbr.family = afi |
993 | nbr.body = pack('=BxxxiHBB', afi, ifindex, Neighbor.NUD_REACHABLE, service_hdr_flags, Route.RTN_UNICAST) | |
994 | nbr.add_attribute(Neighbor.NDA_DST, ip) | |
995 | nbr.add_attribute(Neighbor.NDA_LLADDR, mac) | |
996 | nbr.build_message(self.sequence.next(), self.pid) | |
c268fccf | 997 | return self.tx_nlpacket_get_response(nbr) |
198ded6a JF |
998 | |
999 | def neighbor_del(self, afi, ifindex, ip, mac): | |
1000 | debug = RTM_DELNEIGH in self.debug | |
1001 | service_hdr_flags = 0 | |
1002 | ||
a61d1777 | 1003 | nbr = Neighbor(RTM_DELNEIGH, debug, use_color=self.use_color) |
c268fccf | 1004 | nbr.flags = NLM_F_REQUEST | NLM_F_ACK |
198ded6a JF |
1005 | nbr.family = afi |
1006 | nbr.body = pack('=BxxxiHBB', afi, ifindex, Neighbor.NUD_REACHABLE, service_hdr_flags, Route.RTN_UNICAST) | |
1007 | nbr.add_attribute(Neighbor.NDA_DST, ip) | |
1008 | nbr.add_attribute(Neighbor.NDA_LLADDR, mac) | |
1009 | nbr.build_message(self.sequence.next(), self.pid) | |
c268fccf | 1010 | return self.tx_nlpacket_get_response(nbr) |
3696839d JF |
1011 | |
1012 | def link_add_vxlan(self, ifname, vxlanid, dstport=None, local=None, | |
ec25a08c | 1013 | group=None, learning=True, ageing=None, physdev=None, ttl=None): |
3696839d JF |
1014 | |
1015 | debug = RTM_NEWLINK in self.debug | |
1016 | ||
1017 | info_data = {Link.IFLA_VXLAN_ID: int(vxlanid)} | |
1018 | if dstport: | |
1019 | info_data[Link.IFLA_VXLAN_PORT] = int(dstport) | |
1020 | if local: | |
1021 | info_data[Link.IFLA_VXLAN_LOCAL] = local | |
1022 | if group: | |
1023 | info_data[Link.IFLA_VXLAN_GROUP] = group | |
1024 | ||
d486dd0d | 1025 | info_data[Link.IFLA_VXLAN_LEARNING] = int(learning) |
ec25a08c | 1026 | info_data[Link.IFLA_VXLAN_TTL] = ttl |
3696839d | 1027 | |
3696839d JF |
1028 | if ageing: |
1029 | info_data[Link.IFLA_VXLAN_AGEING] = int(ageing) | |
1030 | ||
a382b488 JF |
1031 | if physdev: |
1032 | info_data[Link.IFLA_VXLAN_LINK] = int(physdev) | |
1033 | ||
a61d1777 | 1034 | link = Link(RTM_NEWLINK, debug, use_color=self.use_color) |
c268fccf | 1035 | link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK |
3696839d JF |
1036 | link.body = pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) |
1037 | link.add_attribute(Link.IFLA_IFNAME, ifname) | |
3696839d JF |
1038 | link.add_attribute(Link.IFLA_LINKINFO, { |
1039 | Link.IFLA_INFO_KIND: "vxlan", | |
1040 | Link.IFLA_INFO_DATA: info_data | |
1041 | }) | |
1042 | ||
1043 | link.build_message(self.sequence.next(), self.pid) | |
c268fccf | 1044 | return self.tx_nlpacket_get_response(link) |
d486dd0d JF |
1045 | |
1046 | # ========= | |
1047 | # Addresses | |
1048 | # ========= | |
1049 | def addr_dump(self): | |
1050 | """ | |
1051 | TODO: add ifname/ifindex filtering: | |
1052 | - via the RTM_GETADDR request packet | |
1053 | - or in python if kernel doesn't support per intf dump | |
1054 | """ | |
1055 | debug = RTM_GETADDR in self.debug | |
1056 | ||
1057 | msg = Address(RTM_GETADDR, debug, use_color=self.use_color) | |
1058 | msg.body = pack('=Bxxxi', socket.AF_UNSPEC, 0) | |
1059 | msg.flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP | |
1060 | ||
1061 | msg.build_message(self.sequence.next(), self.pid) | |
1062 | return self.tx_nlpacket_get_response(msg) | |
3479a0c3 JF |
1063 | |
1064 | # ======= | |
1065 | # Netconf | |
1066 | # ======= | |
1067 | def netconf_dump(self): | |
1068 | """ | |
1069 | The attribute Netconf.NETCONFA_IFINDEX is available but don't let it fool you | |
1070 | it seems like the kernel doesn't really care about this attribute and will dump | |
1071 | everything according of the requested family (AF_UNSPEC for everything). | |
1072 | Device filtering needs to be done afterwards by the user. | |
1073 | """ | |
1074 | debug = RTM_GETNETCONF in self.debug | |
1075 | msg = Netconf(RTM_GETNETCONF, debug, use_color=self.use_color) | |
1076 | msg.body = pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) | |
1077 | msg.flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK | |
1078 | msg.build_message(self.sequence.next(), self.pid) | |
1079 | return self.tx_nlpacket_get_response(msg) |