技术: 网络编程部分汇总

个人是比较喜欢研究网络问题的, 最初可能是为了玩更多高大上配置的服务器, 之后就是为了弄清楚编程中遇到的问题以及其原因.

本文主要记载一些网络基础知识, 但这些知识不是纯碎的理论研究, 而是为了后面传输层/应用层网络编程服务的.

2017年10月更新了本文: nagle算法 + delayed ack算法 + 重新整理排版

该文是我写的网络编程的第一篇文章, 更新成为网络最后一篇, 权当总结.

大致上已经整理了以下内容:(链接都是我站内的链接)

大部分知识来自于书籍(unp 1, unix高级编程, TCP/IP详解 以及 linux系统编程上下册), 腾讯后端的同学的交流, 我的一些个人思考, 最后靠谱网友的博客.

这些整理记录了我的成长(完善知识体系), 也希望为看到的人带来帮助, 至少节约一些时间(于我个人而言, 写作是一种乐趣)

补充上层协议: (2017/9月整理)


post-cover

分层模型

分层: 只暴露应该关注的细节, 方便重用和解耦, 降低软件的复杂度. 例如一个应用层开发人员不需要关心底层也可以做好开发.
这里有两种模型:

  • 学术界的OSI模型
  • 工业&商业界的TCP/IP四层模型

大致对比如下:

协议本身并没有太多分别, 只是大家的认可度不一样罢了,
试想你自己发明一套, 比如IP_MY协议, 大家都不遵守, 这种约定有何意义?
所以即便是都用于互联网, TCP/IP四层模型协议更被开发者所接受, OSI 7层模型只是带过.

OSI模型

OSI 模型中,网络被分为七层,由底层向高层依次是:物理层,数据链路层,网络层,传输层,会话层,表示层和应用层。

物理层 处于 OSI 七层模型的最底端,它的主要任务是将比特流与电子信号进行转换。
数据链路层 处于 OSI 七层模型的第二层,它定义了通过通信介质相互连接的设备之间,数据传输的规范。
网络层 处于OSI参考模型的第三层网络层的主要作用是实现终端节点间的通信。IP协议是网络层的一个重要协议,网络层中还有ARP(获取MAC地址)和ICMP协议(数据发送异常通知)
传输层 处于 OSI 七层模型的第四层, 主要作用是实现应用程序之间的通信。
会话层 处于 OSI 七层模型的第五层, 利用传输层提供的服务,使应用建立和维持会话,并能使会话获得同步。
表示层 处于 OSI 七层模型的第六层, 表示层是处理所有与数据表示及运输有关的问题,包括转换、加密和压缩。(字符集数据格式等处理)
应用层 处于 OSI 七层模型的第七层, 这是最高层, 也是网络应用程序直接使用的协议, 它利用下层协议完成网络通信, 根据用途不同, 种类也不一样.

TCP/IP四层模型

为了实践编码方便, 理所应当采用了简化的四层模型, 即TCP/IP四层模型, 业界俗称为TCP/IP协议族.

下面也以规模性作为重点记录.

各层要点(重要)

TCP/IP协议族按照层次由上到下,层层包装:
最上面的是应用层,这里面有http,ftp, smtp, pop3, telnet等等我们熟悉的协议。
第二层则是传输层,著名的TCP和UDP协议就在这个层次。
第三层是网络层,IP协议就在这里,它负责对数据加上IP地址和其他的数据以确定传输的目标。
第四层是数据链路层,这个层次为待传送的数据加入一个以太网协议头,并进行CRC编码,为最后的数据传输做准备。

TCP/IP协议通信的过程其实就对应着数据入栈与出栈的过程。入栈的过程,数据发送方每层不断地封装首部与尾部,添加一些传输的信息,确保能传输到目的地。
出栈的过程,数据接收方每层不断地拆除首部与尾部,得到最终传输的数据。(以太网帧协议是加装头尾, 注意46-1500字节, 后面还会出现)

完整的以 HTTP为例子:

各层主要涉及的协议:


网络接口层

物理层负责0、1比特流与物理设备电压高低、光的闪灭之间的互换。
数据链路层负责将0、1序列划分为数据帧从一个节点传输到临近的另一个节点,这些节点是通过MAC来唯一标识的(MAC,物理地址,一个主机会有一个MAC地址)。

主要作为围绕 MAC 地址展开:

  • 封装成帧: 把网络层数据报加头和尾,封装成帧,帧头中包括源MAC地址和目的MAC地址。
  • 透明传输: 零比特填充、转义字符。
  • 可靠传输: 在出错率很低的链路上很少用,但是无线链路WLAN会保证可靠传输。
  • 纠错处理: (CRC):接收者检测错误,如果发现差错,丢弃该帧。

个人觉得, 一般谈到 网络接口层, 主要是2个概念再反复说:

  • MAC 地址
    MAC 地址是被烧录到网卡 ROM 中的一串数字,长度为 48 比特,它在世界范围内唯一(不考虑虚拟机自定义 MAC 地址)。由于 MAC 地址的唯一性,它可以被用来区分不同的节点,一旦指定了 MAC 地址,就不可能出现不知道往哪个设备传输数据的情况。
  • 交换机转发原理(转发表)
    交换机是一种在数据链路层工作的网络设备,它有多个端口,可以连接不同的设备。交换机根据每个帧中的目标 MAC 地址决定向哪个端口发送数据,此时它需要参考“转发表”
    转发表并非手动设置,而是交换机自动学习得到的。当某个设备向交换机发送帧时,交换机将帧的源 MAC 地址和接口对应起来,作为一条记录添加到转发表中。

    当然交换机还有其他作用, 就不多说了.

