QUIC Loss Detection and Congesti

2019-05-28  本文已影响0人  涵仔睡觉

[toc]

1 简介

Quic是一个新的基于UDP的多路复用安全传输协议。

2 定义

3 Quic传输机制设计

Quic中的所有传输都有一个包级头部,该头部说明了加密级别(指示了包号空间)和一个包号。在一个连接的生命周期内,一个包号空间上的包号是单调递增的,不能重复使用,这有利于区分包是否是重传包,减低了丢包探测的复杂度。

一个Quic包中会包含不同类型的帧,这会导致不同的重传和拥塞控制逻辑:

3.1 Quic与TCP的区别

Quic的丢包检测和拥塞控制算法与TCP类似,但协议的不同会导致算法的差异。

3.1.1 独立包号空间

除了0-RTT和所有1-RTT密钥使用相同的包号空间外,QUIC为每个加密级别使用单独的包号空间。独立的包号空间确保不同加密级别的包的确认不会导致错误重传。但对于不同包号空间,拥塞控制和RTT估计是一致的。

3.1.2 单调递增的包号

TCP将传输包中的数据的偏移量和包号合并在一起,这样就导致无法区分第一次发送包和重传包。而Quic将两者区分开,包号用于确定传输顺序,应用数据由STREAM帧中的偏移量字段决定。Quic包号是严格递增的,直接说明传输顺序。当一个包确认丢失后,包中的帧会在一个新的包中用新的包号进行传输,解决了重传二义性的问题。因此,使用包号,RTT估计将更加准确,虚假重传容易探测,快重传能够广泛的部署。

3.1.3 无反悔确认

Quic的ACK类似TCP的SACK,但Quic不允许要求已经确认的包重传。

3.1.4 更多的ACK块

ACK帧
ACK块区

与TCP的SACK只有三个SACK块不的同,Quic的ACK帧包含很多ACK块。在高丢包率的场景,这能加速恢复,减少伪重传,确保数据包转发进度。

3.1.5 延迟ACK的显示纠正

Quic的ACK帧在ACK Delay字段包含了接收到在接收到数据包到响应一个ACK的时间。这允许发送端在评估路径RTT时根据该字段进行调整,特别是ACK延迟计时器。

4 丢包检测

Quic发送端通过ACK信息和超时来检测包丢失。RTT估计是重要的一环。

4.1 计算RTT

RTT是在接收到ACK帧时计算的:

RTT = 接收ACK的时间 - 被确认包最新发送时间 - ACK Delay
if RTT < min_rtt
    RTT = 接收ACK的时间 - 被确认包最新发送时间  // 忽略ACK Delay

min_rtt是在调整ACK时延之前,在连接上进行测量的。在RTT小于min_rtt时忽略ACK Delay能防止低估RTT,进而低估平滑RTT。

4.2 基于ACK的丢包检测

基于ack的丢包检测实现了TCP快重传、早期重传、FACK和SACK丢包恢复的意志。

4.2.1 快重传

一个未被确认的包,在该包之后的达到某个数量阈值(kReorderingThreshold)的包被确认,或者在该包发送之后一段时间未被确认,则该包被认为丢失了。接收到确认说明一个延迟的包接收到了。重排阈值(kReorderingThreshold)提供了网络中重排的包一定的容忍度。kReorderingThreshold的推荐初始值是3.

Quic可以使用基于时间的丢包探测,根据包从发送到现在经过了多少时间来处理重排问题,推荐的时间阈值(kTimeReorderingFraction)为1/8RTT。也可以由重排阈值或其他的方法代替。

4.2.2 早期重传

接近结束的未确认包之后可能没有足够的(kReorderingThreshold)包发送,这样包丢失是不能被快重传检测到的。为了使得基于ack的丢包检测在这种情况下也有效,接收到最后一个未完成的可重传数据包的确认,将触发早期重传。

如果有未确认的in-flight包还未处理,那么将视为丢失。为了补偿重排弹性,发送端应该设置一个计时器(时长不小于1.125*max(SRTT,lastest_RTT)),若在这段时间内未确认的in-flight包仍然未确认,那么必须视为丢失。

1.125*max(SRTT,lastest_RTT)可以应对以下两种情况:

系数1.125增加重排弹性。也可以用其他系数,但是要注意:

4.3 基于时间的丢失探测

