#!/usr/bin/env python3

import argparse
import errno
import socket
import sys
from queue import Queue
from threading import Thread

from tabulate import tabulate

errno.errorcode[errno.ECONNREFUSED] = 'No Response'
errno.errorcode[0] = 'Open'
errno.errorcode[errno.EAGAIN] = 'Closed'
errno.errorcode[errno.EHOSTUNREACH] = 'Host Unreachable'


class ScanThread(Thread):
	def __init__(self):
		Thread.__init__(self)

	def run(self):
		while not q.empty():
			h, p = q.get()
			self.__scan__(h, p)

	@staticmethod
	def __scan__(host, port):
		try:
			s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
			s.settimeout(0.5)
			res = s.connect_ex((host, port))
			try:
				server = ' '.join((socket.getservbyport(port)).split())
			except OSError:
				server = ""
			if res == 0:
				ttl_buf = s.getsockopt(socket.IPPROTO_IP, socket.IP_TTL)
				rcv_buf = s.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
				table_data.append([host, port, errno.errorcode[res], server, ttl_buf, rcv_buf])
			s.close()
		except KeyboardInterrupt:
			print("Error: Ctrl + C encountered. Exiting...")
			sys.exit()
		except socket.gaierror:
			print("Error: Host %s:%s not found..." % (host, port))
			sys.exit()
		except socket.error:
			print("Error: Could not connect to server at %s:%s..." % (host, port))
			sys.exit()


if __name__ == '__main__':
	parser = argparse.ArgumentParser(description='Scans ports of given hosts from starting port to ending port excluding the ending port.')
	parser.add_argument("host", help="host to scan")
	parser.add_argument('-p', '--ports', help='starting port (inclusive)', default='0:1024')
	args = vars(parser.parse_args())
	host = args['host']
	start, end = tuple(args['ports'].split(':'))
	start = int(start)
	end = int(end)

	if end < start:
		print("Error: End port is lower than start port")
		sys.exit()

	ports = list(range(int(start), int(end) + 1))

	table_data = []

	q = Queue()
	for pp in ports:
		q.put((host, pp))

	n_threads = 200
	threads = []

	for _ in range(n_threads):
		temp = ScanThread()
		temp.start()
		threads.append(temp)

	try:
		for t in threads:
			t.join()
	except KeyboardInterrupt:
		print("Error: Ctrl + C encountered. Exiting...")
		sys.exit()

	print(tabulate(table_data, headers=['Host', 'Port', 'Status', 'Protocol', 'TTL Size', 'RCVBUF Size'], tablefmt='orgtbl'))
