网络层的设备与协议
网络层的作用可以简要描述为主机级别的数据传输. 相比于端口到端口的传输层协议, 网路层协议更关注于如何将数据分组尽力交付到目标主机上. 在因特网中, 网络层协议不关心数据能否被交付, 能否有序交付, 传输的时延与带宽, 是否安全等, 只会尽力而为.
路由器是网络层中最重要的设备. 由于现实世界中的网络环境一般比较复杂, 数据分组需要经过层层路由才能到达目标主机, 因此如何正确地转发分组以及选择合适的传输路线就成为了网络层需要着重考虑的问题.
- 转发(forwarding): 是指将分组从一个输入链路接口转移到适当的输出链路接口的路由器级别的动作. 转发动作一般由硬件直接控制(烧录好的FPGA), 处理时间为纳秒级别, 归属于路由器处理时延的一部分.
- 路由选择(routing): 是指确定分组从源到目的地所需要经过路径的过程. 传统的路由选择方式是在分组到达某个路由器之后, 才根据当前路由器所记录的转发表(forwarding table)来确定需要转发到哪条链路, 即下一个目标是哪个路由器或主机, 相当于开车时在每个十字路口都问路. 而相对前沿的研究方向是抽取路由器的控制功能, 让控制中台来指示路由器向哪条链路转发数据, 免去了网络层设备因维护路由表而产生的额外开销, 可类比于智能导航APP的路径规划功能. 这种结构被称为软件定义网络(Software-Defined Networking, SDN).
路由器的工作原理
路由选择与路由表的管理完全由软件控制, 因此被称为控制平面. 分组的转发完全由硬件控制, 因此被称为数据平面. 路由器的结构可被抽象为4个部分:
- 输入端口: 负责从链路中读取数据分组
- 输出端口: 负责将数据分组放入链路
- 交换结构: 负责将从输入端口得到的数据分组转发到正确的输出端口. 交换结构的实现包括内存方式, 总线方式, 端口互联方式. 内存方式最简单, 但由于总线的限制, 经过共享内存的总线每次只能执行一个内存读写操作, 因此系统总吞吐量要小于内存带宽的一半. 去掉共享内存就可以变为基于总线的分组交换, 这种方式更适合于小范围的分组交换, 因为没有共享内存作为缓冲会导致系统的吞吐量进一步下降. 而端口互联方式实现起来要更加复杂, 但吞吐量要显著好于上述两种方案, 一般被用于高端路由器中.
- 路由选择处理器: 负责控制平面的业务逻辑, 即维护转发表, 根据转发表计算正确地输出端口. 在SDN中, 路由选择处理器负责与控制中台通信, 接受云端计算好的转发信息.
传统的基于路由表的路由选择方法无法有效利用全局信息, 例如某个路径拥塞时, 路由表无法迅速响应并调整; 又例如某个应用需要低延时, 而某个应用却需要高吞吐量, 那么传统路由表就无法进行数据分组的智能分配. 而SDN却可以根据整个网络的流量分布, 动态调整数据分组的传输路径, 更合理地分配网络资源.
使用路由表的好处在于实现简单且处理时间较短. 路由表的匹配基于最长前缀匹配规则, 即在路由表中寻找最长的匹配项.
无论是输入还是输出端口, 在信道拥塞时都会出现排队现象. 路由器为了防止缓存溢出会丢弃在队尾的元素. 使用恰当的分组调度策略能够降低排队阻塞的风险.
- 先进先出: 最基本的分组调度策略
- 优先权排队: 使用优先级队列实现, 队列中的元素会根据优先级排队. 某些游戏网卡会将游戏数据分组标注为高优先级, 这些分组在支持优先权排队的路由器中能够被优先转发, 显著降低排队延时.
- 循环的加权公平排队: 如果只有一个队列, 应用可以通过给分组标注高优先级来完全抢占路由器资源. 公平起见, 路由器可以提供多个优先级队列以支持被标记为不同类型的数据分组. 循环地取出每个优先级队列中的元素即可实现公平的资源占用.
路由选择算法
最简单的路由选择算法模型就是最短路径算法, 而根据实际传输要求, 可能会添加如: 最少经过节点, 最少丢包率等约束条件. 目前在因特网中常用如下算法:
- 链路状态算法(Link State, LS): 典型的集中式路由选择算法, 需要事先获取从源到目标节点中间所有可能经过的链路的开销.
- 距离向量算法(Distance-Vector, DV): 是分散式路由选择算法的代表, 每个路由节点仅需要记录与其直接相连的链路开销即可.
两种算法各有优劣, 一般会根据网络环境选择合适的算法.
IP:网际协议
IP是无连接协议. 优点是实现简单, 速度快, 但显然传输是不可靠的, 只能做到尽力交付. UDP在IP的基础上只添加了复用分用以及额外的校验和, 因此从应用程序的角度看, UDP与IP或者说DGRAM_SOCKET与RAW_SOCKET的功能几乎相同. 而TCP则为了提供可靠传输实现了流量控制, 拥塞控制, 差错恢复等机制. 但无论是TCP或UDP协议包装的分组, 最终都需要通过IP协议进行主机到主机级别的传输,
目前广泛使用的IPv4协议定义在RFC 791中, 其报文格式如下:
- 版本, 首部长度, 服务类型, 数据报长度
- 16比特标识, 标志, 13比特片偏移(IP分片)
- 寿命, 上层协议, 首部校验和
- 32比特源IP地址
- 32比特目的IP地址
- 选项(不定长)
- 数据
IP报文由于携带了数据导致其大小不确定, 然而由于链路层协议的MTU限制, 数据链路能承载的数据分组有严格的限制, 因此较大的IP报文需要分片传输. 根据报文首部的标志和片偏移字段能够确定当前数据分组是整个IP报文的哪部分, 也能用于检测是否发生了分片丢失. 最后一个片的标志比特被设为0, 而其他片的标志比特都是1.
IPv4的地址长度为32比特, 可容纳约40亿个地址. 日常使用的格式为8比特为一组的点分十进制. 为了更好地利用有限的IP资源, 以及更方便地对相似网络设备进行管理, 需要通过子网划分来隔离多组主机:
- A类地址: 掩码/8, 第一个比特是0, 即
0.0.0.0~127.255.255.255, 占所有地址的1/2 - B类地址: 掩码/16, 前两个比特为10, 即
128.0.0.0~191.255.255.255, 占所有地址的1/4 - C类地址: 掩码/24, 前三个比特为110, 即
192.0.0.0~223.255.255.255, 占所有地址的1/8 - D类地址: 前四个比特为1110, 即
224.0.0.0到239.255.255.255, 用于多播 - E类地址: 前五个比特为11110, 即
240.0.0.0到247.255.255.255, 保留地址 - CIDR与VLSM: 由于B类地址有限, C类地址能够覆盖的主机数量又不够, 能够将多个子网合并成一个超网的CIDR技术被提出; 后来又提出了更灵活的VLSM, 通过可变长的掩码实现对网络的灵活划分, 提高IP地址利用率
广播地址也分为本地广播与直接广播. 本地广播就是子网内的主机向当前子网发送的广播; 直接广播就是主机向其他子网发送的广播. 路由器接收到该广播包之后会判断, 如果是本地广播就丢弃, 如果是直接广播就转发.
主机的IP地址可以手动设置, 但在小型局域网中一般采用动态主机配置协议(Dynamic Host Configuration Protocol, DHCP)实现主机的即插即用. DHCP的协商步骤如下:
- DHCP服务器发现. 新加入的主机需要向DHCP服务器请求自己的IP地址, 但由于新加入的主机不仅不知道自己的IP, 也不知道DHCP服务器的IP, 因此需要构建一个UDP广播报文. 广播报文的目标IP地址需要填写为255.255.255.255, 端口填写为DHCP服务默认的67, 源IP地址填写0.0.0.0. 将该UDP报文递交给网路层并封装为IP报文后就可以顺着链路广播到局域网中的所有主机, 该报文也被称为DHCP发现报文(DHCP discover message).
- DHCP服务器提供. 当DHCP服务器接收到DHCP发现报文时, 会向主机返回DHCP提供报文(DHCP offer message). 由于目前新加入的主机仍未获得IP地址, 服务器在响应DHCP提供报文时仍需要使用广播地址. DHCP提供报文中会标注当前服务器的IP, 能够提供给新主机的IP, 租用时间等信息. 而由于局域网中可能存在多个能够提供DHCP服务的服务器, 新主机可能收到多个DHCP提供报文.
- DHCP请求. 新主机从返回的多个DHCP提供报文中挑选最想要的IP地址, 并给提供该IP的服务器返回DHCP请求报文(DHCP request message).
- DHCP ACK. 服务器确认主机的请求报文, 返回DHCP ACK报文(DHCP ACK message).
DHCP的协商流程可简要概括为: 新主机向局域网发送广播, 多个DHCP服务器返回可用的IP地址与租用时间, 主机挑选一个IP地址并告知提供该IP的服务器, 服务器确认该请求. DHCP协议的缺陷在于, 每当节点连接到一个新子网, 都需要从DHCP服务器获取新的IP地址, 这显著提升了移动终端对网络切换的敏感程度.
尽管网络地址转换(Network Address Translation, NAT)技术的广泛使用让IPv4地址用尽的时限得以推迟, 但对新一代IP协议的研究仍有其必要性. 以ST-2协议为基础的IPv5协议早在20世纪80年代就被提出, 旨在提升IPv4对流式数据的处理效率. 但由于其地址长度仍为32位, 因此没有被正式采用, 部分功能被并入目前使用的IPv4中. 而IPv6则简单粗暴地将地址长度提升到128位, 在可以预见的未来里, 这种数量级的地址完全没有用尽的风险. IPv6的数据报格式如下:
- 版本, 流量类型, 流标签
- 有效载荷长度, 下一个首部, 跳限制
- 源地址
- 目的地址
- 数据
与IPv4显著不同的是, IPv6去掉了首部校验和字段, 将报文的正确性验证交由运输层处理; IPv6还禁止在中间路由器上进行分片与重新组装, 让这种操作只能在源与目的地执行, 一旦中间路由无法处理过大的分组就会给发送方返回ICMP差错报文, 提示分组过大; IPv6通过在下一个首部字段中指明扩展头/TCP/UDP而实现首部定长. 可以看出, IPv6的整体改进思路在于提升处理效率, 在中间路由器分片与组装需要重新计算校验和, 这些都是十分耗时的操作.
某些古老的网络基础设施不支持IPv6, 但假如该节点是局部网络的中心节点, 需要经过该节点的IPv6报文将被丢弃, 因此需要使用IP隧道(IPv6 over IPv4). IP隧道的实现原理就是在IPv6报文头部添加IPv4报文头, 并指出下一个字段是IP首部, 这样该报文就能被IPv4基础设施传输, 并被IPv6基础设施拆解.
ICMP:因特网控制报文协议
差错报告是ICMP的最典型应用场景. 例如: 向目标机器发送HTTP请求时, 网路层的IP报文到达某个路由节点时发现目标链路暂时不可用(服务器宕机), 该路由器就会向发送方回传ICMP报文并指示错误.
尽管ICMP与IP协议同属与网络层, 但ICMP的层次略高于IP, ICMP的报文需要被IP承载. ICMP报文包含一个类型字段与一个编码字段. 常用的ping程序就是通过构建类型为8, 编码为0的ICMP报文来向服务器发送回显请求; 服务器则会返回类型为0, 编码为0的回显回答.
ICMP也可以用于拥塞控制, 通过向源主机发送类型为4, 编码为0的ICMP报文可以限制其发送速率. ICMP很少被用于拥塞控制, 因为运输层的TCP协议已经实现了非常完善的拥塞控制机制.
ICMP可以用于发现与当前节点相连的路由器节点. 主机广播类型为10的路由器请求(Router Solicitation)报文, 路由器回复类型为9的路由器公告消息(Router Advertisement)报文给主机. 但是该方法存在安全漏洞, 任何主机都可以收到路由器请求报文, 并伪装自己是路由器.
与ICMP相似的IGMP用于控制IP的多播, 在ICMPv6中直接整合了IGMP协议的功能. 主机需要向路由器发送MLD类型的IGMP报文来表明自己要接受多播消息, 并且需要给出多播接收地址.
DNS任播的实现基于IP任播. 任播可以理解为对外暴露的微服务接口, 例如拨打119电话, 但最终响应的服务节点取决于报警的位置. 119全国通用, 但你在北京海淀区中关村报警, 响应的单位就是中关村消防站. 但任播的问题在于, 无法保证相同请求被分配到同一个主机处理, 尽管在微服务领域可以通过设置网关规则进行人为控制.
链路层与局域网
在计算机网络中, 主机、路由器、交换机甚至是WiFi接入点都属于节点(node), 而链路(link)则被定义为沿着通信路径连接相邻节点的通信信道. 链路层协议更关心的是如何将数据帧沿着链路从一个节点发送到另一个节点.如果将北京和上海比作节点, 那么北京到上海的航线就是链路, 而飞机就是链路层协议, 飞机上的乘客就是数据帧.
链路层协议一般在网卡及其驱动中实现. 链路层协议可能提供以下服务:
- 成帧: 大部分链路层协议会将网路层传来的IP报文封装为数据帧, 封装的格式完全取决于链路的种类以及所采用的链路层协议.
- 链路接入控制: 媒体访问控制(Medium Access Control, MAC)协议规定了帧在链路上传输的规则. MAC协议用于协调多个节点在共享单个广播链路时的帧传输顺序, 但对于点对点的链路来说, MAC协议的用处不大.
- 可靠交付: 保证数据链路能够无差错地传递网络层的数据报. 与运输层的可靠交付服务类似, 链路层的可靠交付服务也是通过确认和重传来保证的. 大部分链路层协议都不提供可靠交付服务, 但由于无线链路通常会产生高比特差错率的数据帧, 因此WiFi协议提供了可靠交付服务. 而在比特差错率较低的光纤链路中, 提供可靠交付服务就显得无关紧要.
- 差错检测和纠正: 传输过程中的信号衰减或电磁噪声干扰可能导致数据比特出现差错. 通过在帧首部添加校验码可以让节点在接收数据帧时进行差错检测, 如果出现差错就让发送方重传. 此外, 借助特殊的算法也可以直接在接收方实现差错纠正.
差错检测和纠正技术
一般而言, 差错检测和纠正技术越复杂, 导致的开销就越大, 但检测和纠正效果也越好, 在实际使用中需要根据实际网络环境与上层应用的实际需求来挑选差错检测和纠正算法.
- 校验和: 最简单的差错检测方法, 将报文中的数据比特相加, 与校验和对比即可. TCP/UDP通过在报文首部添加校验和字段来判断报文是否出现差错, 因为传输层协议完全是由软件实现的, 需要简单快速.
- 循环冗余检测: 链路层协议的大部分功能可以交由专门的硬件芯片实现, 循环冗余检测(Cyclic Redundancy Check, CRC)编码就是最好的例子. 尽管CRC的效果显著强于校验和, 但在传输层通过软件层面进行CRC编码将耗费大量资源并产生可观的延迟; 而在链路层则可以通过特殊设计的CRC芯片实现超高速CRC编码.
- 奇偶校验: 通过在原始数据后添加奇偶校验位来实现. 奇偶校验位记录的是数据中比特为1的位的数量是否为奇数. 显然, 当多个比特出错时, 奇偶校验的精确度将显著下降, 因此推出了二维奇偶校验方案. 二维奇偶校验不仅可以检测是否出现差错, 通过对出错行和列的定位能够直接找到出错的比特, 从而进行差错纠正. 这种在接收方检测和纠错的方法被称为前向纠错(Forward Error Correction, FEC).
多路访问链路和协议
在共享介质型网络中, 帧碰撞会导致多个数据帧报废, 因此需要对多路访问进行控制.
- 信道划分协议: 时分多址(TDMA), 频分多址(FDMA), 码分多址(CDMA)
- 随机接入协议: 时隙ALOHA, 纯ALOHA, 载波侦听多路访问(Carrier Sense Multiple Access, CSMA), 带有碰撞检测的CSMA(CSMA with Collision Detection. CSMA/CD). 载波侦听就是监听当前信道中是否有其他数据帧在传递, 如果有就等待. 碰撞检测就是当发现再传输过程中出现碰撞时就主动退避.
- 轮流协议: 论询协议(中心化, 中心主机控制其他节点的数据传输), 令牌传递协议(去中心化, 节点轮流持有令牌, 有令牌的节点才可以发送数据)
最简单的方式就是加个交换机. 使用交换机进行分组转发就可以避免帧碰撞, 本质上是去除了共享介质.
地址解析协议
主机名是主机在应用层的地址, IP地址是主机在网络层的地址, 而MAC地址则是主机在链路层的地址. 尽管IP协议被广泛使用, 但在某些特殊的网络中会使用IPX, DECnet等其他网路层协议, 因此路由器接口使用网络层地址+MAC的方式标记目标主机.
MAC地址形如00:11:22:aa:bb:cc, 每个字节通过两个16进制的数表示并以冒号分隔, 共占48位, 其中前24位是机构的唯一标识符. MAC地址可以被随意修改, 但由于以太网使用MAC地址作为主机的唯一标识, 因此在同一子网中的设备绝对不能有相同的MAC地址, 否则会导致寻址出错.
在同一个子网中, 需要使用地址解析协议(Address Resolution Protocol, ARP)将目标主机的IP地址解析为MAC地址, 从而实现链路级别的目标主机定位. 在以太网的主机或路由器中会维护ARP表, 用于记录当前子网中的IP地址与MAC地址的映射关系. 每个表项存在过期时间, 一般为20分钟. ARP协议可以被看作是跨越链路层和网络层的协议.
子网内部通信步骤. 假设主机A与主机B位于同一个子网中:
- 主机A查询本地ARP缓存, 如果能够根据主机B的IP找到对应的MAC地址, 就直接向主机B发送以太网数据帧
- 如果缓存中没有找到主机B的IP与MAC地址映射, 就需要发送ARP广播帧, 内容包括源主机IP, 源主机MAC, 目的主机IP等. 同一子网内的所有主机都会收到该广播帧, 但如果发现目的主机IP与自身IP不匹配则会丢弃该帧. 因此最终只有主机B会处理该请求.
- 主机B收到广播帧后, 首先会将帧中的源主机IP与源主机MAC以映射的形式记录在本地ARP缓存中, 然后将包含自身MAC地址的ARP回复帧发送给主机A,
- 主机A收到ARP回复帧, 更新本地ARP缓存, 之后就可以与主机B正常通信.
跨子网通信步骤, 假设从子网A中的1.1.1.1主机向子网B中的2.2.2.2主机发送数据帧:
- 将数据包发给路由器: 数据帧中需要填写目标主机的IP地址以及路由器的MAC地址. 如果填写目标主机的MAC地址, 则在子网A中由于找不到该MAC地址, 因此链路层会丢弃该分组.
- 路由器将数据包发给目标主机: 分组到达路由器之后, 路由器分析出目标IP位于子网B中, 因此会通过ARP协议在子网B中寻找对应的MAC, 将目标主机的MAC写入数据帧后交由链路层发送.
ARP是整个TCP/IP协议族中最不安全的协议. ARP缓存的初始化依赖广播帧, 且较新的ARP回复帧会更新现有的缓存表, 这就让攻击者能够伪造身份, 从而实现对子网中数据分组的监听、窃取、解析等恶意操作. 尽管ARP规定了主机在接收到ARP广播帧时需要判断目标IP与自身IP是否相同, 然而并不是所有主机都会遵从君子协定来抛弃不属于本机的ARP请求帧. 典型的ARP欺骗攻击流程如下:
- 假设局域网内有若干主机, 其中某台被攻击者控制
- 攻击者获取网关IP, 构建并发送ARP广播帧(源IP: 网关IP, 源MAC: 攻击者的MAC, 目的IP: 根据当前局域网规则循环生成)
- 当前局域网内的主机以为收到的广播帧是网关发出的, 因此更新本地ARP缓存, 并将自己的IP和MAC以ARP回复帧的形式发送给攻击者.
- 攻击者获取局域网内当前在线的所有主机的IP与MAC映射, 可以继续以网关的身份拦截所有访问外网的数据分组, 也可以更换自己的身份, 将自身伪造成某台主机, 进行粒度更细的分组窃取.
局域网内的主机在访问外网时, 都需要将数据分组发送给网关, 但由于本机ARP缓存将攻击者当作网关, 因此会将数据分组错误的发送给攻击者. 攻击者可以丢弃这些报文从而导致局域网无法连接外部网络, 也可以对特定MAC或IP的设备限速, 还可以直接窃取明文通信的数据. 此外, 不正确地使用ARP还容易产生广播风暴.
链路层交换机
集线器工作于物理层, 交换机工作于链路层, 路由器工作于网络层. 每层的设备对其上层都是透明的, 网络层的路由器和主机感受不到交换机的存在. 交换机为每个输出接口设有缓存, 防止数据帧的输出速率超过链路载荷.
交换机能够最大程度地避免以太网中的数据碰撞, 实现链路级别的数据隔离以及监测链路层数据帧的状态. 交换机能够检测输出链路上是否存在正在发送的帧, 如果有就将输出数据暂时写入输出接口的缓存, 等到不会发生冲突时在发送. 尽管这种做法会产生微量的延迟, 但是优势在于不会降低原有链路的传输速率, 也不会因帧冲突而造成带宽浪费. 交换机的总带宽是所有接口速率的和, 而集线器的输出接口会平分输入接口的带宽. 交换机能够隔离数据链路, 接入同一台交换机的链路的速率和媒介都可能是不相同的. 此外, 交换机是非侵入设备, 在原有连接的基础上增加交换机不会产生任何影响. 如果交换机检测到某个主机工作异常, 会在交换机内部主动断开该设备的输入或输出接口, 并反馈状态.
过滤与转发是交换机的核心功能. 转发(forwarding)是决定输入的数据帧应该被传递到哪个输出接口; 过滤(filtering)用于决定数据帧应该被转发到某个接口还是直接丢弃; 这两个功能的实现都基于交换机表(switch table).
交换机表记录的是MAC地址与端口的映射关系, 通过自学习(self-learning)以及给每条记录设置过期时间来实现自动更新. 假设目的MAC地址为$m$的帧从交换机接口$x$到达:
- 如果表中没有地址为$m$的记录. 交换机会向$x$之外的其他接口转发该帧的副本, 即广播该帧.
- 如果表中地址为$m$的记录指向接口$x$. 由于输入和输出接口相同, 交换机会过滤该帧.
- 如果表中地址为$m$的记录指向接口$y$, 且$y \ne x$. 交换机会将该帧转发到接口$y$.
交换机毒化(switch poisoning)是最典型的针对交换机的攻击手段. 攻击者向交换机发送大量具有不同伪造源MAC地址的分组, 填满交换机表, 从而让交换机只能以广播的形式将报文发送到合法主机, 而攻击者就能嗅探到这些广播报文. 但由于伪造源MAC地址的成本过高, 对交换机的攻击性价比远比不上攻击路由器.
交换机与路由器的区别:
- 层次不同: 交换机位于链路层, 路由器位于网路层
- 寻址方式不同: 交换机依据MAC地址寻址, 而路由器依据IP地址寻址
- 部署方式不同: 交换机即插即用, 而路由器需要设置
| 集线器 | 交换机 | 路由器 | |
|---|---|---|---|
| 即插即用 | 有 | 有 | 无 |
| 流量隔离 | 无 | 有 | 有 |
| 优化路由 | 无 | 无 | 有 |
案例1: PPPoE与MTU
PPP协议是链路层的点对点通信协议
运营商一般提供PPPoE协议进行拨号