LABORATORIO Cliente/Servidor

Site: Facultad de Ingeniería U.Na.M.
Course: Redes II - IC421
Book: LABORATORIO Cliente/Servidor
Printed by: Invitado
Date: Wednesday, 3 July 2024, 7:33 AM

1. Consigna

En el presente laboratorio se implementa una simple aplicacion de cliente/servidor la cual el cliente manda datos a un servidor y este muestra en pantalla lo que recibe. Se analizaran los paquetes mediante Wireshark

El alumno debe poder realizar lo siguiente:

  • Identifique los paquetes que se mandan/reciben para establecer un enlace de comunicación TCP/IP
  • Visualizar los datos enviados y paquetes que se mandan
  • Identificar los paquetes que se mandan/reciben para dar por finalizada una sesion TCP/IP

Image2001.gif

2. Desarrollo

Para la primera parte correremos los dos script en la misma PC.

Ejecute primero la aplicacion del servidor. (3. Servidor codigo)

Una vez hecho esto podemos verificar la conexion donde vemos que el puerto se encuentra escuchando

Podemos utilizar netstat tanto en linux como en windows.

tip para windows: Como no contamos con el comando GREP en windows, para facilitar la tareas, podemos copiar la salida de netstar al bloc de notas, y luego buscar ":puerto", de esa manera nos evitamos la busqueda linea por linea.

A medida que vamos utilizando las opciones vamos visualizando los paquetes mediante Wireshark, para poder obtener una captura más limpia utilizaremos el pre-filtro port utilizando el puerto que acabamos de abrir.

Los scripts están realizados utilizando la librería de python socket https://docs.python.org/es/3/library/socket.html


Al ejecutar la aplicacion de cliente(4. Cliente codigo). y tenemos el siguiente menu:

  • Primeramente debemos realizar la conexion mediante la opcion 1. (si ejecutamos la conexion y el servidor no se encuentra ejecutando veremos en wireshark un paquete de RST, pruebe esto)
  • Luego podemos enviar mensajes utilizando las opciones 2 y 3
  • Para cerrar la conexion tenemos las opciones 4 y 5

Tareas a realizar
  • Realice una captura de paquetes utilizando Wireshark
  • Identifique los paquetes intercambiados y compare con lo dado en teoría, guarde captura y comente sobre la inicializacion de la comunicación TCP (three-way handshake).
  • Identifique y guarde captura de los paquetes de datos enviados.
  • Identifique y guarde captura de los paquetes enviados para finalizar la transmision, que diferencias puede notar entre las opcions 4 y 5 de la aplicación, a cual corresponde cada una segun lo dado en teoria?

Conexion en dos ordenadores

  • Pruebe ahora correr los scripts en diferentes PC y vea si se pueden comunicar las aplicaciones. que consideraciones debe tener en cuenta?

3. Servidor codigo

import socket
import sys

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Bind the socket to the port
server_address = ('localhost', 10000)
print('starting up on {} port {}'.format(*server_address))
sock.bind(server_address)

# Listen for incoming connections
sock.listen(1)

while True:
    # Wait for a connection
    print('waiting for a connection')
    connection, client_address = sock.accept()
    try:
        print('connection from', client_address)

        # Receive the data in small chunks and retransmit it
        while True:
            data = connection.recv(300) #para el caso de enviar cifrado, al enviar el dato ocupa muchos mas bytes, tener en cuenta
            print('received {!r}'.format(data))
            if not data:
                print('no data from', client_address)
                break

    finally:
        # Clean up the connection
        connection.close()

4. Cliente codigo

import socket
import sys

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
# Connect the socket to the port where the server is listening
server_address = ('localhost', 10000)

def mostrar_menu(opciones):
    print('Seleccione una opción:')
    for clave in sorted(opciones):
        print(f' {clave}) {opciones[clave][0]}')


def leer_opcion(opciones):
    while (a := input('Opción: ')) not in opciones:
        print('Opción incorrecta, vuelva a intentarlo.')
    return a


def ejecutar_opcion(opcion, opciones):
    opciones[opcion][1]()


def generar_menu(opciones, opcion_salida):
    opcion = None
    while opcion != opcion_salida:
        mostrar_menu(opciones)
        opcion = leer_opcion(opciones)
        ejecutar_opcion(opcion, opciones)
        print()


def menu_principal():
    opciones = {
        '1': ('Conectar socket', accion1),
        '2': ('Enviar mensaje simple', accion2),
        '3': ('Enviar mensaje personalizado', accion3),
        '4': ('Cerrar socket', accion4),
        '5': ('Terminar socket', accion5),
        '6': ('Salir', salir)
    }
    generar_menu(opciones, '6')


