以太网协议

数据链路层 一节,我们虚构了一个网络协议,用于解决多服务器通讯的主要问题。接下来,介绍一个真实的数据链路层协议—— 以太网协议

使用以太网协议进行通信的实体间,必须通过某种介质直接相连。通信介质可以是真实的物理设备,如网线、 网卡 等;也可以是通过虚拟化实现的虚拟设备。

以太网协议在这类通信中,主要解决 寻址 以及 复用/分用 两大问题。

以太网帧

以太网协议数据链路层 中虚构出来的协议几乎一摸一样。

在以太网中,数据通信的基本单位是 以太网帧 ( Frame )。协议规定以太网帧的数据格式如下(注意这里的单位为字节而不是比特了):

../_images/97c13f044de260baf0ed8051091dd251.png以太网帧:目的地址、源地址、类型、数据、校验和

是不是有种似曾相识的感觉!

以太网帧,分为 头部 ( Header )、 数据 ( Data )以及 校验和 ( Checksum )总共 3 大部分。

其中,头部依次是一个 6 字节长的 目的地址 ,用于标记数据由哪台机器接收;一个 6 字节长的 源地址 ,用于标记数据由哪台机器发送;一个 2 字节长的 类型 ,用于标记数据包该如何处理, 0x0800 表示承载的是一个 IP 包(后续介绍)。

除了长度有所拓展之外,跟我们虚构出来的协议并无二致。对了,有一点差异——真实的协议中, 目的地址 放在最前面。这其中有什么特殊考虑吗?

确实是有的。 接收方收到数据最先处理 目的地址 ,如果发现数据不是自己的,后面的字段以及数据就无需处理了。基础网络协议影响方方面面,设计时处理效率也是一个非常重要的考量。

数据 可以是任何需要发送的信息,长度可变, 461500 个字节。数据还有另一个更形象的称谓, 负荷 ( Payload )。自己脑补数据 搭载 交通工具旅行的画面。

哈哈,跟我们假设的套路一模一样!

网卡

现实世界中,计算机通过 网线 连接到一起:

../_images/3026a7ee9579986199ce1c6971fb02ae.png以太网网络

每台通过网线通讯的计算机都需要安装一个硬件设备—— 网卡 ( NIC ), NICNetwork Interface Controller 的缩写。

从物理的层面看,网卡负责将比特流转换成电信号发送出去;反过来,也负责将检测到的电信号转换成比特流并接收。

从软件的层面看,发送数据时,内核协议栈负责封装以太网帧(填充 目的地址源地址类型数据 并计算 校验和),并调用网卡驱动发送;接收数据时,负责验证 目的地址校验和 并取出数据部分,交由上层协议栈处理。

../_images/825d32a1e84c6f1ba1c6970fd677e56a.jpg网卡

每块网卡出厂时,预先分配了一个全球唯一的 MAC地址 ,并烧进硬件。不管后来网卡身处何处,接入哪个网络, MAC 地址均不变。当然,某些操作系统也允许修改网卡的 MAC 地址。

混杂模式

正常情况下,网卡忽略目的地址与自己不符的数据帧。如果想要网卡接收所有数据帧,可以开启 混杂模式

  1. fasion@ubuntu:~$ sudo ip link set enp0s8 promisc on
  2. fasion@ubuntu:~$ ip link show enp0s8
  3. 3: enp0s8: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
  4. link/ether 08:00:27:0e:18:e5 brd ff:ff:ff:ff:ff:ff

设置完成之后,网卡 enp0s8 带上 PROMISC 标志,代表混杂模式已经开启。

混杂模式开启后,网卡接收到的数据,不管目的地址是否是自己,都会提交驱动程序处理。这在使用 tcpdump 等工具抓包分析网络数据时特别有用。

MAC地址

MAC 地址由 6 个字节组成( 48 位),可以支持 248 次方,也就是 281474976710656 个网络设备(比如网卡)。

全球唯一

../_images/aedd5053f3b00748b3fae089741d3b5d.jpgMAC地址

如图, MAC 地址 6 个字节可以划分成两半部分:

  • 3 字节长的 厂商代码 ( OUI ),由国际组织分配给不同的网络设备商;
  • 3 字节长的 序列号 ( SN ),由厂商分配给其生产的网络设备;

以此保证全球唯一性。

冒分十六进制

MAC 地址 6 个字节如何展示呢?能不能当做 ASCII 来解读并显示?