交换机自学过程的原理(端口MAC联系过程):

链路层一般就是用的以太网帧结构, 当前的intel就是基于以太网标准的.
当然以前还有IEEE802标准(简称802.3), 只不过现在很少能抓的到他的包(普通网络, 以及路由器之间的等等, 都是以太网帧).

区别在哪里?
帧的结构不同(源地址后面是类型字段的就是以太网帧结构, 表示帧的类型, 比如0800表示后面是ip(如果是封装IP的帧, 这里链路层的号码就是后面协议类型字段的值), 0806是arp;
如果是长度字段的就是802帧结构, 类型字段在别的地方), 换句话说, 标准不一样. 以太网标准要简单一些.

实际山, 从涉及MTU问题这里, 分组交换就已经涉及到了.
分组交换是指将较大的数据分割为若干个较小的数据,然后依次发送。使用分组交换的原因是不同的数据链路有各自的最大传输单元(MTU: Maximum Transmission Unit)。

ARP及RARP协议 :
(非重点, 我以前电脑中过arp病毒, 所以印象深刻)arp和rarp都是链路层的协议.
ARP (Address Resolution Protocol)是根据IP地址获取 MAC 地址的一种协议。(因为最终确定一台计算机的不是IP, 而是Mac地址, 硬件地址)
如果目标主机处在同一个数据链路上(本地网络),那么可以直接得到目标主机的 MAC 地址,否则会得到下一条路由器的 MAC 地址。

ARP(地址解析)协议是一种解析协议,详细说明即: ARP 请求和 ARP 响应。
首先,源主机会通过广播发送一个 ARP 请求包:“我要与 IP 地址为 xxx 的主机通话,谁知道它的 MAC地址?”。
数据链路上的所有主机都会收到这条消息并检查自己的 IP 地址,如果与 ARP 请求包中的 IP 地址一致,主机就会发送 ARP 响应包:
“我就是 IP 地址为 xxx 的主机,我的 MAC 地址是:xxxx”。

但是反复查询效率很低, 也是不推荐的做法; 通常的做法是把获取到的 MAC 地址缓存一段时间。
(一旦源主机向目标地址发送一个数据包,接下来继续发送多次的概率非常大,因此这种缓存非常容易命中)

当主机要发送一个IP包的时候,会首先查一下自己的ARP高速缓存(就是一个 IP-MAC 地址对应表缓存)。

查看arp缓存(arp映射) : arp -a; 清理: arp -d. (路由器上你要清理的话, 它基本上马上就会去再解析, 除非把接口, 即端重新断开接上)

如果查询的 IP-MAC 值对不存在,那么主机就向网络发送一个ARP协议广播包,这个广播包里面就有待查询的IP地址,而直接收到这份广播的包的所有主机都会查询自己的IP地址.
如果收到广播包的某一个主机发现自己符合条件,那么就准备好一个包含自己的 MAC 地址的ARP包传送给发送ARP广播的主机。(即arp响应)
而广播主机拿到ARP包后会更新自己的ARP缓存(就是存放IP-MAC对应表的地方)。发送广播的主机就会用新的ARP缓存数据准备好数据链路层的的数据包发送工作。

当下一次发送 ARP 请求或超过一定时间后,缓存都会失效,这保证了即使 MAC 地址与 IP 地址的对应关系发生了变化,数据包依然能够被正确的发往目标地址。

总结:
表面上发送的时候, 看逻辑地址, 实际上发送的时候就会去确定该逻辑地址对应的物理地址(如果不是直连网络, 即不是同一数据链路, 会先进行IP的不断查找, 找到下一跳, 直到最终找到相应IP的地址, 再获取到物理地址(技术上是arp全子网广播, 然后相应IP的主机发送arp回应给最开始的发送者)); (arp缓存表是补充手段, 本质还是靠物理地址进行发送的), 确定物理地址之后, 才开始真正发包. 只知道IP并不能发送任何数据(看不到任何IP, TCP流量).

代理arp:
一般路由器是默认就开启了代理arp功能的, 特别是对外进行NAT转换的路由器.
比如说你请求的一个互联网地址, 找到了相应的路由, 如果他没有开启代理arp功能, 那么你的arp广播就会被忽略.
因为只有你请求该路由的IP时才会返回给你mac地址, 而不会对一些对该路由控制的子网主机的arp请求的回应.
其实是对子网的mac查询的支持, 不必具体找到子网内的具体主机再进行arp回应(外部看来, 找到该路由器或者防火墙就找了主机, 他们认为该路由的mac和该子网内的具体主机就是同一个).
(该机制也有弱点, 可以参考《路由交换 卷1》)

自由(free)arp:
开机的时候, 自己给自己发送一个arp请求, 一般情况下不希望有人回应, 那么我就可以用这个ip地址, 但是如果有人回应, 那就坏了, 说明我当前正在使用的ip地址, 有人使用了.
这个时候往往就会报错, duplicate addresss x.x.x.x.
(其他主机收到这个包的时候, 也会尝试去更新本地的mac缓存信息, arp -a可以查看; 小心free arp攻击, 冲掉合法mac地址)

