3 # Copyright (C) 2015, 2017 Cumulus Networks, Inc. all rights reserved
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.
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.
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
19 # https://www.gnu.org/licenses/gpl-2.0-standalone.html
22 # Daniel Walton, dwalton@cumulusnetworks.com
23 # Julien Fortin, julien@cumulusnetworks.com
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
37 log
= logging
.getLogger(__name__
)
40 class NetlinkError(Exception):
44 class NetlinkNoAddressError(NetlinkError
):
48 class NetlinkInterruptedSystemCall(NetlinkError
):
52 class InvalidInterfaceNameVlanCombo(Exception):
56 class Sequence(object):
66 class NetlinkManager(object):
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
78 self
.use_color
= use_color
82 self
.debug_link(False)
83 self
.debug_address(False)
84 self
.debug_neighbor(False)
85 self
.debug_route(False)
88 log
.setLevel(log_level
)
89 set_log_level(log_level
)
92 return 'NetlinkManager'
94 def signal_term_handler(self
, signal
, frame
):
95 log
.info("NetlinkManager: Caught SIGTERM")
96 self
.shutdown_flag
= True
98 def signal_int_handler(self
, signal
, frame
):
99 log
.info("NetlinkManager: Caught SIGINT")
100 self
.shutdown_flag
= True
104 self
.tx_socket
.close()
105 self
.tx_socket
= None
106 log
.info("NetlinkManager: shutdown complete")
108 def _debug_set_clear(self
, msg_types
, enabled
):
110 Enable or disable debugs for all msgs_types messages
120 def debug_link(self
, enabled
):
121 self
._debug
_set
_clear
((RTM_NEWLINK
, RTM_DELLINK
, RTM_GETLINK
, RTM_SETLINK
), enabled
)
123 def debug_address(self
, enabled
):
124 self
._debug
_set
_clear
((RTM_NEWADDR
, RTM_DELADDR
, RTM_GETADDR
), enabled
)
126 def debug_neighbor(self
, enabled
):
127 self
._debug
_set
_clear
((RTM_NEWNEIGH
, RTM_DELNEIGH
, RTM_GETNEIGH
), enabled
)
129 def debug_route(self
, enabled
):
130 self
._debug
_set
_clear
((RTM_NEWROUTE
, RTM_DELROUTE
, RTM_GETROUTE
), enabled
)
132 def debug_this_packet(self
, mtype
):
133 if mtype
in self
.debug
:
137 def tx_socket_allocate(self
):
139 The TX socket is used for install requests, sending RTM_GETXXXX
142 self
.tx_socket
= socket
.socket(socket
.AF_NETLINK
, socket
.SOCK_RAW
, 0)
143 self
.tx_socket
.bind((self
.pid
, 0))
145 def tx_nlpacket_raw(self
, message
):
147 TX a bunch of concatenated nlpacket.messages....do NOT wait for an ACK
149 if not self
.tx_socket
:
150 self
.tx_socket_allocate()
151 self
.tx_socket
.sendall(message
)
153 def tx_nlpacket(self
, nlpacket
):
155 TX a netlink packet but do NOT wait for an ACK
157 if not nlpacket
.message
:
158 log
.error('You must first call build_message() to create the packet')
161 if not self
.tx_socket
:
162 self
.tx_socket_allocate()
163 self
.tx_socket
.sendall(nlpacket
.message
)
165 def tx_nlpacket_get_response(self
, nlpacket
):
167 if not nlpacket
.message
:
168 log
.error('You must first call build_message() to create the packet')
171 if not self
.tx_socket
:
172 self
.tx_socket_allocate()
173 self
.tx_socket
.sendall(nlpacket
.message
)
175 # If nlpacket.debug is True we already printed the following in the
176 # build_message() call...so avoid printing two messages for one packet.
177 if not nlpacket
.debug
:
178 log
.debug("TXed %12s, pid %d, seq %d, %d bytes" %
179 (nlpacket
.get_type_string(), nlpacket
.pid
, nlpacket
.seq
, nlpacket
.length
))
181 header_PACK
= NetlinkPacket
.header_PACK
182 header_LEN
= NetlinkPacket
.header_LEN
186 MAX_ERROR_NLE_INTR
= 3
189 # Now listen to our socket and wait for the reply
192 if self
.shutdown_flag
:
193 log
.info('shutdown flag is True, exiting')
196 # Only block for 1 second so we can wake up to see if self.shutdown_flag is True
198 (readable
, writeable
, exceptional
) = select([self
.tx_socket
, ], [], [self
.tx_socket
, ], 1)
199 except Exception as e
:
200 # 4 is Interrupted system call
201 if isinstance(e
.args
, tuple) and e
[0] == 4:
203 log
.info("select() Interrupted system call %d/%d" % (nle_intr_count
, MAX_ERROR_NLE_INTR
))
205 if nle_intr_count
>= MAX_ERROR_NLE_INTR
:
206 raise NetlinkInterruptedSystemCall(error_str
)
217 # Safety net to make sure we do not spend too much time in
218 # this while True loop
219 if null_read
>= MAX_NULL_READS
:
220 log
.info('Socket was not readable for %d attempts' % null_read
)
230 except Exception as e
:
231 # 4 is Interrupted system call
232 if isinstance(e
.args
, tuple) and e
[0] == 4:
234 log
.info("%s: recv() Interrupted system call %d/%d" % (s
, nle_intr_count
, MAX_ERROR_NLE_INTR
))
236 if nle_intr_count
>= MAX_ERROR_NLE_INTR
:
237 raise NetlinkInterruptedSystemCall(error_str
)
244 log
.info('RXed zero length data, the socket is closed')
249 # Extract the length, etc from the header
250 (length
, msgtype
, flags
, seq
, pid
) = unpack(header_PACK
, data
[:header_LEN
])
252 debug_str
= "RXed %12s, pid %d, seq %d, %d bytes" % (NetlinkPacket
.type_to_string
[msgtype
], pid
, seq
, length
)
254 # This shouldn't happen but it would be nice to be aware of it if it does
255 if pid
!= nlpacket
.pid
:
256 log
.debug(debug_str
+ '...we are not interested in this pid %s since ours is %s' %
261 if seq
!= nlpacket
.seq
:
262 log
.debug(debug_str
+ '...we are not interested in this seq %s since ours is %s' %
267 # See if we RXed an ACK for our RTM_GETXXXX
268 if msgtype
== NLMSG_DONE
:
269 log
.debug(debug_str
+ '...this is an ACK')
272 elif msgtype
== NLMSG_ERROR
:
274 # The error code is a signed negative number.
275 error_code
= abs(unpack('=i', data
[header_LEN
:header_LEN
+4])[0])
276 msg
= Error(msgtype
, nlpacket
.debug
)
277 msg
.decode_packet(length
, flags
, seq
, pid
, data
)
279 # 0 is NLE_SUCCESS...everything else is a true error
281 error_code_str
= msg
.error_to_string
.get(error_code
)
283 error_str
= 'Operation failed with \'%s\'' % error_code_str
285 error_str
= 'Operation failed with code %s' % error_code
289 if error_code
== Error
.NLE_NOADDR
:
290 raise NetlinkNoAddressError(error_str
)
291 elif error_code
== Error
.NLE_INTR
:
293 log
.debug("%s: RXed NLE_INTR Interrupted system call %d/%d" % (s
, nle_intr_count
, MAX_ERROR_NLE_INTR
))
295 if nle_intr_count
>= MAX_ERROR_NLE_INTR
:
296 raise NetlinkInterruptedSystemCall(error_str
)
300 if not error_code_str
:
302 # os.strerror might raise ValueError
303 strerror
= os
.strerror(error_code
)
305 raise NetlinkError('Operation failed with \'%s\'' % strerror
)
307 raise NetlinkError(error_str
)
310 raise NetlinkError(error_str
)
312 log
.debug('%s code NLE_SUCCESS...this is an ACK' % debug_str
)
315 # No ACK...create a nlpacket object and append it to msgs
319 if msgtype
== RTM_NEWLINK
or msgtype
== RTM_DELLINK
:
320 msg
= Link(msgtype
, nlpacket
.debug
, use_color
=self
.use_color
)
322 elif msgtype
== RTM_NEWADDR
or msgtype
== RTM_DELADDR
:
323 msg
= Address(msgtype
, nlpacket
.debug
, use_color
=self
.use_color
)
325 elif msgtype
== RTM_NEWNEIGH
or msgtype
== RTM_DELNEIGH
:
326 msg
= Neighbor(msgtype
, nlpacket
.debug
, use_color
=self
.use_color
)
328 elif msgtype
== RTM_NEWROUTE
or msgtype
== RTM_DELROUTE
:
329 msg
= Route(msgtype
, nlpacket
.debug
, use_color
=self
.use_color
)
332 raise Exception("RXed unknown netlink message type %s" % msgtype
)
334 msg
.decode_packet(length
, flags
, seq
, pid
, data
)
342 def ip_to_afi(self
, ip
):
345 if type_ip
== IPv4Address
:
346 return socket
.AF_INET
347 elif type_ip
== IPv6Address
:
348 return socket
.AF_INET6
350 raise Exception("%s is an invalid IP type" % type_ip
)
352 def request_dump(self
, rtm_type
, family
, debug
):
354 Issue a RTM_GETROUTE, etc with the NLM_F_DUMP flag
355 set and return the results
358 if rtm_type
== RTM_GETADDR
:
359 msg
= Address(rtm_type
, debug
, use_color
=self
.use_color
)
360 msg
.body
= pack('Bxxxi', family
, 0)
362 elif rtm_type
== RTM_GETLINK
:
363 msg
= Link(rtm_type
, debug
, use_color
=self
.use_color
)
364 msg
.body
= pack('Bxxxiii', family
, 0, 0, 0)
366 elif rtm_type
== RTM_GETNEIGH
:
367 msg
= Neighbor(rtm_type
, debug
, use_color
=self
.use_color
)
368 msg
.body
= pack('Bxxxii', family
, 0, 0)
370 elif rtm_type
== RTM_GETROUTE
:
371 msg
= Route(rtm_type
, debug
, use_color
=self
.use_color
)
372 msg
.body
= pack('Bxxxii', family
, 0, 0)
375 log
.error("request_dump RTM_GET %s is not supported" % rtm_type
)
378 msg
.flags
= NLM_F_REQUEST | NLM_F_DUMP
380 msg
.build_message(self
.sequence
.next(), self
.pid
)
381 return self
.tx_nlpacket_get_response(msg
)
386 def _routes_add_or_delete(self
, add_route
, routes
, ecmp_routes
, table
, protocol
, route_scope
, route_type
):
388 def tx_or_concat_message(total_message
, route
):
390 Adding an ipv4 route only takes 60 bytes, if we are adding thousands
391 of them this can add up to a lot of send calls. Concat several of
392 them together before TXing.
395 if not total_message
:
396 total_message
= route
.message
398 total_message
+= route
.message
400 if len(total_message
) >= PACKET_CONCAT_SIZE
:
401 self
.tx_nlpacket_raw(total_message
)
407 rtm_command
= RTM_NEWROUTE
409 rtm_command
= RTM_DELROUTE
412 PACKET_CONCAT_SIZE
= 16384
413 debug
= rtm_command
in self
.debug
416 for (afi
, ip
, mask
, nexthop
, interface_index
) in routes
:
417 route
= Route(rtm_command
, debug
, use_color
=self
.use_color
)
418 route
.flags
= NLM_F_REQUEST | NLM_F_CREATE
419 route
.body
= pack('BBBBBBBBi', afi
, mask
, 0, 0, table
, protocol
,
420 route_scope
, route_type
, 0)
422 route
.add_attribute(Route
.RTA_DST
, ip
)
424 route
.add_attribute(Route
.RTA_GATEWAY
, nexthop
)
425 route
.add_attribute(Route
.RTA_OIF
, interface_index
)
426 route
.build_message(self
.sequence
.next(), self
.pid
)
427 total_message
= tx_or_concat_message(total_message
, route
)
430 self
.tx_nlpacket_raw(total_message
)
434 for (route_key
, value
) in ecmp_routes
.iteritems():
435 (afi
, ip
, mask
) = route_key
437 route
= Route(rtm_command
, debug
, use_color
=self
.use_color
)
438 route
.flags
= NLM_F_REQUEST | NLM_F_CREATE
439 route
.body
= pack('BBBBBBBBi', afi
, mask
, 0, 0, table
, protocol
,
440 route_scope
, route_type
, 0)
442 route
.add_attribute(Route
.RTA_DST
, ip
)
443 route
.add_attribute(Route
.RTA_MULTIPATH
, value
)
444 route
.build_message(self
.sequence
.next(), self
.pid
)
445 total_message
= tx_or_concat_message(total_message
, route
)
448 self
.tx_nlpacket_raw(total_message
)
450 def routes_add(self
, routes
, ecmp_routes
,
451 table
=Route
.RT_TABLE_MAIN
,
452 protocol
=Route
.RT_PROT_XORP
,
453 route_scope
=Route
.RT_SCOPE_UNIVERSE
,
454 route_type
=Route
.RTN_UNICAST
):
455 self
._routes
_add
_or
_delete
(True, routes
, ecmp_routes
, table
, protocol
, route_scope
, route_type
)
457 def routes_del(self
, routes
, ecmp_routes
,
458 table
=Route
.RT_TABLE_MAIN
,
459 protocol
=Route
.RT_PROT_XORP
,
460 route_scope
=Route
.RT_SCOPE_UNIVERSE
,
461 route_type
=Route
.RTN_UNICAST
):
462 self
._routes
_add
_or
_delete
(False, routes
, ecmp_routes
, table
, protocol
, route_scope
, route_type
)
464 def route_get(self
, ip
, debug
=False):
466 ip must be one of the following:
470 # Transmit a RTM_GETROUTE to query for the route we want
471 route
= Route(RTM_GETROUTE
, debug
, use_color
=self
.use_color
)
472 route
.flags
= NLM_F_REQUEST | NLM_F_ACK
474 # Set everything in the service header as 0 other than the afi
475 afi
= self
.ip_to_afi(ip
)
476 route
.body
= pack('Bxxxxxxxi', afi
, 0)
478 route
.add_attribute(Route
.RTA_DST
, ip
)
479 route
.build_message(self
.sequence
.next(), self
.pid
)
480 return self
.tx_nlpacket_get_response(route
)
482 def routes_dump(self
, family
=socket
.AF_UNSPEC
, debug
=True):
483 return self
.request_dump(RTM_GETROUTE
, family
, debug
)
485 def routes_print(self
, routes
):
487 Print a table of 'routes'
489 print "Prefix Nexthop ifindex"
492 if Route
.RTA_DST
not in x
.attributes
:
493 log
.warning("Route is missing RTA_DST")
496 ip
= "%s/%d" % (x
.attributes
[Route
.RTA_DST
].value
, x
.src_len
)
497 print "%-15s %-15s %s" %\
499 str(x
.attributes
[Route
.RTA_GATEWAY
].value
) if Route
.RTA_GATEWAY
in x
.attributes
else None,
500 x
.attributes
[Route
.RTA_OIF
].value
)
505 def _get_iface_by_name(self
, ifname
):
507 Return a Link object for ifname
509 debug
= RTM_GETLINK
in self
.debug
511 link
= Link(RTM_GETLINK
, debug
, use_color
=self
.use_color
)
512 link
.flags
= NLM_F_REQUEST | NLM_F_ACK
513 link
.body
= pack('=Bxxxiii', socket
.AF_UNSPEC
, 0, 0, 0)
514 link
.add_attribute(Link
.IFLA_IFNAME
, ifname
)
515 link
.build_message(self
.sequence
.next(), self
.pid
)
518 return self
.tx_nlpacket_get_response(link
)[0]
520 except NetlinkNoAddressError
:
521 log
.info("Netlink did not find interface %s" % ifname
)
524 def _get_iface_by_index(self
, ifindex
):
526 Return a Link object for ifindex
528 debug
= RTM_GETLINK
in self
.debug
530 link
= Link(RTM_GETLINK
, debug
, use_color
=self
.use_color
)
531 link
.flags
= NLM_F_REQUEST | NLM_F_ACK
532 link
.body
= pack('=Bxxxiii', socket
.AF_UNSPEC
, ifindex
, 0, 0)
533 link
.build_message(self
.sequence
.next(), self
.pid
)
535 return self
.tx_nlpacket_get_response(link
)[0]
536 except NetlinkNoAddressError
:
537 log
.info("Netlink did not find interface %s" % ifindex
)
540 def get_iface_index(self
, ifname
):
542 Return the interface index for ifname
544 iface
= self
._get
_iface
_by
_name
(ifname
)
550 def get_iface_name(self
, ifindex
):
551 iface
= self
._get
_iface
_by
_index
(ifindex
)
554 return iface
.attributes
[Link
.IFLA_IFNAME
].get_pretty_value(str)
557 def link_dump(self
, ifname
=None):
558 msg
= Link(RTM_GETLINK
, False, use_color
=self
.use_color
)
559 msg
.body
= pack('Bxxxiii', socket
.AF_UNSPEC
, 0, 0, 0)
560 msg
.flags
= NLM_F_REQUEST | NLM_F_ACK
563 msg
.add_attribute(Link
.IFLA_IFNAME
, ifname
)
565 msg
.flags |
= NLM_F_DUMP
567 msg
.build_message(self
.sequence
.next(), self
.pid
)
568 return self
.tx_nlpacket_get_response(msg
)
570 def link_set_attrs(self
, ifname
, kind
=None, slave_kind
=None, ifindex
=0, ifla
={}, ifla_info_data
={}, ifla_info_slave_data
={}):
571 debug
= RTM_NEWLINK
in self
.debug
573 link
= Link(RTM_NEWLINK
, debug
, use_color
=self
.use_color
)
574 link
.flags
= NLM_F_REQUEST | NLM_F_ACK
575 link
.body
= pack('Bxxxiii', socket
.AF_UNSPEC
, ifindex
, 0, 0)
577 for nl_attr
, value
in ifla
.items():
578 link
.add_attribute(nl_attr
, value
)
581 link
.add_attribute(Link
.IFLA_IFNAME
, ifname
)
586 linkinfo
[Link
.IFLA_INFO_KIND
] = kind
587 linkinfo
[Link
.IFLA_INFO_DATA
] = ifla_info_data
589 linkinfo
[Link
.IFLA_INFO_SLAVE_KIND
] = slave_kind
,
590 linkinfo
[Link
.IFLA_INFO_SLAVE_DATA
] = ifla_info_slave_data
592 link
.add_attribute(Link
.IFLA_LINKINFO
, linkinfo
)
593 link
.build_message(self
.sequence
.next(), self
.pid
)
594 return self
.tx_nlpacket_get_response(link
)
596 def link_add_set(self
, kind
,
602 ifla_info_slave_data
={}):
604 Build and TX a RTM_NEWLINK message to add the desired interface
606 debug
= RTM_NEWLINK
in self
.debug
608 link
= Link(RTM_NEWLINK
, debug
, use_color
=self
.use_color
)
609 link
.flags
= NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
610 link
.body
= pack('Bxxxiii', socket
.AF_UNSPEC
, ifindex
, 0, 0)
612 for nl_attr
, value
in ifla
.items():
613 link
.add_attribute(nl_attr
, value
)
616 link
.add_attribute(Link
.IFLA_IFNAME
, ifname
)
620 linkinfo
[Link
.IFLA_INFO_KIND
] = kind
621 linkinfo
[Link
.IFLA_INFO_DATA
] = ifla_info_data
623 linkinfo
[Link
.IFLA_INFO_SLAVE_KIND
] = slave_kind
624 linkinfo
[Link
.IFLA_INFO_SLAVE_DATA
] = ifla_info_slave_data
625 link
.add_attribute(Link
.IFLA_LINKINFO
, linkinfo
)
627 link
.build_message(self
.sequence
.next(), self
.pid
)
628 return self
.tx_nlpacket_get_response(link
)
630 def link_del(self
, ifindex
=None, ifname
=None):
631 if not ifindex
and not ifname
:
632 raise ValueError('invalid ifindex and/or ifname')
635 ifindex
= self
.get_iface_index(ifname
)
637 debug
= RTM_DELLINK
in self
.debug
639 link
= Link(RTM_DELLINK
, debug
, use_color
=self
.use_color
)
640 link
.flags
= NLM_F_REQUEST | NLM_F_ACK
641 link
.body
= pack('Bxxxiii', socket
.AF_UNSPEC
, ifindex
, 0, 0)
642 link
.build_message(self
.sequence
.next(), self
.pid
)
643 return self
.tx_nlpacket_get_response(link
)
645 def _link_add(self
, ifindex
, ifname
, kind
, ifla_info_data
):
647 Build and TX a RTM_NEWLINK message to add the desired interface
649 debug
= RTM_NEWLINK
in self
.debug
651 link
= Link(RTM_NEWLINK
, debug
, use_color
=self
.use_color
)
652 link
.flags
= NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
653 link
.body
= pack('Bxxxiii', socket
.AF_UNSPEC
, 0, 0, 0)
654 link
.add_attribute(Link
.IFLA_IFNAME
, ifname
)
657 link
.add_attribute(Link
.IFLA_LINK
, ifindex
)
659 link
.add_attribute(Link
.IFLA_LINKINFO
, {
660 Link
.IFLA_INFO_KIND
: kind
,
661 Link
.IFLA_INFO_DATA
: ifla_info_data
663 link
.build_message(self
.sequence
.next(), self
.pid
)
664 return self
.tx_nlpacket_get_response(link
)
666 def link_add_bridge(self
, ifname
, ifla_info_data
={}):
667 return self
._link
_add
(ifindex
=None, ifname
=ifname
, kind
='bridge', ifla_info_data
=ifla_info_data
)
669 def link_add_vlan(self
, ifindex
, ifname
, vlanid
, vlan_protocol
=None):
671 ifindex is the index of the parent interface that this sub-interface
676 If you name an interface swp2.17 but assign it to vlan 12, the kernel
677 will return a very misleading NLE_MSG_OVERFLOW error. It only does
678 this check if the ifname uses dot notation.
680 Do this check here so we can provide a more intuitive error
683 ifname_vlanid
= int(ifname
.split('.')[-1])
685 if ifname_vlanid
!= vlanid
:
686 raise InvalidInterfaceNameVlanCombo("Interface %s must belong "
687 "to VLAN %d (VLAN %d was requested)" %
688 (ifname
, ifname_vlanid
, vlanid
))
690 ifla_info_data
= {Link
.IFLA_VLAN_ID
: vlanid
}
693 ifla_info_data
[Link
.IFLA_VLAN_PROTOCOL
] = vlan_protocol
695 return self
._link
_add
(ifindex
, ifname
, 'vlan', ifla_info_data
)
697 def link_add_macvlan(self
, ifindex
, ifname
):
699 ifindex is the index of the parent interface that this sub-interface
702 return self
._link
_add
(ifindex
, ifname
, 'macvlan', {Link
.IFLA_MACVLAN_MODE
: Link
.MACVLAN_MODE_PRIVATE
})
704 def vlan_get(self
, filter_ifindex
=None, filter_vlanid
=None, compress_vlans
=True):
706 filter_ifindex should be a tuple if interface indexes, this is a whitelist filter
707 filter_vlandid should be a tuple if VLAN IDs, this is a whitelist filter
709 debug
= RTM_GETLINK
in self
.debug
711 link
= Link(RTM_GETLINK
, debug
, use_color
=self
.use_color
)
712 link
.family
= AF_BRIDGE
713 link
.flags
= NLM_F_DUMP | NLM_F_REQUEST
714 link
.body
= pack('Bxxxiii', socket
.AF_BRIDGE
, 0, 0, 0)
717 link
.add_attribute(Link
.IFLA_EXT_MASK
, Link
.RTEXT_FILTER_BRVLAN_COMPRESSED
)
719 link
.add_attribute(Link
.IFLA_EXT_MASK
, Link
.RTEXT_FILTER_BRVLAN
)
721 link
.build_message(self
.sequence
.next(), self
.pid
)
722 reply
= self
.tx_nlpacket_get_response(link
)
727 if msg
.family
!= socket
.AF_BRIDGE
:
730 if filter_ifindex
and msg
.ifindex
not in filter_ifindex
:
733 ifla_af_spec
= msg
.get_attribute_value(Link
.IFLA_AF_SPEC
)
738 ifname
= msg
.get_attribute_value(Link
.IFLA_IFNAME
)
743 20: 0x1c001a00 .... Length 0x001c (28), Type 0x001a (26) IFLA_AF_SPEC
744 21: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO
746 23: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO
748 25: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO
751 for (x_type
, x_value
) in ifla_af_spec
.iteritems():
752 if x_type
== Link
.IFLA_BRIDGE_VLAN_INFO
:
753 for (vlan_flag
, vlan_id
) in x_value
:
754 if filter_vlanid
is None or vlan_id
in filter_vlanid
:
756 if ifname
not in iface_vlans
:
757 iface_vlans
[ifname
] = []
759 # We store these in the tuple as (vlan, flag) instead (flag, vlan)
760 # so that we can sort the list of tuples
761 iface_vlans
[ifname
].append((vlan_id
, vlan_flag
))
765 def vlan_show(self
, filter_ifindex
=None, filter_vlanid
=None, compress_vlans
=True):
767 def vlan_flag_to_string(vlan_flag
):
769 if vlan_flag
& Link
.BRIDGE_VLAN_INFO_PVID
:
770 flag_str
.append('PVID')
772 if vlan_flag
& Link
.BRIDGE_VLAN_INFO_UNTAGGED
:
773 flag_str
.append('Egress Untagged')
775 return ', '.join(flag_str
)
777 iface_vlans
= self
.vlan_get(filter_ifindex
, filter_vlanid
, compress_vlans
)
778 log
.debug("iface_vlans:\n%s\n" % pformat(iface_vlans
))
779 range_begin_vlan_id
= None
782 print " Interface VLAN Flags"
783 print " ========== ==== ====="
785 for (ifname
, vlan_tuples
) in sorted(iface_vlans
.iteritems()):
786 for (vlan_id
, vlan_flag
) in sorted(vlan_tuples
):
788 if vlan_flag
& Link
.BRIDGE_VLAN_INFO_RANGE_BEGIN
:
789 range_begin_vlan_id
= vlan_id
790 range_flag
= vlan_flag
792 elif vlan_flag
& Link
.BRIDGE_VLAN_INFO_RANGE_END
:
793 range_flag |
= vlan_flag
795 if not range_begin_vlan_id
:
796 log
.warning("BRIDGE_VLAN_INFO_RANGE_END is %d but we never saw a BRIDGE_VLAN_INFO_RANGE_BEGIN" % vlan_id
)
797 range_begin_vlan_id
= vlan_id
799 for x
in xrange(range_begin_vlan_id
, vlan_id
+ 1):
800 print " %10s %4d %s" % (ifname
, x
, vlan_flag_to_string(vlan_flag
))
803 range_begin_vlan_id
= None
807 print " %10s %4d %s" % (ifname
, vlan_id
, vlan_flag_to_string(vlan_flag
))
811 def vlan_modify(self
, msgtype
, ifindex
, vlanid_start
, vlanid_end
=None, bridge_self
=False, bridge_master
=False, pvid
=False, untagged
=False):
813 iproute2 bridge/vlan.c vlan_modify()
815 assert msgtype
in (RTM_SETLINK
, RTM_DELLINK
), "Invalid msgtype %s, must be RTM_SETLINK or RTM_DELLINK" % msgtype
816 assert vlanid_start
>= 1 and vlanid_start
<= 4096, "Invalid VLAN start %s" % vlanid_start
818 if vlanid_end
is None:
819 vlanid_end
= vlanid_start
821 assert vlanid_end
>= 1 and vlanid_end
<= 4096, "Invalid VLAN end %s" % vlanid_end
822 assert vlanid_start
<= vlanid_end
, "Invalid VLAN range %s-%s, start must be <= end" % (vlanid_start
, vlanid_end
)
824 debug
= msgtype
in self
.debug
828 link
= Link(msgtype
, debug
, use_color
=self
.use_color
)
829 link
.flags
= NLM_F_REQUEST | NLM_F_ACK
830 link
.body
= pack('Bxxxiii', socket
.AF_BRIDGE
, ifindex
, 0, 0)
833 bridge_flags |
= Link
.BRIDGE_FLAGS_SELF
836 bridge_flags |
= Link
.BRIDGE_FLAGS_MASTER
839 vlan_info_flags |
= Link
.BRIDGE_VLAN_INFO_PVID
842 vlan_info_flags |
= Link
.BRIDGE_VLAN_INFO_UNTAGGED
844 ifla_af_spec
= OrderedDict()
847 ifla_af_spec
[Link
.IFLA_BRIDGE_FLAGS
] = bridge_flags
850 if vlanid_start
== vlanid_end
:
851 ifla_af_spec
[Link
.IFLA_BRIDGE_VLAN_INFO
] = [(vlan_info_flags
, vlanid_start
), ]
855 ifla_af_spec
[Link
.IFLA_BRIDGE_VLAN_INFO
] = [
856 (vlan_info_flags | Link
.BRIDGE_VLAN_INFO_RANGE_BEGIN
, vlanid_start
),
857 (vlan_info_flags | Link
.BRIDGE_VLAN_INFO_RANGE_END
, vlanid_end
)
860 link
.add_attribute(Link
.IFLA_AF_SPEC
, ifla_af_spec
)
861 link
.build_message(self
.sequence
.next(), self
.pid
)
862 return self
.tx_nlpacket_get_response(link
)
864 def link_add_bridge_vlan(self
, ifindex
, vlanid_start
, vlanid_end
=None, pvid
=False, untagged
=False, master
=False):
866 Add VLAN(s) to a bridge interface
868 bridge_self
= False if master
else True
869 self
.vlan_modify(RTM_SETLINK
, ifindex
, vlanid_start
, vlanid_end
, bridge_self
, master
, pvid
, untagged
)
871 def link_del_bridge_vlan(self
, ifindex
, vlanid_start
, vlanid_end
=None, pvid
=False, untagged
=False, master
=False):
873 Delete VLAN(s) from a bridge interface
875 bridge_self
= False if master
else True
876 self
.vlan_modify(RTM_DELLINK
, ifindex
, vlanid_start
, vlanid_end
, bridge_self
, master
, pvid
, untagged
)
878 def link_set_updown(self
, ifname
, state
):
880 Either bring ifname up or take it down
884 if_flags
= Link
.IFF_UP
885 elif state
== 'down':
888 raise Exception('Unsupported state %s, valid options are "up" and "down"' % state
)
890 debug
= RTM_NEWLINK
in self
.debug
891 if_change
= Link
.IFF_UP
893 link
= Link(RTM_NEWLINK
, debug
, use_color
=self
.use_color
)
894 link
.flags
= NLM_F_REQUEST | NLM_F_ACK
895 link
.body
= pack('=BxxxiLL', socket
.AF_UNSPEC
, 0, if_flags
, if_change
)
896 link
.add_attribute(Link
.IFLA_IFNAME
, ifname
)
897 link
.build_message(self
.sequence
.next(), self
.pid
)
898 return self
.tx_nlpacket_get_response(link
)
900 def link_set_protodown(self
, ifname
, state
):
902 Either bring ifname up or take it down by setting IFLA_PROTO_DOWN on or off
905 protodown
= 1 if state
== "on" else 0
907 debug
= RTM_NEWLINK
in self
.debug
909 link
= Link(RTM_NEWLINK
, debug
, use_color
=self
.use_color
)
910 link
.flags
= NLM_F_REQUEST | NLM_F_ACK
911 link
.body
= pack('=BxxxiLL', socket
.AF_UNSPEC
, 0, 0, 0)
912 link
.add_attribute(Link
.IFLA_IFNAME
, ifname
)
913 link
.add_attribute(Link
.IFLA_PROTO_DOWN
, protodown
)
914 link
.build_message(self
.sequence
.next(), self
.pid
)
915 return self
.tx_nlpacket_get_response(link
)
917 def link_set_master(self
, ifname
, master_ifindex
=0, state
=None):
919 ip link set %ifname master %master_ifindex %state
920 use master_ifindex=0 for nomaster
923 if_change
= Link
.IFF_UP
924 if_flags
= Link
.IFF_UP
925 elif state
== 'down':
926 if_change
= Link
.IFF_UP
932 debug
= RTM_NEWLINK
in self
.debug
934 link
= Link(RTM_NEWLINK
, debug
, use_color
=self
.use_color
)
935 link
.flags
= NLM_F_REQUEST | NLM_F_ACK
936 link
.body
= pack('=BxxxiLL', socket
.AF_UNSPEC
, 0, if_flags
, if_change
)
937 link
.add_attribute(Link
.IFLA_IFNAME
, ifname
)
938 link
.add_attribute(Link
.IFLA_MASTER
, master_ifindex
)
939 link
.build_message(self
.sequence
.next(), self
.pid
)
940 return self
.tx_nlpacket_get_response(link
)
945 def neighbor_add(self
, afi
, ifindex
, ip
, mac
):
946 debug
= RTM_NEWNEIGH
in self
.debug
947 service_hdr_flags
= 0
949 nbr
= Neighbor(RTM_NEWNEIGH
, debug
, use_color
=self
.use_color
)
950 nbr
.flags
= NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
952 nbr
.body
= pack('=BxxxiHBB', afi
, ifindex
, Neighbor
.NUD_REACHABLE
, service_hdr_flags
, Route
.RTN_UNICAST
)
953 nbr
.add_attribute(Neighbor
.NDA_DST
, ip
)
954 nbr
.add_attribute(Neighbor
.NDA_LLADDR
, mac
)
955 nbr
.build_message(self
.sequence
.next(), self
.pid
)
956 return self
.tx_nlpacket_get_response(nbr
)
958 def neighbor_del(self
, afi
, ifindex
, ip
, mac
):
959 debug
= RTM_DELNEIGH
in self
.debug
960 service_hdr_flags
= 0
962 nbr
= Neighbor(RTM_DELNEIGH
, debug
, use_color
=self
.use_color
)
963 nbr
.flags
= NLM_F_REQUEST | NLM_F_ACK
965 nbr
.body
= pack('=BxxxiHBB', afi
, ifindex
, Neighbor
.NUD_REACHABLE
, service_hdr_flags
, Route
.RTN_UNICAST
)
966 nbr
.add_attribute(Neighbor
.NDA_DST
, ip
)
967 nbr
.add_attribute(Neighbor
.NDA_LLADDR
, mac
)
968 nbr
.build_message(self
.sequence
.next(), self
.pid
)
969 return self
.tx_nlpacket_get_response(nbr
)
971 def link_add_vxlan(self
, ifname
, vxlanid
, dstport
=None, local
=None,
972 group
=None, learning
=True, ageing
=None):
974 debug
= RTM_NEWLINK
in self
.debug
976 info_data
= {Link
.IFLA_VXLAN_ID
: int(vxlanid
)}
978 info_data
[Link
.IFLA_VXLAN_PORT
] = int(dstport
)
980 info_data
[Link
.IFLA_VXLAN_LOCAL
] = local
982 info_data
[Link
.IFLA_VXLAN_GROUP
] = group
984 info_data
[Link
.IFLA_VXLAN_LEARNING
] = int(learning
)
987 info_data
[Link
.IFLA_VXLAN_AGEING
] = int(ageing
)
989 link
= Link(RTM_NEWLINK
, debug
, use_color
=self
.use_color
)
990 link
.flags
= NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
991 link
.body
= pack('Bxxxiii', socket
.AF_UNSPEC
, 0, 0, 0)
992 link
.add_attribute(Link
.IFLA_IFNAME
, ifname
)
993 link
.add_attribute(Link
.IFLA_LINKINFO
, {
994 Link
.IFLA_INFO_KIND
: "vxlan",
995 Link
.IFLA_INFO_DATA
: info_data
998 link
.build_message(self
.sequence
.next(), self
.pid
)
999 return self
.tx_nlpacket_get_response(link
)
1004 def addr_dump(self
):
1006 TODO: add ifname/ifindex filtering:
1007 - via the RTM_GETADDR request packet
1008 - or in python if kernel doesn't support per intf dump
1010 debug
= RTM_GETADDR
in self
.debug
1012 msg
= Address(RTM_GETADDR
, debug
, use_color
=self
.use_color
)
1013 msg
.body
= pack('=Bxxxi', socket
.AF_UNSPEC
, 0)
1014 msg
.flags
= NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP
1016 msg
.build_message(self
.sequence
.next(), self
.pid
)
1017 return self
.tx_nlpacket_get_response(msg
)