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