Sunteți pe pagina 1din 21

Basics of Sockets

To understand what sockets are, let's start with the Internet Connection. The Internet Connection
basically connects two points across the internet for data sharing and other stuff. One process from
computer C1 can communicate to a process from computer C2, over an internet connection. It has
following properties:
• Reliable: It means until the cables connecting two computers are safe, data will be
transfered safely.
• Point-to-Point: Connection is established between 2 points.
• Full-Duplex: It means transfer of information can occur in both ways i.e. from client to
server as well as server to client simultaneously(at the same time).
Sockets are the endpoints of a bidirectional, point-to-point communication channel. Given an
internet connection, say between client(a browser) and the server, we will have two sockets. A
Client Socket and a Server Socket.

Socket acts on two parts: IP Address + Port Number

Vaguely speaking, when you clicked on the link that brought you to a website, your browser did
something like the following:
#a socket object is created for communication
clientsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# now connect to the web server on port 80


# - the normal http port
clientsocket.connect(("www.google.com", 80))

This happened on the client side. When the client tries to connect with the server, a random port is
assigned by the operating system for the connection. This random port is called Ephermal Port. In
the above illustration, 1300 is an ephermal port on the source(client) machine. The client socket is
short lived, i.e as soon as the data exchange ends it closes. Now what happens on server is a bit
different from client. So,let's see:
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

#bind the socket to a public host and a well-known port


serversocket.bind((socket.gethostname(), 80))

#become a server socket and listen for connections


serversocket.listen(5)

For now, just focus on Connect and Bind methods.

Connect is used by the client socket to start a connection with the server. This request is fulfilled by
bind method of the server socket. If you are having problem with the code, don't worry. Every bit of
it will be explained separately with examples.
Before concluding, let's see some differences between the client and the server sockets:
• Unlike client sockets, server sockets are not short lived. For example: you might need
youtube.com for a single request but youtube.com has to be up 24*7 for any request which it
might receive from users across the globe.
• Unlike client socket which uses Ephermal Port for connection, server socket requires a
standard or well defined port for connection like: Port 80 for Normal HTTP Connection,
Port 23 for Telnet etc.
Python Sockets - SOCK_STREAM and
SOCK_DGRAM
There are two type of sockets: SOCK_STREAM and SOCK_DGRAM. Below we have a comparison
of both types of sockets.

SOCK_STREAM SOCK_DGRAM
For TCP protocols For UDP protocols
Reliable delivery Unrelible delivery
Guaranteed correct ordering of packets No order guaranteed
Connection-oriented No notion of connection(UDP)
Bidirectional Not Bidirectional

Socket Module in Python


To create a socket, we must use socket.socket() function available in the Python socket
module, which has the general syntax as follows:
S = socket.socket(socket_family, socket_type, protocol=0)

• socket_family: This is either AF_UNIX or AF_INET. We are only going to talk about INET
sockets in this tutorial, as they account for at least 99% of the sockets in use.
• socket_type: This is either SOCK_STREAM or SOCK_DGRAM.
• Protocol: This is usually left out, defaulting to 0.

Now, if you remember we have discussed client-server socket program . Now let's dig deeper into
that program and try to understand the terms and methods used.

Client Socket Methods


Following are some client socket methods:

connect( )
To connect to a remote socket at an address. An address format(host, port) pair is used for
AF_INET address family.

Server Socket Methods


Following are some server socket methods:

bind( )
This method binds the socket to an address. The format of address depends on socket family
mentioned above(AF_INET).
listen(backlog)
This method listens for the connection made to the socket. The backlog is the maximum number of
queued connections that must be listened before rejecting the connection.

accept( )
This method is used to accept a connection. The socket must be bound to an address and listening
for connections. The return value is a pair(conn, address) where conn is a new socket
object which can be used to send and receive data on that connection, and address is the address
bound to the socket on the other end of the connection.

Few General Socket Methods


For the below defined socket object,
s = socket.socket(socket_family, socket_type, protocol=0)

TCP Socket Methods UDP Socket Methods


s.recv()→ Receives TCP messages s.recvfrom()→ Receives UDP messages
s.send()→ Transmits TCP messages s.sendto()→ Transmits UDP messages

Some Basic Socket Methods


• close() This method is used to close the socket connection.
• gethostname() This method returns a string containing the hostname of the machine
where the python interpreter is currently executing. For example: localhost.
• gethostbyname() If you want to know the current machine's IP address, you may use
gethostbyname(gethostname()).
Working with TCP Sockets
Now we will use those methods in a simple Client-Server Program.

Simple Server Program


#!/usr/bin/python

#This is tcp_server.py script

import socket #line 1: Import socket module

s = socket.socket() #line 2: create a socket object


