Network programming in python with examples



GETING A HOST NAME

>>> import socket

>>> host_name = socket.gethostname()

>>> print ("Host name: %s" %host_name)

>>> print ("IP address: %s",host_name)

GETTING A HOST NAME WITH A FUNCTION DEFINITION

import socket

def print_machine_info():

    host_name = socket.gethostname()

    ip_address = socket.gethostbyname(host_name)

    print ("Host name: " ,host_name)

    print ("IP address: " ,ip_address)

if __name__ == '__main__':

    print_machine_info()


The hostname is what you assigned to your computer when you configured your operating system. This output will be different on your machine depending on the system's host configuration. Here hostname indicates where the Python interpreter is currently executing.

Retrieving a remote machine's IP address

If you need to know the IP address of a remote machine, you can use a built-in library function, gethostbyname(). In this case, you need to pass the remote hostname as its parameter.


import socket

def get_remote_machine_info():

    remote_host = 'www.python.org'

    try:

        print ("IP address of %s: %s" ,(remote_host,socket.gethostbyname(remote_host)))

    except socket.error as err_msg:

        print ("%s: %s" %(remote_host, err_msg))

if __name__ == '__main__':

    get_remote_machine_info()


Converting an IPv4 address to different formats

When you would like to deal with low-level network functions, sometimes, the usual string notation of IP addresses are not very useful. They need to be converted to the packed 32-bit binary formats.
The Python socket library has utilities to deal with the various IP address formats. Here, we will use two of them: inet_aton() and inet_ntoa().

Let us create the convert_ip4_address() function, where inet_aton() and inet_ntoa() will be used for the IP address conversion. We will use two sample IP addresses, 127.0.0.1 and 192.168.0.1.


import socket

from binascii import hexlify

def convert_ip4_address():

    for ip_addr in ['127.0.0.1', '192.168.0.1']:

        packed_ip_addr = socket.inet_aton(ip_addr)

        unpacked_ip_addr = socket.inet_ntoa(packed_ip_addr)

        print ("IP Address: %s => Packed: %s,Unpacked: %s" %(ip_addr,

hexlify(packed_ip_addr),unpacked_ip_addr))

if __name__ == '__main__':

    convert_ip4_address()


Finding a service name, given the port and protocol

If you would like to discover network services, it may be helpful to determine what network services run on which ports using either the TCP or UDP protocol.

import socket

def find_service_name():

    protocolname = 'tcp'

    for port in [80, 25]:

        print ("Port: %s => service name: %s" %(port,

        socket.getservbyport(port, protocolname)))

        print ("Port: %s => service name: %s" %(53, socket.getservbyport(53,'udp')))
    
if __name__ == '__main__':

    find_service_name()

Converting integers to and from host to network byte order

If you ever need to write a low-level network application, it may be necessary to handle the low-level data transmission over the wire between two machines. This operation requires some sort of conversion of data from the native host operating system to the network format and vice versa. This is because each one has its own specific representation of data.

import socket

def convert_integer():

    data = 1234

    # 32-bit

    print ("Original: %s => Long host byte order: %s, Network byte order: %s" %(data, socket.ntohl(data), socket.htonl(data)))

    # 16-bit

    print ("Original: %s => Short host byte order: %s, Network byte order: %s" %(data, socket.ntohs(data), socket.htons(data)))

if __name__ == '__main__':

    convert_integer()

Setting and getting the default socket timeout

Sometimes, you need to manipulate the default values of certain properties of a socket library, for example, the socket timeout.

You can make an instance of a socket object and call a gettimeout() method to get the default timeout value and the settimeout() method to set a specific timeout value. This is very useful in developing custom server applications.

import socket

def test_socket_timeout():

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    print ("Default socket timeout: %s" %s.gettimeout())

    s.settimeout(100)

    print ("Current socket timeout: %s" %s.gettimeout())

if __name__ == '__main__':

    test_socket_timeout()
 

 

Handling socket errors gracefully

In any networking application, it is very common that one end is trying to connect, but the other party is not responding due to networking media failure or any other reason. The Python socket library has an elegant method of handing these errors via the socket.error exceptions. In this recipe, a few examples are presented.

import sys

import socket

import argparse

def main():