恐怕不能。一个字节总共有 8 个位,而 ASCII 只定义了其中的 7 位。而且, ASCII 也定义了很多控制字符,能显示的也只有字母、数字以及一些常用符号。以上述地址为例,只有 0x5B 这个字节是可以显示的,对应着 [

好在,我们可以用多个可读字符表示一个原始字节。我们将一个字节分成两部分,高 4 位以及低 4 位,每部分可以用一个十六进制字符来表示。

0x00 这个字节为例,可以用两个字符 00 表示。这样一来,整个地址可以用一个 12 字节长的字符串表示: 0010A4BA875B 。为了进一步提高可读性,可以在中间插入冒号 :00:10:A4:BA:87:5B

这就是冒分十六进制( Colon Hexadecimal Notation )。

注意到,冒分十六进制总共需要 17 个字节。如果算上结尾处的 \0 ,那么将是 18 个字节,原来的整整 3 倍!

实验

../_images/7315024ce1bc2ba86f9419e24f1fb27b.png实验网络

如图,我们用 Virtualbox 创建 3 台虚拟机,依次是 antbee 以及 cicada3 台虚拟机均安装 Linux 操作系统,这里我们选择 Ubuntu 16.04

../_images/96ea51c90a1bd203d59bc8d247e38204.pngVirtualbox虚拟机

每台虚拟机均配置两块网卡:网卡一用于从宿主 SSH 登录服务器(虚拟机);网卡二则连接到同一个以太网络 insect

../_images/4f8f9e4be6c4202234738affb2b19778.png网卡配置

注意

这里混杂模式选择 全部允许 ,即对应网卡开启 混杂模式

实验环境搭建完毕,接下来登录服务器做实验!登录 ant ,查看网卡信息:

  1. fasion@ant:~$ ip link
  2. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
  3. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  4. 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
  5. link/ether 08:00:27:15:e4:94 brd ff:ff:ff:ff:ff:ff
  6. 3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
  7. link/ether 08:00:27:99:7e:e8 brd ff:ff:ff:ff:ff:ff

总共有 3 个网络设备:lo 是环回设备,从这个设备发送出去的数据帧最终还是送回本机;enp0s3 是第一块网卡,用于与宿主机通讯;enp0s8 是第二块网卡,连接到我们的实验以太网络 insect

其他机器的设备结构也是一样的,下面列一下每台机器 enp0s8 网卡绑定的地址:

表格-1 enp0s8网卡物理地址
机器物理地址
ant08:00:27:99:7e:e8
bee08:00:27:58:d6:61
cicada08:00:27:0e:18:e5

接下来,我们通过以太网数据帧从 antbee 发送一个数据。为了观察数据,我们先在 bee 以及 cicada 上运行 tcpdump 命令,嗅探网络数据包:

  1. fasion@bee:~$ sudo tcpdump -eni enp0s8
  2. [sudo] password for fasion:
  3. tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
  4. listening on enp0s8, link-type EN10MB (Ethernet), capture size 262144 bytes

接着,使用 send_ether 命令发送数据帧:

  1. fasion@ant:~$ sudo send_ether -i enp0s8 -t 08:00:27:58:d6:61 -T 0x1024 -d 'Hello, world!'

send_ether 命令 -i 选项指定发送网卡名;-t 选项是目的 MAC 地址,即 bee 服务器 enp0s8 网卡的地址;-T 选项指定类型,这里我们随便取了一个 0x1024-d 选项指定数据,这里是最经典的 Hello, world!

注解

send_ether 是一个为演示以太网编程而开发的示例程序,提供多种语言版本:

bee 上,我们看到了 ant 发送过来的数据包:

  1. fasion@bee:~$ sudo tcpdump -eni enp0s8
  2. [sudo] password for fasion:
  3. tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
  4. listening on enp0s8, link-type EN10MB (Ethernet), capture size 262144 bytes
  5. 18:26:12.203490 08:00:27:99:7e:e8 > 08:00:27:58:d6:61, ethertype Unknown (0x1024), length 60:
  6. 0x0000: 4865 6c6c 6f2c 2077 6f72 6c64 2100 0000 Hello,.world!...
  7. 0x0010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
  8. 0x0020: 0000 0000 0000 0000 0000 0000 0000 ..............

注意到,由于数据至少是 46 字节,系统自动在 Hello, world! 后面填充 \0 字节。

另外,服务器 cicada 也收到这个数据了:

  1. fasion@cicada:~$ sudo tcpdump -eni enp0s8
  2. [sudo] password for fasion:
  3. tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
  4. listening on enp0s8, link-type EN10MB (Ethernet), capture size 262144 bytes
  5. 18:26:12.498898 08:00:27:99:7e:e8 > 08:00:27:58:d6:61, ethertype Unknown (0x1024), length 60:
  6. 0x0000: 4865 6c6c 6f2c 2077 6f72 6c64 2100 0000 Hello,.world!...
  7. 0x0010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
  8. 0x0020: 0000 0000 0000 0000 0000 0000 0000 ..............

但是, cicada 检查数据帧目的地址是 08:00:27:58:d6:61 ,而自己网卡地址是 08:00:27:0e:18:e5 并不匹配,便知道这个数据不是自己的。

如果关闭 混杂模式cicada 服务器网卡将忽略这个数据。

总结

本节,我们学习了一个真实的数据链路层协议—— 以太网协议 ,以太网协议的通信单位为 以太网帧 ( Frame )。一个 以太网帧头部数据 以及 检验和 3 部分组成,而头部又由 目的地址源地址 以及 类型 3 个字段组成。

计算机通过以太网进行通讯,需要安装 网卡 ,并用网线连接起来。每块网卡出厂前,都预先分配一个 全球唯一MAC地址 并烧进硬件。MAC 地址由 6 个字节组成,在以太网协议中用于 寻址

一般情况下,网卡忽略目的地址与自己不符的数据帧。开启 混杂模式 后,网卡将接收所有数据,不管目的地址是什么。

进度

../_images/fb28fd42d6a7e8d61ebfab093e8e906d.png新技能Get✔️

下一步

如果你对如何编写程序发送以太网数据帧感兴趣,那么可以看看 编程实践 相关章节。根据喜欢的编程语言,按需取用:

订阅更新,获取更多学习资料,请关注我们的 微信公众号

../_images/wechat-mp-qrcode.png小菜学编程

参考文献