基于ACK的丢包检测无法处理的包丢失可由基于时间的丢失探测来恢复,它使用一个计时器,在加密重传计时器、尾部探测计时器和重传超时机制之间切换。

4.3.1 加密超时重传

CRYPTO帧中的数据对于Quic传输和加密协商来说是至关重要的,因此其重传计时器比较aggressive,一般初始化为初始RTT的两倍。初始RTT设置如下:

当加密包发送时,发送端必须为其设置加密重传计时器,如果超时了,发送端必须尽可能重传所有未确认的CRYPTO数据。直到服务器验证了客户端在路径上的地址之前,发送的数据量会受到限制。

因为服务器可能被阻塞,直到收到更多的数据包,所以即使没有未确认的加密数据,客户端也必须启动加密重传计时器。

在连续多次未收到新包的ACK加密重传计时器就过期后,发送端必须使加密重传时间加倍。

如果加密包未解决,那么尾部探测(TLP)计时器和重传超时(RTO)计时器不会启动。

4.3.1.1 Retry包和Version Negotiation包

Retry包或Version Negotiation包导致一个客户端发送另一个Initial包,有效重启一个新的连接进程。两种包都指示Initial被接收但未处理,两种包都不能被当作Initial包的确认,但可以用于改进RTT估计。

4.3.2 尾部丢包探测(TLP)

尾包易受缓慢丢失探测攻击,因为需要其后续包来触发基于ACK的丢失探测。为了改善这情况,发送方在静止传输前的最后一个可重发包时设置一个计时器。在超时后,发送一个TLP包来引发接收端的ACK。这个探测超时(PTO)时间设置为:

PTO = max(1.5 * SRTT + MaxAckDelay, kMinTLPTimeout)
PTO = min(RTO, PTO)

为了减少延迟,建议发送方在设置RTO计时器之前设置并允许TLP计时器触发两次。也就是当TLP计时器第一次过期时,将发送一个TLP包,建议对TLP计时器进行第二次调度。当TLP计时器第二次过期时,将发送第二个TLP包,并应调度一个RTO计时器。

TLP包应该尽可能携带新数据。如果新数据不可用或由于流量控制无法发送新数据,TLP包可能会重新传输未确认的数据,从而减少恢复时间。由于TLP计时器用于在标记任何数据包丢失之前向网络发送探针,所以在TLP计时器过期前,不应该将之前未确认的数据包标记为丢失。

发送方可能不知道正在发送的包是尾包。因此,发送方可能需要在每个发送的可重发包上配置或调整TLP计时器。

4.3.3 重传超时(RTO)

RTO是丢包检测的最后解决方案。当最后的TLP包发送后,为RTO时期设置计时器。当该计时器过期后,发送端发送两个包来引发接收端的ACK,并重置RTO计时器。RTO时期设置:

如果最后一个TLP发送了,那么
RTO = max(SRTT + 4*RTTVAR + MaxAckDelay, kMinRTOTimeout)

如果RTO计时器过期,那么
RTO = 2 * RTO

发送方通常会在RTO计时器过期时进行高延迟惩罚,并且这种惩罚在随后的连续RTO事件中呈指数级增长。因此,在RTO事件上发送单个数据包会使连接对单个数据包丢失非常敏感,发送两个包则可以显著提高对两个方向丢包的恢复能力,从而降低连续RTO事件的概率。

QUIC的RTO算法与TCP的不同之处在于:

当接收到RTO事件上发送的数据包的确认时,任何数据包号低于已确认的数据包的未确认数据包都必须标记为丢失。如果RTO上发送的包的确认与第一个RTO之前发送的包的确认同时收到,则认为RTO是假的,使用标准的丢失检测规则。

当RTO定时器过期时发送的数据包可能携带新的数据(如果可用或未确认的数据),从而减少恢复时间。由于此数据包在建立任何数据包丢失之前作为探针发送到网络中,因此不应该将之前未确认的数据包标记为丢失。

在RTO定时器上发送的数据包不能被发送方的拥塞控制器阻塞。然而,发送方必须将这些字节计入In-flight字节数,因为这个包增加了网络负载,而没有标记为丢失包。

4.4 生成ACK

Quic应该延迟确认接收到的数据包,但延迟时间会有限制,避免导致对端的伪超时。最大的延迟确认时间可以通过传输参数max_ack_delay协商,默认为25ms。

