计网实验四

Uncategorized
30k words

[TOC]

本次实验课的内容包括两个部分:ICMP协议分析与验证,以及FTP客户端多进程编程实验。

Windows 10开启FTP或者HTTP服务进行文件分享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
### 实验内容1: ICMP协议分析与验证
- **实验要求**:学生两人一组合作完成,需提交源代码和实验报告,并在下周六晚上12点前提交。
- **实验环境**:使用Windows XP操作系统,支持VC++、Visual Studio、Python等开发工具。
- **实验目的**:通过分析ping命令的实现,理解ICMP ECHO请求报文的构造及工作原理。利用原始套接字发送ICMP ECHO请求报文,并在接收端解析显示报文首部字段。
- **实验步骤**:
- 分析ping命令的实例代码,总结其实现原理。
- 构造并发送ICMP ECHO请求报文。
- 接收并解析ICMP ECHO请求报文,显示各字段值。
- **思考题**:设计一个tracert命令,探讨如何获取从发送端到目的端的路径信息,特别是网关IP地址和MAC地址。
- **助教检查点**:使用Wireshark抓包工具来捕获和分析ICMP ECHO请求报文,确保报文正确无误地传输。

### 实验内容2: FTP客户端多进程编程
- **实验目的**:独立完成FTP客户端的设计与实现,理解控制连接与数据连接(被动模式和主动模式)的建立过程,掌握多进程编程方法。
- **实验内容**:
- 设计FTP客户端系统,区分被动模式和主动模式的数据连接方式。
- 实现FTP客户端,处理控制连接上的命令和数据连接上的数据传输。
- **实验要求**:每位学生单独完成任务,提交不超过10M的源代码和实验报告至指定邮箱network_report@163.com,截止时间为下周六晚12点。
- **实验步骤**:涵盖从建立控制连接开始,到身份认证、数据连接建立、执行目录列表命令、释放连接等一系列操作。
- **助教检查点**:通过抓包工具分析passiv模式下的数据连接建立过程,以及dir命令执行时的数据通信流程,确定数据传输量。

这两个实验旨在让学生深入理解网络协议的工作机制,同时增强实际编程能力。

实验一:ICMP协议分析与验证

实验原理

ICMP(Internet Control Message Protocol,互联网控制消息协议)是TCP/IP协议族中的一个网络层协议,ICMP 主要用于在 IP 网络中传递控制消息和错误报告,以帮助诊断和处理网络问题。
比如,当IP数据包无法到达目的地时,会使用ICMP发送错误信息给源主机;或者用于查询网络状态,如通过ping工具来测试网络连通性。

部分参考
ICMP 协议详解及其消息格式
ICMP

ICMP的主要功能包括:

  1. 错误报告:如果IP数据包在传输过程中遇到问题(例如TTL超时、目标不可达等),沿途的路由器或最终的目标主机将产生一个ICMP错误消息返回给源主机。
  2. 网络诊断:最常用的例子就是ping命令,它利用ICMP Echo Request和Echo Reply消息来测试两个主机之间的连通性,并测量延迟时间。
  3. 重定向通知:当一台主机向某个路由器发送数据包后,如果该路由器发现存在更优路径,则可以使用ICMP重定向消息告知主机更新其路由表。
  4. 参数问题报告:如果接收到的数据包头部包含非法值,接收方可能发送ICMP Parameter Problem消息指出问题所在。

ICMP报文类型

ICMP定义了多种类型的报文,每种类型都有特定的功能。常见的几种类型包括:

  • 0: Echo Reply (回应请求)
  • 8: Echo Request (请求回声)
  • 3: Destination Unreachable (目标不可达)
  • 11: Time Exceeded (时间超过)
  • 5: Redirect (重新定向)

ICMP消息格式

ICMP消息格式1
ICMP消息格式

1. ICMP 头部

ICMP 消息的头部结构如下:

  • 类型 (Type):指示 ICMP 消息的类型。不同类型的 ICMP 消息具有不同的功能。
  • 代码 (Code):进一步细分消息类型,用于表示更具体的错误或状态信息。
  • 校验和 (Checksum):用于错误检测,确保 ICMP 消息在传输过程中没有被篡改或损坏。
  • 标识符 (Identifier):用于匹配请求和应答消息,主要用于 echo 请求和应答消息。
  • 序列号 (Sequence Number):用于跟踪 echo 请求和应答消息的顺序,帮助检测丢包情况。

2. ICMP 消息体

ICMP 消息体的内容根据消息类型的不同而有所变化。

注:

由于ICMP协议的设计初衷是为了提供网络诊断和错误报告等功能,因此它本身并不具备加密或认证机制。这意味着恶意用户可以通过伪造ICMP报文来进行攻击,例如DDoS攻击中的Smurf攻击就是利用大量伪造的ICMP Echo Request泛洪目标网络。因此,在一些安全敏感的环境中,可能会限制或禁止ICMP流量以减少潜在的安全风险。


PING

ping命令是一个常用的网络工具,用于测试主机之间的连通性。它基于ICMP(Internet Control Message Protocol)协议工作,具体来说是使用ICMP的Echo Request和Echo Reply消息来实现其功能。