host = socket.gethostname() #line 3: Get current machine name
port = 9999 #line 4: Get port number for connection

s.bind((host,port)) #line 5: bind with the address

print "Waiting for connection..."


s.listen(5) #line 6: listen for connections

while True:
conn,addr = s.accept() #line 7: connect and accept from client
print 'Got Connection from', addr
conn.send('Server Saying Hi')
conn.close() #line 8: Close the connection

This script will do nothing as of now. It waits for a client to connect at the port specified. If we run
this script now, without having a client, it will wait for the connection,

Similarly, every website you visit has a server on which it is hosted, which is always waiting for
clients to connect. Now let's create a client.py program and try to connect with our
server.py.

Simple Client Program


Below is the client.py program. The client tries to connect to server's port, 9999(well defined
port). The code line, s.connect((host, port)) opens up a TCP connection to the hostname
on the port 9999.
#!/usr/bin/python

#This is tcp_client.py script

import socket
s = socket.socket()
host = socket.gethostname() # Get current machine name
port = 9999 # Client wants to connect to server's
# port number 9999
s.connect((host,port))
print s.recv(1024) # 1024 is bufsize or max amount
# of data to be received at once
s.close()

Now, run the server.py script first(if you haven’t yet) and then run the client.py script. If
everything goes well, you will see the output as follows:

Note: Here we are running both client and server on the same machine but a real life scenario is
surely different from this, but is similar.
Notice that after executing, terminates but server.py is still running. This is what happens in
real scenario too. After fulfiling your request, the server keeps on running 24*7 to serve other users.
After executing client.py, server says: Got Connection from ('192.168.43,217', 43402). Here
port 43402 is random or ephemeral port assigned to the client by the operating system.
Flow Diagram of the Program
Working with UDP Sockets
Well, we defined socket as:
S = socket.socket(socket_family, socket_type, protocol = 0)

But, we have covered TCP sockets we defined TCP socket by merely writing
S=socket.socket(), that is without providing the socket_family and the socket_type. If we
do not mention the socket_family and socket_type, then by default it is TCP. So, if we want to
create a UDP socket than we have to specify socket_family and socket_type explicitly.
For UDP socket we define:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

and, if you explicitly want to define a TCP socket:


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

Simple UDP Server program


This is the udpserver.py script:
#!usr/bin/python

import socket

sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # For UDP

udp_host = socket.gethostname() # Host IP


udp_port = 12345 # specified port to connect

#print type(sock) ============> 'type' can be used to see type


# of any variable ('sock' here)

sock.bind((udp_host,udp_port))

while True:
print "Waiting for client..."
data,addr = sock.recvfrom(1024) #receive data from client
print "Received Messages:",data," from",addr

Output of the above script is as follows. Keep it running and than fire up the client.py module.
Simple UDP Client
This is the udpclient.py script:
#!usr/bin/python

import socket

sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # For UDP

udp_host = socket.gethostname() # Host IP


udp_port = 12345 # specified port to connect

msg = "Hello Python!"


print "UDP target IP:", udp_host
print "UDP target Port:", udp_port

sock.sendto(msg,(udp_host,udp_port)) # Sending message to UDP server

Our udpserver.py is up and running, so now we try to run the udpclient.py script,

And here is what happened to our server after the client sends the request:
Flow Diagram of the Program

So, we have learnt how to setup a successful Client-Server connection using both TCP socket and
UDP sockets.
Handling Received Client Data over TCP
Socket
Now we will have an example in which the client sends some data to the server and the server
handles the data as instructed. We'll see two different usecases for this:
• Echo Client-Server Program
• Handling received data by adding them

Simple Client-Server Program


In this program the server simply echos the data that is received from the client. You must have seen
some web portals which echo(prints) back your details as soon as you visit their page. First, we
create the server. We start by creating a TCP socket object. 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 get connected
and send some data to the server. When the data is received, the server echoes back the data to the
client.
echo_server.py
#!usr/bin/python

import socket
host = socket.gethostname()
port = 12345
s = socket.socket() # TCP socket object
s.bind((host,port))
s.listen(5)

print "Waiting for client..."


conn,addr = s.accept() # Accept connection when client connects
print "Connected by ", addr

while True:
data = conn.recv(1024) # Receive client data
if not data: break # exit from loop if no data
conn.sendall(data) # Send the received data back to client
conn.close()

Above code executes as:


echo_client.py
#!usr/bin/python

import socket
host = socket.gethostname()
port = 12345
s = socket.socket() # TCP socket object

s.connect((host,port))

s.sendall('This will be sent to server') # Send This message to server

data = s.recv(1024) # Now, receive the echoed


# data from server

print data # Print received(echoed) data