4.4.1 加密握手数据

为了快速完成握手并避免由于加密重传超时而造成的伪重传,加密数据包应该使用非常短的延迟确认时间,比如1ms。当加密栈指示已接收到该加密级别的所有数据时,可以立即发送ACK帧。

4.4.2 ACK块

当发送ACK帧时,包含一个或多个已确认的包范围。包含旧的包可以减少由于丢失先前发送的ACK帧而导致的伪重传,代价是ACK帧的大小。

ACK帧应该总是确认最近接收到的包,包的无序程度越高,快速发送更新的ACK帧就越重要,以防止对端声明包丢失并进行虚重传。

4.4.3 ACK帧接收端跟踪

当包含ACK帧的包被确认时,接收方可以停止确认小于或等于发送的ACK帧中已确认的最大的包。

4.5 伪代码

4.5.1 相关常量

4.5.2 相关变量

4.5.3 初始化

连接建立之初,对丢包检测变量的初始化:

loss_detection_timer.reset()
crypto_count = 0
tlp_count = 0
rto_count = 0

if (kUsingTimeLossDetection):
    reordering_threshold = infinite
    time_reordering_fraction = kTimeReorderingFraction
else:
    reordering_threshold = kReorderingThreshold
    time_reordering_fraction = infinite
    
loss_time = 0
smoothed_rtt = 0
rttvar = 0
min_rtt = infinite
largest_sent_before_rto = 0
time_of_last_sent_retransmittable_packet = 0
time_of_last_sent_crypto_packet = 0
largest_sent_packet = 0

4.5.4 发包

发送任何数据包之后,不管是新的传输还是重新绑定的传输,都会调用下面的OnPacketSent函数。

OnPacketSent(packet_number, ack_only, in_flight, is_crypto_packet, sent_bytes):
    // packet_number:发送的包的包号
    // ack_only:该包是否只包含ACK或PADDING帧
    // in_flight:该包是否计入in-flight字节数
    // is_crypto_packet:该包是否包含完成Quic握手的关键加密握手信息
    // sent_bytes:该包包含的字节数,不包括UDP和IP头部
    
    largest_sent_packet = packet_number
    sent_packets[packet_number].packet_number = packet_number
    sent_packets[packet_number].time = now
    sent_packets[packet_number].ack_only = ack_only
    sent_packets[packet_number].in_flight = in_flight
    
    if !ack_only:
    
        if is_crypto_packet:
            time_of_last_sent_crypto_packet = now
            
        time_of_last_sent_retransmittable_packet = now
        OnPacketSentCC(sent_bytes)  // 见5.8.4小节
        sent_packets[packet_number].bytes = sent_bytes
        SetLossDetectionTimer()  // 见4.5.7小节

4.5.5 接收ACK

OnAckReceived(ack):
    largest_acked_packet = ack.largest_acked
    // If the largest acknowledged is newly acked, update the RTT.
    if (sent_packets[ack.largest_acked]):
        latest_rtt = now - sent_packets[ack.largest_acked].time
        UpdateRtt(latest_rtt, ack.ack_delay)
        
    // Find all newly acked packets in this ACK frame
    newly_acked_packets = DetermineNewlyAckedPackets(ack)
    for acked_packet in newly_acked_packets:
        OnPacketAcked(acked_packet.packet_number)  // 见4.5.6小节
        
    if !newly_acked_packets.empty():
        // Find the smallest newly acknowledged packet
        smallest_newly_acked = FindSmallestNewlyAcked(newly_acked_packets)
        // If any packets sent prior to RTO were acked, then the
        // RTO was spurious. Otherwise, inform congestion control.
        if (rto_count > 0 && smallest_newly_acked > largest_sent_before_rto):
            OnRetransmissionTimeoutVerified(smallest_newly_acked)  // 见5.8.9小节
        crypto_count = 0
        tlp_count = 0
        rto_count = 0
        
    DetectLostPackets(ack.largest_acked_packet)  // 见4.5.7小节  
    SetLossDetectionTimer()  // 见4.5.8小节
    
    // Process ECN information if present.
    if (ACK frame contains ECN information):
        ProcessECN(ack)  // 见5.8.7小节