工作流程

  1. 发送Echo Request:

    • 当用户在命令行中输入ping <目标IP地址或域名>时,操作系统会首先将域名解析为IP地址(如果提供了域名的话)。
    • 然后,系统构造一个ICMP Echo Request报文。这个报文包含类型8(表示Echo Request),代码0,以及一个校验和。此外,还会包含一个标识符(Identifier)和序列号(Sequence Number),这两个值通常由本地主机生成,用来唯一标识每个请求。
    • 报文中还可以包含一些额外的数据,比如时间戳,这有助于计算往返时间。
  2. 传输过程:

    • 构造好的ICMP Echo Request通过IP协议封装成IP数据包,并发送到目标主机。
    • 在传输过程中,如果遇到任何问题(如TTL超时、路由错误等),沿途的路由器可能会返回相应的ICMP错误消息给源主机。
  3. 接收Echo Reply:

    • 如果目标主机正常接收到ICMP Echo Request,并且没有配置阻止ICMP流量的安全策略,那么目标主机会构造一个ICMP Echo Reply报文作为响应。
    • Echo Reply报文的类型为0(表示Echo Reply),其余字段与请求中的相同,包括相同的标识符和序列号,以便源主机可以匹配请求和响应。
    • 目标主机将Echo Reply报文封装成IP数据包并发送回源主机。
  4. 处理响应:

    • 源主机接收到Echo Reply后,会检查标识符和序列号以确认这是对之前发送的哪个请求的响应。
    • 通过比较发送时间和接收时间,计算出往返时间(RTT, Round-Trip Time)。
    • ping程序会显示这次请求的结果,包括是否成功、往返时间等信息。
    • 如果在指定时间内没有收到回复(默认情况下通常是几秒钟),则认为这次请求失败,并可能显示“Request timed out”信息。
  5. 重复操作:

    • ping命令通常会连续发送多个请求,直到用户停止或者达到预设的数量。这样可以提供更准确的网络状况评估。

具体实现

切勿直接照搬(这个还是搬吧,求求升级版给我留着55555) (开玩笑的,能跑的最好)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# ping初版
# sender
import socket
import struct
import os
import time

ICMP_ECHO_REQUEST = 8 # ICMP类型:Echo请求
ICMP_CODE = 0 # 代码:Echo请求和Echo应答都为0


