sinetstat 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. #!/usr/bin/env python3
  2. # (c) 2016-2022 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.4.2'
  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' or state == 'TIME_WAIT' or state == 'CLOSE_WAIT' ) 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. if o_reused == False:
  188. if nline not in tcpresult: # Hide multi binds on same Socket (SO_REUSEPORT)
  189. tcpresult.append(nline)
  190. # update v4inv6check list
  191. v4ports.append(l_port)
  192. else:
  193. tcpresult.append(nline)
  194. # update v4inv6check list
  195. v4ports.append(l_port)
  196. return tcpresult
  197. def netstat_tcp6():
  198. '''
  199. This function returns a list of tcp connections utilizing ipv6.
  200. '''
  201. tcpcontent = _tcp6load()
  202. tcpresult = []
  203. for line in tcpcontent:
  204. line_array = _remove_empty(line.split(' '))
  205. l_host,l_port = _convert_ipv6_port(line_array[1])
  206. r_host,r_port = _convert_ipv6_port(line_array[2])
  207. tcp_id = line_array[0]
  208. state = TCP_STATE[line_array[3]]
  209. if state != 'LISTEN' and o_listen == True:
  210. continue
  211. if ( state == 'LISTEN' or state == 'SYN_SENT' or state == 'SYN_RECV' or state == 'TIME_WAIT' or state == 'CLOSE_WAIT' ) and o_estab == True:
  212. continue
  213. try:
  214. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  215. except:
  216. uid = line_array[7]
  217. inode = line_array[9]
  218. if int(inode) > 0:
  219. pid = _get_pid_of_inode(inode)
  220. try: # try read the process name.
  221. if o_wide == True:
  222. with open('/proc/'+pid+'/cmdline','r') as f:
  223. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  224. elif o_wider == True:
  225. with open('/proc/'+pid+'/cmdline','r') as f:
  226. exe = ' '.join(f.read().split('\x00'))
  227. else:
  228. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  229. except:
  230. exe = '-'
  231. else:
  232. pid = '-'
  233. exe = '-'
  234. 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)
  235. if o_reused == False:
  236. if nline not in tcpresult: # Hide multi binds on same Socket (SO_REUSEPORT)
  237. tcpresult.append(nline)
  238. else:
  239. tcpresult.append(nline)
  240. return tcpresult
  241. def netstat_tcp4in6():
  242. '''
  243. Returns a list of tcp ipv4 in ipv6 listen sockets.
  244. '''
  245. #print xx()
  246. tcpcontent = _tcp6load()
  247. tcpresult = []
  248. for line in tcpcontent:
  249. line_array = _remove_empty(line.split(' '))
  250. #if TCP_STATE[line_array[3]] != 'LISTEN':
  251. # continue
  252. l_host,l_port = _convert_ipv6_port(line_array[1])
  253. r_host,r_port = _convert_ipv6_port(line_array[2])
  254. if grep_b(v4ports,l_port):
  255. continue
  256. tcp_id = line_array[0]
  257. state = TCP_STATE[line_array[3]]
  258. if state != 'LISTEN' and o_listen == True:
  259. continue
  260. if ( state == 'LISTEN' or state == 'SYN_SENT' or state == 'SYN_RECV' or state == 'TIME_WAIT' or state == 'CLOSE_WAIT' ) and o_estab == True:
  261. continue
  262. try:
  263. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  264. except:
  265. uid = line_array[7]
  266. inode = line_array[9]
  267. if int(inode) > 0:
  268. pid = _get_pid_of_inode(inode)
  269. try: # try read the process name.
  270. if o_wide == True:
  271. with open('/proc/'+pid+'/cmdline','r') as f:
  272. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  273. elif o_wider == True:
  274. with open('/proc/'+pid+'/cmdline','r') as f:
  275. exe = ' '.join(f.read().split('\x00'))
  276. else:
  277. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  278. except:
  279. exe = '-'
  280. else:
  281. pid = '-'
  282. exe = '-'
  283. if l_host == '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01':
  284. if _check_v4inv6_port("127.0.0.1",l_port):
  285. 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)
  286. tcpresult.append(nline)
  287. if l_host == "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00":
  288. if _check_v4inv6_port("0.0.0.0",l_port):
  289. 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)
  290. tcpresult.append(nline)
  291. #else:
  292. # for a in MYIFS.split():
  293. # _check_v4inv6_port(get_ip_address(a),l_port)
  294. return tcpresult
  295. def netstat_udp4():
  296. '''
  297. Function to return a list with status of udp connections.
  298. '''
  299. udpcontent =_udp4load()
  300. udpresult = []
  301. for line in udpcontent:
  302. line_array = _remove_empty(line.split(' '))
  303. l_host,l_port = _convert_ipv4_port(line_array[1])
  304. r_host,r_port = _convert_ipv4_port(line_array[2])
  305. udp_id = line_array[0]
  306. udp_state = TCP_STATE[line_array[3]]
  307. if ( udp_state != 'ESTABLISHED' and o_estab == True ) or o_estab == False:
  308. continue
  309. if udp_state != 'ESTABLISHED':
  310. udp_state =' ' #UDP is stateless
  311. try:
  312. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  313. except:
  314. uid = line_array[7]
  315. inode = line_array[9]
  316. if int(inode) > 0:
  317. pid = _get_pid_of_inode(inode)
  318. try: # try read the process name.
  319. if o_wide == True:
  320. with open('/proc/'+pid+'/cmdline','r') as f:
  321. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  322. elif o_wider == True:
  323. with open('/proc/'+pid+'/cmdline','r') as f:
  324. exe = ' '.join(f.read().split('\x00'))
  325. else:
  326. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  327. except:
  328. exe = '-'
  329. else:
  330. pid = '-'
  331. exe = '-'
  332. nline = '%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('UDP4', l_host+': '+l_port, r_host+': '+r_port, udp_state, uid, pid, exe)
  333. if o_reused == False:
  334. if nline not in udpresult: # Hide multi binds on same Socket (SO_REUSEPORT)
  335. udpresult.append(nline)
  336. else:
  337. udpresult.append(nline)
  338. return udpresult
  339. def netstat_udp6():
  340. '''
  341. Function to return a list of udp connection utilizing ipv6
  342. '''
  343. udpcontent =_udp6load()
  344. udpresult = []
  345. for line in udpcontent:
  346. line_array = _remove_empty(line.split(' '))
  347. l_host,l_port = _convert_ipv6_port(line_array[1])
  348. r_host,r_port = _convert_ipv6_port(line_array[2])
  349. udp_id = line_array[0]
  350. udp_state = TCP_STATE[line_array[3]]
  351. if ( udp_state != 'ESTABLISHED' and o_estab == True ) or o_estab == False:
  352. continue
  353. if udp_state != 'ESTABLISHED':
  354. udp_state =' ' #UDP is stateless
  355. try:
  356. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  357. except:
  358. uid = line_array[7]
  359. inode = line_array[9]
  360. if int(inode) > 0:
  361. pid = _get_pid_of_inode(inode)
  362. try: # try read the process name.
  363. if o_wide == True:
  364. with open('/proc/'+pid+'/cmdline','r') as f:
  365. exe = ' '.join(f.read().split('\x00')).split(' ')[0]
  366. elif o_wider == True:
  367. with open('/proc/'+pid+'/cmdline','r') as f:
  368. exe = ' '.join(f.read().split('\x00'))
  369. else:
  370. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  371. except:
  372. exe = '-'
  373. else:
  374. pid = '-'
  375. exe = '-'
  376. 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)
  377. if o_reused == False:
  378. if nline not in udpresult: # Hide multi binds on same Socket (SO_REUSEPORT)
  379. udpresult.append(nline)
  380. else:
  381. udpresult.append(nline)
  382. return udpresult
  383. def _get_pid_of_inode(inode):
  384. '''
  385. To retrieve the process pid, check every running process and look for one using
  386. the given inode.
  387. '''
  388. for item in glob.glob('/proc/[0-9]*/fd/[0-9]*'):
  389. try:
  390. if re.search(inode,os.readlink(item)):
  391. return item.split('/')[2]
  392. except:
  393. pass
  394. return None
  395. def check_root():
  396. if os.getuid() == 0:
  397. return True
  398. else:
  399. return False
  400. def _check_v4inv6_port(addr,portnr):
  401. '''
  402. check if a v4 port is listening over ip6. Strange, we do a SYN connect for every port not listening v4.
  403. thats because I think there is no image in the /proc filesystem
  404. '''
  405. #print 'aaacc:', addr, portnr
  406. is_onnected = False
  407. try:
  408. try:
  409. t_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  410. except:
  411. print("Error: Can't open socket!\n")
  412. return False
  413. t_socket.connect((addr, int(portnr)))
  414. is_onnected = True
  415. except:
  416. is_onnected = False
  417. finally:
  418. if(is_onnected and portnr != t_socket.getsockname()[1]):
  419. #print("{}:{} Open \n".format(addr, portnr))
  420. t_socket.close()
  421. return True
  422. t_socket.close()
  423. return False
  424. if __name__ == '__main__':
  425. if not check_root():
  426. print("Starting with non-root privileges ! Some informations cannot be gathered and are missing.\n")
  427. #sys.exit()
  428. #print
  429. # commandline params
  430. o_numeric = True
  431. o_listen = False
  432. o_estab = None
  433. o_wide = False
  434. o_wider = False
  435. o_udp = True
  436. o_tcp = True
  437. o_v6 = True
  438. o_v4 = True
  439. o_reused = False
  440. parser = argparse.ArgumentParser(description='netstat utility V'+VERSION+"\n2017-2022 by sigi <https://wiki.zweiernet.ch/wiki/sinetstat>",
  441. formatter_class=argparse.RawDescriptionHelpFormatter )
  442. parser.add_argument('-l', help="Only listening sockets", action="store_true")
  443. parser.add_argument('-e', help="Only established sockets", action="store_true")
  444. parser.add_argument('-s', help="Show all sockets on reused ports", action="store_true")
  445. parser.add_argument('-r', help="Resolve IP-Addresses", action="store_true")
  446. parser.add_argument('-w', help="Wide (show cmd)", action="store_true")
  447. parser.add_argument('-W', help="Wider (show cmd with arguments)", action="store_true")
  448. parser.add_argument('-t', help="Only TCP", action="store_true")
  449. parser.add_argument('-u', help="Only UDP", action="store_true")
  450. parser.add_argument('-4', dest='v4', help="Only IPv4", action="store_true")
  451. parser.add_argument('-6', dest='v6', help="Only IPv6", action="store_true")
  452. args = parser.parse_args()
  453. if args.r:
  454. o_numeric = False
  455. if args.l:
  456. o_listen = True
  457. if args.e:
  458. o_estab = True
  459. o_listen = False
  460. if args.w:
  461. o_wide = True
  462. if args.W:
  463. o_wider = True
  464. o_wide = False
  465. if args.t:
  466. o_udp = False
  467. if args.u:
  468. o_tcp = False
  469. if args.v4:
  470. o_v6 = False
  471. if args.v6:
  472. o_v4 = False
  473. if args.s:
  474. o_reused = True
  475. # Output
  476. print('%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('Proto', 'Local Address', 'Remote Address', 'State', 'UID', 'PID', 'Program'))
  477. print('%-7s %-24s %-24s %-11s %-8s %-6s %-s' % ('-----', '-------------', '--------------', '-----', '---', '---', '-------'))
  478. #print "\nTCP (v4) Results:\n"
  479. if o_v4 == True and o_tcp == True:
  480. for conn_tcp in netstat_tcp4():
  481. print(conn_tcp)
  482. #print "\nTCP (v4inv6) Results:\n"
  483. if o_v4 == True and o_tcp == True:
  484. for conn_tcp46 in netstat_tcp4in6():
  485. print(conn_tcp46)
  486. #print "\nTCP (v6) Results:\n"
  487. if o_v6 == True and o_tcp == True:
  488. for conn_tcp6 in netstat_tcp6():
  489. print(conn_tcp6)
  490. #print "\nUDP (v4) Results:\n"
  491. if o_v4 == True and o_udp == True and not args.l:
  492. for conn_udp in netstat_udp4():
  493. print(conn_udp)
  494. #print "\nUDP (v6) Results:\n"
  495. if o_v6 == True and o_udp == True and not args.l:
  496. for conn_udp6 in netstat_udp6():
  497. print(conn_udp6)