UpdateRtt(latest_rtt, ack_delay):
    // min_rtt ignores ack delay.
    min_rtt = min(min_rtt, latest_rtt)
    // Adjust for ack delay if it’s plausible.
    if (latest_rtt - min_rtt > ack_delay):
        latest_rtt -= ack_delay
        
    // Based on {{RFC6298}}.
    if (smoothed_rtt == 0):
        smoothed_rtt = latest_rtt
        rttvar = latest_rtt / 2
    else:
        rttvar_sample = abs(smoothed_rtt - latest_rtt)
        rttvar = 3/4 * rttvar + 1/4 * rttvar_sample
        smoothed_rtt = 7/8 * smoothed_rtt + 1/8 * latest_rtt

4.5.6 确认包

一个ACK帧可能确认多个包,需要为每个被确认的包调用一次OnPacketAcked函数。

OnPacketAcked(acked_packet):
    if (!acked_packet.is_ack_only):
        OnPacketAckedCC(acked_packet)  // 见5.8.4小节
        
    sent_packets.remove(acked_packet.packet_number)

4.5.7 设置丢包检测计时器

QUIC丢包检测使用一个单一的定时器进行所有基于定时器的丢包检测。计时器的持续时间基于计时器的模式,该模式在下面的包和计时器事件中设置。下面定义的函数SetLossDetectionTimer显示了如何设置单个计时器。

SetLossDetectionTimer():
    // Don’t arm timer if there are no retransmittable packets
    // in flight.
    if (bytes_in_flight == 0):
        loss_detection_timer.cancel()
        return
        
    if (crypto packets are outstanding):
        // Crypto retransmission timer.
        if (smoothed_rtt == 0):
            timeout = 2 * kInitialRtt
        else:
            timeout = 2 * smoothed_rtt
            
        timeout = max(timeout, kMinTLPTimeout)
        timeout = timeout * (2 ^ crypto_count)
        loss_detection_timer.set(time_of_last_sent_crypto_packet + timeout)
        return
        
    if (loss_time != 0):
        // Early retransmit timer or time loss detection.
        timeout = loss_time - time_of_last_sent_retransmittable_packet
    else:
        // RTO or TLP timer
        // Calculate RTO duration
        timeout = smoothed_rtt + 4 * rttvar + max_ack_delay
        timeout = max(timeout, kMinRTOTimeout)
        timeout = timeout * (2 ^ rto_count)
        if (tlp_count < kMaxTLPs):
            // Tail Loss Probe
            tlp_timeout = max(1.5 * smoothed_rtt + max_ack_delay, kMinTLPTimeout)
            timeout = min(tlp_timeout, timeout)
    loss_detection_timer.set(time_of_last_sent_retransmittable_packet + timeout)

4.5.8 超时

当丢失检测计时器过期时,计时器的模式确定要执行的操作。

OnLossDetectionTimeout():
    if (crypto packets are outstanding):
        // Crypto retransmission timeout.
        RetransmitUnackedCryptoData()
        crypto_count++
    else if (loss_time != 0):
        // Early retransmit or Time Loss Detection
        DetectLostPackets(largest_acked_packet)  // 见4.5.9小节
    else if (tlp_count < kMaxTLPs):
        // Tail Loss Probe.
        SendOnePacket()
        tlp_count++
    else:
        // RTO.
        if (rto_count == 0)
           largest_sent_before_rto = largest_sent_packet
        SendTwoPackets()
        rto_count++
        
    SetLossDetectionTimer()  // 见4.5.7小节

4.5.9 探测丢包

QUIC中,只有当在相同的包号空间中确认比一个包更大的包号时,包才会被认为丢失。
DetectLostPackets在每次收到ack时被调用,并对该包号空间的sent_packet进行操作。
如果丢包检测计时器过期,并且设置了loss_time,则提供之前最大的确认包。