# 计算校验和
def calculate_checksum(data):
checksum = 0
count_to = (len(data) // 2) * 2 # 确保成对处理字节
for count in range(0, count_to, 2):
# 高位在前(大端字节序)
this_val = (data[count] << 8) + data[count + 1]
checksum += this_val
checksum &= 0xFFFFFFFF # 确保32位范围

# 如果是奇数长度,处理最后一个字节
if count_to < len(data):
checksum += data[-1] << 8
checksum &= 0xFFFFFFFF
#
checksum = (checksum >> 16) + (checksum & 0xFFFF)
checksum += (checksum >> 16)
return ~checksum & 0xFFFF


# 构造ICMP Echo请求报文
def create_icmp_packet(seq):
pid = os.getpid() & 0xFFFF
header = struct.pack('!BBHHH', ICMP_ECHO_REQUEST, ICMP_CODE, 0, pid, seq)
data = b'abcdefghijklmnopqrstuvwabcdefghidefghijklmnopqrstuvwabcdefghi' # 数据部分
checksum = calculate_checksum(header + data) # 计算校验和
header = struct.pack('!BBHHH', ICMP_ECHO_REQUEST, ICMP_CODE, checksum, pid, seq)
return header + data
#

# 发送ICMP请求并等待响应
def send_ping(destination, seq):
try:
# 创建原始套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
except PermissionError:
print("请以管理员/root权限运行此脚本。")
return None

# 构造ICMP报文
packet = create_icmp_packet(seq)
sock.sendto(packet, (destination, 1)) # 发送ICMP请求包
print(f"发送ICMP请求,序列号: {seq}")
return sock


if __name__ == "__main__":
target = input("请输入目标地址: ")
seq = 0
while True:
seq += 1
sock = send_ping(target, seq) # 发送ICMP请求

if sock is None: # 如果套接字创建失败,则跳过当前循环
continue
time.sleep(1) # 每次发送间隔1秒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# receiver
import socket
import struct
import time

ICMP_ECHO_REPLY = 0 # ICMP类型:Echo应答
ICMP_CODE = 0 # 代码:Echo请求和Echo应答都为0


# 计算校验和
def calculate_checksum(data):
checksum = 0
count_to = (len(data) // 2) * 2 # 确保成对处理字节
for count in range(0, count_to, 2):
# 高位在前(大端字节序)
this_val = (data[count] << 8) + data[count + 1]
checksum += this_val
checksum &= 0xFFFFFFFF # 确保32位范围

# 如果是奇数长度,处理最后一个字节
if count_to < len(data):
checksum += data[-1] << 8
checksum &= 0xFFFFFFFF

checksum = (checksum >> 16) + (checksum & 0xFFFF)
checksum += (checksum >> 16)
return ~checksum & 0xFFFF


# 解析ICMP报文
def parse_icmp_packet(packet):
icmp_header = packet[20:28] # 假设IPv4头为20字节
icmp_type, code, checksum, p_id, sequence = struct.unpack('!BBHHH', icmp_header)
print(f"\n接收到的ICMP报文解析结果:")
print(f"ICMP类型: {icmp_type} (8表示Echo请求,0表示Echo应答)")
print(f"代码: {code}")
print(f"校验和: {checksum}")
print(f"标识符: {p_id}")
print(f"序列号: {sequence}")

# 检查数据部分长度
if len(packet) > 28:
data = packet[28:]
print(f"数据部分: {data}")
# 如果数据部分包含8字节并且是浮动类型,可以进行解包
if len(data) >= 8:
try:
time_sent = struct.unpack('!d', data[:8])[0]
print(f"往返时间: {time.time() - time_sent:.6f} 秒")
except Exception as e:
print(f"解包时出错: {e}")
else:
print("数据长度不足8字节,无法解包为浮动数值。")


# 接收ICMP请求并发送ICMP响应
def receive_ping():
try:
# 创建原始套接字,监听ICMP报文
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
sock.bind(('', 0)) # 绑定端口
except PermissionError:
print("请以管理员/root权限运行此脚本。")
return

print("等待接收ICMP请求...")
while True:
# 接收ICMP请求
packet, addr = sock.recvfrom(1024) # 接收数据
print(f"接收到来自 {addr} 的ICMP请求")

# 解析接收到的ICMP报文
parse_icmp_packet(packet)

# 构造并发送ICMP响应(Echo应答)
icmp_header = packet[20:28] # 获取ICMP报文头
icmp_type, code, checksum, p_id, seq = struct.unpack('!BBHHH', icmp_header)

if icmp_type == 8: # 如果是Echo请求,则发送Echo应答
icmp_header = struct.pack('!BBHHH', ICMP_ECHO_REPLY, ICMP_CODE, 0, p_id, seq)
data = packet[28:] # 数据部分
checksum = calculate_checksum(icmp_header + data) # 计算校验和
icmp_header = struct.pack('!BBHHH', ICMP_ECHO_REPLY, ICMP_CODE, checksum, p_id, seq)

reply_packet = icmp_header + data
sock.sendto(reply_packet, addr) # 发送ICMP响应
print("发送ICMP Echo应答。")


if __name__ == "__main__":
receive_ping()

关键解释
create_icmp_packet 函数,用于构造一个ICMP Echo请求报文。

函数定义

1
def create_icmp_packet(seq):
  • seq 参数表示ICMP Echo请求的序列号,用于区分不同的请求。

获取进程ID

1
pid = os.getpid() & 0xFFFF
  • os.getpid() 获取当前进程的ID。
  • & 0xFFFF 将进程ID截断为16位(0到65535之间),因为ICMP头部中的标识符字段是16位的。

构造ICMP头部

1
header = struct.pack('!BBHHH', ICMP_ECHO_REQUEST, ICMP_CODE, 0, pid, seq)
  • struct.pack 函数用于将多个Python值打包成一个二进制字符串。
  • !BBHHH 是格式字符串,定义了如何打包这些值:
    • ! 表示使用标准大小和字节序(网络字节序,大端字节序)。
    • B 表示无符号字符(1个字节),用于ICMP类型和代码。
    • H 表示无符号短整型(2个字节),用于校验和、标识符和序列号。
  • ICMP_ECHO_REQUEST 是ICMP类型,值为8。
  • ICMP_CODE 是ICMP代码,值为0。
  • 0 是初始校验和,稍后会重新计算并更新。
  • pid 是标识符,值为当前进程ID的低16位。
  • seq 是序列号,用于区分不同的请求。

构造数据部分

注意:这里的数据部分可以是任意内容,在我使用wireshark捕获windows系统自带的ping命令发送的ICMP Echo请求报文时,发现数据部分为固定字符串 `b'abcdefghijklmnopqrstuvwabcdefghi'`。 于是做了填充参考。
1
data = b'abcdefghijklmnopqrstuvwabcdefghi'
  • data 是ICMP Echo请求的数据部分,这里使用固定的字符串 b'abcdefghijklmnopqrstuvwabcdefghi'。数据部分可以是任意内容,但在这里使用固定字符串是为了简化示例。

计算校验和

1
checksum = calculate_checksum(header + data)
  • calculate_checksum 函数用于计算整个ICMP报文(头部 + 数据部分)的校验和。
  • header + data 将头部和数据部分拼接成一个完整的报文,然后传递给 calculate_checksum 函数。

更新ICMP头部的校验和

1
header = struct.pack('!BBHHH', ICMP_ECHO_REQUEST, ICMP_CODE, checksum, pid, seq)
  • 使用计算出的校验和重新构造ICMP头部。
  • 其他字段保持不变,只有校验和字段被更新。

返回完整的ICMP报文

1
return header + data
  • 将更新后的头部和数据部分拼接成一个完整的ICMP报文,并返回。

完整的函数

1
2
3
4
5
6
7
def create_icmp_packet(seq):
pid = os.getpid() & 0xFFFF
header = struct.pack('!BBHHH', ICMP_ECHO_REQUEST, ICMP_CODE, 0, pid, seq)
data = b'abcdefghijklmnopqrstuvwabcdefghi' # 数据部分
checksum = calculate_checksum(header + data) # 计算校验和
header = struct.pack('!BBHHH', ICMP_ECHO_REQUEST, ICMP_CODE, checksum, pid, seq)
return header + data
总结

这个函数的主要步骤包括:

  1. 获取当前进程的ID并截断为16位。
  2. 构造初始的ICMP头部,校验和字段暂时设为0。
  3. 构造ICMP Echo请求的数据部分。
  4. 计算整个ICMP报文的校验和。
  5. 使用计算出的校验和更新ICMP头部。
  6. 返回完整的ICMP报文。

升级优化版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import socket
import struct
import os
import time
from datetime import datetime

# ICMP Echo请求的类型值
ICMP_ECHO_REQUEST = 8
# ICMP Echo请求和Echo应答的代码都为0
ICMP_CODE = 0
# 设置接收响应的超时时间(秒)
TIMEOUT = 2
# 默认最大ping次数
MAX_PINGS = 4

def calculate_checksum(data):
"""
计算校验和。

参数:
data (bytes): 需要计算校验和的数据包。

返回:
int: 计算后的16位校验和。
"""
checksum = 0
count_to = (len(data) // 2) * 2 # 确保成对处理字节

# 按照每两个字节进行相加,确保32位范围
for count in range(0, count_to, 2):
this_val = (data[count] << 8) + data[count + 1]
checksum += this_val
checksum &= 0xFFFFFFFF

# 如果数据长度是奇数,单独处理最后一个字节
if count_to < len(data):
checksum += data[-1] << 8
checksum &= 0xFFFFFFFF

# 将高16位和低16位相加,并再次将高16位加到低16位上(进位绕回)
checksum = (checksum >> 16) + (checksum & 0xFFFF)
checksum += (checksum >> 16)

# 取反并保留低16位作为校验和
return ~checksum & 0xFFFF


def create_icmp_packet(seq):
"""
构造ICMP Echo请求报文。

参数:
seq (int): 报文的序列号。

返回:
bytes: 完整的ICMP Echo请求报文。
"""
pid = os.getpid() & 0xFFFF # 获取进程ID并取低16位作为标识符
header = struct.pack('!BBHHH', ICMP_ECHO_REQUEST, ICMP_CODE, 0, pid, seq)
timestamp = int(time.time() * 1000) # 使用当前时间戳作为数据部分
data = struct.pack('d', timestamp) # 数据部分使用双精度浮点数表示时间戳
checksum = calculate_checksum(header + data) # 计算校验和
header = struct.pack('!BBHHH', ICMP_ECHO_REQUEST, ICMP_CODE, checksum, pid, seq)
return header + data


def send_and_receive(destination, seq):
"""
发送ICMP Echo请求并等待Echo应答。

参数:
destination (str): 目标地址。
seq (int): 请求报文的序列号。
"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
packet = create_icmp_packet(seq)
start_time = time.time()
sock.sendto(packet, (destination, 1)) # 发送ICMP请求包
print(f"发送ICMP请求,序列号: {seq}")

while True:
sock.settimeout(TIMEOUT) # 设置接收超时
try:
received_packet, addr = sock.recvfrom(1024) # 尝试接收来自目标主机的回应
end_time = time.time()
icmp_header = received_packet[20:28] # 提取ICMP头部信息
type, code, checksum, packet_pid, packet_seq = struct.unpack('!BBHHH', icmp_header)

# 如果收到的是ICMP Echo应答并且PID匹配,则认为是正确的回应
if type == 0 and packet_pid == os.getpid() & 0xFFFF:
elapsed_time = (end_time - start_time) * 1000 # 计算往返延迟
print(f"来自 {addr[0]} 的回复: 字节=56 时间={elapsed_time:.2f}ms")
return
except socket.timeout:
print("请求超时。") # 如果超过设定的时间没有收到回应,则打印超时信息
break
except PermissionError:
print("请以管理员/root权限运行此脚本。") # 处理权限不足的情况
finally:
sock.close() # 确保无论是否成功发送或接收到数据,套接字都会被关闭


if __name__ == "__main__":
target = input("请输入目标地址: ") # 用户输入目标IP地址
seq = 0
try:
for i in range(MAX_PINGS): # 循环发送指定次数的ICMP请求
seq += 1
send_and_receive(target, seq)
time.sleep(1) # 每次发送后等待1秒再发送下一个请求
except KeyboardInterrupt:
print("\nPing 已由用户终止。") # 如果用户按下Ctrl+C,则提前退出循环

注释说明:

  • calculate_checksum函数:详细解释了如何计算ICMP报文的校验和,这是保证报文完整性的重要步骤。
  • create_icmp_packet函数:描述了构造ICMP Echo请求报文的过程,包括设置标识符、序列号、数据部分以及最终的校验和。
  • send_and_receive函数:该函数负责发送ICMP Echo请求,并监听来自目标主机的回应。它包含了对超时情况的处理,以及在接收到正确回应时计算并显示往返延迟。
  • 主程序逻辑:通过循环发送ICMP请求,并根据用户的输入或者预设的最大次数来控制发送的次数。同时,也添加了对用户中断操作的支持。

奇数数据长度校验说明

在计算ICMP报文校验和的过程中,需要确保所有字节都被成对处理。这是因为校验和是基于16位(2个字节)进行计算的,所以当数据长度为奇数时,最后一个字节将没有配对的伙伴。此时,该单个字节会被视为高字节,并且与一个隐含的零低字节组成一对来参与校验和的计算。

具体来说,如果数据长度是奇数,则最后一轮循环不会处理到这个单独的字节,因此我们需要在循环结束后特别处理它。代码中的if count_to < len(data):检查是否剩下一个未处理的奇数字节,如果是,则执行如下操作:

1
2
checksum += data[-1] << 8  # 将最后一个字节作为高字节,并左移8位,使其占据16位值的高位部分
checksum &= 0xFFFFFFFF # 确保结果保持在32位范围内

这里的关键点在于:

  • data[-1]:获取数据序列中最后一个元素,即那个未配对的奇数字节。
  • << 8:将这个字节左移8位,以填补16位值的高字节位置,而低字节则默认为0。
  • checksum &= 0xFFFFFFFF:这是为了保证checksum仍然是一个32位的数值,防止溢出。

假设我们有一个包含奇数个字节的数据包,比如5个字节 [0x41, 0x42, 0x43, 0x44, 0x45],那么在计算校验和时:

  1. 前四个字节会成对处理:(0x4142)(0x4344)
  2. 最后一个字节0x45将被特殊处理,作为16位值的高字节,低字节为0,形成0x4500

然后,这个0x4500也会加入到校验和的累加过程中。这样做可以确保即使数据长度不是偶数,也能正确地计算出整个数据包的校验和。


进位绕回

  • 高16位:指的是32位整数中最左边的16位,即最高有效位(MSB, Most Significant Bit)。这部分代表了数值较大的权重。

  • 低16位:指的是32位整数中最右边的16位,即最低有效位(LSB, Least Significant Bit)。这部分代表了数值较小的权重。
    例如,假设我们有一个32位无符号整数0xAABBCCDD(十六进制表示),那么:

  • 高16位是0xAABB

  • 低16位是0xCCDD

在处理校验和计算时,特别是涉及到二进制反码求和的情况下,可能会出现累加结果超出16位的情况。这时,我们需要将超出的部分(即高16位)加回到低16位上,以确保最终结果仍然是一个有效的16位值。这个过程被称为“进位绕回”。

进位绕回的具体操作

假设我们在累加过程中得到了一个32位的结果0x0001FFFF(这里假设已经发生了溢出,最高位有进位)。为了应用进位绕回,我们会执行以下步骤:

  1. 分离高低16位

    • 高16位:0x0001
    • 低16位:0xFFFF
  2. 将高16位加到低16位上

    • 0xFFFF + 0x0001 = 0x10000
  3. 再次检查是否发生新的溢出

    • 如果发生了新的溢出(如上一步中的情况),则需要重复上述过程,直到没有新的溢出为止。在这个例子中,0x10000又产生了新的溢出,所以我们继续将高16位加到低16位上:
      • 0x0000 + 0x0001 = 0x0001
  4. 取最终结果的低16位

    • 最终的16位校验和为0x0001

Python代码示例

下面是如何在Python中实现进位绕回的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def carry_around_add(a, b):
"""
实现进位绕回加法。

参数:
a (int): 第一个16位整数。
b (int): 第二个16位整数。

返回:
int: 经过进位绕回后的16位整数。
"""
c = a + b
return (c & 0xffff) + (c >> 16)

# 示例使用
checksum = 0x0001FFFF
final_checksum = carry_around_add(checksum & 0xffff, checksum >> 16)
print(f"最终的16位校验和: {final_checksum:04x}")

这段代码展示了如何对一个32位的结果进行进位绕回处理,并最终得到一个16位的校验和。请注意,这里的carry_around_add函数可以递归调用,如果存在连续的溢出情况。但在实际应用中,一般一次或两次进位绕回就足够了,因为很少会出现连续多次溢出的情况。


struct.unpack 是 Python 的 struct 模块中的一个函数,用于将打包的二进制数据(如从网络接收的数据)解包成Python原生数据类型。这个函数非常适用于解析固定格式的二进制数据,比如网络协议中的报文头部。

解析 ICMP 报文头部

ICMP(Internet Control Message Protocol)报文头部通常包含以下字段:

  1. Type:8位,表示ICMP消息类型。
  2. Code:8位,提供关于Type的额外信息。
  3. Checksum:16位,用于错误检测。
  4. **Identifier (ID)**:16位,在回声请求和应答中使用,通常设置为发送进程的ID。
  5. **Sequence Number (Seq)**:16位,标识发送的顺序。

对于ICMP Echo Request 和 Echo Reply 消息,头部格式如下所示:

1
2
3
4
5
6
7
 0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

在你的代码中,struct.unpack('!BBHHH', icmp_header) 这一行是用来解析ICMP头部的前8个字节。让我们来详细解释一下格式字符串 '!BBHHH' 和它对应的字段:

  • **!**:表示网络字节序(大端序),这是跨平台通信时常用的字节序。
  • **B**:无符号字符(unsigned char),占用1个字节。这里有两个B,分别对应ICMP头部的TypeCode字段。
  • **H**:无符号短整型(unsigned short),占用2个字节。这里有三个H,分别对应ChecksumIdentifierSequence Number 字段。

因此,struct.unpack('!BBHHH', icmp_header) 将会返回一个元组,依次包含以下内容:

1
(type, code, checksum, identifier, sequence_number) = struct.unpack('!BBHHH', icmp_header)
示例

假设你接收到一个ICMP Echo Reply报文,其头部的二进制数据为:

1
00 00 9C B7 01 00 00 01

那么使用 struct.unpack('!BBHHH', icmp_header) 解析后,将会得到如下结果:

  • type: 0x00 (0) - 表示这是一个Echo Reply(应答)消息。
  • code: 0x00 (0) - 代码为0,通常与Type一起使用以提供更多信息。
  • checksum: 0x9CB7 (39,991) - 校验和值。
  • identifier: 0x0100 (256) - 标识符,通常是发送者的进程ID。
  • sequence_number: 0x0001 (1) - 序列号,用来匹配请求和响应。

这些值可以帮助你验证和处理接收到的ICMP报文。

注:利用操作系统本身接收端+wireshark抓包即可,无需接受端设计(接受由操作系统负责,我们只能实现监听)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# receiver
import socket
import struct
import time
from datetime import datetime

# ICMP Echo请求和Echo应答的类型值
ICMP_ECHO_REQUEST = 8
ICMP_ECHO_REPLY = 0
# ICMP Echo请求和Echo应答的代码都为0
ICMP_CODE = 0
# 设置接收响应的超时时间(秒)
TIMEOUT = 2

def calculate_checksum(data):
"""
计算校验和。

参数:
data (bytes): 需要计算校验和的数据包。

返回:
int: 计算后的16位校验和。
"""
checksum = 0
count_to = (len(data) // 2) * 2 # 确保成对处理字节

for count in range(0, count_to, 2):
this_val = (data[count] << 8) + data[count + 1]
checksum += this_val
checksum &= 0xFFFFFFFF

if count_to < len(data):
checksum += data[-1] << 8
checksum &= 0xFFFFFFFF

checksum = (checksum >> 16) + (checksum & 0xFFFF)
checksum += (checksum >> 16)

return ~checksum & 0xFFFF


def parse_icmp_packet(packet):
"""
解析ICMP报文。

参数:
packet (bytes): 完整的ICMP报文数据包。

返回:
tuple: 包含解析后的ICMP头部信息和数据部分。
"""
try:
icmp_header = packet[20:28] # 假设IPv4头为20字节
icmp_type, code, checksum, p_id, sequence = struct.unpack('!BBHHH', icmp_header)
print(f"\n接收到的ICMP报文解析结果:")
print(f"时间: {datetime.now()}")
print(f"ICMP类型: {icmp_type} (8表示Echo请求,0表示Echo应答)")
print(f"代码: {code}")
print(f"校验和: {checksum}")
print(f"标识符: {p_id}")
print(f"序列号: {sequence}")

data = packet[28:]
print(f"数据部分长度: {len(data)} 字节")
if len(data) >= 8:
try:
sent_timestamp = struct.unpack('d', data[:8])[0]
elapsed_time = time.time() - sent_timestamp
print(f"客户端到服务器时间: {elapsed_time:.6f} 秒")
except Exception as e:
print(f"解包时出错: {e}")
else:
print("数据长度不足8字节,无法解包为浮动数值。")

return icmp_type, code, checksum, p_id, sequence, data
except Exception as e:
print(f"解析ICMP报文时出错: {e}")
return None


def receive_and_reply():
"""
接收ICMP Echo请求并发送Echo应答。
"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
sock.bind(('', 0)) # 绑定端口
except PermissionError:
print("请以管理员/root权限运行此脚本。")
return

print("等待接收ICMP请求...")
while True:
try:
sock.settimeout(TIMEOUT)
packet, addr = sock.recvfrom(1024) # 接收数据
print(f"接收到来自 {addr} 的ICMP请求")

result = parse_icmp_packet(packet)
if not result:
continue

icmp_type, code, _, p_id, seq, data = result

if icmp_type == ICMP_ECHO_REQUEST: # 如果是Echo请求,则发送Echo应答
reply_header = struct.pack('!BBHHH', ICMP_ECHO_REPLY, ICMP_CODE, 0, p_id, seq)
checksum = calculate_checksum(reply_header + data) # 计算校验和
reply_header = struct.pack('!BBHHH', ICMP_ECHO_REPLY, ICMP_CODE, checksum, p_id, seq)

reply_packet = reply_header + data
sock.sendto(reply_packet, addr) # 发送ICMP响应
print("发送ICMP Echo应答。")
except socket.timeout:
print("等待ICMP请求超时。")
except Exception as e:
print(f"处理ICMP请求时出错: {e}")


if __name__ == "__main__":
receive_and_reply()

实验二:FTP客户端多进程编程

实验原理

参考资料
FTP

FTP(File Transfer Protocol,文件传输协议)是一种用于在网络上进行文件传输的标准协议。FTP基于客户端-服务器模型,允许用户从远程服务器下载文件,或将文件上传到远程服务器。以下是FTP通信的基本原理和主要特性:

基本概念

  1. 客户端-服务器模型

    • 客户端:发起连接请求并执行文件传输操作的计算机。
    • 服务器:接收连接请求并提供文件服务的计算机。
  2. 控制连接和数据连接

    • 控制连接:用于发送命令和接收响应。控制连接通常使用TCP端口21。
    • 数据连接:用于实际的文件传输。数据连接可以使用不同的端口,具体取决于传输模式。

工作模式

FTP支持两种主要的工作模式:

  1. 主动模式(Active Mode)

    • 在主动模式下,客户端连接到服务器的控制端口(通常是21)。
    • 服务器选择一个数据端口(通常大于1024的端口),并向客户端的端口20发起连接。
    • 客户端必须配置防火墙和NAT设备,以允许来自服务器的数据连接。
  2. 被动模式(Passive Mode)

    • 在被动模式下,客户端连接到服务器的控制端口(通常是21)。
    • 客户端向服务器发送一个 PASV 命令,请求服务器打开一个被动数据端口。
    • 服务器选择一个数据端口(通常大于1024的端口),并通过控制连接告知客户端该端口号。
    • 客户端连接到服务器的被动数据端口,进行数据传输。
    • 被动模式适用于客户端位于防火墙或NAT后面的场景,因为客户端主动发起数据连接。

命令和响应

FTP使用一系列标准命令和响应码来进行通信。以下是一些常见的FTP命令和响应码:

常见命令

  • USER username:发送用户名。
  • PASS password:发送密码。
  • PWD:获取当前工作目录。
  • CWD directory:更改当前工作目录。
  • CDUP:更改到父目录。
  • MKD directory:创建目录。
  • RMD directory:删除目录。
  • DELE filename:删除文件。
  • LIST:列出目录内容。
  • NLST:列出目录中的文件名。
  • RETR filename:从服务器下载文件。
  • STOR filename:上传文件到服务器。
  • APPE filename:追加文件到服务器上的现有文件。
  • QUIT:断开连接。

响应码

  • 1xx:信息性响应。
  • 2xx:成功响应。
  • 3xx:需要进一步的操作。
  • 4xx:临时错误。
  • 5xx:永久错误。

数据传输类型

FTP支持两种主要的数据传输类型:

  1. ASCII模式

    • 用于传输文本文件。FTP会在传输过程中进行必要的行结束符转换。
  2. 二进制模式

    • 用于传输二进制文件。FTP不会对文件内容进行任何修改。

安全性

传统的FTP使用明文传输用户名和密码,存在安全风险。为了提高安全性,可以使用以下方法:

  1. FTPS(FTP over SSL/TLS)

    • 使用SSL/TLS加密控制连接和数据连接,确保数据传输的安全性。
  2. SFTP(SSH File Transfer Protocol)

    • 使用SSH协议进行文件传输,提供更强的安全性和认证机制。

示例流程

以下是一个典型的FTP会话流程:

  1. 建立控制连接

    • 客户端连接到服务器的控制端口21。
    • 客户端发送 USERPASS 命令进行身份验证。
  2. 选择工作模式

    • 客户端发送 PASV 命令进入被动模式,或服务器主动发起数据连接。
  3. 发送命令

    • 客户端发送 CWD 命令更改目录。
    • 客户端发送 LIST 命令列出目录内容。
  4. 数据传输

    • 客户端发送 RETR 命令下载文件。
    • 客户端发送 STOR 命令上传文件。
  5. 断开连接

    • 客户端发送 QUIT 命令断开连接。

注意:本代码只实现了连接和显示当前所在目录的功能,下载和上传文件的功能存在bug
12.6 已更新,上传下载未测试,数据传输链接可以建立

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# FTP客户端多进程编程(旧)
import socket
import os
import subprocess
from multiprocessing import Process, Pipe

# FTP服务器地址和端口
FTP_SERVER = 'ftp.example.com'
CONTROL_PORT = 21

# 用户名和密码
USERNAME = 'your_username'
PASSWORD = 'your_password'

# 传输方式:'active' 或 'passive'
TRANSFER_MODE = 'passive'

# 控制连接类
class FTPControlConnection:
def __init__(self, server, port):
self.server = server
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((server, port))
self.recv_response() # 接收初始连接响应

def send_command(self, command):
# 发送命令到服务器
self.sock.sendall(command.encode('utf-8') + b'\r\n')
return self.recv_response()

def recv_response(self):
# 接收服务器响应
response = self.sock.recv(1024).decode('utf-8').strip()
print(f"服务器响应: {response}")
return response

def close(self):
# 关闭控制连接
self.sock.close()

# 数据连接类
class FTPDataConnection:
def __init__(self, server, port):
self.server = server
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((server, port))

def receive_data(self, file_path):
# 接收数据并保存到文件
with open(file_path, 'wb') as f:
while True:
data = self.sock.recv(1024)
if not data:
break
f.write(data)
self.sock.close()
print(f"文件已保存到: {file_path}")

def send_data(self, file_path):
# 发送文件数据
with open(file_path, 'rb') as f:
while True:
data = f.read(1024)
if not data:
break
self.sock.sendall(data)
self.sock.close()
print(f"文件已发送: {file_path}")

# 多进程处理数据连接
def handle_data_connection(control_conn, command, file_path, mode='receive'):
# 发送PASV或PORT命令,获取数据连接的IP和端口
if TRANSFER_MODE == 'passive':
response = control_conn.send_command('PASV')
if 'Entering Passive Mode' in response:
ip, port = parse_pasv_response(response)
else:
print("未能进入被动模式。")
return
elif TRANSFER_MODE == 'active':
local_port = 12345 # 本地端口,可以根据需要调整
control_conn.send_command(f'PORT 127,0,0,1,{local_port >> 8},{local_port & 0xFF}')
response = control_conn.recv_response()
if '200' in response:
ip, port = FTP_SERVER, local_port
else:
print("未能进入主动模式。")
return
else:
print("无效的传输模式。")
return

if mode == 'receive':
# 接收数据
data_conn = FTPDataConnection(ip, port)
data_conn.receive_data(file_path)
elif mode == 'send':
# 发送数据
data_conn = FTPDataConnection(ip, port)
data_conn.send_data(file_path)

# 解析PASV响应
def parse_pasv_response(response):
# 解析PASV响应,提取IP地址和端口
parts = response.split('(')[1].split(')')[0].split(',')
ip = '.'.join(parts[:4])
port = int(parts[4]) * 256 + int(parts[5])
return ip, port

# 主函数
def main():
# 初始化控制连接
control_conn = FTPControlConnection(FTP_SERVER, CONTROL_PORT)

# 登录
control_conn.send_command(f'USER {USERNAME}')
control_conn.send_command(f'PASS {PASSWORD}')

# 列出当前目录
control_conn.send_command('PWD')
control_conn.send_command('LIST')

# 下载文件
file_path = 'downloaded_file.txt'
command = 'RETR downloaded_file.txt'
parent_conn, child_conn = Pipe()
print(f"开始下载文件到: {file_path}")
p = Process(target=handle_data_connection, args=(control_conn, command, file_path, 'receive'))
p.start()
p.join()

# 上传文件
upload_file_path = 'upload_file.txt'
command = 'STOR upload_file.txt'
print(f"开始上传文件: {upload_file_path}")
p = Process(target=handle_data_connection, args=(control_conn, command, upload_file_path, 'send'))
p.start()
p.join()

# 关闭控制连接
control_conn.send_command('QUIT')
control_conn.close()
print("FTP客户端已关闭。")

if __name__ == "__main__":
main()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# 新
import socket
import os
from multiprocessing import Process, Pipe
import re


# FTP服务器地址和端口
FTP_SERVER = '10.60.128.222'
CONTROL_PORT = 21

# 用户名和密码
USERNAME = 'test'
PASSWORD = '123456'

# 传输方式:'active' 或 'passive'
TRANSFER_MODE = 'passive' # 修改为 'active' 以测试主动模式


# 获取本地IP地址
def get_local_ip():
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(('8.8.8.8', 80))
local_ip = sock.getsockname()[0]
sock.close()
print(f"本地IP地址: {local_ip}")
return local_ip
except Exception as e:
print(f"获取本地IP地址失败: {e}")
return '127.0.0.1'


# 控制连接类
class FTPControlConnection:
def __init__(self, server, port):
self.server = server
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((server, port))
self.recv_response() # 接收初始连接响应

def send_command(self, command):
self.sock.sendall(command.encode('utf-8') + b'\r\n')
return self.recv_response()

def recv_response(self):
response = ''
while True:
data = self.sock.recv(1024).decode('utf-8')
response += data
if '\r\n' in data and data.strip().endswith('.'):
break
if len(data) < 1024:
break
print(f"服务器响应: {response.strip()}")
return response.strip()

def close(self):
self.sock.close()


# 数据连接类
class FTPDataConnection:
def __init__(self, server, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((server, port))

def receive_data(self, file_path=None):
if file_path:
with open(file_path, 'wb') as f:
while True:
data = self.sock.recv(1024)
if not data:
break
f.write(data)
print(f"文件已保存到: {file_path}")
else:
data = []
while True:
chunk = self.sock.recv(1024)
if not chunk:
break
data.append(chunk)
print("目录列表:")
print(b''.join(data).decode('utf-8'))
self.sock.close()

def send_data(self, file_path):
with open(file_path, 'rb') as f:
while True:
data = f.read(1024)
if not data:
break
self.sock.sendall(data)
self.sock.close()
print(f"文件已发送: {file_path}")

''''
# 解析PASV响应
def parse_pasv_response(response):
match = re.search(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)', response)
if match:
numbers = match.groups()
ip = '.'.join(numbers[:4])
port = int(numbers[4]) * 256 + int(numbers[5])
return ip, port
raise ValueError("未能解析PASV响应")


# 建立数据连接
def establish_data_connection(control_conn):
if TRANSFER_MODE == 'passive':
response = control_conn.send_command('PASV')
if 'Entering Passive Mode' in response:
ip, port = parse_pasv_response(response)
else:
raise RuntimeError("未能进入被动模式")
elif TRANSFER_MODE == 'active':
local_ip = get_local_ip()
local_port = 12345
control_conn.send_command(f'PORT {local_ip.replace(".", ",")},{local_port >> 8},{local_port & 0xFF}')
response = control_conn.recv_response()
if '200' not in response:
raise RuntimeError("未能进入主动模式")
ip, port = FTP_SERVER, local_port
else:
raise ValueError("无效的传输模式")

return FTPDataConnection(ip, port)
'''
# 建立数据连接
def establish_data_connection(control_conn):
if TRANSFER_MODE == 'passive':
response = control_conn.send_command('PASV')
if 'Entering Passive Mode' in response:
_, port = parse_pasv_response(response)
ip = FTP_SERVER # 使用配置文件中定义的服务器IP地址
else:
raise RuntimeError("未能进入被动模式")
elif TRANSFER_MODE == 'active':
local_ip = get_local_ip()
local_port = 12345
control_conn.send_command(f'PORT {local_ip.replace(".", ",")},{local_port >> 8},{local_port & 0xFF}')
response = control_conn.recv_response()
if '200' not in response:
raise RuntimeError("未能进入主动模式")
ip, port = FTP_SERVER, local_port
else:
raise ValueError("无效的传输模式")

return FTPDataConnection(ip, port)


# 解析PASV响应
def parse_pasv_response(response):
match = re.search(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)', response)
if match:
numbers = match.groups()
port = int(numbers[4]) * 256 + int(numbers[5])
return None, port # 只返回端口号,IP地址将由外部提供
raise ValueError("未能解析PASV响应")

# 处理LIST命令的数据连接
def handle_list_command(control_conn):
data_conn = establish_data_connection(control_conn)
control_conn.send_command('LIST')
data_conn.receive_data()


# 下载文件
def download_file(control_conn, remote_path, local_path):
data_conn = establish_data_connection(control_conn)
control_conn.send_command(f'RETR {remote_path}')
data_conn.receive_data(local_path)


# 上传文件
def upload_file(control_conn, local_path, remote_path):
data_conn = establish_data_connection(control_conn)
control_conn.send_command(f'STOR {remote_path}')
data_conn.send_data(local_path)


# 主函数
def main():
control_conn = FTPControlConnection(FTP_SERVER, CONTROL_PORT)

# 登录
print(control_conn.send_command(f'USER {USERNAME}'))
print(control_conn.send_command(f'PASS {PASSWORD}'))

# 列出当前目录
handle_list_command(control_conn)

# 下载文件(可选)
download = input("是否下载文件?(y/n): ").strip().lower()
if download == 'y':
remote_path = input("请输入要下载的远程文件路径: ")
local_path = input("请输入保存到本地的文件路径: ")
download_file(control_conn, remote_path, local_path)

# 上传文件(可选)
upload = input("是否上传文件?(y/n): ").strip().lower()
if upload == 'y':
local_path = input("请输入要上传的本地文件路径: ")
remote_path = input("请输入保存到远程的文件路径: ")
upload_file(control_conn, local_path, remote_path)

# 关闭控制连接
control_conn.send_command('QUIT')
control_conn.close()
print("FTP客户端已关闭。")


if __name__ == "__main__":
main()

ftp部分讲解

使用 FileZilla Client FTP客户端软件来进行抓包分析。

注意:连接本地FTP服务器时,wireshark抓包需选择127.0.0.1的本地回环地址适配器。如图

My test image

使用明文FTP协议(不安全)(之后就懂了),
传输设置设为被动模式

My test image

使用wireshark抓包,筛选FTP协议ftp

My test image

参考ftp连接流程,发现少了客户端到服务端的第一步连接请求.
改变过滤表达式

1
(ip.src == 192.168.2.2 && ip.dst == 10.60.128.222) || (ip.src == 10.60.128.222 && ip.dst ==192.168.2.2)

可以发现,FTP建立连接的第一步还是涉及到了TCP的三次握手。通信过程中也一样,都有ACK确认回复,可以看出整个过程还是建立在基于TCP的可靠连接上的。

My test image

另外,我们也注意到,传统的最基础的FTP是明文传输,安全性低。账号和密码直接看到了:

My test image

接下来,参考前文的命令,就可以开始FTP客户端的自行设置了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- `USER username`:发送用户名。
- `PASS password`:发送密码。
- `PWD`:获取当前工作目录。
- `CWD directory`:更改当前工作目录。
- `CDUP`:更改到父目录。
- `MKD directory`:创建目录。
- `RMD directory`:删除目录。
- `DELE filename`:删除文件。
- `LIST`:列出目录内容。
- `NLST`:列出目录中的文件名。
- `RETR filename`:从服务器下载文件。
- `STOR filename`:上传文件到服务器。
- `APPE filename`:追加文件到服务器上的现有文件。
- `QUIT`:断开连接。

没有FTP服务器?看这里

1
Windows 10开启FTP或者HTTP服务进行文件分享
2
FileZilla服务端下载
3
手机开启FTP文件传输

1
2
3
我用夸克网盘分享了「应用程序」,点击链接即可保存。打开「夸克APP」,无需下载在线播放视频,畅享原画5倍速,支持电视投屏。
链接:https://pan.quark.cn/s/fac141a58378
提取码:LMdH
1
2
通过网盘分享的文件:应用程序
链接: https://pan.baidu.com/s/1SSw8AwYwtTaAvvVqW78UeA?pwd=sxns 提取码: sxns

自行搜索下载 CX文件管理器(打开从网络访问) 或 MT管理器(侧边栏远程管理)

除此之外,校内网络可以尝试前文中我友情提供的FTP服务器。(可能由于防火墙连不上)
ftp://test:123456@10.60.128.222:21
账户、密码、主机、端口

Comments