PageRenderTime 59ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/salt/modules/network.py

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