DetectLostPackets(largest_acked):
    loss_time = 0
    lost_packets = {}
    delay_until_lost = infinite
    if (kUsingTimeLossDetection):
        delay_until_lost = (1 + time_reordering_fraction) * max(latest_rtt, smoothed_rtt)
    else if (largest_acked.packet_number == largest_sent_packet):
        // Early retransmit timer.
        delay_until_lost = 9/8 * max(latest_rtt, smoothed_rtt)
        
    foreach (unacked < largest_acked.packet_number):
        time_since_sent = now() - unacked.time_sent
        delta = largest_acked.packet_number - unacked.packet_number
        if (time_since_sent > delay_until_lost || delta > reordering_threshold):
            // 包在被确认或标记为丢失前保存在sent_packets中
            sent_packets.remove(unacked.packet_number)
            if (!unacked.is_ack_only):
                lost_packets.insert(unacked)
        else if (loss_time == 0 && delay_until_lost != infinite):
            // 只有进入早期重传阶段,才会设置loss_time
            loss_time = now() + delay_until_lost - time_since_sent
    
    // Inform the congestion controller of lost packets and
    // lets it decide whether to retransmit immediately.
    if (!lost_packets.empty()):
        OnPacketsLost(lost_packets)  // 见5.8.8小节

4.6 讨论

选择较短的延迟确认时间(25ms),是因为较长的延迟确认会导致延迟丢失恢复,对于少数连接来说,它们发送包的频率大于25ms/包,那么对每个数据包进行确认有利于拥塞控制和丢失恢复。

之所以选择默认的初始RTT(100ms),是因为它略高于公共互联网上观察到的min_rtt中位数和平均值。

5 拥塞控制

Quic的拥塞控制是基于TCP的NewReno算法的,该算法基于拥塞窗口进行控制。Quic的拥塞控制是通过字节数来进行的(TCP是根据包个数),因为Quic有更好的控制和恰当的字节计数。Quic主机不能发送超过拥塞窗口字节数的会增加bytes_in_flight的包,除非是在TLP或RTO计时器过期后发送的探测包。

不同终端可能使用不同的拥塞控制算法。QUIC提供的用于拥塞控制的信号是通用的,支持不同的拥塞控制算法。

5.1 显示拥塞通知(ECN)

如果一个路径被验证了支持ECN, QUIC将IP报头中有CE码点视为拥塞的信号。

5.2 慢开始

NewReno算法

QUIC每个连接都是慢开始的,在包丢失或ECN-CE计数器增加时退出慢开始。
当拥塞窗口小于ssthresh时,QUIC就会重新进入慢开始,这通常只发生在RTO之后。
在慢开始时,QUIC会在处理每个ack时将阻塞窗口的字节数增加。

5.3 拥塞避免

慢开始过程中拥塞窗口增加到一个阈值后,进入拥塞避免。在NewReno中,拥塞避免使用一种加法增加乘减少(AIMD)方法,该方法在每个已确认的拥塞窗口中,将拥塞窗口增加一个最大包大小。当检测到丢失时,NewReno将拥塞窗口减半,并将慢开始阈值设置为新的阻塞窗口。

5.4 恢复阶段

恢复是从检测丢失的包或ECN-CE计数器的增加开始后的一段时间。由于QUIC重新传输流数据和控制帧,而不是数据包,所以将恢复结束定义为在确认恢复开始后发送的数据包。这与TCP的恢复定义稍有不同,TCP的恢复定义在确认启动恢复的丢失包时结束。

恢复期将拥塞窗口的减少限制为每次往返一次。在恢复期间,无论出现新的丢包或ECN-CE计数器增加,拥塞窗口都保持不变。

5.5 尾部丢包探测(TLP)

TLP包不能被发送方的拥塞控制器阻塞。
然而,发送方必须将这些字节计入bytes-in-flight,因为TLP在不确定数据包丢失的情况下增加了网络负载。TLP包的确认或丢失与任何其他包一样。

5.6 重传超时(RTO)

当由于RTO计时器而发送重传时,在下一个确认到达之前不会对拥塞窗口进行更改。

5.7 同步

建议发送方根据拥塞控制器的输入对所有in-flight的数据包进行同步发送。例如,当与基于窗口的控制器一起使用时,pacer可以在SRTT上分配拥塞窗口,pacer可以使用基于速率的控制器的速率估计。

拥塞控制器应该要能够很好地与pacer协同工作。例如,pacer可以包装拥塞控制器并控制拥塞窗口的可用性,或者pacer可以对拥塞控制器传递给它的包进行定步。ACK帧的及时交付对于有效的丢包恢复是非常重要的。因此,只包含ACK帧的包不应该被定步,以避免延迟它们发送给对等方。

5.8 伪代码

5.8.1 相关常量

5.8.2 相关变量

5.8.3 初始化

连接之初,初始化拥塞控制变量:

