Hola comunidad!

Estaba algo aburrido y tuve el deseo de aplicar conocimientos de interfaz gráfica y threading a un escaner de puertos simple.

Se puede configurar el timeout (normal 1 segundo), el numero de threads que se ocuparan, ademas se puede guardar el resultado en un .txt ... cosas que lo hacen un scaner menos convencional.




Código:
#!/usr/bin/env python
from Tkinter import *;
from ttk import Separator, Progressbar
from threading import Thread, Semaphore;
from time import sleep, clock;
from tkSimpleDialog import askfloat;
from tkFileDialog import asksaveasfilename
from socket import socket, getservbyport;
from tkMessageBox import showwarning;






def isPortOn(ip, port, timeout):
    sc = socket();
    sc.settimeout(timeout);


    try:
        sc.connect((ip, port));
        sc.close();
        return True;
    except:
        pass


    return False;




listSemaphore = Semaphore();




class WinInfo(Toplevel):
    def __init__(self, master, title):
        Toplevel.__init__(self, master);


        self.transient(master);


        self.geometry("220x170+%d+%d" % (master.winfo_rootx()+50,
                             master.winfo_rooty()+50));


        self.title(title);


        self.focus_set();
        self.grab_set();




help_ = "Inserta los datos del Host/IP que quieres escanear \
y elige cuantos threads (cuantos puertos consultar a la vez).\n\
Es conveniente limitar los threads para que el programa no \
se sobrecargue y falle, el limite correcto puede ser menos de 1000, \
pero entre mas puertos consultes en una misma vez sera mas inexacto \
el resultado, asi que recuerda elegir una cantidad conveniente y \
asi tambien un tiempo de espera conveniente (configurado en segundos).\n\n\
\
Puede que tu internet se sature mientras se haga demasiadas consultas a la vez.";


class WinHelp(WinInfo):
    def __init__(self, master):


        WinInfo.__init__(self, master, "Help")


        self.frame = Frame(self);
        self.frame.pack();
        
        self.msg = Text(self.frame, wrap=WORD);
        self.msg.insert(0.0, help_);


        self.sby = Scrollbar(self.frame, command=self.msg.yview);
        self.msg.configure(yscrollcommand=self.sby.set);
        
        self.sby.pack(side=RIGHT, fill=Y);
        self.msg.pack(side=LEFT);




about = 'U29rb2xlb25hcmRvQG1hc2hhY2tlci5jb20gIChmb3JvLmVsLWhhY2tlci5jb20p\n'.decode("base64") + \
"\n\
Shock Scaner Port.py v0.1 (2018)\n\
 Es un escaner de puertos abiertos de IP's"


class WinAbout(WinInfo):
    def __init__(self, master):


        WinInfo.__init__(self, master, "About")


        
        self.msg = Message(self, text=about, aspect=200);
        self.msg.pack(padx=5, pady=5);