RARP协议的工作与此相反,不做赘述。

只用IP是不行的, 只用Mac是非常低效的.

其中用意个人体会吧, arp和rarp协议有其存在的必要性.

MTU
关于 MTU 争议很多, 具体可以见下面 MTU问题部分.

最后补充, 数据链路层协议工作在物理介质相互连接的端点之间, 范围有限, 所以需要IP, 逻辑地址来夸不同链路.


网络层

数据链路层的作用在于实现同一种数据链路下的包传递,而网络层则可以实现跨越不同数据链路的包传递。(也就是说, 从这一层才涉及跨网络)

这一层内容比较多, 但是主要谈IP协议相关, 其他顺带谈一下.

IP协议是网络层的一个重要协议,网络层中还有ARP(获取MAC地址)和ICMP协议(数据发送异常通知)IP协议是TCP/IP协议的核心,所有的TCP,UDP,ICMP,IGMP的数据都以IP数据格式传输。要注意的是,IP不是可靠的协议,这是说,IP协议没有提供一种数据未传达以后的处理机制,这被认为是上层协议: TCP或UDP要做的事情(ICMP也起到了异常通知的作用)。

IP协议 : (主要说ipv4, 因为ipv6也就我们中国再说, 看看世界上其他国家, 根本不鸟的, 题外话)

  • 地址部分
  • 路由部分
  • 分包重组部分
  • ip协议头(首部)

IP协议的特点:

  • 不可靠: 比如路由器缓存满了, 它会丢掉包, 然后ICMP通知给源, 之后交由TCP保证重传之类的动作.
  • 无连接: 不用建立会话连接才开始发送, 也不按照顺序发, 走的IP路径也不确定. (组装由TCP排序, 顺序不对的包一直被缓存, 它把数据捋顺)

IP 查看就用 ipcofig -all 或者 ifconfig, 或者netstat -ano, 或者你要改ip地址:(linux下)

1
ifconfig eth0 1.2.3.4 netmask 255.255.255.0

寻址部分 :
IP地址是一种在网络层用于识别通信对端信息的地址。它有别于数据链路层中的MAC地址,后者用于标识同一链路下不同的计算机。
IP地址由32位正整数表示,为了直观的表示,我们把它分成4个部分,每个部分由8位整数组成,对应十进制的范围就是0-255。

比如172.20.1.1可以表示为:10101100 00010100 00000001 00000001
转换规则很简单,就是分别把四个部分的十进制(0-255)与8位二进制数字进行转换。

IP地址由两部分组成:网络标识主机标识

  • 网络标识用于区分不同的网段,相同段内的主机必须拥有相同的网络表示,不同段内的主机不能拥有相同的网络标识
  • 主机标识用于区分同一网段下不同的主机,它不能在同一网段内重复出现

32位IP地址被分为两部分,到底前多少位是网络标识呢?
一般有两种方法表示:IP地址分类子网掩码(无类IP)。

IP地址分类 :
IP地址分为四个级别,分别为A类、B类、C类和D类。分类的依据是IP地址的前四位:

  • A类IP地址是第一位为“0”的地址。A类IP地址的前8位是网络标识,用十进制标识的话0.0.0.0-127.0.0.0是A类IP地址的理论范围。另外我们还可以得知,A类IP地址最多只有128个(实际上是126个,下文不赘述),每个网段内主机上限为2的24次方,也就是16,777,214个主机。
  • B类IP地址是前两位为“10“的地址。B类IP地址的前16位是网络标识,用十进制标识的话128.0.0.0-191.255.0.0是B类IP地址的范围。B类IP地址的主机标记长度为16位,因此一个网段内可容纳主机地址上限为65534个。
  • C类IP地址是前三位为“110”的地址。C类IP地址的前24位是网络标识,用十进制标识的话192.0.0.0-223.255.255.0是C类IP地址的范围。C类地址的后8位是主机标识,共容纳254个主机地址。
  • D类IP地址是前四位为“1110”的地址。D类IP地址的网络标识长32位,没有主机标识,因此常用于多播。

简单记忆如下:
A类IP地址: 0.0.0.0~127.0.0.0
B类IP地址: 128.0.0.1~191.255.0.0
C类IP地址: 192.168.0.0~239.255.255.0

这类分类方法往往导致, 小网段不够, 大网段有余. 例如C类网段, 最多254台主机, 如果是255台怎么办? B类的上限是65534个, 明显浪费.
如果控制B类网址的上限数目? 因为B类网址, 说是以10开头, 前16位为网络号, 但是具体是多少真正在用就不一定了.

那么就不对网络号规定位数?, 但是这样就根本没有办法解析IP地址了, 因为不知道前多少位是网络号, 此时就演变出了子网掩码, 用子网掩码来解释子网占了多少位, 而剩余的主机号占了多少位.

子网掩码 :
IP地址分类的本质是区分网络标识和主机标识,另一种更加灵活、细粒度的区分方法是使用子网掩码。
A类, B类型, C类, 这类规定了网络号位数的IP划分方式, 浪费了大量的IP, 现在的无类路由(CIDR), 即mask可以更改网络号的位数的划分方式更有利于充分利用IP地址,
也是对于IPV4的扩充 (单独的mask也可以用于解决子网主机限制的).
它也是32位的, 只不过用其中 1 的数量表示子网, 即网络号的部分, 剩余的表示主机位.

