网络实现
计算机网络模型是一个层次结构的模型,它分为:
- 应用层
- 传输层
- 网络层
- 链路层
- 物理层
这里我们更关注在 Linux 内核中实现的链路层、网络层、传输层。其中,又以链路层和网络层为网络的核心实现。
链路层-以太网、局域网
以太网是一种计算机局域网技术,规定了包括物理层的连线、电子信号和介质访问层协议的内容。
以太网往往就代表了物理网络拓扑结构,只有以太网联通,上层的 IP 网络层才能联通。但是在局域网中的设备,即使没有实现 IP 层协议,设备之间依然可以通过以太网协议进行通信。
以太网中的元素,主要由以太网接口、以太网链路、以太网设备组成:
- 以太网接口就是一个以太网线的接口或者插孔
- 一个以太网接口有一个 MAC 地址,用于标识接口的地址
- 一个设备可以有多个以太网接口,意味着它有多个向外界链接的以太网链路
- 这些链路可能位于不同的局域网中,视具体的网络拓扑结构而定
交换机是一个具有多个接口的以太网设备,它可以根据自身维护的 MAC 表和 MAC 地址转发数据包-以太帧,它是二层数据包的交通枢纽或者中转站。交换机可以设置转发规则,决定某个接口进入的流量,是否可以从某个接口流出。在以太网中,设备有两种角色:
- 数据的收发者
- 数据的中转者
两台设备之间进行收发消息,可能需要第三者、第四者......的协助转运,交换机扮演的就是中转者的角色。
MAC 表
Linux 实现
在 Linux 中,通过 net_device
结构体抽象一个网络接口,或者叫网卡。这是一个极具误导性的命名,它不是严格的网络设备,或者说远没有看上去那么简单。该定义是一个大杂烩,它直接包揽了二层和三层的所有内容,从数据结构上不再区分二层和三层,而网络的协议和数据收发是通过协议栈来进行,网络设备不关心数据操作,只呈现属性。
上层的接口采用 socket 接口进行抽象和定义,与下层无关,并且 socket 接口完全可以选择不走下层的协议栈,例如:unix socket 和 virtual socket。
struct net_device {
const struct net_device_ops *netdev_ops;
const struct header_ops *header_ops;
struct netdev_queue *_tx;
netdev_features_t gso_partial_features;
unsigned int real_num_tx_queues;
unsigned int gso_max_size;
unsigned int gso_ipv4_max_size;
u16 gso_max_segs;
s16 num_tc;
// ... more properties
}
总结来说,Linux 的网络架构分成了三层:
- 接口层/设备层
- 协议层/协议栈
- 插口层/socket 层
三层之间是解耦的。
常见的二层的网络接口包括:
- eth:真实物理以太接口
- tap:虚拟以太网接口
- br:网桥,可以看做是一个多通管道
- bond:聚合接口,可以将多个以太接口进行聚合,从而向上层暴露单个性能更强的设备
以太网流量传输规则
以太网中的设备基于 MAC 地址和以太帧进行流量转发,以太帧可以携带额外的数据,这些数据往往是为上层应用提供载货空间。
虚拟设备
由于网络的分层架构,二层的以太网络接口其实和一层的物理链路之间不一定是强耦合的,所以下层的物理链路可以是基于普通的双绞线网线,也可以基于无线信号、光信号。并且,在 Linux 中,由于可以给用户创建一个二层的网络设备 tap,一个 tap 意味着一个虚拟的网络接口,其数据的传输走的是软件处理,因此数据也可以走虚拟通路进行传输。
TAP 虚拟以太网卡
TAP 网卡是一个 Linux 内部实现的用户空间虚拟以太网接口,同样使用一个 net_device 结构体来表示。尽管真实的物理线路上并没有物理网络接口,但是操作系统可以假装有一个,并将写入这个接口中的数据,发送到其他地方去,如应用层的应用软件,而不是调用真实物理网卡的驱动。这是一个非常强大的设计,这意味着,Linux 的用户可以定义用户空间的"物理"传输链路,这也为虚拟机的网络技术提供了一个简单而可靠的方法。
TAP 网卡拥有前后端:
- 前端是协议栈
- 后端是用户空间的自定义的数据传输软件
我们一般只需要实现 TAP 网卡的后端。当我们创建了一个 TAP 网卡 tap0
之后,在 /dev/net/tap0
路径将会出现我们创建的网卡在 Linux VFS 上的文件抽象。
- 当我们读该文件的时候,相当于模拟网卡处理从协议栈发出的数据,并向外界发送数据
- 当我们写该文件的时候,相当于模拟网卡接收到从外界发送来的数据,并通过中断通知上层协议栈进行处理
更进一步的讲,我们需要向 TAP 设备读写和处理以太帧。
TUN 虚拟三层网卡
Bridge 网桥-虚拟交换机
网桥是一个虚拟二层网卡,也可以将其看做操作系统内部实现的虚拟交换机,使用软件模拟的交换机,性能较差(ps:交换机转发流量的行为逻辑一般是由硬件直接实现的,因此性能极高),它让通用计算机体现出与交换机类似的行为。
VLAN-Virtual Local Area Network 虚拟局域网
VLAN 允许在同一个物理网络上创建多个逻辑隔离的网络。它的主要作用是:
- 隔离广播域,提高安全性
- 灵活管理网络结构
- 将一个物理网络划分为多个独立的逻辑网络
- 允许一个网卡连接多个网桥(通过 VLAN 子接口)
- 减少广播流量,提升网络性能
- 增强安全性(不同 VLAN 之间默认不能直接通信)
- 提高网络管理灵活性
VLAN 通过 VLAN Tagging 允许一个物理网卡创建多个 VLAN 子接口,每个 VLAN 接口可以加入不同的网桥,从而让一个以太网接口(eth0)能够连接多个网桥。这是一种常见的网络管理方式,在虚拟化和云计算环境中非常常见。
Bond
bond0 是**网络绑定(NIC Bonding)**创建的一个逻辑网络接口,用于将多个物理网卡(NIC)绑定在一起,提高网络的带宽、冗余性和可靠性。它是 Linux 网卡聚合(Link Aggregation)的实现,类似于 LACP(IEEE 802.3ad)。
二层隧道
由于 Linux 提供了 TAP 虚拟以太网卡的功能,二层隧道技术应运而生。它可以解决:
- 跨网段的二层网络联通
- 虚拟化场景下的二层网络联通
- 数据加密等需求
其中的一个典型应用就是 VPN 软件:
- VPN 软件在客户端机器中添加一个 TAP 设备
- 读取 TAP 设备中的数据,模拟数据发送
- 将数据进行加密
- 使用机器上的真实网络接口接入互联网,向远程的 VPN 服务器发送加密后的流量
- VPN 服务器解密流量
- 在远程机器上生成网桥,并配置二层转发
- VPN 服务器将解密的数据进行二层转发,由此直接勾连客户端,进入远程服务器所在的以太局域网中,这种效果相当于客户端和远程局域网有网线直连一样
网络层
网络层用于定义网络协议,协议是计算机之间通信的规则,只有遵循特定的规则才能让其他机器理解自己,就像人们在生活中要遵守为人处世的礼仪和规范。
IPv4-广域网、跨网络通信、互联网
我们无法将世界上所有的机器都直接连接在同一个顶层网络之中。网络可以分为大小规模的网络,网络之间可以相互嵌套,层级需要进行划分。当涉及网络之间的嵌套时,数据需要在不同的网络中进行跨网段传递,如何让数据在纷繁的网络中正确找到目标-寻址成为了一个重要的问题。
子网掩码
一个地址并不能代表一台机器,因为机器还需要知道一个子网掩码,用于确认谁是自己的家人,居住在同一个子网中。另外,通过子网,机器可以确认一个网关地址和一个广播地址。处于同一个子网中的主机意味着彼此之间可以直接通信,它们往往通过物理线路直接相连。当机器需要访问子网外部的主机时,机器将向网关发送自己的请求,由网关代为访问外界,并将结果返回给子网中的机器。
网关是子网中的门户,它同时位于多个网络中,并在两个网络间的机器需要进行通信的时候充当代理人进行数据的转发。转发基于三层规则,依据 NAT 表进行。
IPv6
IPv6 的长度为 128 位,由 8 组 16 进制数组成:
2001:db8:abcd:0000:0000:0000:0000:0001/128
连续出现的"0000"组可以被省略为"::",一个 IPv6 地址中只允许出现一个"::":
2001:db8:abcd:0000:0000:0000:0000:0001/128 -> 2001:db8:abcd::0001/128
IPv6 一般的掩码是 64 位,这意味着前 64 位是网络地址,后 64 位为主机地址。
IPv6 兼容 IPv4
掩码为 96 位的"/96"IPv6 网络可以向 IPv4 提供兼容,如果一个网段的掩码为 96,这往往意味着这是一个兼容网络,该网络的路由器可以将 IPv6 和 IPv4 的地址进行转换,并达成跨协议通信。
ICMP
BGP
传输层
TCP
UDP
Linux 网络协议栈
Linux 网络协议栈是实现网络层协议的组件,它基于下层的设备抽象层,同时为上层的 socket 层提供协议的抽象机制。
Routing 表-路由表
路由表决定数据包的转发路径,存储网络目标和下一跳信息。
ARP 表
ARP (Address Resolution Protocol) 表将目标主机的 IP 地址映射为目标主机的 MAC 地址,是实现从三层切换到二层的机制。
FDB 表
FDB (Forwarding Database Table) 表将目标主机的 MAC 地址映射为交换机 port 号的表,是类似于交换机的 MAC 表。
Netfilter 框架 - iptables
Netfilter 是一套 Linux 上的网络过滤框架,它在网络包收发的各个过程中添加钩子,从而拦截和过滤流量,为了网络安全而设计。在这个框架中,使用一个名叫链(Chain)的概念来表达一个钩子,"链"就像是数据包在内核中旅行时要经过的一个个"检查站",每个检查站(链)都有一堆规则(rules),这些规则决定数据包是放行(ACCEPT)、丢弃(DROP)、修改(NAT)还是其他操作,每个点对应数据包生命周期的不同阶段。
该框架包含多张表,定义了为数据收发的不同阶段添加钩子,满足多样的网络过滤规则。包括:filter、NAT、mangle、raw、security 等,其中 filter 和 NAT 最常用。
Filter 表-防火墙
该表直接决定了是否处理一个数据包,是防火墙的底层基础,包含三个钩子(链、Chain):
- INPUT:在三层数据包的目标是本机时触发
- FORWARD:在三层数据包的经过本机进行转发时触发
- OUTPUT:在本机发出三层数据的时候触发
钩子可以指定一些规则,用于匹配数据包的特征,例如可以匹配数据包的源地址、目标地址、协议类型等。当规则被匹配的时候,进行相应的行为,也就是只能支持有限的回调函数,还是系统预定义好的:
- ACCEPT:接收该包
- DROP:丢弃该包
- REJECT:明确拒绝该包
NAT 表-网关
网关是指当一个 Linux 机器位于多个子网中时,那么它可以作为子网之间交互的通道,这个操作就是 NAT 操作。Linux 一般默认支持 NAT 的功能,但是默认关闭,必须为其配置转发规则,才能使得 NAT 开始工作,然后子网中的其他机器可以通过配置路由表项,告诉访问某个网段的时候使用该机器作为网关。
NAT 表直接决定了本机的 NAT 行为是否进行,是网关功能的底层实现,包含三个钩子:
- PREROUTING:数据包进入系统后的第一个处理点,用于修改目标地址(DNAT)
- POSTROUTING:数据包离开系统前的最后一个处理点,用于修改源地址(SNAT 或 MASQUERADE)
- OUTPUT:本地主机发出的数据包,用于修改目标地址(DNAT)
- INPUT(不常用):数据包到达本地主机前,用于修改目标地址(DNAT)
- FORWARD(不常用):理论上存在,但实际很少在 NAT 表中使用
回调:
- DNAT:目标地址修改
- SNAT:源地址修改
- MASQUERADE:掩饰,动态修改数据包的源地址,增强版 SNAT
- REDIRECT:将数据包的目标地址重定向到本地主机的某个端口
- RETURN:自定义链
- ACCEPT
- DROP
- NETMAP
- TPROXY
Traffic Control-QoS (Quality of Service)
流量控制模块,是 Linux 中与协议栈平行的一个网络控制子系统,用来管理网络流量的带宽、延迟和优先级。QoS 的目标是优化网络性能,确保关键流量优先传输,避免拥堵。
队列规则(Queue Discipline, qdisc)
类(Classes)
Rules
应用层
DNS
HTTP
HTTP 也称超文本传输协议,从 web browser 诞生,但却不止用于 web browser,由于其简单而直观的协议机制,如今已经广泛用于各类网络通信中。
HTTP 的特点:
基于纯文本的明文传输
- HTTP 就是一段按照规则书写的文本,在发送之前将文本数据按照特定的编码集对内容进行编码
- HTTP 的头 header必须是 ASCII 编码
- 在体 body可以是自定义的编码,当 body 的编码是其他编码的时候需要在 'Content-Type' 头字段中声明编码和数据类型
一问一答的请求-响应模式
- HTTP 双方一般默认采用的是一问一答的交流方式
- 服务端不会主动发送消息
- 只有客户端主动发送一个 HTTP 的逻辑请求 Request
- 服务端对请求进行解析、处理,然后响应一个 HTTP 响应 Response
协议头+协议体的分包方式 一个请求或者响应的消息包,必须要包含如下的格式,请求头和请求体采用的是不同的编码方式,两种编码方式混合在一起使用:
<请求行> <请求头:键值对> <请求头:键值对> <请求头:键值对> <请求头:键值对> <空行> <请求体>
SSL/TLS
SSH
SSH(Secure Shell)是一种用于远程登录和安全通信的加密协议,主要用于在不安全的网络(如互联网)上安全地管理远程服务器。它的核心作用是提供加密的远程访问,防止密码和数据在传输过程中被窃听或篡改。
SSH 端口转发
SSH 转发基于两个目的:
- 代理
- 加密
当客户端无法直接连接服务端的时候,转发和代理服务就显得重要;当你使用的应用层协议没有自带的加密机制时,可以使用 SSH 进行套壳,从而达到加密效果。
本地转发 将本地机器 A 的端口 p1 映射为远程服务端 C 的某个端口 p2,用于让本地的应用通过访问本地端口 p1 从而连接远程 C 的端口 p2 上的服务,并且获得加密效果。此时,我们需要引入一个 SSH 中介服务器 B,A 通过 SSH 与 B 建立连接,让 B 代为访问 C,然后将 C 的端口映射到 A 的本地。
命令格式:
ssh -L <主机A端口p1>:<主机C>:<主机C端口p2> <主机B的username>@<主机B的hostname>
远程转发
动态转发