This is in continuation of the first part of Python networking with sockets. In this blog post we will further learn how to use more socket APIβs. We will dig deeper into what a networking port is. Then we start implementing a simple port scanner. Finally, we will enhance this port scanner further. For Part I you can refer here. So letβs start.
As you read through the blog, you can also watch my explainer video here:
What is a Networking Port?
Every device that is connected to the internet has a unique IP address. It can run many network applications. e.g. browser, WhatsApp, file-transfer, email etc.
How can one distinguish one application from another on the same device or IP address? This is where the networking port comes to our rescue. A Port number is like another address which helps identify an application. Socket as described in Part I, is an endpoint in communication. It is thus identified with IP address and port number.
Socket = IP Address + Port number
Port Number Ranges
Port is represented by a 16 digit number, hence the number of ports is 2^16 = 65,535. Port ranges assigned by IANA (Internet Assigned Numbers Authority) are divided as
- Well known ports from
0 - 1023
: port 80, 443 for HTTP, HTTPS - Semi reserved ports from
1024 - 49151
: not free for apps - Free or unreserved ports from
49152 - 65535
: free to use for apps
Example Usage of Ports
If you open two browser tabs on your machine, on one tab you look for https://app.finxter.com and on the other you look for www.google.com.
An example connection would be like this
- Machine IP + port 60678 <—socket—-> Finxster server IP + port 443 (HTTPS)
- Machine IP + port 52320 <—socket—-> Google server IP + port 443 (HTTPS)
Note:
- Ports for applications can be random and can be reused after closing the app.
- There are tools like
netstat
which can give info on ports associated with apps running on the system.
Basic Port Scanner
In this section we will put in place a basic port scanning mechanism. This is like one of the features of the Nmap
tool used for hacking/discovering hosts and services. The idea behind port scanning is to know if the remote host ports are open or closed. This gives us info about hosts’ vulnerability and security levels. Port scanning can be used by IT admins or cyber criminals. IT admins use it to know the network security vulnerabilities or protect the system. Cyber criminals use it for attacking weak entry points.
The code for basic port scanner is given below
# This code does a port/ports scan for a remote host# This will serially check the ports in a for loopimport socket from datetime import datetime import errno import sys # STEP1 : Enter a website name (e.g. www.google.com) , get the website IP address #using gethostbyname() remote_server = input("Enter a remote host to scan: ") remote_server_ip = socket.gethostbyname(remote_server) # STEP2 : Enter the range of ports to scan and start a timer to measure time for #port scan print("Enter the range of ports to scan remote server:") startPort = input("Enter Start port: ") endPort = input("Enter End port: ") print("Please wait, scanning remote machine", remote_server_ip) time_init = datetime.now() # STEP 3: Create a socket and connect to the remote host using connect_ex, which #gives port information. The result of connect_ex decides whether the port is open #or closed. If connect is used instead of connect_ex then it generates an exception#terminating the app, while connect_ex reports an error try: for port in range(int(startPort), int(endPort)): print("Checking port {} ...".format(port)) server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_sock.settimeout(5) result = server_sock.connect_ex((remote_server_ip, port)) if result == 0: print("Port {}: Open".format(port)) else: print("Port {}: Closed".format(port)) print("Reason:", errno.errorcode[result]) server_sock.close() except socket.error: print("Couldn't connect to server") sys.exit() # STEP 4: Stop the timer and calculate the time taken for port scan time_finish = datetime.now() total_time = time_finish - time_init print('Time to complete Port Scan: ', total_time)
The above code can be explained in these steps:
- Step 1: We ask users to input a website name e.g. app.finxter.com or www.google.com. Using socket api
gethostbyname()
we convert this name into an IP address. - Step 2: Then the user enters the range of ports i.e. start and end to be scanned. We also start a timer to measure time.
- Step 3: Loop through all ports in the range start to end. Every time create a socket, set a timeout for connection, try to connect to the port using
connect_ex()
. We don’t useconnect()
asconnect()
generates an exception if a connection fails and exits from the application without trying all ports.connect_ex()
on the other hand generates only an error on failure. If the connection is successful then the port is open else it is closed for communication. On failure we can also print the error. Finallyclose()
the socket as we try the next port. - Step 4: Finally after trying all ports, we can close the timer to calculate the total time taken to scan all ports.
Advanced Port Scanner
Letβs advance the scanner a bit, allowing the user to specify the ports one wants to scan instead of the range of ports. Also use threads to make the port scan faster compared to basic port scan with serialisation.
Without any delay letβs code the advanced scanner.
# This code does a port/ports scan for a remote host. # The list of ports and a remote host are entered as command line args. # We create a separate thread to check port connection. Hence it is faster and more # efficient. We can now use connect() as we have a separate thread for each port #connect. import argparse from threading import * from socket import * # This Thread gets created for each port. Tries to connect to def check_socket_connection(host, port): try: server_sock = socket(AF_INET, SOCK_STREAM) server_sock.settimeout(5) result = server_sock.connect((host, port)) print('[+] {}/tcp open'.format(port)) except Exception as exception: print('[-] {}/tcp closed, Reason:{}'.format(port, (str(exception)))) finally: server_sock.close() # Scan the port list by creating threads for each port def portScanner(host, ports): try: ip = gethostbyname(host) print('[+] Scan Results for: ' + ip) except: print("[-] Cannot resolve {}: Unknown host".format(host)) return for port in ports: t = Thread(target=check_socket_connection, args=(ip, int(port))) t.start() if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('H', type=str, help='remote host name') parser.add_argument('P', type=str, nargs='*', help='port numbers') args = parser.parse_args() portScanner(args.H, args.P)
The steps remain the same except that users can now provide the list of ports to scan and host name as part of command line arguments. In a for loop we create a thread check_socket_connection()
for each port. check_socket_connection()
will try to connect with the given host and port. This gives us info if the port is open or closed.
Note: We can now use connect()
here instead of connect_ex()
as we are running the socket connect in a separate thread for each port.
Summary
In this blog tutorial on sockets, we further saw the usage of socket APIβs. We implemented a simple port scanner and an advanced port scanner to simulate the Nmap
tool. We also discussed what a port is and how the socket is determined by the combination of port and IP address. You can further explore networking by implementing your own chat application over UDP, implementing your own FTP or ssh server interaction with python. Networking is limitless π