sinetstat 20 KB

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