link.py 17 KB

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