# setup argument parsing

    parser = argparse.ArgumentParser(description='Socket Error Examples')

    parser.add_argument('--host', action="store", dest="host", required=False)

    parser.add_argument('--port', action="store", dest="port", type=int,required=False)

    parser.add_argument('--file', action="store", dest="file", required=False)

    given_args = parser.parse_args()

    host = given_args.host

    port = given_args.port

    filename = given_args.file

# First try-except block -- create socket

    try:

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        except socket.error as e:

        print ("Error creating socket: %s" % e)

        sys.exit(1)

# Second try-except block -- connect to given host/port

        try:

            s.connect((host, port))

        except socket.gaierror as e:

            print ("Address-related error connecting to server: %s" % e)

        sys.exit(1)

        except socket.error as e:

            print ("Connection error: %s" % e)

        sys.exit(1)

# Third try-except block -- sending data

        try:

            msg = "GET %s HTTP/1.0\r\n\r\n" % filename

            s.sendall(msg.encode('utf-8'))

        except socket.error as e:

            print ("Error sending data: %s" % e)

            sys.exit(1)

            while 1:

# Fourth tr-except block – waiting to receive data from remote host

                try:

                    buf = s.recv(2048)

                except socket.error as e:

                    print ("Error receiving data: %s" % e)

                    sys.exit(1)

                    if not len(buf):

                        break

# write the received data

                        sys.stdout.write(buf.decode('utf-8'))

if __name__ == '__main__':

    main()

Modifying a socket's send/receive buffer sizes

Let us manipulate the default socket buffer size using a socket object's setsockopt()

method.

 

import socket

SEND_BUF_SIZE = 4096

RECV_BUF_SIZE = 4096

def modify_buff_size():

    sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )

# Get the size of the socket's send buffer

    bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)

    print ("Buffer size [Before]:%d" %bufsize)

    sock.setsockopt(socket.SOL_TCP,socket.TCP_NODELAY, 1)

    sock.setsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF,

SEND_BUF_SIZE)

    sock.setsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF,

RECV_BUF_SIZE)

    bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)

    print ("Buffer size [After]:%d" %bufsize)

if __name__ == '__main__':

    modify_buff_size()


Changing a socket to the blocking/non-blocking mode

By default, TCP sockets are placed in a blocking mode. This means the control is not returned to your program until some specific operation is complete. If you call the connect() API, the connection blocks your program until the operation is complete. On many occasions, you don't want to keep your program waiting forever, either for a response from the server or for any error to stop the operation. For example, when you write a web browser client that connects to a web server, you should consider a stop functionality that can cancel the connection process in the middle of this operation. This can be achieved by placing the socket in the non-blocking mode.

Let us see what options are available under Python. In Python, a socket can be placed in the blocking or non-blocking mode. In the non-blocking mode, if any call to API, for example, send() or recv(), encounters any problem, an error will be raised. However, in the blocking mode, this will not stop the operation. We can create a normal TCP socket and experiment with both the blocking and non-blocking operations.

To manipulate the socket's blocking nature, we should create a socket object first.

We can then call setblocking(1) to set up blocking or setblocking(0) to unset
blocking. Finally, we bind the socket to a specific port and listen for incoming connections.

import socket

def test_socket_modes():

    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    s.setblocking(1)

    s.settimeout(0.5)

    s.bind(("127.0.0.1", 0))

    socket_address = s.getsockname()

    print ("Trivial Server launched on socket: %s" %str(socket_address))

    while(1):

        s.listen(1)

if __name__ == '__main__':

    test_socket_modes()

 

 Reusing socket addresses

You want to run a socket server always on a specific port even after it is closed intentionally or unexpectedly. This is useful in some cases where your client program always connects to that specific server port. So, you don't need to change the server port.
If you run a Python socket server on a specific port and try to rerun it after closing it once, you won't be able to use the same port.

The remedy to this problem is to enable the socket reuse option, SO_REUSEADDR.
After creating a socket object, we can query the state of address reuse, say an old state. Then, we call the setsockopt() method to alter the value of its address reuse state. Then,we follow the usual steps of binding to an address and listening for incoming client connections.

Import socket

import sys

def reuse_socket_addr():

    sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )

# Get the old state of the SO_REUSEADDR option

    old_state = sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR )

    print ("Old sock state: %s" %old_state)

