7 months, 4 weeks

Programming with Sockets




 

Python provides a good coverage on the low-level networking interface. It all starts with BSD(Berkeley Software Distribution) socket interface. As you can assume, Python has a socket module that gives you the necessary functionality to work with the socket Interface.a network socket is a virtual end point where entities can perform inter-process communication. For example, one process sitting in a computer, exchanges data with another process sitting on the same or another computer.


After creating a socket object, the server process binds that socket to a particular IP address and port.Then the client/server enters into the request/response loop. The client process sends data to the server process, and the server process processes the data and returns a response to the client. When the client process finishes, it exits by closing down the connection. At that moment, the server process probably goes back to the 
listening state. The above interaction between client and server is a very simplified representation of the actual reality. In practice, any production server process has multiple threads or subprocesses to handle concurrent connections from thousands of clients over respective virtual channels.

Working with TCP sockets

 

from socket import*                                                                                          
import socket                                                                                                   

s = socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0)            
print('Socket created')                                                                                      

• Socket family: This is the domain of socket, such as AF_INET (about 90 percent of the sockets of the Internet fall under this category) or AF_UNIX,which is sometimes used as well. In Python 3, you can create a Bluetooth socket using AF_BLUETOOTH.
• Socket type: Depending on your need, you need to specify the type of socket. For example, TCP and UDP-based sockets are created by specifying SOCK_STREAM and SOCK_DGRAM, respectively.
• Protocol: This specifies the variation of protocol within a socket family and type. Usually, it is left as zero.


import socket                                                                                      
import sys                                                                                           
if __name__ == '__main__':                                                               
 try:                                                                                                     
     sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 except socket.error as err:                                                                
     print("Failed to crate a socket")                                                    
     print("Reason: %s" %str(err))                                                         
     sys.exit();                                                                                         
 
 print('Socket created')                                                                        
 
 target_host = input("Enter the target host name to connect: ")          
 target_port = input("Enter the target port: ")                                       
 
 try:                                                                                                      
     sock.connect((target_host, int(target_port)))                                 
     print("Socket Connected to %s on port: %s" %(target_host,  target_port))
     sock.shutdown(2)                                                                          
 except socket.error as err:                                                                 
     print("Failed to connect to %s on port %s" %(target_host,  target_port))
     print("Reason: %s" %str(err))                                                        
     sys.exit();                                                                                       

import socket                                                                                       
HOST = 'localhost'                                                                              
PORT = 80                                                                                         
BUFSIZ = 4096                                                                                   
ADDR = (HOST, PORT)                                                                     
if __name__ == '__main__':                                                                 
    client_sock = socket.socket(socket.AF_INET,  socket.SOCK_STREAM)
    client_sock.connect(ADDR)                                                            
    while True:                                                                                     
         data = 'GET / HTTP/1.0\r\n\r\n'                                                
    if not data:                                                                                   
         break                                                                                     
    client_sock.send(data.encode('utf-8'))                                         
    data = client_sock.recv(BUFSIZ)                                                
    if not data:                                                                                    
         break                                                                                     
    print(data.decode('utf-8'))                                                          
    client_sock.close()                                                                      

You can configure Wireshark(network packet capturing tool) to filter packets by port or host. You can get the options under the Capture | Options menu and type port 80 in the input box next to the Capture Filter option.

 

TCP servers                                                                                                                         

 

 

The following code snippet shows how to create a TCP server:

import socket                                                                                              
from time import ctime                                                                               
HOST = 'localhost'                                                                                     
PORT = 1234                                                                                             
BUFSIZ = 1024                                                                                        
ADDR = (HOST, PORT)                                                                                
if __name__ == '__main__':                                                                               
    server_socket = socket.socket(socket.AF_INET,  socket.SOCK_STREAM)
    server_socket.bind(ADDR)
    server_socket.listen(5)
    server_socket.setsockopt( socket.SOL_SOCKET,  socket.SO_REUSEADDR, 1 )
    while True:                                       
        print('Server waiting for connection...')
        client_sock, addr = server_socket.accept()
        print('Client connected from: ', addr)
    while True:                                                             
        data = client_sock.recv(BUFSIZ)
        if not data or data.decode('utf-8') == 'END':
            break                                                           
        print("Received from client: %s" % data.decode('utf- 8'))
        print("Sending the server time to client: %s"  %ctime())
        try:                                                                
           client_sock.send(bytes(ctime(), 'utf-8'))
        except KeyboardInterrupt:
           print("Exited by user")
           client_sock.close()
        server_socket.close()

The following is an example of an enhanced TCP client:

import socket                                                             
HOST = 'localhost'                                                    
PORT = 123                                                             
BUFSIZ = 256                                                            
if __name__ == '__main__':                                             
    client_sock = socket.socket(socket.AF_INET,  socket.SOCK_STREAM)
    host = input("Enter hostname [%s]: " %HOST) or HOST
    port = input("Enter port [%s]: " %PORT) or PORT
    sock_addr = (host, int(port))
    client_sock.connect(sock_addr)
    payload = 'GET TIME'                        
  try:                                                            
     while True:                                          
        client_sock.send(payload.encode('utf-8'))
        data = client_sock.recv(BUFSIZ)
        print(repr(data))                                     
        more = input("Want to send more data to server[y/n]  :")
        if more.lower() == 'y':                           
             payload = input("Enter payload: ")
        else:                               
           break                            
 except KeyboardInterrupt:
     print("Exited by user") 
    client_sock.close()

