¿Que es un socket?
Un socket es un mecanismo de bajo nivel que permite la conexión entre dos extremos, estos extremos podrían ser elementos interconectados en una red o bien procesos dentro de un mismo sistema operativo. Un socket puede usarse como cliente o servidor dependiendo de para que se va a usar.
Cuando un socket cliente se conecta a un socket servidor, el socket cliente envía una solicitud al servidor y el servidor le brinda una respuesta al cliente.
Cómo funciona un socket
El siguiente procedimiento muestra cómo funciona un socket:
- Primero: Tenemos que poner en funcionamiento el proceso que se encargará de recibir los datos, el cual estará a la espera de recibir la comunicación del cliente.
- Segundo: Ejecutamos el proceso cliente, que será un socket encargado de enviar datos al servidor que se encuentra esperando nuestra conexión.
- Tercero: Por último, el cliente realizará una petición al servidor y el servidor gestionará la respuesta que quiere darle. Y finalmente, el cliente recibirá la respuesta del servidor.
Tipos de socket
Dependiendo el protocolo con el que vamos a realizar la conexión, tendremos dos tipos de socket, los que utilizan el protocolo TCP, y los que utilizan el protocolo UDP.
El protocolo TCP tiene las siguientes características:
- Está orientado a la conexión.
- Garantiza la correcta transmisión de los ficheros.
- Mantiene el orden de los ficheros en la transmisión.
- Cuando llegan los paquetes el receptor emite un mensaje de recepción (ACK).
El protocolo UDP tiene las siguientes características:
- No está orientado a la conexión.
- Los datagramas o paquetes pueden viajar en cualquier orden.
- No garantiza que lleguen todos los paquetes.
Funciones principales del modulo socket
socket()
: Se utiliza para crear un nuevo socket. Esta función toma dos argumentos opcionales. El primer argumento es el tipo de socket que se desea crear. Los valores válidos sonAF_INET
para un socket de Internet yAF_UNIX
para un socket de Unix. El segundo argumento es el tipo de protocolo que se va a utilizar con el socket. Los valores válidos sonSOCK_STREAM
para un socket orientado a conexión (TCP) ySOCK_DGRAM
para un socket datagrama (UDP).
import socket
# Crear un socket de Internet orientado a conexión
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Una vez que se ha creado un socket, puede utilizar métodos como bind()
, connect()
, send()
y recv()
para enviar y recibir datos a través del socket.
bind()
: Se utiliza para asociar un socket a una dirección y un puerto en particular. Esta función toma una tupla que contiene la dirección y el puerto al que se va a asociar el socket como argumento. Por ejemplo, si quieres crear un servidor que escuche en el puerto 8000 de tu máquina local, puedes utilizar la funciónbind()
de la siguiente manera:
import socket
# Crear un socket de Internet orientado a conexión
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Asociar el socket a la dirección y puerto locales
s.bind(("localhost", 8000))
Una vez que se ha asociado un socket a una dirección y un puerto, puedes utilizar métodos como listen()
y accept()
para escuchar y aceptar conexiones entrantes.
listen()
: se utiliza para poner un socket en modo de escucha. Esta función toma un argumento opcional que especifica el número máximo de conexiones entrantes que se pueden encolar antes de que se rechacen nuevas conexiones. Por ejemplo, si quieres crear un servidor que escuche en el puerto 8000 de tu máquina local y acepte hasta 10 conexiones entrantes, puedes utilizar la funciónlisten()
de la siguiente manera:
import socket
# Crear un socket de Internet orientado a conexión
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Asociar el socket a la dirección y puerto locales
s.bind(("localhost", 8000))
# Poner el socket en modo de escucha
s.listen(10)
Una vez que un socket está en modo de escucha, puedes utilizar el método accept()
para aceptar conexiones entrantes.
accept()
: Se utiliza para aceptar una conexión entrante en un socket en modo de escucha. Esta función bloquea el flujo del programa hasta que se establezca una conexión entrante. Una vez que se ha establecido una conexión, devuelve una tupla que contiene un nuevo socket y la dirección del cliente que se ha conectado. Por ejemplo, si quieres crear un servidor que escuche en el puerto 8000 de tu máquina local y acepte conexiones entrantes, puedes utilizar la funciónaccept()
de la siguiente manera:
import socket
# Crear un socket de Internet orientado a conexión
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Asociar el socket a la dirección y puerto locales
s.bind(("localhost", 8000))
# Poner el socket en modo de escucha
s.listen()
# Aceptar una conexión entrante
conn, addr = s.accept()
Una vez que se ha aceptado una conexión, puedes utilizar el método send()
del socket devuelto para enviar datos al cliente y el método recv()
para recibir datos del cliente.
connect()
: Se utiliza para establecer una conexión con un servidor a través de un socket. Esta función toma una tupla que contiene la dirección y el puerto del servidor al que se desea conectar como argumento. Por ejemplo, si quieres conectarte a un servidor que se encuentra en la direcciónlocalhost
y el puerto 8000, puedes utilizar la funciónconnect()
de la siguiente manera:
import socket
# Crear un socket de Internet orientado a conexión
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Conectarse al servidor
s.connect(("localhost", 8000))
Una vez que se ha establecido la conexión, puedes utilizar el método send()
del socket para enviar datos al servidor y el método recv()
para recibir datos del servidor.
connect_ex()
: es similar a la funciónconnect()
, pero en lugar de lanzar una excepción en caso de error, devuelve un código de error. Esta función toma una tupla que contiene la dirección y el puerto del servidor al que se desea conectar como argumento. Por ejemplo, si quieres conectarte a un servidor que se encuentra en la direcciónlocalhost
y el puerto 8000, puedes utilizar la funciónconnect_ex()
de la siguiente manera:
import socket
# Crear un socket de Internet orientado a conexión
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Conectarse al servidor
error_code = s.connect_ex(("localhost", 8000))
if error_code == 0:
# Conexión exitosa
else:
# Error al conectarse al servidor
Una vez que se ha establecido la conexión, puedes utilizar el método send()
del socket para enviar datos al servidor y el método recv()
para recibir datos del servidor.
send()
: Se utiliza para enviar datos a través de un socket. Esta función toma una cadena de bytes como argumento y devuelve el número de bytes enviados. Si no se pueden enviar todos los bytes de una sola vez, la funciónsend()
enviará la cantidad máxima posible y devolverá la cantidad de bytes enviados. Por ejemplo, si quieres enviar un mensaje a través de un socket que ya está conectado a un servidor, puedes utilizar la funciónsend()
de la siguiente manera:
import socket
# Crear un socket de Internet orientado a conexión
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Conectarse al servidor
s.connect(("localhost", 8000))
# Enviar un mensaje al servidor
message = "Hola, servidor!"
num_bytes_sent = s.send(message.encode())
Es importante tener en cuenta que la función send()
sólo puede enviar cadenas de bytes, por lo que es necesario codificar cualquier cadena de texto en una cadena de bytes antes de enviarla. También es importante tener en cuenta que la función send()
puede enviar menos bytes de los que se le piden, por lo que es importante verificar el número de bytes enviados y, si es necesario, reintentar el envío de los bytes restantes.
recv()
: Se utiliza para recibir datos a través de un socket. Esta función toma un argumento opcional que especifica la cantidad máxima de bytes que se deben recibir. Si no se especifica una cantidad máxima, la funciónrecv()
recibirá todos los bytes disponibles. La funciónrecv()
devuelve una cadena de bytes con los datos recibidos. Por ejemplo, si quieres recibir un mensaje de un servidor a través de un socket que ya está conectado al servidor, puedes utilizar la funciónrecv()
de la siguiente manera:
import socket
# Crear un socket de Internet orientado a conexión
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Conectarse al servidor
s.connect(("localhost", 8000))
# Recibir un mensaje del servidor
message = s.recv(1024) # Recibir hasta 1024 bytes
message = message.decode() # Decodificar la cadena de bytes en una cadena de texto
Es importante tener en cuenta que la función recv()
sólo puede recibir cadenas de bytes, por lo que es necesario decodificar la cadena de bytes en una cadena de texto después de recibir los datos. También es importante tener en cuenta que la función recv()
puede bloquear el flujo del programa si no hay datos disponibles para recibir, por lo que es importante manejar este caso de manera adecuada en tu código.
close()
: es un método de los objetos de socket en Python que se utiliza para cerrar un socket. Una vez que se ha cerrado un socket, ya no puede enviar ni recibir datos a través de él. La funciónclose()
libera cualquier recurso del sistema operativo asociado con el socket, lo que permite que el sistema operativo los reutilice. Aquí hay un ejemplo de cómo se puede utilizar la funciónclose()
:
import socket
# Crear un socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Conectarse a un servidor
sock.connect(('www.example.com', 80))
# Enviar datos a través del socket
sock.sendall(b'GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n')
# Recibir datos del socket
data = sock.recv(1024)
# Cerrar el socket
sock.close()
En este ejemplo, se crea un socket y se conecta a un servidor web en el puerto 80. Luego se envía una solicitud HTTP GET y se recibe la respuesta. Finalmente, se cierra el socket con sock.close()
.
Ejemplos
- Obtener IP a partir de un dominio:
import socket
def hostname_to_ip(hostname):
return socket.gethostbyname(hostname)
print(hostname_to_ip("google.com"))
- Obtener dominio a partir de una IP:
import socket
def ip_to_hostname(ip_address):
return socket.gethostbyaddr(ip_address)[0]
print(ip_to_hostname("172.217.11.110"))
- Scanner de puertos con concurrencia:
import concurrent.futures
import socket
def scan_port(ip,port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as scanner:
scanner.settimeout(0.5)
try:
if scanner.connect_ex((ip, port)) == 0:
print(f"[+] Port: {port} open")
except:
pass
target = "google.com"
ip = socket.gethostbyname(target)
print(f"Target: {target}")
print(f"IP: {ip}")
with concurrent.futures.ThreadPoolExecutor(max_workers=5000) as executor:
for port in range(0xFFFF):
executor.submit(scan_port, ip, port + 1)
- Realización de banner grabbing
import socket
def grab_banner(ip, port):
try:
with socket.socket() as s:
s.connect((ip, port))
banner = s.recv(1024)
return banner
except:
return None
ip = '127.0.0.1'
port = 21
banner = grab_banner(ip, port)
if banner:
print(banner)
else:
print('Unable to grab banner')
- Cliente socket like a Netcat
import sys
import socket
# Leer el host y port desde la linea de comandos
host = sys.argv[1]
port = int(sys.argv[2])
# Crear socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connectar al host y puerto objetivo
sock.connect((host, port))
# Utilizar un bucle while para leer interactivamente la entrada del usuario y enviarla al objetivo.
while True:
# Leer una linea en el input del usuario
line = input("> ")
# Enviar la línea al objetivo
sock.sendall(line.encode())
# Recibir e imprimir la respuesta del objetivo
response = sock.recv(4096).decode()
print(response, end="")
- Servidor socket like a Netcat:
import sys
import socket
# Leer el puerto desde la línea de comandos
port = int(sys.argv[1])
# Crear socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Vincular el socket al puerto
sock.bind(("", port))
# Iniciar la escucha de conexiones entrantes
sock.listen()
# Aceptar una conexión entrante
conn, addr = sock.accept()
# Utilizar un bucle while para recibir datos del cliente de forma interactiva y devolverlos.
while True:
# Recibir datos del cliente
data = conn.recv(4096).decode()
# Si los datos están vacíos, el cliente ha cerrado la conexión
if not data:
break
# Imprimir los datos recibidos
print(data, end="")
# Enviar los datos al cliente
conn.sendall(data.encode())
# Cerrar la conexión y el socket
conn.close()
Con la colaboración de elborikua y OpenAI (Que nos dejará sin empleo)