子网掩码的方式, 涵盖并对应了 IP分类的方式, 但是又比规定死位数要灵活.

例如, B类网址, 即以10开头的网络号, 它本来网络号规定是16位, 可以容纳65534台.
现在通过子网掩码, 可以规定它的网络号是26位, 而不是规定死的16位, 可以容纳64台主机(除去主机号全0表示本子网, 全1表示广播外, 实际容纳62台)

但注意一下, 缺省情况下, 子网掩码和IP分类保持一致: 例如A类地址的缺省子网掩码为255.0.0.0,B类为255.255.0.0,C类为255.255.255.0。

全0的IP, 即表示本子网的IP, 只能是源IP, 不可能是目的IP; (DHCP 动态地址获取协议有用到, DHCP其实是封装了UDP包, 再没有获取真实IP地址前, 源IP地址就是0)

关于子网广播的内容部分, IP划分的子网, 比如8位, 24位等只能作为目的地址, 不能作为源地址. (常见的广播地址就是目的地址啦)
但是全1的, 比如255.255.255.255或者..*.255的有些操作系统, 比如window, 你如果Ping的话就不行.
另外一些则可以支持, 比如linux, 它会让所有子网内的主机都返回信息.(你可以认为它是被保留的, 不能作为主机去ping)

微软的很多东西就是不按照标准来, 它自己去实现了一套, 从子网广播这里就能看出来.

其他扩充手段:

  • IPV4, 最多划分42亿个IP.
  • IPV6不仅是长度(128位)的扩充, 而且, 它对于字段的规划&划分&长度要求等更加规范,标准.

回环IP :
loop ip: 127打头的IP地址, 简单理解: 去了还能回来的接口. (IP输入(in 收), IP输出(out 出))

比如广播/多播, 还是ping自己, 都是通过本地回环接口(回环驱动程序), 直接进行IP输出输入的. 如果目标地址不是本地/本机网络机制, 那么才去走arp那条路, 之后返回给IP输入端口.

还有在配置nat的时候, 本机的某个地址如果配置成 IP_A – ANY, 所以所有本地访问出去的都要进行公网转换; 那么本地的 IP_B 也是会进行转换的(本地网络应该直连不用转换).
这个时候, 就可以利用一下回环IP, 配置策略路由, 即把源是 IP_A , 目的是 IP_B 的, 送到环回口.
因为NAT配置要满足3个条件:

  • 内部接口入
  • 外部接口出
  • 满足配置的列表

我们破坏了第二个条件, out条件, 直接输出到IP回环地址上, 再从改地址配置列表到IP_B. 从路由的角度, 它看到是环回口进来的, 就不走NAT了, 而是直接匹配它的目的地址.

路由部分

路由控制(Routing)是指将分组数据发送到目标地址的功能,这个功能一般由路由器完成。
路由器中保存着路由控制表(存储转发表),它在路由控制表中查找目标IP地址对应的下一个路由器地址。

下图描述了这一过程:

由图中也可看出路由器连接着两个子网, 并且在路由表中进行范围或者最佳匹配. 同一个数据链路网络(本地网络)直接传送, 没有太多要说的是.
但是如果经过路由的, 即不同网络: 目的IP地址不变, 但是源mask地址, 即子网地址是在不断的变化的, 其实是路由器的网关地址.

源站路由(严格, 宽松)指定跳的路径(一次性指定多个中间目的地址, 普通的IP包的目的地址是不变的, 但是这样情况IP包地址是可变的).
(防火墙一般会对tcp, ip设置选项部分的, 一概不予通过; 源站路由也是被防火墙封杀的)

路由控制表可以由管理员手动设置,称为静态路由控制,或者路由器可以和其他路由器互换信息比即使自动刷新路由表,
这个信息交换的协议并没有在IP协议中定义,而是由一个叫做“路由协议”的协议管理. netstat -r 查看路由表(网关或者静态路由).

总结:
IP选路和路由表相关, 一般就是udp, tcp包来了, 借用它来查询下一跳(然后往后传递); 或者从网络接口层拿到数据处理IP选项然后上发.
netstat -rn 可以查看路由表(但不能更新路由表, 更新路由表,一般是route命令).

实际传输过程中, 数据包也会在路由器这里拆包并封包再转发, 因为要重新填写下一跳路由地址

路由控制这有会有两个问题:

  • 转发成环问题(不存在相关的IP地址)
    IP首部解决这个问题
  • 分割与重组
    IP受限于链路层的MTU, 过大的数据就要分组, 远端主机再重组(首部中的标志和偏移)

由于分组问题都依赖于IP首部结构, 下面就说说.

IP首部

ip协议头(20字节, 加上选项部分, 最大60字节), 这里有很多设计讲究.

8位的TTL字段(解决环的问题, 不可能让你无限传下去)
这个字段规定该数据包在穿过多少个路由之后才会被抛弃。某个IP数据包每穿过一个路由器,该数据包的TTL数值就会减少1,当该数据包的TTL成为零,它就会被自动抛弃。
这个字段的最大值也就是255,也就是说一个协议包也就在路由器里面穿行255次就会被抛弃了,根据系统的不同,这个数字也不一样,一般是32或者是64。