def accion1():
    print('connecting to {} port {}'.format(*server_address))
    sock.connect(server_address)

def accion2():
    message = b'hola mundo'
    print('sending {!r}'.format(message))
    sock.sendall(message)

def accion3():
    message = bytes(input("Ingrese texto: "), 'utf-8')
    print('sending {!r}'.format(message))
    sock.sendall(message)

def accion4():
    print('Shutdown socket')
    sock.shutdown(socket.SHUT_RDWR)
    sock.close()
exit()
    
def accion5():
    print('Close socket')
    sock.close()
    exit()
    
def salir():
    print('Saliendo')


if __name__ == '__main__':
    menu_principal()

5. Servidor 2 codigo

Este servidor reenvia los datos recibidos, como en la aplicacion cliente no estamos contemplando la recepcion de los datos quedarian paquetes pendientes de recibir, por lo que el servidor cliente estaria ocupado. al utilizar la funcion close() (opcion 5) estamos forzando al cierre del canal. Al estar ocupado el cliente , este envia un paquete RST para finalizar el intercambio de paquetes.

import socket
import sys

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Bind the socket to the port
server_address = ('localhost', 10000)
print('starting up on {} port {}'.format(*server_address))
sock.bind(server_address)

# Listen for incoming connections
sock.listen(1)

while True:
    # Wait for a connection
    print('waiting for a connection')
    connection, client_address = sock.accept()
    try:
        print('connection from', client_address)

        # Receive the data in small chunks and retransmit it
        while True:
            data = connection.recv(300) #para el caso de enviar cifrado, al enviar el dato ocupa muchos mas bytes, tener en cuenta
            print('received {!r}'.format(data))
            if data:
                print('reenvia datos')
                connection.sendall(data)
            else:
                print('no data from', client_address)
                break

    finally:
        # Clean up the connection
        connection.close()

6. FIN vs RST

La finalizacion de la comuniacion puede terminar de dos formas:

Después de establecer un enlace con un handshake de 3 vías y una transferencia de datos exitosa, generalmente se envía un paquete FIN desde el servidor o el cliente para terminar una conexión. Un paquete RST se envía en medio cuando el servidor rechaza la conexión o no está disponible, O en medio de la transferencia de datos cuando el servidor o el cliente rechazan más comunicaciones sin pasar por el proceso formal de finalización de la conexión TCP de 4 vías .

7. Seguridad

Como vemos los datos enviados son facilmente legibles mediante el sniffer Wireshark.Si se trata de un dato importante no puede ser descubierto por cualquiera en la red. intente realizar una modifiación al script para implementar una libreria de cifrado, puede utilizar la libreria RSA.

8. Creacion de llaves

Para la creacion de un par de llaves podemos utilizar los metodos ya vistos en el anterior laboratorio o podemos hacerlo mediante la propia libreria de python rsa. El siguiente codigo se encarga de generar 2 llaves simples , imprimiendo el resultado en pantalla y generando dos archivos externos.

import rsa

(pubkey, privkey) = rsa.newkeys(512)

print(privkey.save_pkcs1().decode('utf-8'))
print(pubkey.save_pkcs1().decode('utf-8'))

#guardar llaves en archivos
pubfile = open('public.pem','w+')
pubfile.write(pubkey.save_pkcs1().decode())
pubfile.close()

privfile = open('private.pem','w+')
privfile.write(privkey.save_pkcs1().decode())
privfile.close()

9. Implementacion de cifrado en cliente

En el cliente debemos cifrar los datos a enviar. para ello utilizaremos la llave publica, luego el servidor decifrara utilizando la llave privada.

Importamos la llave:

with open('public.pem', mode='rb') as privatefile:
    keydata = privatefile.read()

publickey = rsa.PublicKey.load_pkcs1(keydata, 'PEM')


y luego solamente debemos cifrar el mensaje antes de enviarlo:


    message = b'hola'
    crypto = rsa.encrypt(message, publickey)
    sock.sendall(crypto)

10. Implementacion de cifrado en el servidor

En el servidor utilizaremos entonces la llave privada para decifrar el mensaje recibido.

Importamos la llave:

with open('private.pem', mode='rb') as privatefile:
    keydata = privatefile.read()

privatekey = rsa.PrivateKey.load_pkcs1(keydata, 'PEM')


y luego solamente debemos descifrar el mensaje recibido:

data = connection.recv(100)
print('received {!r}'.format(data)) #mensaje cifrado
message = rsa.decrypt(data, privatekey)
print('descifrado: {!r}'.format(message)) #mensaje original