s.close() # close the connection

Now, as the server is already up and running, we should run our echo_client.py

Performing Operation on Data in Client-Server Program


In this program we will send information to the server and the server will sum up the data and will
send back to the client. But, What's new in this?
Well, you'll see that when we are sending 2 numbers to server for addition we are not sending it as
two integers rather we will be sending the data as a string. Say, we want to add 4 and 5 so we will
send 4 and 5 as a string '4,5'. Notice the comma , in between 4 and 5. This acts as a separator
for the two integers.
On the server, as we receive the string '4,5', we will extract the integers from the string, add
them and then send the result back to the client by converting the addition result into a string.
add_server.py
#!usr/bin/python

import socket
host = socket.gethostname()
port = 12345
s = socket.socket() # TCP socket object
s.bind((host,port))

s.listen(5)

conn, addr = s.accept()


print "Connected by ", addr
while True:
data=conn.recv(1024)
# Split the received string using ','
# as separator and store in list 'd'
d = data.split(",")

# add the content after converting to 'int'


data_add = int(d[0]) +int(d[1])

conn.sendall(str(data_add)) # Send added data as string


# String conversion is MUST!
conn.close()

add_client.py
#!usr/bin/python

import socket

host = socket.gethostname()
port = 12345

a = str(raw_input('Enter first number: ')) # Enter the numbers


b = str(raw_input('Enter second number: ')) # to be added
c = a+','+b # Generate a string from numbers

print "Sending string {0} to server" .format(c)

s = socket.socket()
s.connect((host,port))

s.sendall(c) # Send string 'c' to server


data = s.recv(1024) # receive server response
print int(data) # convert received dat to 'int'

s.close() #Close the Connection

Now, run add_server.py first and after that run add_client.py

Output:
Blocking and Non-Blocking Socket I/O
In client server applications, when a client makes a request to a server, server processes the request
and sends back a response. For this, both the client and the server first needs to establish a
connection with one another through sockets (TCP or UDP). we also saw, how a client can send
data in form of request to the server and the server can operate on it, and then send a response back
to the client.

Blocking Socket I/O


By default, TCP sockets are placed in a blocking mode. This means that the control is not returned
to your program until some specific operation is complete.
For example, if you call the connect() method, the connection blocks your program until the
operation is complete. On many occasions, we don't want to keep our program waiting forever.
Taking another example, when we write a web browser client that connects to a web server, we
should consider a stop functionality that can cancel an active connection process in the middle of
its operation. This can be achieved by placing the socket in the non-blocking mode.

Non-Blocking Socket I/O


We can call setblocking(1) to set up blocking or setblocking(0) to unset blocking. Let's
understand it with the help of an example. First of all, let's consider a Blocking Socket:
block_client.py
#!usr/bin/python

import socket

sock = socket.socket()

host = socket.gethostname()
sock.connect((host, 12345))
sock.setblocking(1)

# Or simply omit this line as by default TCP sockets


# are in blocking mode

data = "Hello Python\n" *10*1024*1024 # Huge amount of data to be sent


assert sock.send(data) # Send data till true
block_server.py
#!usr/bin/python

#block_server.py

import socket

s = socket.socket()

host = socket.gethostname()
port = 12345

s.bind((host,port))
s.listen(5)

while True:
conn, addr = s.accept() # accept the connection

data = conn.recv(1024)
while data: # till data is coming
print data
data = conn.recv(1024)
print "All Data Received" # Will execute when all data is received
conn.close()
break

Now, run block_server.py first and then block_client.py. You'll notice that the server
keeps on printing Hello Python. This will go on and on till all the data is sent. In the above code the
line All Data Received will not be printed for long time, because as the client has to send a large
number of string, which will take time, and until then the socket input-output will get blocked.
What's going on here? The send() method will try to transmit all the data to the server, while the
write buffer will get filled up. The kernel will put the process to sleep until the data in the buffer is
transferred to the destination and the buffer is empty again. When the buffer becomes empty, the
kernel will wake the process up again to get the next chunk of data that is to be transferred. In short,
your code will block and it will not let anything else proceed.

Now consider a Non-Blocking Socket


#!usr/bin/python

# non_blocking_client.py

import socket

sock = socket.socket()

host = socket.gethostname()
sock.connect((host, 12345))
sock.setblocking(0) # Now setting to non-blocking mode

data = "Hello Python\n" *10*1024*1024 # Huge amount of data to be sent


assert sock.send(data) # Send data till true
Now, if we run the non_blocking_client.py, you'll notice that the program will run for a
small time, it will print the last line "All Data Received" and soon terminate.
What's going on here? Here the client did not send all the data. When we make a socket non-
blocking by calling setblocking(0), it will never wait for the operation to complete. So when
we call the send() method, it will put as much data in the buffer as possible and return.