其他部分介绍:(从上往下一层层看)

  • 4位版本信息(表示该协议的内容解析方式, 也记录了选项部分最多40字节: 15*4-20=40; 注意4bit最大值就是15)
  • TOS 8位: 分别分为3组: 3bit(IP优先级), 4bit(最小延迟, 最大吞吐量, 最高可靠性, 最小费用; 置1表示选择; 全0表示普通包), 1bit(保留).
    一般是给路由用的(给路由的建议, 路由器不理睬就没办法了), 保证其服务质量和服务侧重. (比如FTP就需要最大吞吐量, 它就会把这里面的最大吞吐量位置1)
  • 总长度(Total Length):表示IP首部与数据部分总的字节数,该段长16比特,所以IP包的最大长度为65535字节(2^16)。虽然不同数据链路的MTU不同,但是IP协议屏蔽了这些区别,通过自己实现的数据分片功能,从上层的角度来看,IP协议总是能够以65535为最大包长进行传输。
    (上层看不到下层的MTU限制)
  • 16位标识(ID:Identification):用于分片重组。属于同一个分片的帧的ID相同。但即使ID相同,如果目标地址、源地址、上层协议中有任何一个不同,都被认为不属于同一个分片。
  • 标志(Flags):由于分片重组,由3个比特构成。
    第一个比特未使用,目前必须是0。
    第二个比特表示是否进行分片,0表示可以分片,1表示不能分片。(例如如果你要分片, 我设置了df位, 你就不要分了, 直接丢弃然后icmp返回路由信息即可) (DF)
    第三个比特表示在分片时,是否表示最后一个包。1表示不是最后一个包,0表示分配中最后一个包。(MF)
  • 片偏移(FO: Fragment Offset):由13比特组成,表示被分片的段相对于原始数据的位置。它可以表示8192(2^13)个位置,单位为8字节,所以最大可以表示8 x 8192 = 65536字节的偏移量。
  • 协议: 表示IP首部的下一个首部属于哪个协议。比如ICMP协议号为1. TCP协议的编号为6,UDP编号为17.
  • 首部校验和:用于检查IP首部是否损坏(IP校验和只校验IP首部)
  • 可选项:仅在试验或诊断时用,可以没有。如果有,需要配合填充(Padding)占满32比特。
    比如路径记录(路由的IP地址), 时间戳, 严格选站路径(每跳目的IP地址都变, 一般的情况只是源IP地址会变)

IP数据报最多65535字节, 但是实际上, 还受到应用程序的限制, 即socket api的实现限制; 以及内核实现(源/目的主机的网络实现)也有关系.
内核到用户空间的数据拷贝也和内核实现有关, 应用程序接口socket api有关.

IP分片
卷1里面讲完udp数据报协议之后才讲了IP分片.

术语:

  • IP数据报: 一个完整的IP数据包
  • IP分组: IP层和链路层传输的单元(即不分片的时是数据报, 分片时是一个分片)

在TCP的时候, 由MSS保证了应用数据的分/合(分片), 并且只按照字节计算, 所以TCP的包一般不存在IP分组, 可以理解成TCP帮助处理了流量数据, 所以避免了IP层在动手.
而UDP协议的时候, udp协议不做这个事情(udp不做判断, 也不做处理), 因为整个应用数据是报文(有边界), 直接按照应用数据来封包, 也不进行拆分, 一股脑交给IP, 即:

1
应用数据 --> UDP头部+应用数据 --> IP头部+UDP头部+应用数据

所以这个时候, IP在传输的时候, 可能就要进行分组传输了(由链路层的MTU限制了)

udp 不做判断也有好处, 来一个任务, 就封装一个包, 直接传输走(tcp可能会嫌弃包的字节数太少, 等积攒一定数据才走, 流的重组装), 实时性高; 但是udp不管对方是否收到.
(tcp 保证可靠传输, 但是其建立连接握手, 协商, 重传等都是需要时间的)

IP把数据包和MTU进行比较(送出的时候才查), 然后决定是否分片, 中间跳可能多次再分片, 直到目的方才进行组装.
(但是udp层不知道 ip是否分片, 发送方也不知道中途是否会被分片; tcp自己处理了包的大小, 一般不存在分片)
中途如果丢了一个数据分片, 等于说整个包就失效了(tcp则可能就要重传了, 因为包不完整是不回应ack的, tcp不知道分片的事儿).
因为发起方(IP知道, 但是IP的上层才是发起方, 他们不知道)可能都不知道中途会被分片, 所以重传某一片是不可能的.

分片大小一般整体低于1500, 但是分片不涉及IP头部部分.
udp首部信息只能在第一片中出现(如果端口转发的路由, 后面的分片由于没有头部信息, 就转发不了), 后面的包的大小一般是8的倍数.

(如果最后一个分片不足, 应该填充0; 补齐到46字节) 尽量调整MTU大小, 避免IP分片.

NAT 和 NAPT 技术 :
NAT (Network Address Translator) 是一种用于将局域网中的私有地址转换成全局 IP 地址的技术。

在连接上无线路由器的时候,如果检查一下设备的 IP 地址,也许你会发现是类似于 192.168.1.1 这样的局域网 IP 地址。那不同网段中,IP 地址都是 192.168.1.1 的主机改如何通信呢?

下图描绘了 NAT 的工作原理:

局域网中 IP 地址为 10.0.0.10 的主机向全局 IP 地址 163.221.120.9 发送数据。NAT 路由器将数据包的源地址修改成自己的全局 IP 地址:202.244.174.37。同理,接收数据时,路由器把目标地址 202.244.174.37 翻译成内网地址:10.0.0.10


