Files
STM32F4-Base/test/ftp_test.py
2026-08-28 12:22:10 +08:00

209 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FTP Server 测试脚本 — 连接 STM32F4 上的 lftpd 服务器
用法python ftp_test.py [--host 192.168.1.100] [--port 21]
"""
import socket
import sys
import time
import argparse
HOST = "192.168.1.100"
PORT = 21
TIMEOUT = 10.0
def recv_line(sock):
"""读取 FTP 响应行(以 \r\n 结尾)"""
data = b""
sock.settimeout(TIMEOUT)
while True:
try:
ch = sock.recv(1)
except socket.timeout:
print(f"[TIMEOUT] after receiving: {data!r}")
return None
if not ch:
return data if data else None
data += ch
if data.endswith(b"\r\n"):
return data
def send_cmd(sock, cmd):
"""发送 FTP 命令并读取响应"""
print(f">>> {cmd}")
sock.sendall((cmd + "\r\n").encode())
time.sleep(0.1)
response = recv_line(sock)
if response:
print(f"<<< {response.decode(errors='replace').rstrip()}")
else:
print(f"<<< [NO RESPONSE / CONNECTION CLOSED]")
return response
def parse_pasv(response):
"""解析 PASV 227 响应,返回 (ip, port)"""
import re
m = re.search(r"(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)", response.decode())
if m:
a, b, c, d, e, f = [int(x) for x in m.groups()]
ip = f"{a}.{b}.{c}.{d}"
port = e * 256 + f
return ip, port
return None, None
def test_ftp(host, port):
print(f"=== FTP Test: {host}:{port} ===\n")
checks = [] # (label, ok, detail)
def check(label, ok, detail=""):
checks.append((label, ok, detail))
mark = "PASS" if ok else "FAIL"
print(f" [{mark}] {label}{(' - ' + detail) if detail else ''}")
# 1. Connect
print("[1] Connecting to control channel...")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(TIMEOUT)
try:
sock.connect((host, port))
print(f" Connected to {host}:{port}")
check("Connect", True)
except Exception as e:
print(f" CONNECT FAILED: {e}")
check("Connect", False, str(e))
return False
# 2. Read welcome
print("\n[2] Reading welcome banner...")
resp = recv_line(sock)
if resp:
print(f" {resp.decode(errors='replace').rstrip()}")
check("Welcome 220", resp.startswith(b"220"),
resp.decode(errors='replace').rstrip())
else:
print(" NO WELCOME BANNER")
check("Welcome 220", False, "no banner")
sock.close()
return False
# 3. Login
print("\n[3] Login...")
resp = send_cmd(sock, "USER anonymous")
if not resp:
sock.close()
check("Login USER", False, "no response")
return False
resp = send_cmd(sock, "PASS test@test.com")
if not resp:
sock.close()
check("Login PASS", False, "no response")
return False
login_ok = b"230" in resp
check("Login (USER/PASS 230)", login_ok,
resp.decode(errors='replace').rstrip())
# 4. PWD
print("\n[4] PWD...")
resp = send_cmd(sock, "PWD")
pwd_ok = bool(resp) and resp.startswith(b"257")
check("PWD 257", pwd_ok,
resp.decode(errors='replace').rstrip() if resp else "no response")
# 5. PASV mode
print("\n[5] PASV...")
resp = send_cmd(sock, "PASV")
pasv_ip, pasv_port = (None, None)
if resp:
pasv_ip, pasv_port = parse_pasv(resp)
if pasv_port is None and resp and b"227" in resp:
# 227 响应但解析端口失败
pasv_port = None
if pasv_port is None:
# Try EPSV
print("\n[5b] Trying EPSV...")
resp = send_cmd(sock, "EPSV")
if resp and b"229" in resp:
import re
m = re.search(r"\(\|\|\|(\d+)\|\)", resp.decode())
if m:
pasv_port = int(m.group(1))
pasv_ip = host
print(f" EPSV: {pasv_ip}:{pasv_port}")
pasv_ok = pasv_port is not None
check("PASV/EPSV data port", pasv_ok,
f"{pasv_ip}:{pasv_port}" if pasv_ok else "no port")
# 6. LIST (correct PASV flow: connect data first, then send LIST)
if pasv_ok:
print(f" Data channel: {pasv_ip}:{pasv_port}")
print("\n[6] LIST...")
data_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
data_sock.settimeout(5.0)
try:
data_sock.connect((pasv_ip, pasv_port))
print(f" Data connected to {pasv_ip}:{pasv_port}")
resp = send_cmd(sock, "LIST")
list_open = bool(resp) and resp.startswith(b"150")
data = b""
while True:
try:
chunk = data_sock.recv(1024)
if not chunk:
break
data += chunk
except socket.timeout:
break
print(f" LIST result ({len(data)} bytes):")
print(data.decode(errors='replace'))
data_sock.close()
resp = recv_line(sock)
list_close = bool(resp) and resp.startswith(b"226")
print(f" LIST response: "
f"{resp.decode(errors='replace').rstrip() if resp else 'none'}")
list_ok = list_open and list_close
check("LIST 150->226", list_ok,
f"open={list_open} close={list_close} bytes={len(data)}")
except Exception as e:
print(f" LIST FAILED: {e}")
data_sock.close()
check("LIST 150->226", False, str(e))
else:
print(" Skipping LIST (no data port)")
# 7. QUIT
print("\n[7] QUIT...")
resp = send_cmd(sock, "QUIT")
quit_ok = bool(resp) and resp.startswith(b"221")
check("QUIT 221", quit_ok,
resp.decode(errors='replace').rstrip() if resp else "no response")
sock.close()
# Summary
passed = all(ok for _, ok, _ in checks)
print("\n" + "=" * 40)
print("FTP Test Summary:")
for label, ok, _ in checks:
print(f" [{'PASS' if ok else 'FAIL'}] {label}")
print("=" * 40)
if passed:
print(">>> TEST PASSED <<<")
else:
print(">>> TEST FAILED <<<")
print("=== Test Complete ===")
return passed
def main():
parser = argparse.ArgumentParser(description="FTP Server Test")
parser.add_argument("--host", default=HOST, help=f"FTP server IP (default: {HOST})")
parser.add_argument("--port", type=int, default=PORT, help=f"FTP server port (default: {PORT})")
args = parser.parse_args()
success = test_ftp(args.host, args.port)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()