congestion_window = kInitialWindow
bytes_in_flight = 0
end_of_recovery = 0
ssthresh = infinite
ecn_ce_counter = 0

5.8.4 发包

无论何时发送一个包(它包含非ack帧),该包都会增加bytes_in_flight。

OnPacketSentCC(bytes_sent):
    bytes_in_flight += bytes_sent

5.8.5 确认包

从丢失检测调用OnPacketAcked,并提供了来自sent_packet的acked_packet。

InRecovery(packet_number):
    return packet_number <= end_of_recovery

OnPacketAckedCC(acked_packet):
    // Remove from bytes_in_flight.
    bytes_in_flight -= acked_packet.bytes
    if (InRecovery(acked_packet.packet_number)):
        // Do not increase congestion window in recovery period.
        return
        
    if (congestion_window < ssthresh):
        // Slow start.
        congestion_window += acked_packet.bytes
    else:
        // Congestion avoidance.
        congestion_window += kMaxDatagramSize * acked_packet.bytes / congestion_window

5.8.6 新的拥塞事件

当检测到新的拥塞事件时,从ProcessECN和OnPacketsLost调用。启动一个新的恢复周期并减少阻塞窗口。

CongestionEvent(packet_number):
    // Start a new congestion event if packet_number
    // is larger than the end of the previous recovery epoch.
    if (!InRecovery(packet_number)):  // 见5.8.5小节
        end_of_recovery = largest_sent_packet
        congestion_window *= kLossReductionFactor
        congestion_window = max(congestion_window, kMinimumWindow)
        ssthresh = congestion_window

5.8.7 处理ECN信息

当从对端接收到带有ECN码点的ACK帧时调用。

ProcessECN(ack):
    // If the ECN-CE counter reported by the peer has increased, this could be a new congestion event.
    if (ack.ce_counter > ecn_ce_counter):
        ecn_ce_counter = ack.ce_counter
        // Start a new congestion event if the last acknowledged
        // packet is past the end of the previous recovery epoch.
        CongestionEvent(ack.largest_acked_packet)  // 见5.8.6小节

5.8.8 包丢失

当检测到新包丢失时,由DetectLostPackets调用。

OnPacketsLost(lost_packets):
    // Remove lost packets from bytes_in_flight.
    for (lost_packet : lost_packets):
        bytes_in_flight -= lost_packet.bytes
        
    largest_lost_packet = lost_packets.last()
    // Start a new congestion epoch if the last lost packet
    // is past the end of the previous recovery epoch.
    CongestionEvent(largest_lost_packet.packet_number)  // 见5.8.6小节

5.8.9 重新传输超时证实

一旦证实了重新传输超时(RTO),QUIC将拥塞窗口减少到最小值,并删除在新确认的RTO包之前发送的任何包。

OnRetransmissionTimeoutVerified(packet_number):
    congestion_window = kMinimumWindow
    // Declare all packets prior to packet_number lost.
    for (sent_packet: sent_packets):
        if (sent_packet.packet_number < packet_number):
            bytes_in_flight -= sent_packet.bytes
            sent_packets.remove(sent_packet.packet_number)

6 安全考虑

6.1 拥塞信号

拥塞控制从根本上涉及到从未经身份验证的实体消耗信号(包括丢失和ECN码点)。On-path攻击者可以欺骗或改变这些信号。攻击者可以通过丢弃数据包来降低终端的发送速率,或者通过更改ECN码点来更改发送速率。

6.2 流量分析

ack-only包可以通过观察包大小来直观地识别。确认模式可能暴露关于链接特征或应用程序行为的信息。终端可以使用填充帧或将确认信息与其他帧捆绑在一起,以减少泄漏信息。

6.3 谎报ECN标记

接收方可以谎报ECN标记来更改发送方的拥塞响应。抑制ECN-CE标记的报告可能会导致发送方增加其发送速率,可能导致堵塞和丢包。发送方可能试图通过标记偶尔使用ECN-CE发送的包来检测报告是否被抑制。如果使用ECN-CE标记的包在被确认时没有被报告为已被标记,则发送方应针对该路径禁用ECN。

报告额外的ECN-CE标记将导致发送方降低其发送速率,这与发布减少连接流控制限制的效果类似,因此这样做没有任何好处。

上一篇下一篇

猜你喜欢

热点阅读