sinetstat 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. #!/usr/bin/env python
  2. # (c) 2016-2019 by Siegrist(SystemLoesungen) <PSS @ ZweierNet.ch>
  3. # Website: [https://wiki.zweiernet.ch/wiki/Sinetstat]
  4. #
  5. # This program is free software under the terms of the GNU General Public License.
  6. #
  7. # 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.3.1'
  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[14:16],s[12:14],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. return ':'.join(ip)
  88. def _ip6q(s):
  89. ip = [s[6:8]+s[4:6],s[2:4]+s[0:2],s[14:16]+s[12:14],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]]
  90. return ':'.join(ip)
  91. def _conv_v6(s):
  92. return s
  93. def _remove_empty(array):
  94. return [x for x in array if x !='']
  95. def _convert_ipv4_port(array):
  96. host,port = array.split(':')
  97. return _ip(host),_hex2dec(port)
  98. def _convert_ipv6_port(array):
  99. host,port = array.split(':')
  100. return _ip6(host),_hex2dec(port)
  101. def _convert_ipv6(array):
  102. host,port = array.split(':')
  103. return _ip6q(host)
  104. def _addr_normal(s):
  105. return ':'.join(["%x" % x for x in [int(x, 16) for x in s.split(':')]])
  106. def _countFollowingZeros(l):
  107. """Return number of elements containing 0 at the beginning of the list."""
  108. #print 'aaa:', l
  109. if len(l) == 0:
  110. return 0
  111. elif l[0] != 0:
  112. return 0
  113. else:
  114. return 1 + _countFollowingZeros(l[1:])
  115. def _compress_v6(addr):
  116. hextets = [int(x, 16) for x in addr.split(':')]
  117. #print hextets
  118. followingzeros = [0] * 8
  119. for i in range(len(hextets)):
  120. followingzeros[i] = _countFollowingZeros(hextets[i:])
  121. # compressionpos is the position where we can start removing zeros
  122. compressionpos = followingzeros.index(max(followingzeros))
  123. if max(followingzeros) > 1:
  124. # genererate string with the longest number of zeros cut out
  125. # now we need hextets as strings
  126. hextets = [x for x in _addr_normal(addr).split(':')]
  127. while compressionpos < len(hextets) and hextets[compressionpos] == '0':
  128. del(hextets[compressionpos])
  129. hextets.insert(compressionpos, '')
  130. if compressionpos + 1 >= len(hextets):
  131. hextets.append('')
  132. if compressionpos == 0:
  133. hextets = [''] + hextets
  134. return ':'.join(hextets)
  135. else:
  136. return _addr_normal(addr)
  137. def _resolve_ip(host):
  138. """
  139. resolve ip und update dictionary res_cache {'ip': 'name'}.
  140. If resolution for a ip failed, 'name' is n_try ... 0.
  141. """
  142. try:
  143. hname = socket.gethostbyaddr(host)[0]
  144. return str(hname)
  145. except:
  146. return str(host)
  147. def netstat_tcp4():
  148. '''
  149. Function to return a list with status of tcp4 connections on Linux systems.
  150. '''
  151. tcpcontent =_tcp4load()
  152. tcpresult = []
  153. for line in tcpcontent:
  154. line_array = _remove_empty(line.split(' ')) # Split lines and remove empty spaces.
  155. l_host,l_port = _convert_ipv4_port(line_array[1]) # Convert ipaddress and port from hex to decimal.
  156. r_host,r_port = _convert_ipv4_port(line_array[2])
  157. tcp_id = line_array[0]
  158. state = TCP_STATE[line_array[3]]
  159. if state != 'LISTEN' and o_listen == True:
  160. continue
  161. if ( state == 'LISTEN' or state == 'SYN_SENT' or state == 'SYN_RECV' ) and o_estab == True:
  162. continue
  163. try:
  164. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  165. except:
  166. uid = line_array[7]
  167. inode = line_array[9] # Need the inode to get process pid.
  168. if int(inode) > 0:
  169. pid = _get_pid_of_inode(inode)
  170. try: # try read the process name.
  171. if o_wide == True:
  172. with open('/proc/'+pid+'/cmdline','r') as f:
  173. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  174. elif o_wider == True:
  175. with open('/proc/'+pid+'/cmdline','r') as f:
  176. exe = ' '.join(f.read().split('\x00'))
  177. else:
  178. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  179. except:
  180. exe = '-'
  181. else:
  182. pid = '-'
  183. exe = '-'
  184. if o_numeric == False:
  185. r_host = _resolve_ip(r_host)
  186. nline = '%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('TCP4', l_host+': '+l_port, r_host+': '+r_port, state, uid, pid, exe)
  187. tcpresult.append(nline)
  188. # update v4inv6check list
  189. v4ports.append(l_port)
  190. return tcpresult
  191. def netstat_tcp6():
  192. '''
  193. This function returns a list of tcp connections utilizing ipv6.
  194. '''
  195. tcpcontent = _tcp6load()
  196. tcpresult = []
  197. for line in tcpcontent:
  198. line_array = _remove_empty(line.split(' '))
  199. l_host,l_port = _convert_ipv6_port(line_array[1])
  200. r_host,r_port = _convert_ipv6_port(line_array[2])
  201. tcp_id = line_array[0]
  202. state = TCP_STATE[line_array[3]]
  203. if state != 'LISTEN' and o_listen == True:
  204. continue
  205. if ( state == 'LISTEN' or state == 'SYN_SENT' or state == 'SYN_RECV' ) and o_estab == True:
  206. continue
  207. try:
  208. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  209. except:
  210. uid = line_array[7]
  211. inode = line_array[9]
  212. if int(inode) > 0:
  213. pid = _get_pid_of_inode(inode)
  214. try: # try read the process name.
  215. if o_wide == True:
  216. with open('/proc/'+pid+'/cmdline','r') as f:
  217. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  218. elif o_wider == True:
  219. with open('/proc/'+pid+'/cmdline','r') as f:
  220. exe = ' '.join(f.read().split('\x00'))
  221. else:
  222. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  223. except:
  224. exe = '-'
  225. else:
  226. pid = '-'
  227. exe = '-'
  228. 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)
  229. tcpresult.append(nline)
  230. return tcpresult
  231. def netstat_tcp4in6():
  232. '''
  233. Returns a list of tcp ipv4 in ipv6 listen sockets.
  234. '''
  235. #print xx()
  236. tcpcontent = _tcp6load()
  237. tcpresult = []
  238. for line in tcpcontent:
  239. line_array = _remove_empty(line.split(' '))
  240. #if TCP_STATE[line_array[3]] != 'LISTEN':
  241. # continue
  242. l_host,l_port = _convert_ipv6_port(line_array[1])
  243. r_host,r_port = _convert_ipv6_port(line_array[2])
  244. if grep_b(v4ports,l_port):
  245. continue
  246. tcp_id = line_array[0]
  247. state = TCP_STATE[line_array[3]]
  248. if state != 'LISTEN' and o_listen == True:
  249. continue
  250. if ( state == 'LISTEN' or state == 'SYN_SENT' or state == 'SYN_RECV' ) and o_estab == True:
  251. continue
  252. try:
  253. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  254. except:
  255. uid = line_array[7]
  256. inode = line_array[9]
  257. if int(inode) > 0:
  258. pid = _get_pid_of_inode(inode)
  259. try: # try read the process name.
  260. if o_wide == True:
  261. with open('/proc/'+pid+'/cmdline','r') as f:
  262. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  263. elif o_wider == True:
  264. with open('/proc/'+pid+'/cmdline','r') as f:
  265. exe = ' '.join(f.read().split('\x00'))
  266. else:
  267. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  268. except:
  269. exe = '-'
  270. else:
  271. pid = '-'
  272. exe = '-'
  273. if l_host == '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01':
  274. if _check_v4inv6_port("127.0.0.1",l_port):
  275. 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)
  276. tcpresult.append(nline)
  277. if l_host == "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00":
  278. if _check_v4inv6_port("0.0.0.0",l_port):
  279. 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)
  280. tcpresult.append(nline)
  281. #else:
  282. # for a in MYIFS.split():
  283. # _check_v4inv6_port(get_ip_address(a),l_port)
  284. return tcpresult
  285. def netstat_udp4():
  286. '''
  287. Function to return a list with status of udp connections.
  288. '''
  289. udpcontent =_udp4load()
  290. udpresult = []
  291. for line in udpcontent:
  292. line_array = _remove_empty(line.split(' '))
  293. l_host,l_port = _convert_ipv4_port(line_array[1])
  294. r_host,r_port = _convert_ipv4_port(line_array[2])
  295. udp_id = line_array[0]
  296. udp_state = TCP_STATE[line_array[3]]
  297. if ( udp_state != 'ESTABLISHED' and o_estab == True ) or o_estab == False:
  298. continue
  299. if udp_state != 'ESTABLISHED':
  300. udp_state =' ' #UDP is stateless
  301. try:
  302. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  303. except:
  304. uid = line_array[7]
  305. inode = line_array[9]
  306. if int(inode) > 0:
  307. pid = _get_pid_of_inode(inode)
  308. try: # try read the process name.
  309. if o_wide == True:
  310. with open('/proc/'+pid+'/cmdline','r') as f:
  311. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  312. elif o_wider == True:
  313. with open('/proc/'+pid+'/cmdline','r') as f:
  314. exe = ' '.join(f.read().split('\x00'))
  315. else:
  316. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  317. except:
  318. exe = '-'
  319. else:
  320. pid = '-'
  321. exe = '-'
  322. nline = '%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('UDP4', l_host+': '+l_port, r_host+': '+r_port, udp_state, uid, pid, exe)
  323. udpresult.append(nline)
  324. return udpresult
  325. def netstat_udp6():
  326. '''
  327. Function to return a list of udp connection utilizing ipv6
  328. '''
  329. udpcontent =_udp6load()
  330. udpresult = []
  331. for line in udpcontent:
  332. line_array = _remove_empty(line.split(' '))
  333. l_host,l_port = _convert_ipv6_port(line_array[1])
  334. r_host,r_port = _convert_ipv6_port(line_array[2])
  335. udp_id = line_array[0]
  336. udp_state = TCP_STATE[line_array[3]]
  337. if ( udp_state != 'ESTABLISHED' and o_estab == True ) or o_estab == False:
  338. continue
  339. if udp_state != 'ESTABLISHED':
  340. udp_state =' ' #UDP is stateless
  341. try:
  342. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  343. except:
  344. uid = line_array[7]
  345. inode = line_array[9]
  346. if int(inode) > 0:
  347. pid = _get_pid_of_inode(inode)
  348. try: # try read the process name.
  349. if o_wide == True:
  350. with open('/proc/'+pid+'/cmdline','r') as f:
  351. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  352. elif o_wider == True:
  353. with open('/proc/'+pid+'/cmdline','r') as f:
  354. exe = ' '.join(f.read().split('\x00'))
  355. else:
  356. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  357. except:
  358. exe = '-'
  359. else:
  360. pid = '-'
  361. exe = '-'
  362. 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)
  363. udpresult.append(nline)
  364. return udpresult
  365. def _get_pid_of_inode(inode):
  366. '''
  367. To retrieve the process pid, check every running process and look for one using
  368. the given inode.
  369. '''
  370. for item in glob.glob('/proc/[0-9]*/fd/[0-9]*'):
  371. try:
  372. if re.search(inode,os.readlink(item)):
  373. return item.split('/')[2]
  374. except:
  375. pass
  376. return None
  377. def check_root():
  378. if os.getuid() == 0:
  379. return True
  380. else:
  381. return False
  382. def _check_v4inv6_port(addr,portnr):
  383. '''
  384. check if a v4 port is listening over ip6. Strange, we do a SYN connect for every port not listening v4.
  385. thats because I think there is no image in the /proc filesystem
  386. '''
  387. #print 'aaacc:', addr, portnr
  388. is_onnected = False
  389. try:
  390. try:
  391. t_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  392. except:
  393. print("Error: Can't open socket!\n")
  394. return False
  395. t_socket.connect((addr, int(portnr)))
  396. is_onnected = True
  397. except:
  398. is_onnected = False
  399. finally:
  400. if(is_onnected and portnr != t_socket.getsockname()[1]):
  401. #print("{}:{} Open \n".format(addr, portnr))
  402. t_socket.close()
  403. return True
  404. t_socket.close()
  405. return False
  406. if __name__ == '__main__':
  407. if not check_root():
  408. print("Starting with non-root privileges ! Some informations cannot be gathered and are missing.\n")
  409. #sys.exit()
  410. #print
  411. # commandline params
  412. o_numeric = True
  413. o_listen = False
  414. o_estab = None
  415. o_wide = False
  416. o_wider = False
  417. o_udp = True
  418. o_tcp = True
  419. o_v6 = True
  420. o_v4 = True
  421. parser = argparse.ArgumentParser(description='netstat utility V'+VERSION+"\n2017-2019 by sigi <https://wiki.zweiernet.ch/wiki/sinetstat>",
  422. formatter_class=argparse.RawDescriptionHelpFormatter )
  423. parser.add_argument('-l', help="Only listening sockets", action="store_true")
  424. parser.add_argument('-e', help="Only established sockets", action="store_true")
  425. parser.add_argument('-r', help="Resolve IP-Addresses", action="store_true")
  426. parser.add_argument('-w', help="Wide (show cmd)", action="store_true")
  427. parser.add_argument('-W', help="Wider (show cmd with arguments)", action="store_true")
  428. parser.add_argument('-t', help="Only TCP", action="store_true")
  429. parser.add_argument('-u', help="Only UDP", action="store_true")
  430. parser.add_argument('-4', dest='v4', help="Only IPv4", action="store_true")
  431. parser.add_argument('-6', dest='v6', help="Only IPv6", action="store_true")
  432. args = parser.parse_args()
  433. if args.r:
  434. o_numeric = False
  435. if args.l:
  436. o_listen = True
  437. if args.e:
  438. o_estab = True
  439. o_listen = False
  440. if args.w:
  441. o_wide = True
  442. if args.W:
  443. o_wider = True
  444. o_wide = False
  445. if args.t:
  446. o_udp = False
  447. if args.u:
  448. o_tcp = False
  449. if args.v4:
  450. o_v6 = False
  451. if args.v6:
  452. o_v4 = False
  453. # Output
  454. print('%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('Proto', 'Local Address', 'Remote Address', 'State', 'UID', 'PID', 'Program'))
  455. print('%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('-----', '-------------', '--------------', '-----', '---', '---', '-------'))
  456. #print "\nTCP (v4) Results:\n"
  457. if o_v4 == True and o_tcp == True:
  458. for conn_tcp in netstat_tcp4():
  459. print(conn_tcp)
  460. #print "\nTCP (v4inv6) Results:\n"
  461. if o_v4 == True and o_tcp == True:
  462. for conn_tcp46 in netstat_tcp4in6():
  463. print(conn_tcp46)
  464. #print "\nTCP (v6) Results:\n"
  465. if o_v6 == True and o_tcp == True:
  466. for conn_tcp6 in netstat_tcp6():
  467. print(conn_tcp6)
  468. #print "\nUDP (v4) Results:\n"
  469. if o_v4 == True and o_udp == True and not args.l:
  470. for conn_udp in netstat_udp4():
  471. print(conn_udp)
  472. #print "\nUDP (v6) Results:\n"
  473. if o_v6 == True and o_udp == True and not args.l:
  474. for conn_udp6 in netstat_udp6():
  475. print(conn_udp6)