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