import socket                                           
HOST = 'localhost'                         
PORT = 123                                  
BUFSIZ = 256                                         
if __name__ == '__main__':                                              
    client_sock = socket.socket(socket.AF_INET,  socket.SOCK_STREAM)
    host = input("Enter hostname [%s]: " %HOST) or HOST
    port = input("Enter port [%s]: " %PORT) or PORT
    sock_addr = (host, int(port))
    client_sock.connect(sock_addr)
    payload = 'GET TIME'
    try:                                           
       while True:                                                
          client_sock.send(payload.encode('utf-8'))
          data = client_sock.recv(BUFSIZ)
          print(repr(data))
          more = input("Want to send more data to server[y/n]  :")
          if more.lower() == 'y':
             payload = input("Enter payload: ")
          else:                       
             break           
   except KeyboardInterrupt:
       print("Exited by user") 
       client_sock.close()

Working with UDP sockets

Unlike TCP, UDP doesn't check for errors in the exchanged datagram. You can create a UDP client to send some data to the UDP server.

from socket import socket, AF_INET, SOCK_DGRAM
MAX_SIZE = 4096
PORT = 12345
if __name__ == '__main__':
   sock = socket(AF_INET,SOCK_DGRAM)
   msg = "Hello UDP server"
   sock.sendto(msg.encode(),('', PORT))
   data, addr = sock.recvfrom(MAX_SIZE)
   print("Server says:")
   print(repr(data))

UDP is often termed as a connectionless protocol as there is no acknowledgment or error checking involved.

What is port forwarding?

Port forwarding, sometimes called port mapping, allows computers or services in private networks to connect over the internet with other public or private computers or services.Port forwarding achieves by creating an association called a map between a router’s public, wide area network (WAN) internet protocol (IP) address and a private, local area network (LAN) IP address for a device on that private network.

TCP port forwarding

We can do with TCP socket programming is to set up a TCP port forwarding. For instance, if you are running an insecure program like FTP in a public server that doesn't have any SSL capability to do secure communication (FTP passwords can be seen clear-text over the wires). Since this server is accessible from Internet, you must not login with your password to the server without ensuring that the passwords are encrypted. One way of doing this is to use Secure FTP or SFTP.We can use a simple SSH tunnel to display how this approach works thus, any communication between your local FTP client and remote FTP server will happen via this encrypted channel.

Python has a third party sshtunnel module that is a wrapper around the Paramiko's SSH library. 

import sshtunnel                                                  
from getpass import getpass
ssh_host = '172.20.80.1'
ssh_port = 22
ssh_user = 'YOUR_SSH_USERNAME'
REMOTE_HOST = '172.20.80.1'
REMOTE_PORT = 21
from sshtunnel import SSHTunnelForwarder
ssh_password = getpass('Enter YOUR_SSH_PASSWORD: ')
server = SSHTunnelForwarder( ssh_address=(ssh_host, ssh_port), ssh_username=ssh_user,  ssh_password=ssh_password,
 remote_bind_address=(REMOTE_HOST, MOTE_PORT))
server.start()
print('Connect the remote service via local port: %s'  %server.local_bind_port)

try:                                       
   while True:
       pass
except KeyboardInterrupt:
   print("Exiting user user request.\n")
   server.stop()                                    

 

A non-blocking socket I/O                                                                                     

 

The following is an example of non-blocking I/O:

import socket                                                                                    
if __name__ == '__main__':                                                
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setblocking(0)
    sock.settimeout(0.5)
    sock.bind(("127.0.0.1", 0))
    socket_address =sock.getsockname()
    print("Asynchronous socket server launched on socket: %s"  %str(socket_address))
    while(1):                                              
        sock.listen(1)                                                                 

Securing sockets with TLS/SSL

You have probably come across the discussion around secure web communication using Secure Socket Layer (SSL), or more precisely Transport Layer Security (TLS), which is adopted by many other high-level protocols.Python has the built-in ssl module, which serves this purpose.

The following is an example of a secure socket-based client:

import socket                                                             
import ssl                                                                       
from ssl import wrap_socket, CERT_NONE, PROTOCOL_TLSv1, SSLError
from ssl import SSLContext
from ssl import HAS_SNI
from pprint import pprint
TARGET_HOST = 'www.google.com'
SSL_PORT = 443

CA_CERT_PATH = '/usr/local/lib/python3.3/dist-  packages/requests/cacert.pem'
def ssl_wrap_socket(sock, keyfile=None, certfile=None,  cert_reqs=None, ca_certs=None, server_hostname=None,  ssl_version=None):
     context = SSLContext(ssl_version)
     context.verify_mode = cert_reqs
     if ca_certs:
         try:                                          
             context.load_verify_locations(ca_certs)
         except Exception as e:
             raise SSLError(e)
     if certfile:                                                 
         context.load_cert_chain(certfile, keyfile)
     if HAS_SNI:        # OpenSSL enabled SNI return context.wrap_socket(sock,  server_hostname=server_hostname)
         return context.wrap_socket(sock)
