1 # 2 # Copyright (c) 2011 Thomas Graf <tgraf (at] suug.ch> 3 # 4 5 """Module providing access to network links 6 7 This module provides an interface to view configured network links, 8 modify them and to add and delete virtual network links. 9 10 The following is a basic example: 11 import netlink.core as netlink 12 import netlink.route.link as link 13 14 sock = netlink.Socket() 15 sock.connect(netlink.NETLINK_ROUTE) 16 17 cache = link.LinkCache() # create new empty link cache 18 cache.refill(sock) # fill cache with all configured links 19 eth0 = cache['eth0'] # lookup link "eth0" 20 print eth0 # print basic configuration 21 22 The module contains the following public classes: 23 24 - Link -- Represents a network link. Instances can be created directly 25 via the constructor (empty link objects) or via the refill() 26 method of a LinkCache. 27 - LinkCache -- Derived from netlink.Cache, holds any number of 28 network links (Link instances). Main purpose is to keep 29 a local list of all network links configured in the 30 kernel. 31 32 The following public functions exist: 33 - get_from_kernel(socket, name) 34 35 """ 36 37 from __future__ import absolute_import 38 39 __version__ = '0.1' 40 __all__ = [ 41 'LinkCache', 42 'Link', 43 'get_from_kernel', 44 ] 45 46 import socket 47 from .. import core as netlink 48 from .. import capi as core_capi 49 from . import capi as capi 50 from .links import inet as inet 51 from .. import util as util 52 53 # Link statistics definitions 54 RX_PACKETS = 0 55 TX_PACKETS = 1 56 RX_BYTES = 2 57 TX_BYTES = 3 58 RX_ERRORS = 4 59 TX_ERRORS = 5 60 RX_DROPPED = 6 61 TX_DROPPED = 7 62 RX_COMPRESSED = 8 63 TX_COMPRESSED = 9 64 RX_FIFO_ERR = 10 65 TX_FIFO_ERR = 11 66 RX_LEN_ERR = 12 67 RX_OVER_ERR = 13 68 RX_CRC_ERR = 14 69 RX_FRAME_ERR = 15 70 RX_MISSED_ERR = 16 71 TX_ABORT_ERR = 17 72 TX_CARRIER_ERR = 18 73 TX_HBEAT_ERR = 19 74 TX_WIN_ERR = 20 75 COLLISIONS = 21 76 MULTICAST = 22 77 IP6_INPKTS = 23 78 IP6_INHDRERRORS = 24 79 IP6_INTOOBIGERRORS = 25 80 IP6_INNOROUTES = 26 81 IP6_INADDRERRORS = 27 82 IP6_INUNKNOWNPROTOS = 28 83 IP6_INTRUNCATEDPKTS = 29 84 IP6_INDISCARDS = 30 85 IP6_INDELIVERS = 31 86 IP6_OUTFORWDATAGRAMS = 32 87 IP6_OUTPKTS = 33 88 IP6_OUTDISCARDS = 34 89 IP6_OUTNOROUTES = 35 90 IP6_REASMTIMEOUT = 36 91 IP6_REASMREQDS = 37 92 IP6_REASMOKS = 38 93 IP6_REASMFAILS = 39 94 IP6_FRAGOKS = 40 95 IP6_FRAGFAILS = 41 96 IP6_FRAGCREATES = 42 97 IP6_INMCASTPKTS = 43 98 IP6_OUTMCASTPKTS = 44 99 IP6_INBCASTPKTS = 45 100 IP6_OUTBCASTPKTS = 46 101 IP6_INOCTETS = 47 102 IP6_OUTOCTETS = 48 103 IP6_INMCASTOCTETS = 49 104 IP6_OUTMCASTOCTETS = 50 105 IP6_INBCASTOCTETS = 51 106 IP6_OUTBCASTOCTETS = 52 107 ICMP6_INMSGS = 53 108 ICMP6_INERRORS = 54 109 ICMP6_OUTMSGS = 55 110 ICMP6_OUTERRORS = 56 111 112 class LinkCache(netlink.Cache): 113 """Cache of network links""" 114 115 def __init__(self, family=socket.AF_UNSPEC, cache=None): 116 if not cache: 117 cache = self._alloc_cache_name('route/link') 118 119 self._info_module = None 120 self._protocol = netlink.NETLINK_ROUTE 121 self._nl_cache = cache 122 self._set_arg1(family) 123 124 def __getitem__(self, key): 125 if type(key) is int: 126 link = capi.rtnl_link_get(self._nl_cache, key) 127 else: 128 link = capi.rtnl_link_get_by_name(self._nl_cache, key) 129 130 if link is None: 131 raise KeyError() 132 else: 133 return Link.from_capi(link) 134 135 @staticmethod 136 def _new_object(obj): 137 return Link(obj) 138 139 def _new_cache(self, cache): 140 return LinkCache(family=self.arg1, cache=cache) 141 142 class Link(netlink.Object): 143 """Network link""" 144 145 def __init__(self, obj=None): 146 netlink.Object.__init__(self, 'route/link', 'link', obj) 147 self._rtnl_link = self._obj2type(self._nl_object) 148 149 if self.type: 150 self._module_lookup('netlink.route.links.' + self.type) 151 152 self.inet = inet.InetLink(self) 153 self.af = {'inet' : self.inet } 154 155 def __enter__(self): 156 return self 157 158 def __exit__(self, exc_type, exc_value, tb): 159 if exc_type is None: 160 self.change() 161 else: 162 return false 163 164 @classmethod 165 def from_capi(cls, obj): 166 return cls(capi.link2obj(obj)) 167 168 @staticmethod 169 def _obj2type(obj): 170 return capi.obj2link(obj) 171 172 def __cmp__(self, other): 173 return self.ifindex - other.ifindex 174 175 @staticmethod 176 def _new_instance(obj): 177 if not obj: 178 raise ValueError() 179 180 return Link(obj) 181 182 @property 183 @netlink.nlattr(type=int, immutable=True, fmt=util.num) 184 def ifindex(self): 185 """interface index""" 186 return capi.rtnl_link_get_ifindex(self._rtnl_link) 187 188 @ifindex.setter 189 def ifindex(self, value): 190 capi.rtnl_link_set_ifindex(self._rtnl_link, int(value)) 191 192 # ifindex is immutable but we assume that if _orig does not 193 # have an ifindex specified, it was meant to be given here 194 if capi.rtnl_link_get_ifindex(self._orig) == 0: 195 capi.rtnl_link_set_ifindex(self._orig, int(value)) 196 197 @property 198 @netlink.nlattr(type=str, fmt=util.bold) 199 def name(self): 200 """Name of link""" 201 return capi.rtnl_link_get_name(self._rtnl_link) 202 203 @name.setter 204 def name(self, value): 205 capi.rtnl_link_set_name(self._rtnl_link, value) 206 207 # name is the secondary identifier, if _orig does not have 208 # the name specified yet, assume it was meant to be specified 209 # here. ifindex will always take priority, therefore if ifindex 210 # is specified as well, this will be ignored automatically. 211 if capi.rtnl_link_get_name(self._orig) is None: 212 capi.rtnl_link_set_name(self._orig, value) 213 214 @property 215 @netlink.nlattr(type=str, fmt=util.string) 216 def flags(self): 217 """Flags 218 Setting this property will *Not* reset flags to value you supply in 219 Examples: 220 link.flags = '+xxx' # add xxx flag 221 link.flags = 'xxx' # exactly the same 222 link.flags = '-xxx' # remove xxx flag 223 link.flags = [ '+xxx', '-yyy' ] # list operation 224 """ 225 flags = capi.rtnl_link_get_flags(self._rtnl_link) 226 return capi.rtnl_link_flags2str(flags, 256)[0].split(',') 227 228 def _set_flag(self, flag): 229 if flag.startswith('-'): 230 i = capi.rtnl_link_str2flags(flag[1:]) 231 capi.rtnl_link_unset_flags(self._rtnl_link, i) 232 elif flag.startswith('+'): 233 i = capi.rtnl_link_str2flags(flag[1:]) 234 capi.rtnl_link_set_flags(self._rtnl_link, i) 235 else: 236 i = capi.rtnl_link_str2flags(flag) 237 capi.rtnl_link_set_flags(self._rtnl_link, i) 238 239 @flags.setter 240 def flags(self, value): 241 if not (type(value) is str): 242 for flag in value: 243 self._set_flag(flag) 244 else: 245 self._set_flag(value) 246 247 @property 248 @netlink.nlattr(type=int, fmt=util.num) 249 def mtu(self): 250 """Maximum Transmission Unit""" 251 return capi.rtnl_link_get_mtu(self._rtnl_link) 252 253 @mtu.setter 254 def mtu(self, value): 255 capi.rtnl_link_set_mtu(self._rtnl_link, int(value)) 256 257 @property 258 @netlink.nlattr(type=int, immutable=True, fmt=util.num) 259 def family(self): 260 """Address family""" 261 return capi.rtnl_link_get_family(self._rtnl_link) 262 263 @family.setter 264 def family(self, value): 265 capi.rtnl_link_set_family(self._rtnl_link, value) 266 267 @property 268 @netlink.nlattr(type=str, fmt=util.addr) 269 def address(self): 270 """Hardware address (MAC address)""" 271 a = capi.rtnl_link_get_addr(self._rtnl_link) 272 return netlink.AbstractAddress(a) 273 274 @address.setter 275 def address(self, value): 276 capi.rtnl_link_set_addr(self._rtnl_link, value._addr) 277 278 @property 279 @netlink.nlattr(type=str, fmt=util.addr) 280 def broadcast(self): 281 """Hardware broadcast address""" 282 a = capi.rtnl_link_get_broadcast(self._rtnl_link) 283 return netlink.AbstractAddress(a) 284 285 @broadcast.setter 286 def broadcast(self, value): 287 capi.rtnl_link_set_broadcast(self._rtnl_link, value._addr) 288 289 @property 290 @netlink.nlattr(type=str, immutable=True, fmt=util.string) 291 def qdisc(self): 292 """Name of qdisc (cannot be changed)""" 293 return capi.rtnl_link_get_qdisc(self._rtnl_link) 294 295 @qdisc.setter 296 def qdisc(self, value): 297 capi.rtnl_link_set_qdisc(self._rtnl_link, value) 298 299 @property 300 @netlink.nlattr(type=int, fmt=util.num) 301 def txqlen(self): 302 """Length of transmit queue""" 303 return capi.rtnl_link_get_txqlen(self._rtnl_link) 304 305 @txqlen.setter 306 def txqlen(self, value): 307 capi.rtnl_link_set_txqlen(self._rtnl_link, int(value)) 308 309 @property 310 @netlink.nlattr(type=str, immutable=True, fmt=util.string) 311 def arptype(self): 312 """Type of link (cannot be changed)""" 313 type_ = capi.rtnl_link_get_arptype(self._rtnl_link) 314 return core_capi.nl_llproto2str(type_, 64)[0] 315 316 @arptype.setter 317 def arptype(self, value): 318 i = core_capi.nl_str2llproto(value) 319 capi.rtnl_link_set_arptype(self._rtnl_link, i) 320 321 @property 322 @netlink.nlattr(type=str, immutable=True, fmt=util.string, title='state') 323 def operstate(self): 324 """Operational status""" 325 operstate = capi.rtnl_link_get_operstate(self._rtnl_link) 326 return capi.rtnl_link_operstate2str(operstate, 32)[0] 327 328 @operstate.setter 329 def operstate(self, value): 330 i = capi.rtnl_link_str2operstate(value) 331 capi.rtnl_link_set_operstate(self._rtnl_link, i) 332 333 @property 334 @netlink.nlattr(type=str, immutable=True, fmt=util.string) 335 def mode(self): 336 """Link mode""" 337 mode = capi.rtnl_link_get_linkmode(self._rtnl_link) 338 return capi.rtnl_link_mode2str(mode, 32)[0] 339 340 @mode.setter 341 def mode(self, value): 342 i = capi.rtnl_link_str2mode(value) 343 capi.rtnl_link_set_linkmode(self._rtnl_link, i) 344 345 @property 346 @netlink.nlattr(type=str, fmt=util.string) 347 def alias(self): 348 """Interface alias (SNMP)""" 349 return capi.rtnl_link_get_ifalias(self._rtnl_link) 350 351 @alias.setter 352 def alias(self, value): 353 capi.rtnl_link_set_ifalias(self._rtnl_link, value) 354 355 @property 356 @netlink.nlattr(type=str, fmt=util.string) 357 def type(self): 358 """Link type""" 359 return capi.rtnl_link_get_type(self._rtnl_link) 360 361 @type.setter 362 def type(self, value): 363 if capi.rtnl_link_set_type(self._rtnl_link, value) < 0: 364 raise NameError('unknown info type') 365 366 self._module_lookup('netlink.route.links.' + value) 367 368 def get_stat(self, stat): 369 """Retrieve statistical information""" 370 if type(stat) is str: 371 stat = capi.rtnl_link_str2stat(stat) 372 if stat < 0: 373 raise NameError('unknown name of statistic') 374 375 return capi.rtnl_link_get_stat(self._rtnl_link, stat) 376 377 def enslave(self, slave, sock=None): 378 if not sock: 379 sock = netlink.lookup_socket(netlink.NETLINK_ROUTE) 380 381 return capi.rtnl_link_enslave(sock._sock, self._rtnl_link, slave._rtnl_link) 382 383 def release(self, slave, sock=None): 384 if not sock: 385 sock = netlink.lookup_socket(netlink.NETLINK_ROUTE) 386 387 return capi.rtnl_link_release(sock._sock, self._rtnl_link, slave._rtnl_link) 388 389 def add(self, sock=None, flags=None): 390 if not sock: 391 sock = netlink.lookup_socket(netlink.NETLINK_ROUTE) 392 393 if not flags: 394 flags = netlink.NLM_F_CREATE 395 396 ret = capi.rtnl_link_add(sock._sock, self._rtnl_link, flags) 397 if ret < 0: 398 raise netlink.KernelError(ret) 399 400 def change(self, sock=None, flags=0): 401 """Commit changes made to the link object""" 402 if sock is None: 403 sock = netlink.lookup_socket(netlink.NETLINK_ROUTE) 404 405 if not self._orig: 406 raise netlink.NetlinkError('Original link not available') 407 ret = capi.rtnl_link_change(sock._sock, self._orig, self._rtnl_link, flags) 408 if ret < 0: 409 raise netlink.KernelError(ret) 410 411 def delete(self, sock=None): 412 """Attempt to delete this link in the kernel""" 413 if sock is None: 414 sock = netlink.lookup_socket(netlink.NETLINK_ROUTE) 415 416 ret = capi.rtnl_link_delete(sock._sock, self._rtnl_link) 417 if ret < 0: 418 raise netlink.KernelError(ret) 419 420 ################################################################### 421 # private properties 422 # 423 # Used for formatting output. USE AT OWN RISK 424 @property 425 def _state(self): 426 if 'up' in self.flags: 427 buf = util.good('up') 428 if 'lowerup' not in self.flags: 429 buf += ' ' + util.bad('no-carrier') 430 else: 431 buf = util.bad('down') 432 return buf 433 434 @property 435 def _brief(self): 436 return self._module_brief() + self._foreach_af('brief') 437 438 @property 439 def _flags(self): 440 ignore = [ 441 'up', 442 'running', 443 'lowerup', 444 ] 445 return ','.join([flag for flag in self.flags if flag not in ignore]) 446 447 def _foreach_af(self, name, args=None): 448 buf = '' 449 for af in self.af: 450 try: 451 func = getattr(self.af[af], name) 452 s = str(func(args)) 453 if len(s) > 0: 454 buf += ' ' + s 455 except AttributeError: 456 pass 457 return buf 458 459 def format(self, details=False, stats=False, indent=''): 460 """Return link as formatted text""" 461 fmt = util.MyFormatter(self, indent) 462 463 buf = fmt.format('{a|ifindex} {a|name} {a|arptype} {a|address} '\ 464 '{a|_state} <{a|_flags}> {a|_brief}') 465 466 if details: 467 buf += fmt.nl('\t{t|mtu} {t|txqlen} {t|weight} '\ 468 '{t|qdisc} {t|operstate}') 469 buf += fmt.nl('\t{t|broadcast} {t|alias}') 470 471 buf += self._foreach_af('details', fmt) 472 473 if stats: 474 l = [['Packets', RX_PACKETS, TX_PACKETS], 475 ['Bytes', RX_BYTES, TX_BYTES], 476 ['Errors', RX_ERRORS, TX_ERRORS], 477 ['Dropped', RX_DROPPED, TX_DROPPED], 478 ['Compressed', RX_COMPRESSED, TX_COMPRESSED], 479 ['FIFO Errors', RX_FIFO_ERR, TX_FIFO_ERR], 480 ['Length Errors', RX_LEN_ERR, None], 481 ['Over Errors', RX_OVER_ERR, None], 482 ['CRC Errors', RX_CRC_ERR, None], 483 ['Frame Errors', RX_FRAME_ERR, None], 484 ['Missed Errors', RX_MISSED_ERR, None], 485 ['Abort Errors', None, TX_ABORT_ERR], 486 ['Carrier Errors', None, TX_CARRIER_ERR], 487 ['Heartbeat Errors', None, TX_HBEAT_ERR], 488 ['Window Errors', None, TX_WIN_ERR], 489 ['Collisions', None, COLLISIONS], 490 ['Multicast', None, MULTICAST], 491 ['', None, None], 492 ['Ipv6:', None, None], 493 ['Packets', IP6_INPKTS, IP6_OUTPKTS], 494 ['Bytes', IP6_INOCTETS, IP6_OUTOCTETS], 495 ['Discards', IP6_INDISCARDS, IP6_OUTDISCARDS], 496 ['Multicast Packets', IP6_INMCASTPKTS, IP6_OUTMCASTPKTS], 497 ['Multicast Bytes', IP6_INMCASTOCTETS, IP6_OUTMCASTOCTETS], 498 ['Broadcast Packets', IP6_INBCASTPKTS, IP6_OUTBCASTPKTS], 499 ['Broadcast Bytes', IP6_INBCASTOCTETS, IP6_OUTBCASTOCTETS], 500 ['Delivers', IP6_INDELIVERS, None], 501 ['Forwarded', None, IP6_OUTFORWDATAGRAMS], 502 ['No Routes', IP6_INNOROUTES, IP6_OUTNOROUTES], 503 ['Header Errors', IP6_INHDRERRORS, None], 504 ['Too Big Errors', IP6_INTOOBIGERRORS, None], 505 ['Address Errors', IP6_INADDRERRORS, None], 506 ['Unknown Protocol', IP6_INUNKNOWNPROTOS, None], 507 ['Truncated Packets', IP6_INTRUNCATEDPKTS, None], 508 ['Reasm Timeouts', IP6_REASMTIMEOUT, None], 509 ['Reasm Requests', IP6_REASMREQDS, None], 510 ['Reasm Failures', IP6_REASMFAILS, None], 511 ['Reasm OK', IP6_REASMOKS, None], 512 ['Frag Created', None, IP6_FRAGCREATES], 513 ['Frag Failures', None, IP6_FRAGFAILS], 514 ['Frag OK', None, IP6_FRAGOKS], 515 ['', None, None], 516 ['ICMPv6:', None, None], 517 ['Messages', ICMP6_INMSGS, ICMP6_OUTMSGS], 518 ['Errors', ICMP6_INERRORS, ICMP6_OUTERRORS]] 519 520 buf += '\n\t%s%s%s%s\n' % (33 * ' ', util.title('RX'), 521 15 * ' ', util.title('TX')) 522 523 for row in l: 524 row[0] = util.kw(row[0]) 525 row[1] = self.get_stat(row[1]) if row[1] else '' 526 row[2] = self.get_stat(row[2]) if row[2] else '' 527 buf += '\t{0[0]:27} {0[1]:>16} {0[2]:>16}\n'.format(row) 528 529 buf += self._foreach_af('stats') 530 531 return buf 532 533 def get(name, sock=None): 534 """Lookup Link object directly from kernel""" 535 if not name: 536 raise ValueError() 537 538 if not sock: 539 sock = netlink.lookup_socket(netlink.NETLINK_ROUTE) 540 541 link = capi.get_from_kernel(sock._sock, 0, name) 542 if not link: 543 return None 544 545 return Link.from_capi(link) 546 547 _link_cache = LinkCache() 548 549 def resolve(name): 550 _link_cache.refill() 551 return _link_cache[name] 552