路由器只有一个对外的全局 IP 地址,如果有多个内网主机都向外部通讯怎么办呢?这时就要使用 NAPT 技术,它和 NAT 从原理上类似,但它可以转换 TCP 和 UDP 端口号。

使用 NAPT 技术时,不同的内网 IP 被转换成同一个公共 IP 地址,也就是路由器对外显示的全局 IP 地址,但是被附加不同的端口号以示区分:


不管是 NAT 还是 NAPT,都需要路由器路由器内部维护一张自动生成的地址转换表。以 TCP 为例,建立 TCP 连接首次握手的 SYN 包发出时会生成这个表,关闭连接时会发出 FIN 包,收到这个包的应答时转换表被删除。

ICMP协议 :
ICMP被认为是IP的一个附属协议(ICMP报文整体还是使用ip首部, 即ICMP报文封装在ip数据报内部), 负责异常通知(反应网络中出现的问题).
实际上不仅仅是不分包, 丢包的时候会发ICMP信息通知源, ping, traceroute等都有用到该协议. (ICMP其实蛮复杂的)

IP协议并不是一个可靠的协议,它不保证数据被送达,那么,自然的,保证数据送达的工作应该由其他的模块来完成。
其中一个重要的模块就是ICMP(网络控制报文)协议。

当传送IP数据包发生错误。比如主机不可达,路由不可达等等,ICMP协议将会把错误信息封包,然后传送回给主机。
给主机一个处理错误的机会,这也就是为什么说建立在 IP 层以上的协议是可能做到安全的原因。

其报文主要分为两种: 查询,差错报文.用类型 + 代码区分: 8位类型, 8位代码.
比如ping request, 类型为8, 代码为0; response, 类型为0, 代码为0. 当然还有其他的代码, 比如不可达时候, 类型为3, 后面具体的代码根据不同的情况而不同(端口不可达是3,3).

也就是说, 可达不仅仅是包括IP层的可达, 即主机是否可达, 也还包含其他不可达, 比如协议不可达, 端口不可达等, 总之就是再反馈信息.
(反馈报文 里面除了包含包ID, 还包含协议端口信息, 即IP分片的第一片(它含有端口号信息))

该协议有一个重要应用, ping 命令, 具体可以参考下面 其他部分.
哦, 还有一个TTL检测工具, traceroute 也需要借助它.

一般的网络问题, 处理起来大致是:

  • 抓包
  • 分析网络, 包, 协议分析

要么就看首部, 报文; 或者就是看ICMP差错报文信息.


传输层

这里已经不仅仅是理论了, 而且有大量的code实践, 可以单独参考我的其他文章:


应用层

主要应用层协议:
FTP, Telnet, HTTP, SMTP/POP3, SSL, SNMP(了解即可), 语音协议(SIP等)

FTP : 数据传输的主流协议(块数据流)

  • 两个端口: 控制信道(tcp 21) + 数据信道(tcp 20); 因为下面封装的是TCP所以都要经历TCP的握手和分手过程
  • 两种模式: 主动模式(客户端PORT+6个数字地址信息) + 被动模式 (都是从服务器端来说的, 主要是第二信道的区别, 被动模式第二信道是服务器连接客户端; 第一信道一般都是由客户端发起)

其中客户端建立控制信道通信时 PORT(建立第二信道), LIST(罗列目录), PASV(询问是否支持下载), RETR(获取文件)等命令比较重要,
但是具体的数据是通过第二信道传输的(比如反馈具体的数据结果).

防火墙一般做控制就是根据这些命令来的, 比如不让外部下载, 那么看到RETR命令直接掐死. (NAT, NPAT或者防火墙都是要做地址转换的)

FTP不安全, 如果使用了账号密码, 域管理员抓包是可以看到具体的信息的(nc + ftp命令模拟一下就知道了);

TELNET : 交互式数据流(你的行为有反馈)
远程登录协议和主要方式(使用23号端口), 但不安全. 一般现在使用ssh协议.(不再是明文, 而是密文; 乱码)
(多个字符可能在多个包中, 收到第一个包才能收第二个; 可以回显出结果)

SMTP/POP3 : 发/收邮件的协议 (25号端口)
这个协议也完全可以用命令行指令(在TCP建立连接之后), 例如RCPT, DATA, QUIT等命令完成收发邮件.

防火墙监控, 也是通过上述协议里面的命令进行控制的. 上面所有这些协议, 都可以通过nc IP 端口然后进行模拟.

DNS : Domain Name System,域名解析系统
因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。
通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。 (也还分A类解析, B类解析等)

以解析域名: www.ietf.org为例:

根服务器其实并不知道 www.ietf.org 的 IP 地址,但是它知道 itef.org 域名服务器的地址,所以它把这条查询请求转发给 itef.org 域名服务器。
DNS请求被逐层下发,直到找到对应的 IP 地址为止。

DNS协议运行在UDP协议之上,使用端口号53; (当然也能运行在TCP之上, 只是同时查询广播, 用UDP更好)

应用层, 我接触的,最主要的还是 HTTP, HTTPS协议, 由于比较重要, 我还是单独成文了.

