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