if __name__ == '__main__':
     hostname = input("Enter target host:") or TARGET_HOST
     client_sock = socket.socket(socket.AF_INET,  socket.SOCK_STREAM)
     client_sock.connect((hostname, 443))
 
     ssl_socket = ssl_wrap_socket(client_sock, 
     ssl_version=PROTOCOL_TLSv1, 
     cert_reqs=ssl.CERT_REQUIRED, 
     ca_certs=CA_CERT_PATH,  server_hostname=hostname)
     print("Extracting remote host certificate details:")
     cert = ssl_socket.getpeercert()
     pprint(cert)
     if not cert or ('commonName', TARGET_HOST) not in  cert['subject'][4]:
          raise Exception("Invalid SSL cert for host %s. Check if      this is a man-in-the-middle attack!" )
     ssl_socket.write('GET / \n'.encode('utf-8'))
             

     ssl_socket.close()                           
     client_sock.close()                            

This function takes the following parameters as arguments:
• sock: TCP socket
• keyfile: SSL private key file path
• certfile: SSL public certificate path
• cert_reqs: Confirmation if certificate is required from other side to make 
connection and if validation test is required
• ca_certs: Public certificate authority certificate path
• server_hostname: The target remote server's hostname
• ssl_version: The intended SSL version to be used by the client

The SSLContext object takes the SSL version argument, that in our example is set to PROTOCOL_TLSv1, or you should use the latest version. Note that SSLv2 and SSLv3 are broken and must not be used in any production code for serious security issues.


Creating a custom SSL client/server

import socket                                                       
import ssl                                         
SSL_SERVER_PORT = 8000
if __name__ == '__main__':
    server_socket = socket.socket()
    server_socket.bind(('', SSL_SERVER_PORT))
    server_socket.listen(5)
    print("Waiting for ssl client on port %s" %SSL_SERVER_PORT)
    newsocket, fromaddr = server_socket.accept()
 # Generate your server's public certificate and private key  pairs.
    ssl_conn = ssl.wrap_socket(newsocket, server_side=True, 
    certfile="server.crt", keyfile="server.key", 
    ssl_version=ssl.PROTOCOL_TLSv1)
    print(ssl_conn.read())
    ssl_conn.write('200 OK\r\n\r\n'.encode())
    print("Served ssl client. Exiting...")
    ssl_conn.close()
    server_socket.close()


 You can easily generate the certificate by following any step-by-step guide found on the Internet. For example, http://www.akadia.com/services/ssh_test_
certificate.html suggests to generate the SSL certificate in a few steps.

from socket import socket
import ssl
from pprint import pprint
TARGET_HOST ='localhost'
TARGET_PORT = 8000
CA_CERT_PATH = 'server.crt'
if __name__ == '__main__':
 sock = socket()
 ssl_conn = ssl.wrap_socket(sock, cert_reqs=ssl.CERT_REQUIRED,  ssl_version=ssl.PROTOCOL_TLSv1, ca_certs=CA_CERT_PATH)
 target_host = TARGET_HOST 
 target_port = TARGET_PORT 
 ssl_conn.connect((target_host, int(target_port)))

 cert = ssl_conn.getpeercert()
 print("Checking server certificate")
 pprint(cert)
 if not cert or ssl.match_hostname(cert, target_host):
 raise Exception("Invalid SSL cert for host %s. Check if  this is a man-in-the-middle attack!" %target_host )
 print("Server certificate OK.\n Sending some custom request...  GET ")
 ssl_conn.write('GET / \n'.encode('utf-8'))
 print("Response received from server:")
 print(ssl_conn.read())
 ssl_conn.close()

 

 

 

An echo protocol


Our echo server should listen until a client connects and sends a bytes string, then we want it to echo that string back to the client.
 We only need a few basic rules for doing this. These rules are as follows:

1. Communication will take place over TCP.
2. The client will initiate an echo session by creating a socket connection to the server.
3. The server will accept the connection and listen for the client to send a bytes string.
4. The client will send a bytes string to the server.
5. Once it sends the bytes string, the client will listen for a reply from the server
6. When it receives the bytes string from the client, the server will send the bytes string back to the client.
7. When the client has received the bytes string from the server, it will close its socket to end the session.

Framing
This problem is called framing.
The main ones are described here:
1. Make it a protocol rule that only one message will be sent per connection, and once a message has been sent, the sender will immediately close 
the socket.
2. Use fixed length messages. The receiver will read the number of bytes and know that they have the whole message.
3. Prefix the message with the length of the message. The receiver will read the length of the message from the stream first, then it will read the indicated number of bytes to get the rest of the message.
4. Use special character delimiters for indicating the end of a message. The receiver will scan the incoming stream for a delimiter, and the message 
comprises everything up to the delimiter.

 


Responses(0)







Related