HTTP :
超文本传输协议(请求应答协议, 80端口)
它其中也有一些命令比如GET, HEAD等8个(OPTIONS, DELETE, POST等服务器考虑到安全性, 一般是不支持的), 但一般由于浏览器已经可以使用HTTP协议, 所以一般看不到.
也还有cookie和session来保存客户端或者服务端的状态信息(因为HTTP是无状态&瞬时协议, 完成任务连接就没有了, 根本不维护数据或者状态或者连接), 让多次HTTP连接可以共用一些信息.
例如我已经登录了微博, 我在新开一个窗口, 发现还是登录的.

多连接问题, 比如远端网页信息比较大, 它会分多次get去拿数据, 即一次通信可能就有几十个连接同时发出.

HTTP非加密传输(中途可能会用到Base64编码方式(基本认证), 相当于明文), 总之是不安全的, 有些抓包软件比如omni peek直接给解码了.

一般要走认证的服务, 可以使用https, 而不是使用http.

详细可以参考: HTTP相关

HTTPS :
详细可以参考: HTTPS相关

其他问题

MTU 相关问题

MTU, 根据协议性质&网络性质&操作系统(内核协议栈)实现等因素的不同而不同, 通常认为这也是IP分片传输或者设置TCP MSS的依据.
其实这这个MTU指的是链路层的数据区, 并不包括链路层的首部和尾部的18个字节.

链路层谈MTU, 即链路层的传输极限. 一般认为MTU如果是以太网就是1500(802.3shi 1492).
并且都是出方向去查MTU, 例如如果IP层的数据大于1500, 那么IP层就会进行分片(fragmentation). 分为若干片, 让每一片都小于MTU. (组装只会在目的地进行组装)
(实际上从卷1的介绍来看, 其实tcp有mss保证, 所以一般不会经由IP再分片; 但是udp本身不处理数据大小, 所以会让IP插手分片事宜)
链路层的MTU(最大传输单元)会和操作系统(Unix, Linux, windows), 和 TCP分节(MSS, max sgement size), 网络类型(以太网, ASDL)会扯上关系:

  • 如果只谈 以太网 最大传输单元, 那么就是 1500 字节
  • 如果谈IP数据报的数据区长度, 最大为1480字节
  • 如果谈TCP packet 最大长度, 那么 window 是, 1500-20-20=1460(减掉ip, tcp头部各20字节), linux/mac/unix则还要减去12字节 tcp time stamp, 即1448字节
    tcp time stamp 是避免序列回绕以及计算RTT的依据; 当然windows没有这么实现, 自然也不需要.
  • 如果谈udp, UDP数据报的首部8字节, 所以UDP数据报的数据区最大长度为1472字节

强调一下, 这里说的是最大, 最小为64字节 (以太网最大数据帧1500+14+4=1518字节, 头14字节, 尾CRC校验位4字节)
大于MTU, 这个时候发送方IP层就需要分片(fragmentation). 把数据报分成若干片,使每一片都小于MTU.而接收方IP层则需要进行数据报的重组.

用于ping时, 一般就是1500-20-8 = 1472, ipv4中icmp协议头为8字节, 但是ipv6中可能是4也可能是8.

MTU 路径发现

但是 MTU 不是一成不变的, 它会根据网络的性质(比如中途有低速链路), 通信状态(网络的传输能力可能会有一些差别)进行一些列的调整.
比如传输过程经过3条(3个链路网络), 分别是: 800, 600, 500; 此时路径MTU就是500, 保证每个包(分组)的数据小于路径MTU, 那么才绝对不会分片(IP分片).
否则, 不能保证一定不被分; 例如数据大小是700, 第一跳(hop)不被分, 但是第二跳就被分了.

并且, 一旦分片, 可能还会被多次分片. 这样带来的丢失重传处理, 很麻烦/浪费时间, 降低传输效率.
但同时也希望在不分片的基础上, 尽可能大的数据包去发送.

