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