]> git.proxmox.com Git - mirror_ifupdown2.git/blame - nlmanager/nlmanager.py
Merge pull request #80 from BarbarossaTM/tunnel-fixes-master
[mirror_ifupdown2.git] / nlmanager / nlmanager.py
CommitLineData
198ded6a
JF
1#!/usr/bin/env python
2
26d1e82b 3from collections import OrderedDict
198ded6a
JF
4from ipaddr import IPv4Address, IPv6Address
5from nlpacket import *
6from select import select
7from struct import pack, unpack
198ded6a
JF
8import logging
9import os
10import socket
11
12log = logging.getLogger(__name__)
13
14
15class NetlinkError(Exception):
16 pass
17
18
cee2e13f
JF
19class NetlinkNoAddressError(NetlinkError):
20 pass
21
22
23class NetlinkInterruptedSystemCall(NetlinkError):
198ded6a
JF
24 pass
25
26
27class InvalidInterfaceNameVlanCombo(Exception):
28 pass
29
30
31class Sequence(object):
32
33 def __init__(self):
34 self._next = 0
35
36 def next(self):
37 self._next += 1
38 return self._next
39
40
41class NetlinkManager(object):
42
711d7575 43 def __init__(self, pid_offset=0, use_color=True, extra_debug=False):
9f25ff0d
SE
44 # PID_MAX_LIMIT is 2^22 allowing 1024 sockets per-pid. We default to 0
45 # in the upper space (top 10 bits), which will simply be the PID. Other
46 # NetlinkManager instantiations in the same process can choose other
47 # offsets to avoid conflicts with each other.
48 self.pid = os.getpid() | (pid_offset << 22)
198ded6a
JF
49 self.sequence = Sequence()
50 self.shutdown_flag = False
51 self.ifindexmap = {}
52 self.tx_socket = None
a61d1777 53 self.use_color = use_color
198ded6a
JF
54
55 # debugs
56 self.debug = {}
57 self.debug_link(False)
58 self.debug_address(False)
59 self.debug_neighbor(False)
60 self.debug_route(False)
711d7575 61 set_extra_debug(extra_debug)
198ded6a
JF
62
63 def __str__(self):
64 return 'NetlinkManager'
65
66 def signal_term_handler(self, signal, frame):
67 log.info("NetlinkManager: Caught SIGTERM")
68 self.shutdown_flag = True
69
70 def signal_int_handler(self, signal, frame):
71 log.info("NetlinkManager: Caught SIGINT")
72 self.shutdown_flag = True
73
74 def shutdown(self):
75 if self.tx_socket:
76 self.tx_socket.close()
77 self.tx_socket = None
78 log.info("NetlinkManager: shutdown complete")
79
80 def _debug_set_clear(self, msg_types, enabled):
81 """
82 Enable or disable debugs for all msgs_types messages
83 """
84
85 for x in msg_types:
86 if enabled:
87 self.debug[x] = True
88 else:
89 if x in self.debug:
90 del self.debug[x]
91
92 def debug_link(self, enabled):
93 self._debug_set_clear((RTM_NEWLINK, RTM_DELLINK, RTM_GETLINK, RTM_SETLINK), enabled)
94
95 def debug_address(self, enabled):
96 self._debug_set_clear((RTM_NEWADDR, RTM_DELADDR, RTM_GETADDR), enabled)
97
98 def debug_neighbor(self, enabled):
99 self._debug_set_clear((RTM_NEWNEIGH, RTM_DELNEIGH, RTM_GETNEIGH), enabled)
100
101 def debug_route(self, enabled):
102 self._debug_set_clear((RTM_NEWROUTE, RTM_DELROUTE, RTM_GETROUTE), enabled)
103
104 def debug_this_packet(self, mtype):
105 if mtype in self.debug:
106 return True
107 return False
108
109 def tx_socket_allocate(self):
110 """
111 The TX socket is used for install requests, sending RTM_GETXXXX
112 requests, etc
113 """
114 self.tx_socket = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, 0)
115 self.tx_socket.bind((self.pid, 0))
116
117 def tx_nlpacket_raw(self, message):
118 """
119 TX a bunch of concatenated nlpacket.messages....do NOT wait for an ACK
120 """
121 if not self.tx_socket:
122 self.tx_socket_allocate()
123 self.tx_socket.sendall(message)
124
125 def tx_nlpacket(self, nlpacket):
126 """
127 TX a netlink packet but do NOT wait for an ACK
128 """
129 if not nlpacket.message:
130 log.error('You must first call build_message() to create the packet')
131 return
132
133 if not self.tx_socket:
134 self.tx_socket_allocate()
135 self.tx_socket.sendall(nlpacket.message)
136
137 def tx_nlpacket_get_response(self, nlpacket):
138
139 if not nlpacket.message:
140 log.error('You must first call build_message() to create the packet')
141 return
142
143 if not self.tx_socket:
144 self.tx_socket_allocate()
145 self.tx_socket.sendall(nlpacket.message)
146
26d1e82b
JF
147 # If nlpacket.debug is True we already printed the following in the
148 # build_message() call...so avoid printing two messages for one packet.
198ded6a 149 if not nlpacket.debug:
cb80e1d3 150 log.debug("TXed %12s, pid %d, seq %d, %d bytes" %
198ded6a
JF
151 (nlpacket.get_type_string(), nlpacket.pid, nlpacket.seq, nlpacket.length))
152
153 header_PACK = NetlinkPacket.header_PACK
154 header_LEN = NetlinkPacket.header_LEN
155 null_read = 0
cee2e13f
JF
156 nle_intr_count = 0
157 MAX_NULL_READS = 3
158 MAX_ERROR_NLE_INTR = 3
198ded6a
JF
159 msgs = []
160
161 # Now listen to our socket and wait for the reply
162 while True:
163
164 if self.shutdown_flag:
165 log.info('shutdown flag is True, exiting')
166 return msgs
167
168 # Only block for 1 second so we can wake up to see if self.shutdown_flag is True
cee2e13f
JF
169 try:
170 (readable, writeable, exceptional) = select([self.tx_socket, ], [], [self.tx_socket, ], 1)
171 except Exception as e:
172 # 4 is Interrupted system call
173 if isinstance(e.args, tuple) and e[0] == 4:
174 nle_intr_count += 1
175 log.info("select() Interrupted system call %d/%d" % (nle_intr_count, MAX_ERROR_NLE_INTR))
176
177 if nle_intr_count >= MAX_ERROR_NLE_INTR:
178 raise NetlinkInterruptedSystemCall(error_str)
179 else:
180 continue
181 else:
182 raise
198ded6a 183
cee2e13f
JF
184 if readable:
185 null_read = 0
186 else:
198ded6a
JF
187 null_read += 1
188
189 # Safety net to make sure we do not spend too much time in
190 # this while True loop
191 if null_read >= MAX_NULL_READS:
cee2e13f 192 log.info('Socket was not readable for %d attempts' % null_read)
198ded6a 193 return msgs
cee2e13f
JF
194 else:
195 continue
198ded6a
JF
196
197 for s in readable:
cee2e13f
JF
198 data = []
199
200 try:
201 data = s.recv(4096)
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("%s: recv() Interrupted system call %d/%d" % (s, 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
198ded6a
JF
214
215 if not data:
216 log.info('RXed zero length data, the socket is closed')
217 return msgs
218
219 while data:
220
221 # Extract the length, etc from the header
222 (length, msgtype, flags, seq, pid) = unpack(header_PACK, data[:header_LEN])
223
224 debug_str = "RXed %12s, pid %d, seq %d, %d bytes" % (NetlinkPacket.type_to_string[msgtype], pid, seq, length)
225
226 # This shouldn't happen but it would be nice to be aware of it if it does
227 if pid != nlpacket.pid:
228 log.debug(debug_str + '...we are not interested in this pid %s since ours is %s' %
229 (pid, nlpacket.pid))
230 data = data[length:]
231 continue
26d1e82b 232
198ded6a
JF
233 if seq != nlpacket.seq:
234 log.debug(debug_str + '...we are not interested in this seq %s since ours is %s' %
235 (seq, nlpacket.seq))
236 data = data[length:]
237 continue
26d1e82b 238
198ded6a
JF
239 # See if we RXed an ACK for our RTM_GETXXXX
240 if msgtype == NLMSG_DONE:
241 log.debug(debug_str + '...this is an ACK')
242 return msgs
243
244 elif msgtype == NLMSG_ERROR:
245
246 # The error code is a signed negative number.
247 error_code = abs(unpack('=i', data[header_LEN:header_LEN+4])[0])
248 msg = Error(msgtype, nlpacket.debug)
249 msg.decode_packet(length, flags, seq, pid, data)
250
198ded6a
JF
251 # 0 is NLE_SUCCESS...everything else is a true error
252 if error_code:
2e04b8c0 253 error_code_str = msg.error_to_string.get(error_code)
19c95699
JF
254 if error_code_str:
255 error_str = 'Operation failed with \'%s\'' % error_code_str
256 else:
257 error_str = 'Operation failed with code %s' % error_code
258
259 log.debug(debug_str)
cee2e13f 260
198ded6a 261 if error_code == Error.NLE_NOADDR:
2e04b8c0 262 raise NetlinkNoAddressError(error_str)
cee2e13f
JF
263 elif error_code == Error.NLE_INTR:
264 nle_intr_count += 1
19c95699 265 log.debug("%s: RXed NLE_INTR Interrupted system call %d/%d" % (s, nle_intr_count, MAX_ERROR_NLE_INTR))
cee2e13f
JF
266
267 if nle_intr_count >= MAX_ERROR_NLE_INTR:
268 raise NetlinkInterruptedSystemCall(error_str)
26d1e82b 269
198ded6a 270 else:
26d1e82b 271 msg.dump()
19c95699
JF
272 if not error_code_str:
273 try:
274 # os.strerror might raise ValueError
275 strerror = os.strerror(error_code)
276 if strerror:
277 raise NetlinkError('Operation failed with \'%s\'' % strerror)
278 else:
279 raise NetlinkError(error_str)
280 except ValueError:
281 pass
2e04b8c0 282 raise NetlinkError(error_str)
198ded6a 283 else:
2e04b8c0 284 log.debug('%s code NLE_SUCCESS...this is an ACK' % debug_str)
198ded6a
JF
285 return msgs
286
287 # No ACK...create a nlpacket object and append it to msgs
288 else:
cee2e13f 289 nle_intr_count = 0
198ded6a 290
198ded6a 291 if msgtype == RTM_NEWLINK or msgtype == RTM_DELLINK:
a61d1777 292 msg = Link(msgtype, nlpacket.debug, use_color=self.use_color)
198ded6a
JF
293
294 elif msgtype == RTM_NEWADDR or msgtype == RTM_DELADDR:
a61d1777 295 msg = Address(msgtype, nlpacket.debug, use_color=self.use_color)
198ded6a
JF
296
297 elif msgtype == RTM_NEWNEIGH or msgtype == RTM_DELNEIGH:
a61d1777 298 msg = Neighbor(msgtype, nlpacket.debug, use_color=self.use_color)
198ded6a
JF
299
300 elif msgtype == RTM_NEWROUTE or msgtype == RTM_DELROUTE:
a61d1777 301 msg = Route(msgtype, nlpacket.debug, use_color=self.use_color)
198ded6a
JF
302
303 else:
304 raise Exception("RXed unknown netlink message type %s" % msgtype)
305
306 msg.decode_packet(length, flags, seq, pid, data)
307 msgs.append(msg)
308
26d1e82b
JF
309 if nlpacket.debug:
310 msg.dump()
311
198ded6a
JF
312 data = data[length:]
313
314 def ip_to_afi(self, ip):
315 type_ip = type(ip)
316
317 if type_ip == IPv4Address:
318 return socket.AF_INET
319 elif type_ip == IPv6Address:
320 return socket.AF_INET6
321 else:
322 raise Exception("%s is an invalid IP type" % type_ip)
323
324 def request_dump(self, rtm_type, family, debug):
325 """
326 Issue a RTM_GETROUTE, etc with the NLM_F_DUMP flag
327 set and return the results
328 """
329
330 if rtm_type == RTM_GETADDR:
a61d1777 331 msg = Address(rtm_type, debug, use_color=self.use_color)
198ded6a
JF
332 msg.body = pack('Bxxxi', family, 0)
333
334 elif rtm_type == RTM_GETLINK:
a61d1777 335 msg = Link(rtm_type, debug, use_color=self.use_color)
198ded6a
JF
336 msg.body = pack('Bxxxiii', family, 0, 0, 0)
337
338 elif rtm_type == RTM_GETNEIGH:
a61d1777 339 msg = Neighbor(rtm_type, debug, use_color=self.use_color)
198ded6a
JF
340 msg.body = pack('Bxxxii', family, 0, 0)
341
342 elif rtm_type == RTM_GETROUTE:
a61d1777 343 msg = Route(rtm_type, debug, use_color=self.use_color)
198ded6a
JF
344 msg.body = pack('Bxxxii', family, 0, 0)
345
346 else:
347 log.error("request_dump RTM_GET %s is not supported" % rtm_type)
348 return None
349
350 msg.flags = NLM_F_REQUEST | NLM_F_DUMP
351 msg.attributes = {}
352 msg.build_message(self.sequence.next(), self.pid)
353 return self.tx_nlpacket_get_response(msg)
354
355 # ======
356 # Routes
357 # ======
358 def _routes_add_or_delete(self, add_route, routes, ecmp_routes, table, protocol, route_scope, route_type):
359
360 def tx_or_concat_message(total_message, route):
361 """
362 Adding an ipv4 route only takes 60 bytes, if we are adding thousands
363 of them this can add up to a lot of send calls. Concat several of
364 them together before TXing.
365 """
366
367 if not total_message:
368 total_message = route.message
369 else:
370 total_message += route.message
371
372 if len(total_message) >= PACKET_CONCAT_SIZE:
373 self.tx_nlpacket_raw(total_message)
374 total_message = None
375
376 return total_message
377
378 if add_route:
379 rtm_command = RTM_NEWROUTE
380 else:
381 rtm_command = RTM_DELROUTE
382
383 total_message = None
384 PACKET_CONCAT_SIZE = 16384
385 debug = rtm_command in self.debug
386
387 if routes:
388 for (afi, ip, mask, nexthop, interface_index) in routes:
a61d1777 389 route = Route(rtm_command, debug, use_color=self.use_color)
198ded6a
JF
390 route.flags = NLM_F_REQUEST | NLM_F_CREATE
391 route.body = pack('BBBBBBBBi', afi, mask, 0, 0, table, protocol,
392 route_scope, route_type, 0)
393 route.family = afi
394 route.add_attribute(Route.RTA_DST, ip)
395 if nexthop:
396 route.add_attribute(Route.RTA_GATEWAY, nexthop)
397 route.add_attribute(Route.RTA_OIF, interface_index)
398 route.build_message(self.sequence.next(), self.pid)
399 total_message = tx_or_concat_message(total_message, route)
400
401 if total_message:
402 self.tx_nlpacket_raw(total_message)
403
404 if ecmp_routes:
405
406 for (route_key, value) in ecmp_routes.iteritems():
407 (afi, ip, mask) = route_key
408
a61d1777 409 route = Route(rtm_command, debug, use_color=self.use_color)
198ded6a
JF
410 route.flags = NLM_F_REQUEST | NLM_F_CREATE
411 route.body = pack('BBBBBBBBi', afi, mask, 0, 0, table, protocol,
412 route_scope, route_type, 0)
413 route.family = afi
414 route.add_attribute(Route.RTA_DST, ip)
415 route.add_attribute(Route.RTA_MULTIPATH, value)
416 route.build_message(self.sequence.next(), self.pid)
417 total_message = tx_or_concat_message(total_message, route)
418
419 if total_message:
420 self.tx_nlpacket_raw(total_message)
421
422 def routes_add(self, routes, ecmp_routes,
423 table=Route.RT_TABLE_MAIN,
424 protocol=Route.RT_PROT_XORP,
425 route_scope=Route.RT_SCOPE_UNIVERSE,
426 route_type=Route.RTN_UNICAST):
427 self._routes_add_or_delete(True, routes, ecmp_routes, table, protocol, route_scope, route_type)
428
429 def routes_del(self, routes, ecmp_routes,
430 table=Route.RT_TABLE_MAIN,
431 protocol=Route.RT_PROT_XORP,
432 route_scope=Route.RT_SCOPE_UNIVERSE,
433 route_type=Route.RTN_UNICAST):
434 self._routes_add_or_delete(False, routes, ecmp_routes, table, protocol, route_scope, route_type)
435
436 def route_get(self, ip, debug=False):
437 """
438 ip must be one of the following:
439 - IPv4Address
440 - IPv6Address
441 """
442 # Transmit a RTM_GETROUTE to query for the route we want
a61d1777 443 route = Route(RTM_GETROUTE, debug, use_color=self.use_color)
198ded6a
JF
444 route.flags = NLM_F_REQUEST | NLM_F_ACK
445
446 # Set everything in the service header as 0 other than the afi
447 afi = self.ip_to_afi(ip)
448 route.body = pack('Bxxxxxxxi', afi, 0)
449 route.family = afi
450 route.add_attribute(Route.RTA_DST, ip)
451 route.build_message(self.sequence.next(), self.pid)
452 return self.tx_nlpacket_get_response(route)
453
454 def routes_dump(self, family=socket.AF_UNSPEC, debug=True):
455 return self.request_dump(RTM_GETROUTE, family, debug)
456
457 def routes_print(self, routes):
458 """
d8a846b8 459 Print a table of 'routes'
198ded6a 460 """
d8a846b8 461 print "Prefix Nexthop ifindex"
198ded6a
JF
462
463 for x in routes:
464 if Route.RTA_DST not in x.attributes:
465 log.warning("Route is missing RTA_DST")
466 continue
467
d8a846b8
JF
468 ip = "%s/%d" % (x.attributes[Route.RTA_DST].value, x.src_len)
469 print "%-15s %-15s %s" %\
470 (ip,
471 str(x.attributes[Route.RTA_GATEWAY].value) if Route.RTA_GATEWAY in x.attributes else None,
472 x.attributes[Route.RTA_OIF].value)
198ded6a
JF
473
474 # =====
475 # Links
476 # =====
477 def _get_iface_by_name(self, ifname):
478 """
479 Return a Link object for ifname
480 """
481 debug = RTM_GETLINK in self.debug
482
a61d1777 483 link = Link(RTM_GETLINK, debug, use_color=self.use_color)
198ded6a
JF
484 link.flags = NLM_F_REQUEST | NLM_F_ACK
485 link.body = pack('=Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
486 link.add_attribute(Link.IFLA_IFNAME, ifname)
487 link.build_message(self.sequence.next(), self.pid)
488
489 try:
490 return self.tx_nlpacket_get_response(link)[0]
491
492 except NetlinkNoAddressError:
493 log.info("Netlink did not find interface %s" % ifname)
494 return None
495
496 def get_iface_index(self, ifname):
497 """
498 Return the interface index for ifname
499 """
500 iface = self._get_iface_by_name(ifname)
501
502 if iface:
503 return iface.ifindex
504 return None
505
506 def _link_add(self, ifindex, ifname, kind, ifla_info_data):
507 """
508 Build and TX a RTM_NEWLINK message to add the desired interface
509 """
510 debug = RTM_NEWLINK in self.debug
511
a61d1777 512 link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
c268fccf 513 link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
198ded6a
JF
514 link.body = pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
515 link.add_attribute(Link.IFLA_IFNAME, ifname)
516 link.add_attribute(Link.IFLA_LINK, ifindex)
517 link.add_attribute(Link.IFLA_LINKINFO, {
518 Link.IFLA_INFO_KIND: kind,
519 Link.IFLA_INFO_DATA: ifla_info_data
520 })
521 link.build_message(self.sequence.next(), self.pid)
c268fccf 522 return self.tx_nlpacket_get_response(link)
198ded6a
JF
523
524 def link_add_vlan(self, ifindex, ifname, vlanid):
525 """
526 ifindex is the index of the parent interface that this sub-interface
527 is being added to
528 """
529
530 '''
531 If you name an interface swp2.17 but assign it to vlan 12, the kernel
532 will return a very misleading NLE_MSG_OVERFLOW error. It only does
533 this check if the ifname uses dot notation.
534
535 Do this check here so we can provide a more intuitive error
536 '''
537 if '.' in ifname:
538 ifname_vlanid = int(ifname.split('.')[-1])
539
540 if ifname_vlanid != vlanid:
541 raise InvalidInterfaceNameVlanCombo("Interface %s must belong "
542 "to VLAN %d (VLAN %d was requested)" %
543 (ifname, ifname_vlanid, vlanid))
544
545 return self._link_add(ifindex, ifname, 'vlan', {Link.IFLA_VLAN_ID: vlanid})
546
547 def link_add_macvlan(self, ifindex, ifname):
548 """
549 ifindex is the index of the parent interface that this sub-interface
550 is being added to
551 """
552 return self._link_add(ifindex, ifname, 'macvlan', {Link.IFLA_MACVLAN_MODE: Link.MACVLAN_MODE_PRIVATE})
553
26d1e82b 554 def vlan_get(self, filter_ifindex=(), filter_vlanid=(), compress_vlans=True):
198ded6a 555 """
26d1e82b
JF
556 filter_ifindex should be a tuple if interface indexes, this is a whitelist filter
557 filter_vlandid should be a tuple if VLAN IDs, this is a whitelist filter
198ded6a 558 """
26d1e82b 559 debug = RTM_GETLINK in self.debug
198ded6a 560
a61d1777 561 link = Link(RTM_GETLINK, debug, use_color=self.use_color)
c4fd4972 562 link.family = AF_BRIDGE
26d1e82b
JF
563 link.flags = NLM_F_DUMP | NLM_F_REQUEST
564 link.body = pack('Bxxxiii', socket.AF_BRIDGE, 0, 0, 0)
198ded6a 565
26d1e82b
JF
566 if compress_vlans:
567 link.add_attribute(Link.IFLA_EXT_MASK, Link.RTEXT_FILTER_BRVLAN_COMPRESSED)
198ded6a 568 else:
26d1e82b
JF
569 link.add_attribute(Link.IFLA_EXT_MASK, Link.RTEXT_FILTER_BRVLAN)
570
571 link.build_message(self.sequence.next(), self.pid)
572 reply = self.tx_nlpacket_get_response(link)
573
574 iface_vlans = {}
575
576 for msg in reply:
577 if msg.family != socket.AF_BRIDGE:
578 continue
579
580 if filter_ifindex and msg.ifindex not in filter_ifindex:
581 continue
582
583 ifla_af_spec = msg.get_attribute_value(Link.IFLA_AF_SPEC)
584
585 if not ifla_af_spec:
586 continue
587
588 ifname = msg.get_attribute_value(Link.IFLA_IFNAME)
589
590 '''
591 Example IFLA_AF_SPEC
592
593 20: 0x1c001a00 .... Length 0x001c (28), Type 0x001a (26) IFLA_AF_SPEC
594 21: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO
595 22: 0x00000a00 ....
596 23: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO
597 24: 0x00001000 ....
598 25: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO
599 26: 0x00001400 ....
600 '''
601 for (x_type, x_value) in ifla_af_spec.iteritems():
602 if x_type == Link.IFLA_BRIDGE_VLAN_INFO:
603 for (vlan_flag, vlan_id) in x_value:
604 if filter_vlanid is None or vlan_id in filter_vlanid:
605
606 if ifname not in iface_vlans:
607 iface_vlans[ifname] = []
608
609 # We store these in the tuple as (vlan, flag) instead (flag, vlan)
610 # so that we can sort the list of tuples
611 iface_vlans[ifname].append((vlan_id, vlan_flag))
612
613 return iface_vlans
614
615 def vlan_show(self, filter_ifindex=None, filter_vlanid=None, compress_vlans=True):
616
617 def vlan_flag_to_string(vlan_flag):
618 flag_str = []
619 if vlan_flag & Link.BRIDGE_VLAN_INFO_PVID:
620 flag_str.append('PVID')
621
622 if vlan_flag & Link.BRIDGE_VLAN_INFO_UNTAGGED:
623 flag_str.append('Egress Untagged')
624
625 return ', '.join(flag_str)
626
627 iface_vlans = self.vlan_get(filter_ifindex, filter_vlanid, compress_vlans)
628 log.debug("iface_vlans:\n%s\n" % pformat(iface_vlans))
629 range_begin_vlan_id = None
630 range_flag = None
631
632 print " Interface VLAN Flags"
633 print " ========== ==== ====="
634
635 for (ifname, vlan_tuples) in sorted(iface_vlans.iteritems()):
636 for (vlan_id, vlan_flag) in sorted(vlan_tuples):
637
638 if vlan_flag & Link.BRIDGE_VLAN_INFO_RANGE_BEGIN:
639 range_begin_vlan_id = vlan_id
640 range_flag = vlan_flag
641
642 elif vlan_flag & Link.BRIDGE_VLAN_INFO_RANGE_END:
643 range_flag |= vlan_flag
644
645 if not range_begin_vlan_id:
646 log.warning("BRIDGE_VLAN_INFO_RANGE_END is %d but we never saw a BRIDGE_VLAN_INFO_RANGE_BEGIN" % vlan_id)
647 range_begin_vlan_id = vlan_id
648
649 for x in xrange(range_begin_vlan_id, vlan_id + 1):
650 print " %10s %4d %s" % (ifname, x, vlan_flag_to_string(vlan_flag))
651 ifname = ''
652
653 range_begin_vlan_id = None
654 range_flag = None
655
656 else:
657 print " %10s %4d %s" % (ifname, vlan_id, vlan_flag_to_string(vlan_flag))
658 ifname = ''
659
660
661 def vlan_modify(self, msgtype, ifindex, vlanid_start, vlanid_end=None, bridge_self=False, bridge_master=False, pvid=False, untagged=False):
662 """
663 iproute2 bridge/vlan.c vlan_modify()
664 """
665 assert msgtype in (RTM_SETLINK, RTM_DELLINK), "Invalid msgtype %s, must be RTM_SETLINK or RTM_DELLINK" % msgtype
666 assert vlanid_start >= 1 and vlanid_start <= 4096, "Invalid VLAN start %s" % vlanid_start
667
668 if vlanid_end is None:
669 vlanid_end = vlanid_start
670
671 assert vlanid_end >= 1 and vlanid_end <= 4096, "Invalid VLAN end %s" % vlanid_end
672 assert vlanid_start <= vlanid_end, "Invalid VLAN range %s-%s, start must be <= end" % (vlanid_start, vlanid_end)
198ded6a
JF
673
674 debug = msgtype in self.debug
26d1e82b
JF
675 bridge_flags = 0
676 vlan_info_flags = 0
198ded6a 677
a61d1777 678 link = Link(msgtype, debug, use_color=self.use_color)
198ded6a
JF
679 link.flags = NLM_F_REQUEST | NLM_F_ACK
680 link.body = pack('Bxxxiii', socket.AF_BRIDGE, ifindex, 0, 0)
26d1e82b
JF
681
682 if bridge_self:
683 bridge_flags |= Link.BRIDGE_FLAGS_SELF
684
685 if bridge_master:
686 bridge_flags |= Link.BRIDGE_FLAGS_MASTER
687
688 if pvid:
689 vlan_info_flags |= Link.BRIDGE_VLAN_INFO_PVID
690
691 if untagged:
692 vlan_info_flags |= Link.BRIDGE_VLAN_INFO_UNTAGGED
693
694 ifla_af_spec = OrderedDict()
695
696 if bridge_flags:
697 ifla_af_spec[Link.IFLA_BRIDGE_FLAGS] = bridge_flags
698
699 # just one VLAN
700 if vlanid_start == vlanid_end:
701 ifla_af_spec[Link.IFLA_BRIDGE_VLAN_INFO] = [(vlan_info_flags, vlanid_start), ]
702
703 # a range of VLANs
704 else:
705 ifla_af_spec[Link.IFLA_BRIDGE_VLAN_INFO] = [
706 (vlan_info_flags | Link.BRIDGE_VLAN_INFO_RANGE_BEGIN, vlanid_start),
707 (vlan_info_flags | Link.BRIDGE_VLAN_INFO_RANGE_END, vlanid_end)
708 ]
709
710 link.add_attribute(Link.IFLA_AF_SPEC, ifla_af_spec)
198ded6a 711 link.build_message(self.sequence.next(), self.pid)
c268fccf 712 return self.tx_nlpacket_get_response(link)
198ded6a 713
26d1e82b
JF
714 def link_add_bridge_vlan(self, ifindex, vlanid_start, vlanid_end=None, pvid=False, untagged=False, master=False):
715 """
716 Add VLAN(s) to a bridge interface
717 """
718 bridge_self = False if master else True
719 self.vlan_modify(RTM_SETLINK, ifindex, vlanid_start, vlanid_end, bridge_self, master, pvid, untagged)
198ded6a 720
26d1e82b
JF
721 def link_del_bridge_vlan(self, ifindex, vlanid_start, vlanid_end=None, pvid=False, untagged=False, master=False):
722 """
723 Delete VLAN(s) from a bridge interface
724 """
725 bridge_self = False if master else True
726 self.vlan_modify(RTM_DELLINK, ifindex, vlanid_start, vlanid_end, bridge_self, master, pvid, untagged)
198ded6a
JF
727
728 def link_set_updown(self, ifname, state):
729 """
730 Either bring ifname up or take it down
731 """
732
733 if state == 'up':
734 if_flags = Link.IFF_UP
735 elif state == 'down':
736 if_flags = 0
737 else:
738 raise Exception('Unsupported state %s, valid options are "up" and "down"' % state)
739
740 debug = RTM_NEWLINK in self.debug
741 if_change = Link.IFF_UP
742
a61d1777 743 link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
c268fccf 744 link.flags = NLM_F_REQUEST | NLM_F_ACK
198ded6a
JF
745 link.body = pack('=BxxxiLL', socket.AF_UNSPEC, 0, if_flags, if_change)
746 link.add_attribute(Link.IFLA_IFNAME, ifname)
747 link.build_message(self.sequence.next(), self.pid)
c268fccf 748 return self.tx_nlpacket_get_response(link)
198ded6a
JF
749
750 def link_set_protodown(self, ifname, state):
751 """
752 Either bring ifname up or take it down by setting IFLA_PROTO_DOWN on or off
753 """
754 flags = 0
755 protodown = 1 if state == "on" else 0
756
757 debug = RTM_NEWLINK in self.debug
758
a61d1777 759 link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
c268fccf 760 link.flags = NLM_F_REQUEST | NLM_F_ACK
198ded6a
JF
761 link.body = pack('=BxxxiLL', socket.AF_UNSPEC, 0, 0, 0)
762 link.add_attribute(Link.IFLA_IFNAME, ifname)
763 link.add_attribute(Link.IFLA_PROTO_DOWN, protodown)
764 link.build_message(self.sequence.next(), self.pid)
c268fccf 765 return self.tx_nlpacket_get_response(link)
198ded6a 766
282b84af
JF
767 def link_set_master(self, ifname, master_ifindex=0, state=None):
768 """
769 ip link set %ifname master %master_ifindex %state
770 use master_ifindex=0 for nomaster
771 """
772 if state == 'up':
773 if_change = Link.IFF_UP
774 if_flags = Link.IFF_UP
775 elif state == 'down':
776 if_change = Link.IFF_UP
777 if_flags = 0
778 else:
779 if_change = 0
780 if_flags = 0
781
782 debug = RTM_NEWLINK in self.debug
783
784 link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
785 link.flags = NLM_F_REQUEST | NLM_F_ACK
786 link.body = pack('=BxxxiLL', socket.AF_UNSPEC, 0, if_flags, if_change)
787 link.add_attribute(Link.IFLA_IFNAME, ifname)
788 link.add_attribute(Link.IFLA_MASTER, master_ifindex)
789 link.build_message(self.sequence.next(), self.pid)
790 return self.tx_nlpacket_get_response(link)
791
198ded6a
JF
792 # =========
793 # Neighbors
794 # =========
795 def neighbor_add(self, afi, ifindex, ip, mac):
796 debug = RTM_NEWNEIGH in self.debug
797 service_hdr_flags = 0
798
a61d1777 799 nbr = Neighbor(RTM_NEWNEIGH, debug, use_color=self.use_color)
c268fccf 800 nbr.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
198ded6a
JF
801 nbr.family = afi
802 nbr.body = pack('=BxxxiHBB', afi, ifindex, Neighbor.NUD_REACHABLE, service_hdr_flags, Route.RTN_UNICAST)
803 nbr.add_attribute(Neighbor.NDA_DST, ip)
804 nbr.add_attribute(Neighbor.NDA_LLADDR, mac)
805 nbr.build_message(self.sequence.next(), self.pid)
c268fccf 806 return self.tx_nlpacket_get_response(nbr)
198ded6a
JF
807
808 def neighbor_del(self, afi, ifindex, ip, mac):
809 debug = RTM_DELNEIGH in self.debug
810 service_hdr_flags = 0
811
a61d1777 812 nbr = Neighbor(RTM_DELNEIGH, debug, use_color=self.use_color)
c268fccf 813 nbr.flags = NLM_F_REQUEST | NLM_F_ACK
198ded6a
JF
814 nbr.family = afi
815 nbr.body = pack('=BxxxiHBB', afi, ifindex, Neighbor.NUD_REACHABLE, service_hdr_flags, Route.RTN_UNICAST)
816 nbr.add_attribute(Neighbor.NDA_DST, ip)
817 nbr.add_attribute(Neighbor.NDA_LLADDR, mac)
818 nbr.build_message(self.sequence.next(), self.pid)
c268fccf 819 return self.tx_nlpacket_get_response(nbr)
3696839d
JF
820
821 def link_add_vxlan(self, ifname, vxlanid, dstport=None, local=None,
53f34704 822 group=None, learning='on', ageing=None, physdev=None):
3696839d
JF
823
824 debug = RTM_NEWLINK in self.debug
825
826 info_data = {Link.IFLA_VXLAN_ID: int(vxlanid)}
827 if dstport:
828 info_data[Link.IFLA_VXLAN_PORT] = int(dstport)
829 if local:
830 info_data[Link.IFLA_VXLAN_LOCAL] = local
831 if group:
832 info_data[Link.IFLA_VXLAN_GROUP] = group
53f34704
MW
833 if physdev:
834 info_data[Link.IFLA_VXLAN_LINK] = int (physdev)
3696839d
JF
835
836 learning = 0 if learning == 'off' else 1
837 info_data[Link.IFLA_VXLAN_LEARNING] = learning
838
3696839d
JF
839 if ageing:
840 info_data[Link.IFLA_VXLAN_AGEING] = int(ageing)
841
a61d1777 842 link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
c268fccf 843 link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
3696839d
JF
844 link.body = pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
845 link.add_attribute(Link.IFLA_IFNAME, ifname)
3696839d
JF
846 link.add_attribute(Link.IFLA_LINKINFO, {
847 Link.IFLA_INFO_KIND: "vxlan",
848 Link.IFLA_INFO_DATA: info_data
849 })
850
851 link.build_message(self.sequence.next(), self.pid)
c268fccf 852 return self.tx_nlpacket_get_response(link)