MTU路径发现, 就是解决办法:
主机会首先获取整个路径中所有数据链路的最小MTU,保证每次传输都小于该值。
传输过程(通常都只是检查出去方向的, 即由发送方控制)中的任何一个路由器都不用进行(再)分片工作(但路由器有这个能力)。

  • 为了找到路径MTU,主机首先发送整个数据包,并将IP首部的禁止分片标志设为1.
    这样路由器在遇到需要分片才能处理的包时不会分片,而是直接丢弃数据并通过ICMP协议将整个不可达的消息发回给主机
    (如果路由器配合的话, 还会返回下一跳, 即出口MTU的值; [不配合的话, 那么就逐跳减少探测)
  • 主机将ICMP通知中的MTU设置为当前MTU,根据整个MTU对数据进行分片处理。如此反复下去,直到不再收到ICMP通知,此时的MTU就是路径MTU。

一般互联网, 即以太网链路层的网络, 一般都是1500, 而如果是隧道, 比如vpn, 那么可能就需要自己配置一下了(因为还有物理接口, VPN等字段需要配置&传输), 比如降低一下MTU值.

还是以udp为例子:(1500-20-8=1472的理论值)

上图也可以看到ICMP协议也是用的IP首部

由链路层的传输能力, 限制了上层的IP层的分片(分组), tcp的分节(segment)MSS. MTU是对输出链路探测的结果, MSS大小是双方协商的结果.

MSS

TCP中谈论的更多的是, MSS (maximum segment size) 最大分节大小. 一般为了避免分节, 也会进行相关的协商或者探测, 取网络的最小值.
撇开系统因素(比如linux/unix下可能会有tcp time stamp部分), 一般是:

1
2
ipv4 1500-20-20 = 1460 字节
ipv6 1500-20-40 = 1440 字节

MSS属于TCP的内容, 它通常也会和通知窗口, 拥塞窗口搅和在一起, 但是总可以认为, 不管其他因素怎么变, 一般TCP发送的大小最大是MSS.
因为它是可靠传输, 它自己处理好上层应用数据的分节, 这样才不会让IP层再进行分片/分组交付给链路层; 其实也方便它自己处理丢包重传等问题.

详细内容请查看 TCP 的文章: how-about-tcp

大小端问题

编写一个程序, 看一下主机字节序是little-endian还是big-endian.

下面给出一种简单的方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main()
{

int a = 0x1122;
char *b = (char*)&a; //取低8位

if(*b == 0x11){
printf("BE\n");
}else{
printf("LE\n");
}

printf("%d\n", *b==0x22); //1
return 0;
}

当然你用联合union也挺好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

int main()
{
typedef union {
int num;
char num_low8;
} NUM;

NUM number;
number.num = 0x1122;
if(number.num_low8 == 0x11){
printf("BE\n");
}else{
printf("LE\n");
}

printf("%d\n", number.num_low8==0x22); //1

return 0;
}

注意:操作系统读写内存都是从低地址到高地址。

一般本地主机是小端,但是网络字节序就是大端了

这个时候涉及到转换问题,一般是IP地址的转换(端口可能也需要),有相应的函数htons()、ntohs()、htonl()、ntohl() ,
并且像protobuf库也有相关的API, asio库直接就封装好了, 什么都不用管.

Ping原理

ping可以说是ICMP的最著名的应用,是TCP/IP协议的一部分。利用“ping”命令可以检查网络是否连通,可以很好地帮助我们分析和判定网络故障。

例如:当我们某一个网站上不去的时候。通常会ping一下这个网站, 如果丢包研究就是网络问题, 如果其他连接不可达, 就会出现超时或者主机拒绝连接等信息。

它利用ICMP协议包来侦测另一个主机是否可达, 原理如下:

用类型码为0的ICMP发请求, 收到请求的主机则用类型码为8的ICMP回应

ping: 除了type和code注意, 还要注意TCP/IP在内核中就支持了ping服务端.
并且ping报文部分, 即ICMP报文部分就包含了标识符, 即用户进程的id号(linux实现), 但是windows实现, 是全局统一编号, 而不是进程对应;
其他部分还包括收发包的时间(报文体记录或者window上是由系统记录).

ping -R <ip> 路径IP记录, 最多记录9个IP (2条就会用掉6个)
(因为IP首部整个最多60字节, 去掉20字节, 选项部分还剩下40字节(40/4=10); 加上ping自己需要传送的信息, 最多就只能记录9个路径ip地址)

用于ping时, MTU一般是1500-20-8 = 1472, ipv4中icmp协议头为8字节, 但是ipv6中可能是4也可能是8.

Traceroute原理

这个工具比较强大, 给的选项也比较多, 通常是来检查跳(hop)数.
Traceroute是用来侦测主机到目的主机之间所经路由情况的重要工具,也是最便利的工具。

原理: (改变ttl数, 借助icmp反馈)

它收到到目的主机的IP后,首先给目的主机发送一个TTL=1的UDP数据包,而经过的第一个路由器收到这个数据包以后,就自动把TTL减1,而TTL变为0以后,路由器就把这个包给抛弃了,并同时产生 一个主机不可达的ICMP数据报给主机
主机收到这个数据报以后再发一个TTL=2的UDP数据报给目的主机,然后第二个路由器给主机发ICMP数据报
如此往复直到到达目的主机

traceroute 用的udp的包(目的端口号33434), 但ttl=1, 即路由不转发该包, 然后收到icmp反馈(超时差错报文), 知道该跳的相应时间.
之后发送ttl=2(此时目的端口号33435, 之后慢慢增长), 然后再收到反馈, 直到到达目的地(此时端口号肯定大于33434), 然后该服务器反馈端口不可达差错.

这样,traceroute就拿到了所有的路由器IP.


就写这么多吧.

参考:

  1. 《图解TCP/IP》
  2. 《TCP/IP详解》
  3. http://www.cnblogs.com/hjslovewcl/archive/2011/07/18/2314316.html
  4. http://www.choudan.net/2014/09/12/%E7%BD%91%E7%BB%9C%E5%AD%A6%E4%B9%A0%E7%82%B9%E6%BB%B4-%E4%B8%80.html
  5. https://juejin.im/post/598ba1d06fb9a03c4d6464ab
  6. https://www.zhihu.com/question/21064101
文章目录
  1. 1. 分层模型
    1. 1.1. OSI模型
    2. 1.2. TCP/IP四层模型
    3. 1.3. 各层要点(重要)
      1. 1.3.1. 网络接口层
      2. 1.3.2. 网络层
      3. 1.3.3. 传输层
      4. 1.3.4. 应用层
  2. 2. 其他问题
    1. 2.1. MTU 相关问题
      1. 2.1.1. MTU 路径发现
      2. 2.1.2. MSS
    2. 2.2. 大小端问题
    3. 2.3. Ping原理
    4. 2.4. Traceroute原理
  3. 3. 参考:
|