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