One of the most common parts of network debugging and security testing is port checking. Whether your application is supposed to use a given port, as in P2P or server-client relations of different protocols, or whether you are sweeping for an opening on your network, being able to automate the checking of ports on a set of servers is a helpful facility to have.
This tutorial guides you through building a port scanner in Python using three modules: sys, optparse, and socket. The first is for cleanly exiting the program. The second is for enabling options (aka "flags") on the command line. The third is the base level module for network programming in Python. The brains of the program will rely simply on opening and connecting to a socket via TCP.
First step after the bang line is to import the modules.
Note that we are not importing the optparse module in its entirety. Rather, we are selecting only the OptionParser class; for more on what is available in the optparse module, open a Python shell, import the entire optparse module ('import optparse'), and type 'help(optparse)' for the module's help page.
#!/usr/bin/env python import socket, sys from optparse import OptionParser
Further, note that we are importing from optparse at the beginning of the program for the sake of simplicity - to keep the discussion about imported modules in one place. The better way of programming for this task is to put the optparse import under a if-clause that tests whether the program is being called directly (discussed below). This way, the optparse module is not imported when the program itself is imported by another program as a Python module but only when called independently.
Next we will look at the function that handles all of the network connections and port scanning.
Next we need to write a function to check a server's port. To do this, we need only create a TCP socket. The full function is as follows:
It is important to note that this function only uses the TCP protocol and does not reciprocate with the complementary handshake of TCP/IP. One of the critical results of this is that the connection is not ordinarily reported in the server's logs. This is obviously good, on the one hand, as it does not clog up one's logs with port checks. However, it also means that anyone can scan a system without the scan being recorded by the server.
def scan_server(address, port): s = socket.socket() print "Attempting to connect to %s on port %s." %(address, port) try: s.connect((address, port)) print "Connected to server %s on port %s." %(address, port) return True except socket.error, e: print "Connecting to %s on port %s failed with the following error: %s" %(address, port, e) return False
With the socket object 's', we get the ability through the socket module to create a TCP connection. Simply feed the connect method an address and port, and it will endeavour to connect. If the try clause does not work, Python defaults to the except section with the error message contained in 'e'.
As usual, a function is not worth much until you call it. Here we check whether the program has been imported (i.e., called as part of another program) or whether it has been called independently. To do this, we check the value of __name__.
After verifying that the program is running on its own, we need to add some options and scan the port. We add several options to the program as part of an object parser of OptionParser, a class of the optparse module.
if __name__ == '__main__': parser = OptionParser() parser.add_option("-a", "--address", dest="address", default="localhost", help="ADDRESS for server", metavar="ADDRESS") parser.add_option("-p", "--port", dest="port", help="PORT for server", metavar="PORT") (options, args) = parser.parse_args() if options.port == 'all': print 'checking all ports...' for x in range(1,65536): print 'checking port %s on %s' %(x, options.address) check = scan_server(options.address, x) print 'scan_server returned %s' %(check) else: options.port = int(options.port) print 'options: %s, args: %s' %(options, args) check = scan_server(options.address, options.port) print 'scan_server returned %s' %(check) sys.exit(not check)
We then call scan_server to scan the given port. Depending on whether the port instance is 'all' or not, the scan_server function will be called repeatedly with a range of addresses or once. All output goes to the screen, so be prepared for off-the-screen output if you use the 'all' option. If the value for port does not otherwise resolve to an integer through the int() function, Python will throw a ValueError. When all calls are completed, we exit the program using sys.exit().