Từng bước để tạo một công cụ scan port vô cùng đơn giản và hiệu quả trên python bằng cách sử dụng thư viện socket, trong bài viết này, chúng ta sẽ tìm hiểu sơ về các sử dụng thư viện socket.
Thư viện socket
thư viện Socket trong python là một module được tích hợp sẵn khi cài đặt python, nó là module tiêu chuẩn để lập trình mạng, cho phép các chương trình giao tiếp qua Internet hoặc mạng nội bộ bằng cách gửi và nhận dữ liệu giữ các thiết bị. Là cầu nối giữ ứng dụng và tầng giao vận (tầng thứ 2 trong mô hình OSI hay còn gọi là Transport layer) của hệ điều hành.
Soket là gì?
Một socket là điểm cuối (endpoint) của một kết nối mạng hai chiều giữa hai tiến trình.
Có thể dùng để giao tiếp giữa các tiến trình trên cùng một máy hoặc giữa các máy khác nhau trên cùng một mạng (có thể là mạng LAN hoặc Internet).
Mối socket két được xác định bởi địa chỉ IP (IP address) và Cổng (port).
Các loại socket chính
Sơ lược về kiến thức mạng thì tại transport layer chúng ta có hai giao thức cốt lõi dùng để truyền các bit dữ liệu hay còn gọi là các gói tin đi qua môi trường mạng (LAN hoặc Internet).
Đầu tiên là TCP (Transmission Control Protocol): Thông qua quá trình bắt tay ba bước, tạo một kết nối an toàn trước khi truyền dữ liệu. Truyền các gói tin tuần tự bằng cách đánh số thứ tự cho các gói tin, từ đó có thể kiểm tra, phát hiện và gửi lại gói tin bị mất trong quá trình truyền, đảm bảo tính toàn vẹn của gói tin.
Thứ hai là UDP (User Datagram Protocol): Gửi dữ liệu trực tiếp mà không cần kết nối trước, bỏ qua các bước kiểm tra nhằm mục đích đảm bảo tốc độ truyền dữ liệu nhanh chóng.
Từ đó tương tự ta cũng thấy sẽ có hai loại socket là
Cách sử dụng thư viện socket
Để hiểu đơn giản cách sử dụng socket module, ta có ví dụ sau:
import socket # Tạo socket TCP s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Kết nối đến server (Máy chủ dịch vụ) s.connect(("192.168.1.10", 8080)) # Gửi dữ liệu s.send(b"Hello server!") # Nhận phản hồi data = s.recv(1024) print("Server trả lời:", data.decode()) # Đóng kết nối s.close()
Giải thích:
Cú pháp s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) thật ra là đây là mặc định khi ta tạo socket, với cú pháp trên ta hoàn toàn có thể viết là s = socket.socket().
Nguyên nhân viết như trên cho dài ra vì ta cần làm rõ các cài đặt tùy chọn, tránh lầm lẫn với các cài đặt khác, socket.AF_INET đây là ta lựa chọn dùng IPv4 (ví dụ: 192.168.1.1), nếu lựa dùng IPv6 thì ta sử dụng cú pháp AF_INET6. Tương tự socket.SOCK_STREAM là dùng giao thức TCP, ta có thể đổi lại UDP bằng socket.SOCK_DGRAM
s.connect(("192.168.1.10", 8080)) với 192.168.1.10 là IP của server mà ta muốn kết nối và 8080 là port để kết nối.
s.send(b"...") khi dùng send của socket module để gửi dữ liệu, thì dữ liệu đó phải được gửi dưới dạng byte, nên chữ b trong lệnh trên nhằm mục đích chuyển chuỗi dữ liệu ta muốn gửi đi thành dạng byte.
s.recv(1024) dùng để giới hạn dữ liệu đọc được ở 1024 byte, nếu dữ liệu lớn hơn 1024 byte thì cần dùng nhiều recv() hơn hoặc dùng vòng lặp.
data.decode() dữ liệu ta nhận được ở dạng byte nên cần decode() đề chuyển về dạng string mà con người có thể đọc được.
Ứng dụng cụ thể
Từ ví dụ của phần trên, ta có thể tạo một mô hình client-server đơn giản để gửi và nhận tin nhắn.
Tạo một server để lắng nghe và phản hồi
# Server.py import socket HOST = 'localhost' PORT = 12345 server = socket.socket() sever.bind((HOST, PORT)) # Gán địa chỉ và cổng cho socket server.listen(1) # Bắt đầu lắng nghe print(f"Server đang lắng nghe tại {HOST}:{PORT}") conn, addr = server.accept() # Chấp nhận kết nối print(f"Kêt nối từ {addr}") while True: data = conn.recv(1024) if not data: break print("Tin nhắn từ clien: ", data.decode()) conn.send(b"Server đã nhận tin nhắn!") conn.close()
Giải thích:
server.listen(1) ở đây số 1 tức là số lượng kết nối tối đa, nếu có nhiều hơn 1 client kết nối cùng lúc thì chỉ có 1 client được xử lý.
conn, addr = server.accept() đây là lệnh quan trọng, accept() sẽ làm hai việc. Cái thứ nhất là một socket mới dùng dùng để giao tiếp với client, nó sẽ được gán vào biến đầu tiên. Cái thứ hai là một tuple chứa thông tin địa chỉ IP và Port của client gán vào biến thứ hai.
while True vòng lặp nhận dữ liệu liên tục, cho phép server xử lý nhiều tin nhắn từ client.
if not data: break lệnh này kiểm tra client đã đóng kết nối chưa bằng cách: khi client đóng kết nối thì recv() sẽ trả về chuỗi rỗng b'', ta sẽ dùng break để ngắt vòng lặp.
Tạo Client để gửi tin nhắn
# Client.py import socket HOST = 'localhost' PORT = 12345 client = socket.socket() client.connect((HOST, PORT)) client.send(b"Hello server") response = client.recv(1024) print("Phản hồi từ server: ", response.decode()) client.close()
Các lỗi khi thường gặp connect trên socket
Khi ta gọi connect() trên python không thành công, chương trình thường sẽ ném ra các ngoại lệ (exception) như sau và nguyên nhân của chúng:
ConnectionRefusedError nguyên nhân là do không có server nào đang lắng nghe tại IP và Port đó.
Ví dụ: ConnectionRefusedError: [Errno 111] Connection refused
socket.timeout do kết nối mất quá nhiều thời gian, thường là do host không phản hồi. Có thể xử lý bằng cách đặt giới hạn thời gian bằng settimeout(seconds) trước khi gọi connect().
socket.gaierror nguyên nhân là do không phân giải được tên miền hoặc sai IP.
Ví dụ: socket.gaierror: [Errno -2] Name or service not known
OSError hoặc socket.error nguyên nhân do lỗi hệ thống mạng, ví dụ như mạng bị ngắt, firewall chặn hoặc IP không hợp lệ.
Dùng try-except để kiểm tra lỗi:
import socket sock = socket.socket() sock.settimeout(5) try: sock.connect(("192.168.1.100", 8080)) print("Kết nối thành công!!!") except ConnectionRefusedError: print("Kết nối bị từ chối. Không có server tại địa chỉ đó.") except socket.timeout: print("Kết nối bị timeout. Host không phản hồi") except socket.gaierror: print("Lỗi phân giải địa chỉ. Kiểm tra lại IP hoặc tên miền") except Exception as e: print(f"Lỗi không xác định: {e}") finally: sock.close()
Tạo công cụ Scan Port
Từ các ví dụ trên, chúng ta sẻ sử dụng socket module để tạo một công cụ scan port để kiểm tra server có đang chạy các service thông dụng hay không.
Hàm scan port kiểm tra cổng đang mở
# scanPory.py import socket def scanPort(ip, port): try: s = socket.socket() s.settimeout(1) s.connect((ip, port)) print(f"Port {port} is OPEN in {ip}") except: pass finally: s.close()
Giải thích: except: pass để bỏ qua các lỗi vì ta chỉ cần biết cổng đó có kết nối được hay không.
Tạo biến và gán giá trị cho biến
targetIP = "192.168.1.1"
listPort = [20, 21, 22, 23, 25, 53, 67, 68, 80, 81, 8080, 110, 143, 443, 3306, 3389, 5900]
Danh sách các port thông dụng và service tương ứng với chúng:
20, 21 FTP
22 SSH
23 Telnet
25 SMTP
53 DNS
67 68 DHCP
80, 81, 8080 HTTP
110 POP3
143 IMAP
443 HTTPS
3306 MySQL
3389 RDP
5900 VNC
Tiến hành scan port
for port in listPort: scanPort(targetIP, port)
Vòng lặp sẽ chạy thử từ port trong port list với hàm scanPort để kiểm tra port đó có mở hay không.
Thêm đối số
Khi tới bước trên thì chương trình của ta đã tương đối hoàn thiện, nhưng mình muốn tối ưu hơn, như khi sử dụng ta có thể gán IP cần scan trực tiếp trên câu lệnh chạy chương trình mà không cần mở mã nguồn lên để thay đổi giá trị của biến.
import sys
...
targetIP = sys.argv[1]
Kiểm tra đối số
Để tránh trường hợp quên không thêm đối số vào lệnh chạy chương trình thì cần thêm lệnh để kiểm tra và hướng dẫn người dùng phải nhập đối số khi sử dụng
# đặt đoạn mã này trước khi khởi tạo biến targetIP if len(sys.argv) < 2 or not sys.argv[1].strip(): print("python3 scanPort.py TargetIP") sys.exit(1)
len(sys.argv) < 2 tránh việc không có đối số.
sys.argv[1].strip() tránh việc đối số chỉ có khoảng trắng.
exit(1) thoát chương trình do lỗi.
Chương trình đầy đủ
# scanPory.py import socket import sys def scanPort(ip, port): try: s = socket.socket() s.settimeout(1) s.connect((ip, port)) print(f"Port {port} is OPEN in {ip}") except: pass finally: s.close() if len(sys.argv) < 2 or not sys.argv[1].strip(): print("python3 scanPort.py TargetIP") sys.exit(1) targetIP = sys.argv[1] listPort = [20, 21, 22, 23, 25, 53, 67, 68, 80, 81, 8080, 110, 143, 443, 3306, 3389, 5900] for port in listPort: scanPort(targetIP, port)
Kết luận
socket module là cầu nối giữa ứng dụng và transport layer (TCP/UDP).
Cho phép thiết lập kết nối hai chiều giữa các tiến trình từ cùng trong một thiết bị cho đến kết nối ra ngoài Internet.
Dễ dàng sử dụng và có thể mở rộng thêm tính ví dụ với threading, asyncio, hoặc select để xử lý đa kết nối.
Bên cạnh đó để sử dụng socket module hiệu quả, ta cần nắm vững các khái niệm như: IP, port, bắt tay TCP, blocking và non-blocking, mã hóa dữ liệu, timeout.

Nhận xét
Đăng nhận xét