LABORATORIO Cliente/Servidor
Sitio: | Facultad de Ingeniería U.Na.M. |
Curso: | Redes II - IC421 |
Libro: | LABORATORIO Cliente/Servidor |
Imprimido por: | Invitado |
Día: | miércoles, 4 de diciembre de 2024, 23:13 |
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.
2. Desarrollo
Para la primera parte correremos los dos script en la misma PC.
Ejecute primero la aplicacion del servidor. (3. Servidor código).
Una vez hecho esto podemos verificar la conexión 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 netstat al bloc de notas, y luego buscar ":puerto", de esa manera nos evitamos la búsqueda línea por línea.
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 aplicación de cliente (4. Cliente código) y tenemos el siguiente menú:
- Primeramente debemos realizar la conexión mediante la opcion 1. (si ejecutamos la conexión 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 conexión tenemos las opciones 4 y 5.
Tareas a realizar
- Realice capturas de paquetes utilizando Wireshark!
- Identifique los paquetes intercambiados y compare con lo dado en teoría, guarde captura de pantalla y comente sobre la inicialización 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 transmisión, queédiferencias puede notar entre las opciones 4 y 5 de la aplicación, a cuál corresponde cada una según lo dado en teoría?
Conexion en dos ordenadores
- Pruebe ahora correr los scripts en diferentes PC y vea si se pueden comunicar las aplicaciones.
- ¿Qué 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 reenvía los datos recibidos, como en la aplicación cliente no estamos contemplando la recepción de los datos quedarían paquetes pendientes de recibir, por lo que el servidor cliente estaría ocupado. Al utilizar la funcion close() (opción 5) estamos forzando al cierre del canal. Al estar ocupado el cliente, éste envía 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 finalización de la comunicación 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 fácilmente 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 librería de cifrado, puede utilizar la libreria RSA.
8. Creacion de llaves
Para la creación de un par de llaves podemos utilizar los métodos ya vistos en el anterior laboratorio o podemos hacerlo mediante la propia librería de python rsa. El siguiente código 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 pública, luego el servidor decifrará 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 descifrar 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