class APP(Tk):
    def __init__(self):
        Tk.__init__(self);


        self.title("Shock Scanner Port v0.1");
        self.geometry("315x240");
        self.resizable(False, False);


        self.menuBar = Frame(self, relief=GROOVE, borderwidth=1);
        self.menuBar.pack(side = TOP, fill=X);


        self.bmFile = Menubutton(self.menuBar, text="Report");
        self.bmFile.pack(side=LEFT);


        self.mFile = Menu(self.bmFile, tearoff=False);
        self.bmFile["menu"] = self.mFile;


        self.mFile.add_command(label="Save result", command=self.saveReport);
        #self.mFile.add_command(label="Clear result");
        self.mFile.add_separator();
        self.mFile.add_command(label="Exit", command=self.destroy);






        self.sep1 = Separator(self.menuBar, orient=VERTICAL);
        self.sep1.pack(side=LEFT, fill=Y, pady=3);


        self.bmConfig = Menubutton(self.menuBar, text="Config");
        self.bmConfig.pack(side=LEFT);


        self.mConfig = Menu(self.bmConfig, tearoff=False);
        self.bmConfig["menu"] = self.mConfig;


        self.mConfig.add_command(label="Set timeout", command=self.SetTimeOut);
        


        self.sep2 = Separator(self.menuBar, orient=VERTICAL);
        self.sep2.pack(side=LEFT, fill=Y, pady=3);
        


        self.bmAbout = Menubutton(self.menuBar, text="About");
        self.bmAbout.pack(side=LEFT);


        self.mAbout = Menu(self.bmAbout, tearoff=False);
        self.bmAbout["menu"] = self.mAbout;


        self.mAbout.add_command(label="About program", command=self.initAbout);
        self.mAbout.add_separator();
        self.mAbout.add_command(label="Help", command=self.initHelp);






        self.lb1 = Label(self, text="Ports On:");
        self.lb1.place(x=3, y=40, height=18);


        self.listPorts = Listbox(self, selectmode=SINGLE, activestyle=NONE, highlightthickness=0);
        self.listPorts.place(x=3, y=58, width=150, height=150);
        self.sby = Scrollbar(self.listPorts, command=self.listPorts.yview);
        self.sby.pack(side=RIGHT, fill=Y);
        self.listPorts.configure(yscrollcommand=self.sby.set);
        


        self.lb2 = Label(self, text="IP Host:");
        self.lb2.place(x=160, y=58, height=18);


        self.entryIP = Entry(self);
        self.entryIP.insert(END, "example.com");
        self.entryIP.place(x=160, y=76, width=130, height=18);


        self.lb3 = Label(self, text="From port:");
        self.lb3.place(x=160, y=94, height=18);


        self.entryPort1 = Entry(self, justify=CENTER);
        self.entryPort1.insert(END, "5");
        self.entryPort1.place(x=160, y=112, width=55, height=18);




        self.lb4 = Label(self, text="To port:");
        self.lb4.place(x=160, y=130, height=18);


        self.entryPort2 = Entry(self, justify=CENTER);
        self.entryPort2.insert(END, "88");
        self.entryPort2.place(x=160, y=148, width=55, height=18);


        self.lb5 = Label(self, text="Use threads:");
        self.lb5.place(x=230, y=130, height=18);


        self.entryThreads = Spinbox(self, justify=CENTER, values=range(1,100));
        self.entryThreads.place(x=230, y=148, width=65, height=18);
        self.entryThreads.delete(0, END);
        self.entryThreads.insert(END, 4);


        self.btnStart = Button(self, text="Start!", command=self.Start);
        self.btnStart.place(x=160, y=185, width=65, height=23);


        self.btnStop = Button(self, text="Stop!", state=DISABLED, command=self.__stop);
        self.btnStop.place(x=230, y=185, width=65, height=23);


        self.frameState = Frame(self, relief=GROOVE, borderwidth=1);
        self.frameState.pack(side=BOTTOM, fill=X);


        self.progress = Progressbar(self.frameState, length=100);
        self.progress.pack(side=LEFT, padx=5, pady=1, fill=X);


        self.labelState = Label(self.frameState, text=".");
        self.labelState.pack(side=LEFT, pady=1);


        self.timeout = 1.0
        self.threads = [];


    def initHelp(self):
        wHelp = WinHelp(self);


    def initAbout(self):
        wAbout = WinAbout(self);


    def saveReport(self):
        filename = asksaveasfilename(defaultextension=".txt", filetypes=[("Text file", "*.txt"),("All files", "*")]);


        if filename:
            fw = file(filename, "wb");
            fw.write("Ports ON on host %s:" % self.getvar("ip"));


            ports = self.listPorts.get(0, END);


            for port in ports:
                fw.write("\r\n"+str(port));


            fw.close();


    def SetTimeOut(self):
        timeout = askfloat("Time out", "Set the time out value:", initialvalue=self.timeout, parent=self);
        if timeout:
            self.timeout=timeout;


    def __stop(self):
        self.setvar("state", "stop");
        self.btnStart.configure(text="Start!", command=self.Start, state=DISABLED);
        self.btnStop.configure(state=DISABLED);
        self.blockStates(NORMAL);


        
        def waitThreads():
            if not len(self.threads):
                self.btnStart.configure(state=NORMAL);
            else:
                self.after(50, waitThreads);


        waitThreads();


    def __continue(self):
        self.setvar("state", "init");
        self.btnStart.configure(text="Pause", command=self.__pause);


    def __pause(self):
        self.setvar("state", "pause");
        self.btnStart.configure(text="Continue", command=self.__continue);


    def blockStates(self, s):
        self.entryPort1.configure(state=s);
        self.entryPort2.configure(state=s);
        self.entryIP.configure(state=s);
        self.entryThreads.configure(state=s);
        self.mConfig.entryconfigure(0, state=s);
        self.mFile.entryconfigure(0, state=s);
        
    def Start(self):
        p1 = self.entryPort1.get();
        p2 = self.entryPort2.get();
        if not p1.isdigit() or not p2.isdigit() or int(p2) < int(p1):
            showwarning("Port error", "Port: value error.");
            return 0;


        nt = self.entryThreads.get()
        if not nt.isdigit() or int(nt) < 1:
            showwarning("Thread error", "Thread: value error.");
            return 0;
            
        self.setvar("nThreads", int(self.entryThreads.get()));
        self.setvar("ip", self.entryIP.get());
        self.setvar("n1Port", int(self.entryPort1.get()));
        self.setvar("n2Port", int(self.entryPort2.get()));
        self.setvar("state", "init");


        self.listPorts.delete(0, END);


        self.btnStart.configure(text="Pause", command=self.__pause);
        self.btnStop.configure(state=NORMAL);


        self.progress.configure(length=100);


        self.blockStates(DISABLED);


        thread = Thread(target=self.__process, args=());
        thread.start();


    def __testPort(self, host, port):
        state = isPortOn(host, port, self.timeout);
        
        if state:
            serv = "";
            try:
                serv = " " + getservbyport(port);
            except:
                pass
            self.addPortSucces(str(port)+serv);


    def addPortSucces(self, port):
        listSemaphore.acquire();
        self.listPorts.insert(END, port);
        self.listPorts.yview_moveto(1.0);
        listSemaphore.release();


    def __process(self):
        
        n1Port = self.getvar("n1Port"); #indice
        n2Port = self.getvar("n2Port");
        iPort = 0;
        maxPort = n2Port-n1Port;
        lastPort = 0;




        timer1 = clock();


        
        strtime = "--"
        
        while True:
            #sleep(0.010);


            for thread in self.threads:
                if not thread.isAlive():
                    self.threads.remove(thread);
                    iPort+=1


                    if thread.port > lastPort:
                        lastPort = thread.port;
                    
                        promedio = (clock() - timer1) / float(lastPort);
                        secs = (n2Port - lastPort) * promedio;


                        mins = secs / 60
                        secs = secs % 60;
                        
                        hour = mins / 60
                        mins = mins % 60


                        strtime = "%02d:%02d:%02d" % (hour, mins, secs);


                        percent = (lastPort-n1Port) / float(maxPort) * 100
                        
                        self.progress.configure(value=percent);
                        self.labelState.configure(text="%d:%d - %d%% - %s" % (lastPort, n2Port, percent, strtime) );






            if self.getvar("state") == "pause":
                continue;


            if not len(self.threads) < self.getvar("nThreads"):
                continue;
                    
            if self.getvar("state") == "stop" or iPort > maxPort:
                if len(self.threads):
                    continue;
                break;
            
            port = n1Port+iPort;


            thread = Thread(target=self.__testPort, args=(self.getvar("ip"), port));
            thread.port = port;
            thread.start();


            self.threads.append(thread);


            


        self.__stop();




machine = APP();
machine.mainloop();
No me acostumbraré a hacer estas cosas, ya no quiero programar, pero esta vez me perdí un poco.

Saludos!