sinetstat 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. #!/usr/bin/python2
  2. # (c) 2016 by Siegrist(SystemLoesungen) <PSS @ ZweierNet.ch>
  3. # Website: [https://wiki.zweiernet.ch/wiki/Sinetstat]
  4. #
  5. # This program is free software.
  6. #
  7. # The program is based on a python netstat script that was written by da667 available on https://github.com/da667/netstat
  8. # who had it adapted from Ricardo Pascal, available on http://voorloopnul.com/blog/a-python-netstat-in-less-than-100-lines-of-code.
  9. #
  10. # This version has some improvements make it an acceptable alternative to the original netstat command.
  11. # So it can explore IPv4 in IPv6 listening sockets and some other information over and above the original netstat.
  12. #
  13. # Simply try: 'sinetstat -h'
  14. #
  15. import pwd
  16. import os
  17. import re
  18. import glob
  19. import socket
  20. import sys
  21. import string
  22. import fcntl
  23. import struct
  24. import argparse
  25. VERSION = '1.01'
  26. PROC_TCP4 = "/proc/net/tcp"
  27. PROC_UDP4 = "/proc/net/udp"
  28. PROC_TCP6 = "/proc/net/tcp6"
  29. PROC_UDP6 = "/proc/net/udp6"
  30. MAX_IPV4_ADDRESS = 0xffffffff
  31. MAX_IPV6_ADDRESS = 0xffffffffffffffffffffffffffffffff
  32. TCP_STATE = {
  33. '01':'ESTABLISHED',
  34. '02':'SYN_SENT',
  35. '03':'SYN_RECV',
  36. '04':'FIN_WAIT1',
  37. '05':'FIN_WAIT2',
  38. '06':'TIME_WAIT',
  39. '07':'CLOSE',
  40. '08':'CLOSE_WAIT',
  41. '09':'LAST_ACK',
  42. '0A':'LISTEN',
  43. '0B':'CLOSING'
  44. }
  45. v4ports = []
  46. opt_l = True
  47. def grep_b(list, search):
  48. return [True for i in list if search in i]
  49. def get_ip_address(ifname):
  50. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  51. return socket.inet_ntoa(fcntl.ioctl(
  52. s.fileno(),
  53. 0x8915, # SIOCGIFADDR
  54. struct.pack('256s', ifname[:15])
  55. )[20:24])
  56. def _tcp4load():
  57. ''' Read the table of tcp connections & remove the header '''
  58. with open(PROC_TCP4,'r') as f:
  59. content = f.readlines()
  60. content.pop(0)
  61. return content
  62. def _tcp6load():
  63. ''' Read the table of tcpv6 connections & remove the header'''
  64. with open(PROC_TCP6,'r') as f:
  65. content = f.readlines()
  66. content.pop(0)
  67. return content
  68. def _udp4load():
  69. '''Read the table of udp connections & remove the header '''
  70. with open(PROC_UDP4,'r') as f:
  71. content = f.readlines()
  72. content.pop(0)
  73. return content
  74. def _udp6load():
  75. '''Read the table of udp connections & remove the header '''
  76. with open(PROC_UDP6,'r') as f:
  77. content = f.readlines()
  78. content.pop(0)
  79. return content
  80. def _hex2dec(s):
  81. return str(int(s,16))
  82. def _ip(s):
  83. ip = [(_hex2dec(s[6:8])),(_hex2dec(s[4:6])),(_hex2dec(s[2:4])),(_hex2dec(s[0:2]))]
  84. return '.'.join(ip)
  85. def _ip6(s):
  86. ip = [s[6:8],s[4:6],s[2:4],s[0:2],s[12:14],s[14:16],s[10:12],s[8:10],s[22:24],s[20:22],s[18:20],s[16:18],s[30:32],s[28:30],s[26:28],s[24:26]]
  87. #print '66666:', ':'.join(ip), s
  88. return ':'.join(ip)
  89. def _ip6q(s):
  90. ip = [s[6:8]+s[4:6],s[2:4]+s[0:2],s[12:14]+s[14:16],s[10:12]+s[8:10],s[22:24]+s[20:22],s[18:20]+s[16:18],s[30:32]+s[28:30],s[26:28]+s[24:26]]
  91. #print '666qqq:', ':'.join(ip), s
  92. return ':'.join(ip)
  93. def _conv_v6(s):
  94. return s
  95. def _remove_empty(array):
  96. return [x for x in array if x !='']
  97. def _convert_ipv4_port(array):
  98. host,port = array.split(':')
  99. return _ip(host),_hex2dec(port)
  100. def _convert_ipv6_port(array):
  101. host,port = array.split(':')
  102. return _ip6(host),_hex2dec(port)
  103. def _convert_ipv6(array):
  104. host,port = array.split(':')
  105. return _ip6q(host)
  106. def _addr_normal(s):
  107. return ':'.join(["%x" % x for x in [int(x, 16) for x in s.split(':')]])
  108. def _countFollowingZeros(l):
  109. """Return number of elements containing 0 at the beginning of the list."""
  110. #print 'aaa:', l
  111. if len(l) == 0:
  112. return 0
  113. elif l[0] != 0:
  114. return 0
  115. else:
  116. return 1 + _countFollowingZeros(l[1:])
  117. def _compress_v6(addr):
  118. hextets = [int(x, 16) for x in addr.split(':')]
  119. #print hextets
  120. followingzeros = [0] * 8
  121. for i in xrange(len(hextets)):
  122. followingzeros[i] = _countFollowingZeros(hextets[i:])
  123. # compressionpos is the position where we can start removing zeros
  124. compressionpos = followingzeros.index(max(followingzeros))
  125. if max(followingzeros) > 1:
  126. # genererate string with the longest number of zeros cut out
  127. # now we need hextets as strings
  128. hextets = [x for x in _addr_normal(addr).split(':')]
  129. while compressionpos < len(hextets) and hextets[compressionpos] == '0':
  130. del(hextets[compressionpos])
  131. hextets.insert(compressionpos, '')
  132. if compressionpos + 1 >= len(hextets):
  133. hextets.append('')
  134. if compressionpos == 0:
  135. hextets = [''] + hextets
  136. return ':'.join(hextets)
  137. else:
  138. return _addr_normal(addr)
  139. def _resolve_ip(host):
  140. """
  141. resolve ip und update dictionary res_cache {'ip': 'name'}.
  142. If resolution for a ip failed, 'name' is n_try ... 0.
  143. """
  144. try:
  145. hname = socket.gethostbyaddr(host)[0]
  146. return str(hname)
  147. except:
  148. return str(host)
  149. def netstat_tcp4():
  150. '''
  151. Function to return a list with status of tcp4 connections on Linux systems.
  152. '''
  153. tcpcontent =_tcp4load()
  154. tcpresult = []
  155. for line in tcpcontent:
  156. line_array = _remove_empty(line.split(' ')) # Split lines and remove empty spaces.
  157. l_host,l_port = _convert_ipv4_port(line_array[1]) # Convert ipaddress and port from hex to decimal.
  158. r_host,r_port = _convert_ipv4_port(line_array[2])
  159. tcp_id = line_array[0]
  160. state = TCP_STATE[line_array[3]]
  161. if state != 'LISTEN' and o_listen == True:
  162. continue
  163. if ( state == 'LISTEN' or state == 'SYN_SENT' or state == 'SYN_RECV' ) and o_estab == True:
  164. continue
  165. try:
  166. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  167. except:
  168. uid = line_array[7]
  169. inode = line_array[9] # Need the inode to get process pid.
  170. if int(inode) > 0:
  171. pid = _get_pid_of_inode(inode)
  172. try: # try read the process name.
  173. if o_wide == True:
  174. with open('/proc/'+pid+'/cmdline','r') as f:
  175. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  176. elif o_wider == True:
  177. with open('/proc/'+pid+'/cmdline','r') as f:
  178. exe = ' '.join(f.read().split('\x00'))
  179. else:
  180. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  181. except:
  182. exe = '-'
  183. else:
  184. pid = '-'
  185. exe = '-'
  186. if o_numeric == False:
  187. r_host = _resolve_ip(r_host)
  188. nline = '%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('TCP4', l_host+': '+l_port, r_host+': '+r_port, state, uid, pid, exe)
  189. tcpresult.append(nline)
  190. # update v4inv6check list
  191. v4ports.append(l_port)
  192. return tcpresult
  193. def netstat_tcp6():
  194. '''
  195. This function returns a list of tcp connections utilizing ipv6.
  196. '''
  197. tcpcontent = _tcp6load()
  198. tcpresult = []
  199. for line in tcpcontent:
  200. line_array = _remove_empty(line.split(' '))
  201. l_host,l_port = _convert_ipv6_port(line_array[1])
  202. r_host,r_port = _convert_ipv6_port(line_array[2])
  203. tcp_id = line_array[0]
  204. state = TCP_STATE[line_array[3]]
  205. if state != 'LISTEN' and o_listen == True:
  206. continue
  207. if ( state == 'LISTEN' or state == 'SYN_SENT' or state == 'SYN_RECV' ) and o_estab == True:
  208. continue
  209. try:
  210. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  211. except:
  212. uid = line_array[7]
  213. inode = line_array[9]
  214. if int(inode) > 0:
  215. pid = _get_pid_of_inode(inode)
  216. try: # try read the process name.
  217. if o_wide == True:
  218. with open('/proc/'+pid+'/cmdline','r') as f:
  219. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  220. elif o_wider == True:
  221. with open('/proc/'+pid+'/cmdline','r') as f:
  222. exe = ' '.join(f.read().split('\x00'))
  223. else:
  224. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  225. except:
  226. exe = '-'
  227. else:
  228. pid = '-'
  229. exe = '-'
  230. nline = '%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('TCP6', _compress_v6(_convert_ipv6(line_array[1]))+': '+l_port, _compress_v6(_convert_ipv6(line_array[2]))+': '+r_port, state, uid, pid, exe)
  231. tcpresult.append(nline)
  232. return tcpresult
  233. def netstat_tcp4in6():
  234. '''
  235. Returns a list of tcp ipv4 in ipv6 listen sockets.
  236. '''
  237. #print xx()
  238. tcpcontent = _tcp6load()
  239. tcpresult = []
  240. for line in tcpcontent:
  241. line_array = _remove_empty(line.split(' '))
  242. #if TCP_STATE[line_array[3]] != 'LISTEN':
  243. # continue
  244. l_host,l_port = _convert_ipv6_port(line_array[1])
  245. r_host,r_port = _convert_ipv6_port(line_array[2])
  246. if grep_b(v4ports,l_port):
  247. continue
  248. tcp_id = line_array[0]
  249. state = TCP_STATE[line_array[3]]
  250. if state != 'LISTEN' and o_listen == True:
  251. continue
  252. if ( state == 'LISTEN' or state == 'SYN_SENT' or state == 'SYN_RECV' ) and o_estab == True:
  253. continue
  254. try:
  255. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  256. except:
  257. uid = line_array[7]
  258. inode = line_array[9]
  259. if int(inode) > 0:
  260. pid = _get_pid_of_inode(inode)
  261. try: # try read the process name.
  262. if o_wide == True:
  263. with open('/proc/'+pid+'/cmdline','r') as f:
  264. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  265. elif o_wider == True:
  266. with open('/proc/'+pid+'/cmdline','r') as f:
  267. exe = ' '.join(f.read().split('\x00'))
  268. else:
  269. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  270. except:
  271. exe = '-'
  272. else:
  273. pid = '-'
  274. exe = '-'
  275. if l_host == '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01':
  276. if _check_v4inv6_port("127.0.0.1",l_port):
  277. nline = '%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('TCP4in6', '127.0.0.1: '+l_port, _compress_v6(_convert_ipv6(line_array[2]))+': '+r_port, state, uid, pid, exe)
  278. tcpresult.append(nline)
  279. if l_host == "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00":
  280. if _check_v4inv6_port("0.0.0.0",l_port):
  281. nline = '%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('TCP4in6', '0.0.0.0: '+l_port, _compress_v6(_convert_ipv6(line_array[2]))+': '+r_port, state, uid, pid, exe)
  282. tcpresult.append(nline)
  283. #else:
  284. # for a in MYIFS.split():
  285. # _check_v4inv6_port(get_ip_address(a),l_port)
  286. return tcpresult
  287. def netstat_udp4():
  288. '''
  289. Function to return a list with status of udp connections.
  290. '''
  291. udpcontent =_udp4load()
  292. udpresult = []
  293. for line in udpcontent:
  294. line_array = _remove_empty(line.split(' '))
  295. l_host,l_port = _convert_ipv4_port(line_array[1])
  296. r_host,r_port = _convert_ipv4_port(line_array[2])
  297. udp_id = line_array[0]
  298. udp_state = TCP_STATE[line_array[3]]
  299. if ( udp_state != 'ESTABLISHED' and o_estab == True ) or o_estab == False:
  300. continue
  301. if udp_state != 'ESTABLISHED':
  302. udp_state =' ' #UDP is stateless
  303. try:
  304. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  305. except:
  306. uid = line_array[7]
  307. inode = line_array[9]
  308. if int(inode) > 0:
  309. pid = _get_pid_of_inode(inode)
  310. try: # try read the process name.
  311. if o_wide == True:
  312. with open('/proc/'+pid+'/cmdline','r') as f:
  313. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  314. elif o_wider == True:
  315. with open('/proc/'+pid+'/cmdline','r') as f:
  316. exe = ' '.join(f.read().split('\x00'))
  317. else:
  318. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  319. except:
  320. exe = '-'
  321. else:
  322. pid = '-'
  323. exe = '-'
  324. nline = '%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('UDP4', l_host+': '+l_port, r_host+': '+r_port, udp_state, uid, pid, exe)
  325. udpresult.append(nline)
  326. return udpresult
  327. def netstat_udp6():
  328. '''
  329. Function to return a list of udp connection utilizing ipv6
  330. '''
  331. udpcontent =_udp6load()
  332. udpresult = []
  333. for line in udpcontent:
  334. line_array = _remove_empty(line.split(' '))
  335. l_host,l_port = _convert_ipv6_port(line_array[1])
  336. r_host,r_port = _convert_ipv6_port(line_array[2])
  337. udp_id = line_array[0]
  338. udp_state = TCP_STATE[line_array[3]]
  339. if ( udp_state != 'ESTABLISHED' and o_estab == True ) or o_estab == False:
  340. continue
  341. if udp_state != 'ESTABLISHED':
  342. udp_state =' ' #UDP is stateless
  343. try:
  344. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  345. except:
  346. uid = line_array[7]
  347. inode = line_array[9]
  348. if int(inode) > 0:
  349. pid = _get_pid_of_inode(inode)
  350. try: # try read the process name.
  351. if o_wide == True:
  352. with open('/proc/'+pid+'/cmdline','r') as f:
  353. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  354. elif o_wider == True:
  355. with open('/proc/'+pid+'/cmdline','r') as f:
  356. exe = ' '.join(f.read().split('\x00'))
  357. else:
  358. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  359. except:
  360. exe = '-'
  361. else:
  362. pid = '-'
  363. exe = '-'
  364. nline = '%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('UDP6', _compress_v6(_convert_ipv6(line_array[1]))+': '+l_port, _compress_v6(_convert_ipv6(line_array[2]))+': '+r_port, udp_state, uid, pid, exe)
  365. udpresult.append(nline)
  366. return udpresult
  367. def _get_pid_of_inode(inode):
  368. '''
  369. To retrieve the process pid, check every running process and look for one using
  370. the given inode.
  371. '''
  372. for item in glob.glob('/proc/[0-9]*/fd/[0-9]*'):
  373. try:
  374. if re.search(inode,os.readlink(item)):
  375. return item.split('/')[2]
  376. except:
  377. pass
  378. return None
  379. def check_root():
  380. if os.getuid() == 0:
  381. return True
  382. else:
  383. return False
  384. def _check_v4inv6_port(addr,portnr):
  385. '''
  386. check if a v4 port is listening over ip6. Strange, we do a SYN connect for every port not listening v4.
  387. thats because I think there is no image in the /proc filesystem
  388. '''
  389. #print 'aaacc:', addr, portnr
  390. is_onnected = False
  391. try:
  392. try:
  393. t_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  394. except:
  395. print("Error: Can't open socket!\n")
  396. return False
  397. t_socket.connect((addr, int(portnr)))
  398. is_onnected = True
  399. except:
  400. is_onnected = False
  401. finally:
  402. if(is_onnected and portnr != t_socket.getsockname()[1]):
  403. #print("{}:{} Open \n".format(addr, portnr))
  404. t_socket.close()
  405. return True
  406. t_socket.close()
  407. return False
  408. if __name__ == '__main__':
  409. if not check_root():
  410. print("This program needs root privileges !\nThats because of reading the /proc filesystem and using functions like getpwuid().\nSo I give up\n")
  411. sys.exit()
  412. #print
  413. # commandline params
  414. o_numeric = True
  415. o_listen = False
  416. o_estab = None
  417. o_wide = False
  418. o_wider = False
  419. o_udp = True
  420. o_tcp = True
  421. o_v6 = True
  422. o_v4 = True
  423. parser = argparse.ArgumentParser(description='netstat utility V'+VERSION+"\n2017 by sigi <https://wiki.zweiernet.ch/wiki/sinetstat>",
  424. formatter_class=argparse.RawDescriptionHelpFormatter )
  425. parser.add_argument('-l', help="Only listening sockets", action="store_true")
  426. parser.add_argument('-e', help="Only established sockets", action="store_true")
  427. parser.add_argument('-r', help="Resolve IP-Addresses", action="store_true")
  428. parser.add_argument('-w', help="Wide (show cmd)", action="store_true")
  429. parser.add_argument('-W', help="Wider (show cmd with arguments)", action="store_true")
  430. parser.add_argument('-t', help="Only TCP", action="store_true")
  431. parser.add_argument('-u', help="Only UDP", action="store_true")
  432. parser.add_argument('-4', dest='v4', help="Only IPv4", action="store_true")
  433. parser.add_argument('-6', dest='v6', help="Only IPv6", action="store_true")
  434. args = parser.parse_args()
  435. if args.r:
  436. o_numeric = False
  437. if args.l:
  438. o_listen = True
  439. if args.e:
  440. o_estab = True
  441. o_listen = False
  442. if args.w:
  443. o_wide = True
  444. if args.W:
  445. o_wider = True
  446. o_wide = False
  447. if args.t:
  448. o_udp = False
  449. if args.u:
  450. o_tcp = False
  451. if args.v4:
  452. o_v6 = False
  453. if args.v6:
  454. o_v4 = False
  455. # Output
  456. print '%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('Proto', 'Local Address', 'Remote Address', 'State', 'UID', 'PID', 'Program')
  457. print '%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('-----', '-------------', '--------------', '-----', '---', '---', '-------')
  458. #print "\nTCP (v4) Results:\n"
  459. if o_v4 == True and o_tcp == True:
  460. for conn_tcp in netstat_tcp4():
  461. print conn_tcp
  462. #print "\nTCP (v4inv6) Results:\n"
  463. if o_v4 == True and o_tcp == True:
  464. for conn_tcp46 in netstat_tcp4in6():
  465. print conn_tcp46
  466. #print "\nTCP (v6) Results:\n"
  467. if o_v6 == True and o_tcp == True:
  468. for conn_tcp6 in netstat_tcp6():
  469. print conn_tcp6
  470. #print "\nUDP (v4) Results:\n"
  471. if o_v4 == True and o_udp == True and not args.l:
  472. for conn_udp in netstat_udp4():
  473. print conn_udp
  474. #print "\nUDP (v6) Results:\n"
  475. if o_v6 == True and o_udp == True and not args.l:
  476. for conn_udp6 in netstat_udp6():
  477. print conn_udp6