]>
Commit | Line | Data |
---|---|---|
198ded6a JF |
1 | #!/usr/bin/env python |
2 | ||
26d1e82b | 3 | from collections import OrderedDict |
198ded6a JF |
4 | from ipaddr import IPv4Address, IPv6Address |
5 | from nlpacket import * | |
6 | from select import select | |
7 | from struct import pack, unpack | |
198ded6a JF |
8 | import logging |
9 | import os | |
10 | import socket | |
11 | ||
12 | log = logging.getLogger(__name__) | |
13 | ||
14 | ||
15 | class NetlinkError(Exception): | |
16 | pass | |
17 | ||
18 | ||
cee2e13f JF |
19 | class NetlinkNoAddressError(NetlinkError): |
20 | pass | |
21 | ||
22 | ||
23 | class NetlinkInterruptedSystemCall(NetlinkError): | |
198ded6a JF |
24 | pass |
25 | ||
26 | ||
27 | class InvalidInterfaceNameVlanCombo(Exception): | |
28 | pass | |
29 | ||
30 | ||
31 | class 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 | ||
41 | class 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) |