# Enable the SO_REUSEADDR option

    sock.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )

    new_state = sock.getsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR )

    print ("New sock state: %s" %new_state)

    local_port = 8282

    srv = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    srv.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)

    srv.bind( ('', local_port) )

    srv.listen(1)

    print ("Listening on port: %s " %local_port)

    while True:

    try:

        connection, addr = srv.accept()

        print ('Connected by %s:%s'% (addr[0], addr[1]))

    except KeyboardInterrupt:

        break

    except socket.error as msg:

        print ('%s' % (msg,))

if __name__ == '__main__':

    reuse_socket_addr()


Printing the current time from the internet time server

Writing an SNTP client

import socket

import struct

import sys

import time

NTP_SERVER = "0.uk.pool.ntp.org"

TIME1970 = 2208988800

def sntp_client():

    client = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )

    data = '\x1b' + 47 * '\0'

    client.sendto( data.encode('utf-8'),( NTP_SERVER, 123 ))

    data, address = client.recvfrom( 1024 )

    if data:

        print ('Response received from:', address)

        t = struct.unpack( '!12I', data )[10]

        t -= TIME1970

        print ('\tTime=%s' % time.ctime(t))

if __name__ == '__main__':

        sntp_client()

How it works…

This SNTP client creates a socket connection and sends the protocol data. After receiving the response from the NTP server (in this case, 0.uk.pool.ntp.org), it unpacks the data with struct. Finally, it subtracts the reference time, which is January 1, 1970, and prints the time using the ctime() built-in met hod in the Python time module.

Writing a simple TCP echo client/server application

After testing with basic socket APIs in Python, let us create a TCP socket server and client now. Here, you will have the chance to utilize your basic knowledge gained in the previous recipes.

How to do it...

In this example, a server will echo whatever it receives from the client. We will use the Python argparse module to specify the TCP port from a command line.

Both the server and client script will take this argument.

First, we create the server. We start by creating a TCP socket object. Then, we set the reuse address so that we can run the server as many times as we need. We bind the socket to the given port on our local machine. In the listening stage, we make sure we listen to multiple clients in a queue using the backlog argument to the listen() method. Finally, we wait for the client to be connected and send some data to the server. When the data is received, the server echoes back the data to the client.

import socket

import sys

import argparse

host = 'localhost'

data_payload = 2048

backlog = 5

def echo_server(port):

""" A simple echo server """

# Create a TCP socket

    sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

# Enable reuse address/port

    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# Bind the socket to the port

    server_address = (host, port)

    print ("Starting up echo server on %s port %s" % server_address)

    sock.bind(server_address)

# Listen to clients, backlog argument specifies the max no of queued connections

    sock.listen(backlog)

    while True:

        print ("Waiting to receive message from client")

        client, address = sock.accept()

        data = client.recv(data_payload)

        if data:

            print ("Data: %s" %data)

            client.send(data)

            print ("sent %s bytes back to %s" % (data, address))

# end connection

            client.close()

if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='Socket Server Example')

    parser.add_argument('--port',action="store", dest="port", type=int,

required=True)

    given_args = parser.parse_args()

    port = given_args.port

    echo_server(port)


On the client side code, we create a client socket using the port argument and connect to the server. Then, the client sends the message, Test message. This will be echoed to the server, and the client immediately receives the message back in a few segments. Here, two try-except blocks are constructed to catch any exception during this interactive session.

import socket

import sys

import argparse

host = 'localhost'

def echo_client(port):

""" A simple echo client """

# Create a TCP/IP socket

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Connect the socket to the server

    server_address = (host, port)

    print ("Connecting to %s port %s" % server_address)

    sock.connect(server_address)

# Send data

    try:

# Send data

        message = "Test message. This will be echoed"

        print ("Sending %s" % message)

        sock.sendall(message.encode('utf-8'))

# Look for the response

        amount_received = 0

        amount_expected = len(message)

        while amount_received < amount_expected:

            data = sock.recv(16)

            amount_received += len(data)

            print ("Received: %s" % data)
        
            except socket.error as e:

                print ("Socket error: %s" %str(e))

            except Exception as e:

                print ("Other exception: %s" %str(e))

            finally:

                print ("Closing connection to the server")

            sock.close()

if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='Socket Server Example')

    parser.add_argument('--port', action="store",dest="port", type=int, required=True)

    given_args = parser.parse_args()

    port = given_args.port

    echo_client(port)

Comments

Check out our works

WINDOWS SYSTEM PROGRAMMING WITH C/C++

PYTHON PROGRAMMING IN AND OUT

Creating a Reverse Shell in Python