PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/salt/modules/network.py

https://github.com/inthecloud247/salt
Python | 499 lines | 478 code | 9 blank | 12 comment | 1 complexity | 0fd417011c85d7329fbe2d767035c4e1 MD5 | raw file
Possible License(s): Apache-2.0
  1. '''
  2. Module for gathering and managing network information
  3. '''
  4. # Import python libs
  5. import re
  6. import logging
  7. # Import salt libs
  8. import salt.utils.socket_util
  9. log = logging.getLogger(__name__)
  10. def __virtual__():
  11. '''
  12. Only work on posix-like systems
  13. '''
  14. # Disable on Windows, a specific file module exists:
  15. if __grains__['os'] in ('Windows',):
  16. return False
  17. return 'network'
  18. def _cidr_to_ipv4_netmask(cidr_bits):
  19. '''
  20. Returns an IPv4 netmask
  21. '''
  22. netmask = ''
  23. for idx in range(4):
  24. if idx:
  25. netmask += '.'
  26. if cidr_bits >= 8:
  27. netmask += '255'
  28. cidr_bits -= 8
  29. else:
  30. netmask += '{0:d}'.format(256 - (2 ** (8 - cidr_bits)))
  31. cidr_bits = 0
  32. return netmask
  33. def _number_of_set_bits_to_ipv4_netmask(set_bits): # pylint: disable-msg=C0103
  34. '''
  35. Returns an IPv4 netmask from the integer representation of that mask.
  36. Ex. 0xffffff00 -> '255.255.255.0'
  37. '''
  38. return _cidr_to_ipv4_netmask(_number_of_set_bits(set_bits))
  39. # pylint: disable-msg=C0103
  40. def _number_of_set_bits(x):
  41. '''
  42. Returns the number of bits that are set in a 32bit int
  43. '''
  44. #Taken from http://stackoverflow.com/a/4912729. Many thanks!
  45. x -= (x >> 1) & 0x55555555
  46. x = ((x >> 2) & 0x33333333) + (x & 0x33333333)
  47. x = ((x >> 4) + x) & 0x0f0f0f0f
  48. x += x >> 8
  49. x += x >> 16
  50. return x & 0x0000003f
  51. # pylint: enable-msg=C0103
  52. def _interfaces_ip(out):
  53. '''
  54. Uses ip to return a dictionary of interfaces with various information about
  55. each (up/down state, ip address, netmask, and hwaddr)
  56. '''
  57. ret = dict()
  58. def parse_network(value, cols):
  59. '''
  60. Return a tuple of ip, netmask, broadcast
  61. based on the current set of cols
  62. '''
  63. brd = None
  64. if '/' in value: # we have a CIDR in this address
  65. ip, cidr = value.split('/') # pylint: disable-msg=C0103
  66. else:
  67. ip = value # pylint: disable-msg=C0103
  68. cidr = 32
  69. if type == 'inet':
  70. mask = _cidr_to_ipv4_netmask(int(cidr))
  71. if 'brd' in cols:
  72. brd = cols[cols.index('brd') + 1]
  73. elif type == 'inet6':
  74. mask = cidr
  75. return (ip, mask, brd)
  76. groups = re.compile('\r?\n\d').split(out)
  77. for group in groups:
  78. iface = None
  79. data = dict()
  80. for line in group.splitlines():
  81. if not ' ' in line:
  82. continue
  83. match = re.match('^\d*:\s+([\w.]+)(?:@)?(\w+)?:\s+<(.+)>', line)
  84. if match:
  85. iface, parent, attrs = match.groups()
  86. if 'UP' in attrs.split(','):
  87. data['up'] = True
  88. else:
  89. data['up'] = False
  90. if parent:
  91. data['parent'] = parent
  92. continue
  93. cols = line.split()
  94. if len(cols) >= 2:
  95. type, value = tuple(cols[0:2])
  96. iflabel = cols[-1:][0]
  97. if type in ('inet', 'inet6'):
  98. if 'secondary' not in cols:
  99. ipaddr, netmask, broadcast = parse_network(value, cols)
  100. if type == 'inet':
  101. if 'inet' not in data:
  102. data['inet'] = list()
  103. addr_obj = dict()
  104. addr_obj['address'] = ipaddr
  105. addr_obj['netmask'] = netmask
  106. addr_obj['broadcast'] = broadcast
  107. addr_obj['label'] = iflabel
  108. data['inet'].append(addr_obj)
  109. elif type == 'inet6':
  110. if 'inet6' not in data:
  111. data['inet6'] = list()
  112. addr_obj = dict()
  113. addr_obj['address'] = ipaddr
  114. addr_obj['prefixlen'] = netmask
  115. data['inet6'].append(addr_obj)
  116. else:
  117. if 'secondary' not in data:
  118. data['secondary'] = list()
  119. ip_, mask, brd = parse_network(value, cols)
  120. data['secondary'].append({
  121. 'type': type,
  122. 'address': ip_,
  123. 'netmask': mask,
  124. 'broadcast': brd,
  125. 'label': iflabel,
  126. })
  127. del ip_, mask, brd
  128. elif type.startswith('link'):
  129. data['hwaddr'] = value
  130. if iface:
  131. ret[iface] = data
  132. del iface, data
  133. return ret
  134. def _interfaces_ifconfig(out):
  135. '''
  136. Uses ifconfig to return a dictionary of interfaces with various information
  137. about each (up/down state, ip address, netmask, and hwaddr)
  138. '''
  139. ret = dict()
  140. piface = re.compile('^(\S+):?')
  141. pmac = re.compile('.*?(?:HWaddr|ether) ([0-9a-fA-F:]+)')
  142. pip = re.compile('.*?(?:inet addr:|inet )(.*?)\s')
  143. pip6 = re.compile('.*?(?:inet6 addr: (.*?)/|inet6 )([0-9a-fA-F:]+)')
  144. pmask = re.compile('.*?(?:Mask:|netmask )(?:(0x[0-9a-fA-F]{8})|([\d\.]+))')
  145. pmask6 = re.compile('.*?(?:inet6 addr: [0-9a-fA-F:]+/(\d+)|prefixlen (\d+)).*')
  146. pupdown = re.compile('UP')
  147. pbcast = re.compile('.*?(?:Bcast:|broadcast )([\d\.]+)')
  148. groups = re.compile('\r?\n(?=\S)').split(out)
  149. for group in groups:
  150. data = dict()
  151. iface = ''
  152. updown = False
  153. for line in group.splitlines():
  154. miface = piface.match(line)
  155. mmac = pmac.match(line)
  156. mip = pip.match(line)
  157. mip6 = pip6.match(line)
  158. mupdown = pupdown.search(line)
  159. if miface:
  160. iface = miface.group(1)
  161. if mmac:
  162. data['hwaddr'] = mmac.group(1)
  163. if mip:
  164. if 'inet' not in data:
  165. data['inet'] = list()
  166. addr_obj = dict()
  167. addr_obj['address'] = mip.group(1)
  168. mmask = pmask.match(line)
  169. if mmask:
  170. if mmask.group(1):
  171. mmask = _number_of_set_bits_to_ipv4_netmask(
  172. int(mmask.group(1), 16))
  173. else:
  174. mmask = mmask.group(2)
  175. addr_obj['netmask'] = mmask
  176. mbcast = pbcast.match(line)
  177. if mbcast:
  178. addr_obj['broadcast'] = mbcast.group(1)
  179. data['inet'].append(addr_obj)
  180. if mupdown:
  181. updown = True
  182. if mip6:
  183. if 'inet6' not in data:
  184. data['inet6'] = list()
  185. addr_obj = dict()
  186. addr_obj['address'] = mip6.group(1) or mip6.group(2)
  187. mmask6 = pmask6.match(line)
  188. if mmask6:
  189. addr_obj['prefixlen'] = mmask6.group(1) or mmask6.group(2)
  190. data['inet6'].append(addr_obj)
  191. data['up'] = updown
  192. ret[iface] = data
  193. del data
  194. return ret
  195. def interfaces():
  196. '''
  197. Return a dictionary of information about all the interfaces on the minion
  198. CLI Example::
  199. salt '*' network.interfaces
  200. '''
  201. ifaces = dict()
  202. if __salt__['cmd.has_exec']('ip'):
  203. cmd1 = __salt__['cmd.run']('ip link show')
  204. cmd2 = __salt__['cmd.run']('ip addr show')
  205. ifaces = _interfaces_ip(cmd1 + '\n' + cmd2)
  206. elif __salt__['cmd.has_exec']('ifconfig'):
  207. cmd = __salt__['cmd.run']('ifconfig -a')
  208. ifaces = _interfaces_ifconfig(cmd)
  209. return ifaces
  210. def hwaddr(iface):
  211. '''
  212. Return the hardware address (a.k.a. MAC address) for a given interface
  213. CLI Example::
  214. salt '*' network.hwaddr eth0
  215. '''
  216. return interfaces().get(iface, {}).get('hwaddr', '')
  217. def _get_net_start(ipaddr, netmask):
  218. ipaddr_octets = ipaddr.split('.')
  219. netmask_octets = netmask.split('.')
  220. net_start_octets = [str(int(ipaddr_octets[x]) & int(netmask_octets[x]))
  221. for x in range(0, 4)]
  222. return '.'.join(net_start_octets)
  223. def _get_net_size(mask):
  224. binary_str = ''
  225. for octet in mask.split('.'):
  226. binary_str += bin(int(octet))[2:].zfill(8)
  227. return len(binary_str.rstrip('0'))
  228. def _calculate_subnet(ipaddr, netmask):
  229. return '{0}/{1}'.format(_get_net_start(ipaddr, netmask),
  230. _get_net_size(netmask))
  231. def _ipv4_to_bits(ipaddr):
  232. '''
  233. Accepts an IPv4 dotted quad and returns a string representing its binary
  234. counterpart
  235. '''
  236. return ''.join([bin(int(x))[2:].rjust(8, '0') for x in ipaddr.split('.')])
  237. def subnets():
  238. '''
  239. Returns a list of subnets to which the host belongs
  240. CLI Example::
  241. salt '*' network.subnets
  242. '''
  243. ifaces = interfaces()
  244. subnets = []
  245. for ipv4_info in ifaces.values():
  246. for ipv4 in ipv4_info.get('inet', []):
  247. if ipv4['address'] == '127.0.0.1':
  248. continue
  249. network = _calculate_subnet(ipv4['address'], ipv4['netmask'])
  250. subnets.append(network)
  251. return subnets
  252. def in_subnet(cidr):
  253. '''
  254. Returns True if host is within specified subnet, otherwise False
  255. CLI Example::
  256. salt '*' network.in_subnet 10.0.0.0/16
  257. '''
  258. try:
  259. netstart, netsize = cidr.split('/')
  260. netsize = int(netsize)
  261. except:
  262. log.error('Invalid CIDR \'{0}\''.format(cidr))
  263. return False
  264. netstart_bin = _ipv4_to_bits(netstart)
  265. if netsize < 32 and len(netstart_bin.rstrip('0')) > netsize:
  266. log.error('Invalid network starting IP \'{0}\' in CIDR '
  267. '\'{1}\''.format(netstart, cidr))
  268. return False
  269. netstart_leftbits = netstart_bin[0:netsize]
  270. for ip_addr in ip_addrs():
  271. if netsize == 32:
  272. if netstart == ip_addr:
  273. return True
  274. else:
  275. ip_leftbits = _ipv4_to_bits(ip_addr)[0:netsize]
  276. if netstart_leftbits == ip_leftbits:
  277. return True
  278. return False
  279. def ip_addrs(interface=None, include_loopback=False):
  280. '''
  281. Returns a list of IPv4 addresses assigned to the host. 127.0.0.1 is
  282. ignored, unless 'include_loopback=True' is indicated. If 'interface' is
  283. provided, then only IP addresses from that interface will be returned.
  284. CLI Example::
  285. salt '*' network.ip_addrs
  286. '''
  287. ret = []
  288. ifaces = interfaces()
  289. if interface is None:
  290. target_ifaces = ifaces
  291. else:
  292. target_ifaces = dict([(k, v) for k, v in ifaces.iteritems()
  293. if k == interface])
  294. if not target_ifaces:
  295. log.error('Interface {0} not found.'.format(interface))
  296. for ipv4_info in target_ifaces.values():
  297. for ipv4 in ipv4_info.get('inet', []):
  298. if include_loopback \
  299. or (not include_loopback and ipv4['address'] != '127.0.0.1'):
  300. ret.append(ipv4['address'])
  301. return ret
  302. def ip_addrs6(interface=None, include_loopback=False):
  303. '''
  304. Returns a list of IPv6 addresses assigned to the host. ::1 is ignored,
  305. unless 'include_loopback=True' is indicated. If 'interface' is provided,
  306. then only IP addresses from that interface will be returned.
  307. CLI Example::
  308. salt '*' network.ip_addrs6
  309. '''
  310. ret = []
  311. ifaces = interfaces()
  312. if interface is None:
  313. target_ifaces = ifaces
  314. else:
  315. target_ifaces = dict([(k, v) for k, v in ifaces.iteritems()
  316. if k == interface])
  317. if not target_ifaces:
  318. log.error('Interface {0} not found.'.format(interface))
  319. for ipv6_info in target_ifaces.values():
  320. for ipv6 in ipv6_info.get('inet6', []):
  321. if include_loopback \
  322. or (not include_loopback and ipv6['address'] != '::1'):
  323. ret.append(ipv6['address'])
  324. return ret
  325. def ping(host):
  326. '''
  327. Performs a ping to a host
  328. CLI Example::
  329. salt '*' network.ping archlinux.org
  330. '''
  331. cmd = 'ping -c 4 {0}'.format(salt.utils.socket_util.sanitize_host(host))
  332. return __salt__['cmd.run'](cmd)
  333. # FIXME: Does not work with: netstat 1.42 (2001-04-15) from net-tools 1.6.0 (Ubuntu 10.10)
  334. def netstat():
  335. '''
  336. Return information on open ports and states
  337. CLI Example::
  338. salt '*' network.netstat
  339. '''
  340. ret = []
  341. cmd = 'netstat -tulpnea'
  342. out = __salt__['cmd.run'](cmd).splitlines()
  343. for line in out:
  344. comps = line.split()
  345. if line.startswith('tcp'):
  346. ret.append({
  347. 'inode': comps[7],
  348. 'local-address': comps[3],
  349. 'program': comps[8],
  350. 'proto': comps[0],
  351. 'recv-q': comps[1],
  352. 'remote-address': comps[4],
  353. 'send-q': comps[2],
  354. 'state': comps[5],
  355. 'user': comps[6]})
  356. if line.startswith('udp'):
  357. ret.append({
  358. 'inode': comps[6],
  359. 'local-address': comps[3],
  360. 'program': comps[7],
  361. 'proto': comps[0],
  362. 'recv-q': comps[1],
  363. 'remote-address': comps[4],
  364. 'send-q': comps[2],
  365. 'user': comps[5]})
  366. return ret
  367. # FIXME: This is broken on: Modern traceroute for Linux, version 2.0.14, May 10 2010 (Ubuntu 10.10)
  368. # FIXME: traceroute is deprecated, make this fall back to tracepath
  369. def traceroute(host):
  370. '''
  371. Performs a traceroute to a 3rd party host
  372. CLI Example::
  373. salt '*' network.traceroute archlinux.org
  374. '''
  375. ret = []
  376. cmd = 'traceroute {0}'.format(salt.utils.socket_util.sanitize_host(host))
  377. out = __salt__['cmd.run'](cmd)
  378. for line in out:
  379. if not ' ' in line:
  380. continue
  381. if line.startswith('traceroute'):
  382. continue
  383. comps = line.split()
  384. result = {
  385. 'count': comps[0],
  386. 'hostname': comps[1],
  387. 'ip': comps[2],
  388. 'ms1': comps[4],
  389. 'ms2': comps[6],
  390. 'ms3': comps[8],
  391. 'ping1': comps[3],
  392. 'ping2': comps[5],
  393. 'ping3': comps[7]}
  394. ret.append(result)
  395. return ret
  396. def dig(host):
  397. '''
  398. Performs a DNS lookup with dig
  399. CLI Example::
  400. salt '*' network.dig archlinux.org
  401. '''
  402. cmd = 'dig {0}'.format(salt.utils.socket_util.sanitize_host(host))
  403. return __salt__['cmd.run'](cmd)
  404. def arp():
  405. '''
  406. Return the arp table from the minion
  407. CLI Example::
  408. salt '*' \* network.arp
  409. '''
  410. ret = {}
  411. out = __salt__['cmd.run']('arp -an')
  412. for line in out.splitlines():
  413. comps = line.split()
  414. if len(comps) < 4:
  415. continue
  416. ret[comps[3]] = comps[1].strip('(').strip(')')
  417. return ret