Securing the Sockets over TSL/SSL


We will understand the concept of securing the sockets over TSL/SSL.

TSL/SSL
Before diving into the depth of TSL and SSL, we should first understand what is Encryption.
According to wikipedia, "Encryption is the process of encoding a message or information in such a
way that only authorized parties can access it. In an encryption scheme, the intended information
or message, referred to as plaintext, is encrypted using an encryption algorithm, generating
ciphertext that can only be read if decrypted."
Encryption is necessary to ensure the confidentiality of message/data.

TLS(Transport Layer Security) and SSL(Secure Sockets Layer) are protocols that provide data
encryption and authentication between applications and servers in scenarios where that data is being
sent across an insecure network.

The terms SSL and TLS are often used interchangeably, but one is in fact the predecessor of the
other — SSL 3.0 served as the basis for TLS 1.0 which, as a result, is sometimes referred to as SSL
3.1.
SSL Handshake
For SSL/TLS handshake to take place, the system administrator must have:
• Private Key: Used for data encryption.
• Certificate: To ensure the authenticity of client.
Although SSL handshake is a bit more complex. We will explain it in simplest possible way.
Building a custom Port Scanner using Sockets

What is a Port Scanner?


Well, consider a situation where you have to enter into a bungalow. How will you do that? The
obvious answer is to search for doors leading into the bungalow. What if the doors are locked? Well,
in that case you will scan every other door and windows of the house and you might find your way
into the house.
Now, you might remember that ports are like doors/windows in a system. So, the fundamental step
to enter a system is to scan the system for opening ports. So, it is always adviced to close
unnecessary ports of your system(server) to avoid any mishappening. For a web server port 80 is
open by default for HTTP request and response.
In this lesson, we will learn how to make an elementary port scanner using which you can scan
open ports of any web service.

port_scanner.py
#!usr/bin/python

#port_scanner.py

import socket

t_host = str(raw_input("Enter the host to be scanned: ")) # Target Host,


www.example.com
t_ip = socket.gethostbyname(t_host) # Resolve t_host to IPv4 address

print t_ip # Print the IP address

while 1:
t_port = int(raw_input("Enter the port: ")) # Enter the port to
be scanned

try:
sock = socket.socket()
res = sock.connect((t_ip, t_port))
print "Port {}: Open" .format(t_port)
sock.close()
except:
print "Port {}: Closed" .format(t_port)

print "Port Scanning complete"

When you run the above program, it prompts you to enter a hostname(www.example.com) and
then it keeps on asking for port numbers you want to scan.
To terminate the program, press Ctrl + C.
Output:
Using the Nmap Port Scanner with Python
What is Nmap?
Nmap(Network Mapper) is a security scanner, originally written by Gordon Lyon(also known by
his pseudonym Fyodor Vaskovich), and used to discover hosts and services on a computer network,
thereby building a map of the network. To accomplish its goal, Nmap sends specially crafted
packets to the target host(s) and then analyzes their responses.
Some of the useful Nmap features include:
• Host Discovery: This enables to identify hosts on any network. For example, listing the
hosts that respond to TCP and/or ICMP requests or have a particular port open.
• Port Scanning: Enumerating(counting and listing one by one) all the open ports on the
target hosts.
• Version Detection: Interrogating network services on remote devices to determine
application name and version number.
• OS Detection: Determining the operating system and hardware characteristics of the
network devices.
• Scriptable Interaction with the target: Using Nmap Scripting Engine(NSE) and Lua
programming language, we can easily write sripts to perform operations on the network
devices.
Although Nmap is a command line interface, you can download and install the GUI interface for
Nmap known as zenmap.
Above is the screenshot of the command line, when you run nmap command, all the options
available for Target specifications and Host discovery, Scan Techniques etc are listed for your
assistance. In case you want to install nmap in your machine, then:
• Run, sudo apt-get install nmap for Linux.
• For Windows and Mac OS X, download and install Nmap: https://nmap.org/download.html
The Port scanner program provides a quick script for performing a TCP connect scan. This is very
limited as we might require the ability to perform additional scan types such as ACK, RST, FIN, or
SYN-ACK scans provided by the Nmap toolkit. Nmap, delivers a rather extensive amount of
functionality. This begs the question, why not just use Nmap? Why bother about writing a script for
Port scanner?
Nmap is written in C and LUA programming languages, and can be easily integrated into Python.
Nmap produces XML based output which provides us with the ability to utilize the full functionality
of Nmap from within a Python script. So our Port Scanner script is just the outer shell, inside it we
will be using Nmap now.

S-ar putea să vă placă și