• 软件定义网络(SDN)研究进展


    写在前面


    这是我入门SDN以来的第一篇论文,它是一篇中文综述,看起来相对容易。也让我对SDN有了进一步的认识。下面是我的一些心得。

    全文框架


    SDN 将数据平面与控制平面解耦合,简化了网络管理.

    1. SDN诞生背景。
    2. SDN三层结构及关键技术
      • 数据层
      • 控制层
      • 应用层
    3. SDN 在不同应用场景下的最新研究成果。
    4. 未来工作。

    概述


    • 随着网络规模的不断扩大,封闭的网络设备内置了过多的复杂协议,增加了运营商定制优化网络的难度,科研人员无法在真实环境中规模部署新协议
    • 同时,互联网流量的快速增长(预计到2018年,全球流量将达到1.6´1021字节),无法做到真正的负载均衡,网络运营商的变革意愿强烈。
    • SDN起源于Stanford的Clean Slate 研究课题,Mckeown 教授正式提出了SDN 概念。
    • SDN分为三层
      • 控制层:可编程的控制器,掌控全网信息。
      • 数据层:哑的(dumb)交换机,仅仅负责转发功能。
      • 应用层:通过北向接口与控制层交互,实现特定的需求。

    1. SDN的体系结构


    SDN 标准接口机制确保层次之间既保持相对独立,又能正常通信,因此,标准接口设计的好坏是SDN 成功设计的关键.

    1.1 SDN诞生背景

    随着网络的快速发展,传统互联网出现了如传统网络配置复杂度高等诸多问题,这些问题说明网络架构需要革新

    当然最先出现的不是SDN,而是一些其它的可编程网络架构,这些可编程网络的相关研究为SDN的产生提供了可参考的理论依据

    架构名称 特点 缺陷
    主动网络 允许数据包携带用户程序,并能够由网络设备自动执行.用户可以通过编程方式动态地配置网络,达到了方便管理网络的目的。 需求低、协议兼容性差
    4D架构 将可编程的决策平面(即控制层)从数据平面分离,使控制平面逻辑中心化与自动化。

    借鉴计算机系统的抽象结构,未来的网络结构将存在转发抽象、分布状态抽象和配置抽象这3 类虚拟化概念。

    抽象类型 简介
    转发抽象 剥离了传统交换机的控制功能,将控制功能交由控制层来完成,并在数据层和控制层之间提供了标准接口,确保交换机完成识别转发数据的任务.
    分布状态抽象 控制层需要将设备的分布状态抽象成全网视图,以便众多应用能够通过全网信息进行网络的统一配置
    配置抽象 用户仅需通过控制层提供的应用接口对网络进行简单配置,就可自动完成沿路径转发设备的统一部署
    • SDN标准制定组织:ONF(Open Networking Foundation)

    1.2 体系结构概述

    针对不同的需求,许多组织提出了相应的架构。

    SDN架构
    • SDN 架构最先由 ONF 组织提出,并已经成为学术界和产业界普遍认可的架构。

    • 数据平面与控制平面之间利用SDN 控制数据平面接口(control-data-plane interface,简称CDPI)进行通信,CDPI 具有统一的通信标准,目前主要采用OpenFlow 协议
    • 控制平面与应用平面之间由SDN 北向接口(northbound interface,简称NBI)负责通信,NBI 允许用户按实际需求定制开发。用户无需关心底层设备的技术细节,仅通过简单的编程就能实现新应用的快速部署
    • 数据平面由交换机等网络元素组成,各网络元素之间由不同规则形成的SDN 网络数据通路形成连接。
    • 控制平面包含逻辑中心的控制器,负责运行控制逻辑策略,维护着全网视图。通过访问CDPI代理来调用相应的网络数据通路。

    NFV架构
    • 欧洲电信标准化组织(European Telecommunications Standards Institute,简称ETSI)提出的 NFV 架构随之发展起来,该体系结构主要针对运营商网络。
    • 网络运营商的网络由专属设备来部署,这些专属设备功能变得繁杂,而管理这些繁杂的硬件设备造成运营成本及能耗的增加,从而导致运营商网络的发展遇到瓶颈。
    • NFV采用了资源虚拟化的方式,在通用设备上运行虚拟机或者容器来实现网络功能,NFV降低了设备成本,缩短了新网络服务的部署周期,从而适应网络运营商的发展需求。
    • NFV 既可以基于非OpenFlow 协议,例如ForCES等多种传统接口标准化协议,又能与OpenFlow协同工作。

    OpenDaylight架构
    • 各大设备厂商和软件公司共同提出了OpenDaylight,目的是为了具体实现SDN 架构,以便用于实际部署。
    • OpenDaylight 继承了SDN架构形式,同时又结合了NFV 的特点
    • ODL与SDN都具有三层架构,ODL的控制层采用自带的Java虚拟机实现。
    • ODL与SDN架构最大的不同在于:OpenDaylight控制器的南向接口除了支持OpenFlow 协议之外,还支持NETCONF等配置协议和BGP等路由协议,并支持生产厂商的专有协议(如思科的OnePK 协议).
    • 为了能够处理不同的标准协议,OpenDaylight 增加了服务抽象层SAL,它负责将不同的底层协议标准转换成OpenDaylight 控制层所理解的请求服务。

    三种架构对比

    1.3 开放式接口与协议设计

    东西向接口
    • 由于单一控制机制容易造成控制节点失效,严重影响性能,可采用多控制器方式,此时,多控制器之间将采用东西向通信方式。
    南向接口协议
    • 逻辑上,它既要保证数据层与控制层之间的正常通信,又要支持两层独立更新。
    • 物理上,设备生产厂商需要开发支持这种标准接口的设备,因为传统网络设备是不能在SDN 网络之中运行的
    • 然而南向接口协议有很多种。
    Openflow
    • 主流的南向接口是Openflow,是基于流的概念来匹配规则的,因此,交换机需要维护一个流表(flow table)来支持OpenFlow,并按流表进行数据转发,而流表的建立、维护及下发均由控制器来完成
    • Openflow发展史
    版本 简介
    Openflow 1.0.0 规定流表头为12 元组(如源/目的IP 地址、源/目的MAC 地址等.然而,1.0.0 版本还不完善,如支持的规则和动作过少、仅支持单表、无关联动作的组合容易造成组合爆炸等问题。
    Openflow 1.1.0 增加了部分规则,并开始支持多级流表、群组表和动作集等功能
    Openflow 1.2 增加了对IPv6 源/目的地址的支持
    Openflow 1.3 支持流控机制
    Openflow 1.4.0 增加了流表删除和复制机制,并考虑了流表一致性问题.
    • 随着OpenFlow 支持的功能不断增加,流表将容易产生负载过重的问题。如何支持不同粒度、任意组合的功能,是OpenFlow 下一步发展的关键所在。
    • Openflow 并未指定如何配置和管理转发设备环境,因此,ONF 提出了OF-CONFIG 协议。作为配置协议,OF-CONFIG 扩展了NETCONF 标准,采用XML 配置交换机环境,填补了OpenFlow 在配置方面的缺失。

    ForCES协议
    • IETF 提出了ForCES 协议。
    • ForCES 对网络设备内部结构重新定义,将转发元素(forwarding element,简称FE)和控制元素(control element,简称CE)分离,形成两个独立的逻辑实体。
    • 两个逻辑实体之间依靠 ForCES 协议通信.该协议工作在主从模式下,即,CE 通过ForCES协议主动将指令下发给FE,FE 被动接受这些指令,并通过硬件执行每数据包级的分组转发

    OnePK
    • 思科公司提出了OnePK 协议。
    • OnePK 则是思科公司针对 SDN 产品专门开发的接口协议,该协议可以运行在思科所研发的专属平台上,并支持开发者用C 或Java 编写的程序。OnePK 除了支持专有的OnePK 协议之外,还可支持OpenFlow 协议等

    典型的三种南向接口协议对比

    北向接口
    • 北向接口并没有统一标准
    • 北向接口负责控制层与各种业务应用之间的通信,应用层各项业务通过编程方式调用所需网络抽象资源,掌握全网信息,方便用户对网络配置和应用部署等业务的快速推进。
    • 由于应用业务具有多样性,使得北向接口亦呈现多样性,开发难度较大。
    • 起初,SDN允许用户针对不同应用场景定制适合的北向接口标准。统一的北向接口标准将直接影响着各项应用业务的顺利开展。
    • 为了统一北向接口,各组织开始制订北向接口标准,如ONF 的NBI 接口标准OpenDaylight 的REST 接口标准等。然而,这些标准仅对功能作了描述,而未详细说明实现方式。
    • 如何实现统一的北向接口标准,成为业界下一步主要推动的工作。

    2. 数据层关键技术研究


    在 SDN 中,数据层与控制层分离,交换机将繁重的控制策略部分交由控制器来负责,而它仅根据控制器下发的规则对数据包进行快速转发。为了避免交换机与控制器频繁交互,双方约定的规则是基于流的,而并非基于每个数据包的。SDN 数据层的功能相对简单,相关技术研究主要集中在交换机和转发规则方面:首先是交换机设计研究,即设计可扩展的快速转发设备,它既可以灵活匹配规则,又能快速转发数据流;其次是转发规则的相关研究,例如规则失效后的一致性更新问题等。

    2.1 交换机设计问题

    • 交换机有两种转发方式:硬件和软件。
    硬件处理
    • 速度快、成本低和功耗小等优点。
    • 硬件分为三种。
    名称 速度 灵活性
    交换机芯片 比CPU 处理速度快两个数量级 远低于CPU 和NP 等可编程器件
    网络处理器(network processor,简称NP) 比CPU 处理速度快一个数量级 可编程
    CPU - 可编程
    • 做到既保证硬件的转发速率,同时还能确保识别转发规则的灵活性,成为目前研究的热点问题。
    • 为了使硬件能够灵活解决数据层的转发规则匹配严格和动作集元素数量太少等限制性问题,研究人员提出了如下两个硬件处理模型。
    RMT模型
    • 该模型实现了一个可重新配置的匹配表,它允许在流水线(pipeline)阶段支持任意宽度和深度的流表。
      • 允许随意替换或增加域定义。
      • 允许指定流表的数量、拓扑、宽度和深度,仅仅受限于芯片的整体资源 (如芯片内存大小等)。
      • 允许创建新动作。
      • 可以随意将数据包放到不同的队列中,并指定发送端口。

    • RMT 模型是由解析器、多个逻辑匹配部件以及可配置输出队列组成
      • 通过修改解析器来增加域定义(包头域)
      • 修改逻辑匹配部件的匹配表来完成新域的匹配
      • 修改逻辑匹配部件的动作集来实现新的动作
      • 修改队列规则来产生新的队列
    • 所有更新操作是通过解析器来实现的,无需修改硬件,只需在芯片设计时留出可配置的接口即可,实现了硬件对数据的灵活处理。
    FlowAdapter模型
    • FlowAdapter采用交换机分层的方式来实现高效、灵活的多表流水线业务。
    • FlowAdapter 交换机分为3层
      • 最上层是可以通过更新来支持任何新协议的软件数据平面
      • 底层是相对固定但转发效率高的硬件数据平面
      • 中部的FlowAdapter 层负责软件数据平面和硬件数据平面之间的通信

    当控制器下发规则时,软件数据平面将存储这些规则,形成M 个阶段的流表.由于这些规则相对灵活,不能全部由交换机直接转化成相应转发动作,而硬件数据平面可以实现规则的高速匹配转发.因此可利用中间层FlowAdapter将两个数据平面中的规则进行无缝转换,即,将相对灵活的M阶段的流表转换成能够被硬件所识别的N阶段的流表.

    为了达到转换目的,FlowAdapter首先检查软件数据平面的全部规则,然后根据完整的规则将M阶段的流表转换成1阶段流表,最后再将1阶段流表转换成N阶段流表发送给硬件数据平面.通过这种无缝转换,理论上解决了传统交换机硬件与控制器之间多表流水线技术不兼容的问题。

    • 另外,FlowAdapter相对控制器完全透明,对FlowAdapter交换机的更新不会影响控制器的正常运行
    软件处理
    • 软件处理的优点:软件方式可以最大限度地提升规则处理的灵活性,同时又能避免由于硬件自身内存较小、流表大小受限、无法有效处理突发流等问题。
    • 软件处理的缺点:速度低。
    使用CPU处理
    • 利用交换机CPU处理转发规则,可以避免硬件灵活性差的问题。由于CPU处理数据包的能力变得越来越强,商用交换机很自然地也会采用这种更强的CPU。这样,在软件处理转发速度与硬件差别变小的同时,灵活处理转发规则的能力得到提。
    使用NP处理
    • 由于NP 专门用来处理网络的各种任务,如数据包转发、路由查找和协议分析等,因此在网络处理方面,NP比CPU具有更高效的处理能力

    在数据平面中,哪些元素可以交给硬件处理,哪些元素可以交给软件处理,也是值得考虑的问题。例如,原本设计到硬件中的计数器功能并不合理,而应当放置到CPU中处理,这样既可以保证计数器的灵活性,又能节省硬件空间,降低复杂度,同时还能够避免硬件计数的限制。

    2.2 转发规则的相关研究

    与传统网络类似,SDN中也会出现网络节点失效的问题,导致网络中的转发规则被迫改变,严重影响了网络的可靠性.此外,流量负载转移或网络维护等也会带来转发规则的变化

    • 改变规则有以下两种策略:
    较低抽象层次(low-level)更新
    • 也就是管理人员自主更新规则
    • 使用较低抽象层次管理方式来更新规则容易出现失误,导致出现规则更新不一致的现象。
    • 即便没有出现失误,由于存在更新延迟问题,在更新过程中,转发路径上有些交换机已经拥有新规则,而另一些交换机还使用旧规则,仍然会造成规则更新不一致性的问题。
    较高抽象层次(high-level)更新
    • 使用较高抽象层次(high-level)的管理方式统一更新,就可防止低层管理引起的不一致性问题。
    • 采用两阶段提交方式来更新规则:
      • 第一阶段:当某个规则需要更新时,控制器询问每个交换机是否处理完对应旧规则的流,并对处理完毕的所有交换机进行规则更新
      • 第二阶段:当所有交换机都更新完毕时,才完成更新,否则将取消该更新操作。
    • 两阶段提交方式的具体做法:

    在预处理阶段,对数据包打上标签以标示新旧策略的版本号。在转发过程中,交换机将检查标签的版本,并按照对应版本的规则执行相应的转发动作,当数据包从出口交换机转发出去时,则去掉标签。

    • 缺点:这种方式需要等待旧规则的数据包全部处理完毕才能处理新规则的数据包,新规则的数据包需要等待,造成了不必要的开销。
    • 解决方式:增量式一致性更新算法

    将规则更新分成多轮进行,每一轮都采用二阶段提交方式更新一个子集,这样可以节省规则空间,达到更新时间与规则空间的折中。

    • 解决安全性问题:

    McGeer提出了基于OpenFlow的安全更新协议来完善抽象层两阶段提交方式的安全性,该协议将无法识别新旧规则的报文发送至控制器,来保证流转发的正确性。

    • 解决性能问题:

    Ghorbani等人研究了虚拟机场景下规则更新算法,该算法可以确保在更新过程中拥有足够的带宽,同时不会影响到其他流的正常转发

    3. 控制层关键技术研究


    控制器是控制层的核心组件,通过控制器,用户可以逻辑上集中控制交换机,实现数据的快速转发,便捷安全地管理网络,提升网络的整体性能。本节首先详细阐述了以NOX控制器为基础的两种技术改进方法:一种是采用多线程的控制模式,另一种是通过增加分布式控制器数量,实现扁平式和层次式控制模式。然后介绍了主流接口语言的研究发展,实现控制语言抽象。最后,深入分析了控制器的一致性、可用性和容错性等特性。

    3.1 控制器设计问题

    • 问题出现:单一结构集中控制的控制器(如NOX)处理能力受到限制,扩展困难,遇到了性能瓶颈
    • 解决方案:
      • 一种是通过提高自身控制器处理能力的方式。
      • 另一种是采用多控制器的方式来提升整体控制器的处理能力。
    通过提升自身
    NOX-MT
    • 它是具有多线程处理功能的NOX控制器。
    • 并未改变NOX控制器的基本结构,而是利用传统的并行技术提升性能
    • NOX 可以快速更新至NOX-MT,且不会由于控制器平台的更替产生不一致性问题。
    Maestro
    • 它通过良好的并行处理架构,充分发挥了高性能服务器的多核并行处理能力
    • 使其在网络规模较大情况下的性能明显优于NOX。

    多控制器的方式
    集中式控制的缺陷

    • 一个较大规模的网络可分为若干个域
    • 若保持单一控制器集中控制的方式来处理交换机请求,因为距离等原因,该控制器将与其他域的交换机之间存在较大延迟
    • 单一集中控制存在单点失效问题。

    通过扩展控制器的数量可以解决上述问题,也就是将控制器物理分布在整个网络中,仅需保持逻辑中心控制特性即可。这样可使每个交换机都与较近的控制器进行交互,从而提升网络的整体性能。

    扁平式扩展方式

    • 所有控制器被放置在不相交的区域里。各控制器间的地位相等,逻辑上均作为全局控制器,掌握全网状态,并通过东西向接口进行通信。
    • 当网络拓扑变化时,所有控制器将同步进行更新,而交换机仅需调整与控制器的地址映射即可,无需进行复杂的更新操作,因此,扁平分布式扩展对数据层的影响较小
    • 典型案例:Onix。
      • 通过网络信息库(network information base,简称NIB)进行管理。
      • 每个控制器都有对应的NIB,通过保持NIB 的一致性,实现控制器之间的同步更新。
    • 典型案例:HyperFlow。
      • 通过注册和广播机制进行通信。
      • 并在某控制器失效时,通过手动配置的方式将失效控制器管理的交换机重新配置到新控制器上,保证了可用性。
    • 缺陷:
      • 虽然每个控制器掌握全网状态,但只控制局部网络,造成了一定资源的浪费。
      • 增加了网络更新时控制器的整体负载。
      • 在实际部署中,不同的域可能属于拥有不同经济实体的运营商,无法做到控制器在不同域之间的平等通信。

    层次式扩展方式

    • 层次控制方式按照用途将控制器进行了分类。
    • 局部控制器相对靠近交换机,它负责本区域内包含的节点,仅掌握本区域的网络状态。
    • 全局控制器负责全网信息的维护,可以完成需要全网信息的路由等操作。
    • 层次控制器交互存在两种方式:
      • 一种是局部控制器与全局控制器之间的交互。
      • 一种是全局控制器之间的交互。
    • 典型案例:Kandoo。
      • 当交换机转发未知报文时,首先询问较近的局部控制器。
      • 若该报文属于局部信息,局部控制器迅速做出回应。
      • 若局部控制器无法处理该报文,它将询问全局控制器,并将获取的信息下发给交换机。
      • 该方式避免了全局控制器的频繁交互,有效降低了流量负载。
      • 由于这种方式取决于局部控制器所处理信息的命中率,因此在局部应用较多的场景中具有较高的执行效率。
    控制器的开发语言
    • 高级语言相对计算机抽象程度高,对人的抽象程度低。所以开发相对机器语言容易,但是执行效率却不高。
    • 相比之下,Python开发效率较高,执行效率较低;而C++执行效率很高,开发效率却很低。
    • 科研人员致力于开发通用的平台,尽可能地在开发与执行之间达到一个较好的平衡。
    • 典型案例:Beacon。
      • 基于Java的平台。
      • 它向用户提供了一系列相关的库与接口用于开发,并提供运行时模块化的功能。实现了开发与执行两者之间的平衡。
    各种控制器的情况对比

    接口语言

    因为高级语言存在的缺点,研究人员致力于开发一种抽象的、高级配置语言。这些抽象语言能够统一北向接口,改善接口的性能,从而全面降低网络的配置成本。

    Nettle
    • 是一种描述性语言,采用了函数响应式编程(functional reactive programming,简称FRP)方式。
    • FRP 是基于事件响应的编程,符合控制器应对各种应用的实际响应情况。
    • Nettle 的目标是实现网络配置的可编程化。而可编程化的网络配置要求控制器性能足够高

    McNettle
    • 一种多核Nettle 语言。
    • McNettle并未改变描述性与FRP的特性。
    • 利用共享内存多核处理的方式增强了用户开发体验。

    Procera
    • 进一步对语言抽象作了优化,采用高
      级的网络策略来应对各种应用。

    Maple

    • 进一步抽象,允许用户使用自当以抽象策略。
    • 为了高效的将抽象策略下发给交换机,Maple采用了高效的多核调度器和跟踪运行时优化器(tracing runtime optimizer)来优化性能。
      • 该优化器一方面通过记录可重用的策略,将负载尽可能地转移到交换机来处理。
      • 另一方面,通过动态跟踪抽象策略与数据内容及环境的依赖性,使流表始终处于最新状态,从而确保抽象策略转成可用规则的效率。

    多种接口语言对比

    3.3 控制层特性研究

    控制层存在一致性可用性容错性等特性,而所提到的这3种特性的需求无法同时满足,达到三者之间的平衡,是今后的重要工作之一。

    一致性
    • SDN集中控制,获取全网的视图,理论上保证了网络配置的一致性。
    • 分布式的控制器任然具有潜在的不一致问题。
    • 严格保证分布式状态全局统一的控制器,将无法保证网络性能;反之,如果控制器能够快速响应请求,下发策略,则无法保证全局状态一致性。
    • 并发策略也会导致不一致性,新策略的下达是通过前面提到的两阶段提交的方式实现。
    • 我们知道并发下发两种策略可能会造成资源上的冲突,流表下发错误,从而造成不一致性问题的发生。
    • 利用细粒度锁(fine-grained locking)确保组合策略无冲突发生。
    • 典型案例:HFT。
      • 采用了层次策略方案,它将并发策略分解,组织成树的形式。树的每个节点都可独立形成转发规则。
      • HFT首先对每个节点进行自定义冲突处理操作,这样,整个冲突处理过程就转化成利用自定义冲突处理规则逆向搜索树的过程,从而解决了并发策略一致性问题。
    可用性
    • 规则备份可以提升网络的可用性。

    RuleBricks针对规则备份提出一种高可用性方案。在RuleBricks中,不同的规则对应不同颜色的“砖块”,“砖块”的大小由通配符的地址空间大小决定,其中,最上层的“砖块”对应的规则是目前的活跃规则。如果因为网络节点失效导致某种颜色的“砖块”消失,则下面的备份“砖块”会显现出来成为新的活跃规则。

    • 控制器过重的负载将影响SDN的可用性。
    • 分布式控制器会平衡负载,特别是层次式架构。局部控制器承担交换机的多数请求,全局控制器则可以更好地为用户提供服务。
    • 当网络流量不均匀时,分布式架构也会出现有些控制器利用率低的问题,下面是解决方案:

    ElastiCon采用负载窗口的方式来动态调整各控制器间的流量。ElastiCon 周期性地检查负载窗口,当负载窗口的总负荷发生改变时,将动态扩充或压缩控制器池,以适应当前实际需求。如果负载超过控制器池最大值时,则需要另外增加新的控制器,以保证网络的可用性。

    • 减少交换机的请求次数,也可以提高控制层的可用性。下面是案例:

    DIFANE架构旨在解决数据平面转发规则粒度过细和对集中控制依赖的问题。DevoFlow按粒度将数据流分成长短流,将短流处理规则主动下发到数据层面,并在转发器上建立一些特定规则。使数据层能够直接处理短流,因此交换机不再将每流的第1个数据包传到控制器。仅有少量的长流才交由控制层处理。根据Zipf定律,长流数量远少于短流数量。因此,DevoFlow采用的策略可以最大程度地降低控制器负载。

    容错性

    SDN同样面临着网络节点或链路失效的问题。然而,SDN控制器可以通过全网信息快速恢复失效节点,具有较强的容错能力。下面描述一下SDN节点的收敛过程。

    • 某台交换机失效:
      • 当某台交换机失效时,其他交换机察觉出变化。
      • 交换机将变化情况通知控制器。
      • 控制器根据所掌握的信息,计算出需要恢复的规则。
      • 下发这些新规则给交换机,交换机更新流表。

    在SDN 架构中,失效信息一般不是通过洪泛方式通知全网,而是直接发送给控制层,并由控制器来做恢复决策,因而不易出现路由振荡的现象。

    • 交换机和控制器之间的链接失效
      • 可以采用传统网络的IGP(如OSPF 协议)通信,并通过洪泛方式恢复。
      • 也可以采用故障转移(failover)方式,同样能够缓解链路失效收敛时间问题。通过在交换机上安装用于验证拓扑连接性的静态转发规则,可以更好地实现网络故障的快速收敛
  • 相关阅读:
    漫谈架构读书笔记
    软件架构怎样进行架构
    大型网站技术架构阅读笔记6
    beta里程碑总结
    团队总结
    用户体验
    小强大扫荡
    团队测试计划
    团队第二次冲刺第十天
    团队第二次冲刺第九天
  • 原文地址:https://www.cnblogs.com/031602523liu/p/9068414.html
Copyright © 2020-2023  润新知