OSI 网络模型

OSI 网络模型是国际标准化组织提出的一个网络互联抽象概念模型,并不实际存在,是为了在一个宏观而非细节的角度上,让人理解网络是如何运作的。

OSI 7层模型TCP/IP 五层模型作用示例协议
应用层应用层与用户的交互而提供应用功能HTTP、FTP、SMTP、DNS、SSH、Telnet、MQTT...
表示层数据的编码和解码
会话层建立、维护、管理会话链接
传输层传输层提供端到端的通信TCP、UDP、STCP
网络层网络层路由和寻址IP、ICMP、OSPF、BGP
数据链路层数据链路层传输数据帧FDDI、Ethernet、PPP
物理层物理层将 bits 转换为电、无线电或光信号传输IEEE802、IEEE802.2

TCP/IP 网络模型

TCP/IP 五层或者四层模型是对于开发人员来讲的一个学习分层模型,每层的封装增加的信息为以下:

应用层应用数据
传输层TCP/UDP头部(其中有源目端口号) + 上一层的内容   
网络层IP头部(其中有源和目的IP地址) + 上一层内容
数据链路层以太网帧头(其中有源目MAC地址)+ 上一层内容 + 尾部的帧校验
物理层把上一层内容按照每个比特位转换电信号或光信号

假如本机 DNS 请求 223.5.5.5 解析 www.baidu.com 域名 IP 举例,大致封包过程如下:

根据机器上路由表,会选择从 eth0 192.168.2.112 网卡发送出去,分配的随机 client 端口为 51234

应用层应用数据
传输层数据(www.baidu.com 的 IP 地址是多少)  
网络层UDP头部(51234|53)|数据 
数据链路层IP头部(192.168.2.112|223.5.5.5)|UDP头部|数据
物理层把上一层内容按照每个比特位转换电信号或光信号

网络层发现 223.5.5.5 不在自己同一个二层内网,匹配路由表发往网关

封装二层以太网结构,需要目标的 MAC 地址,会先发一次 arp 请求获取网关的 MAC

然后封装 数据链路层 里的目标 MAC 地址为网关地址

以下是结合网络设备的 TCP/IP 四层模型的流量图,现实中网络设备和多层的 NAT 暂不考虑,以下是一个简化的图:

在现实内部环境开发或者一些网络排查中,浏览器访问 URL 不通(通常来讲报错能反映一些问题),拿内网环境举例,一般的常见情况如下:

  • 机器的端口没起来,也就是进程没起来或者没监听这个端口,或者 bind 错误了 IP
  • 机器上防火墙没放行端口
  • 网络报文没路由到该机器
  • IP 冲突,arp 错误
  • 机器没开机

参考教程

图文教程

  • 大佬 无聊的闪客 公众号 如果让你来设计网络 文章,从两台机器互联到多台机器,衍生出二层三层协议的由来

视频动画教程

二层记住最常见的太网协议,它里面包含源目 MAC 地址就行了,就像上面说的 223.5.5.5 dns 请求封装后发出去:

  1. 交换机因为在机器的连接中间而收到报文,它会解析报文里的源目 MAC 地址,发往对应的网口,所以说交换机只解析查看二层协议部分,交换机是二层设备。利用转发特性,可以做到很多一个网口的设备,通过交换机连接在一起。
  2. 这点后面的 host-gw 和 calico 的 BGP 模式下,NODE 添加了路由表下,Pod 跨节点封装的报文内的源目 MAC 地址是源目 NODE 节点,网络层 IP 是 PodIP 就很好理解了。利用同一个二层转发过去。

扩展教程

内网穿透和NAT打洞是什么?

协议和排查命令

二层 MAC

特别局域网内出现 IP 冲突时候,故障的机器无法上网,其他机器可以,多半是 IP 冲突,可以网关或者内网其他机器上查看 arp 表(存储 IP-MAC-NIC 映射记录):

$ arp -n
IP address       HW type     Flags       HW address            Mask     Device
192.168.0.6      0x1         0x2         70:9c:xx:6f:xx:xx     *        br-lan
192.168.1.1      0x1         0x2         34:d8:xx:xx:06:xx     *        eth3
192.168.0.172    0x1         0x2         34:ea:34:xx:50:xx     *        br-lan
192.168.0.2      0x1         0x2         06:22:d2:xx:ca:xx     *        br-lan
192.168.0.141    0x1         0x0         70:9c:d1:xx:df:xx     *        br-lan
# 也可以 ip 命令
$ ip neighbour show
...

删掉一个指定条目:

arp --delete <IP_ADDRESS>
ip neighbour delete <ip_address> dev <interface_name>

三层 IP 层

TCP/IP 四层模型里,很多时候网络故障,都是先看最下层是否发生故障,例如家里上不了网了,可以:

  • ping 网关 IP,确认 arp 、没有出现环路广播风暴和高延迟之类的
  • ping 公网 IP,例如 223.5.5.5,114.114.114.114 从 2023 年开始很多地方 ping 不通了
  • ping 公网域名检查 DNS
  • curl -v 公网一个 http(s) url

当然,上层是下层承载着的,例如后续的 K8S 跨节点故障的时候,可以从应用层到下层逐步看:

  • curl 非本机的 coredns 的 metrics web 接口 :9153/metrics
  • ping 非本机的 POD_IP

实际生产环境中,最常见的就是利用(三层协议 ICMP) ping 看下 IP 可达不(Linux 系统防火墙和内核参数通常是允许被 ping 的),另外要注意,一些公共场所的 WIFI 之类的开了 AP 隔离,局域网除了网关以外互相无法访问。

IP

在一些例如 rescue mode Linux 里,要从外部拷贝文件,而不想去找怎么配置网卡,可以 ip 命令临时配置下:

# ip 命令是新趋势,功能十分强大,下面的都以 ip 命令举例
$ ip addr add 192.168.2.113/24 dev eth0
# ip addr del 192.168.2.113/24 dev eth0

这样同一个二层下其他机器就能和它可达了,或者 scp 二层其他机器上的文件,addr 其他子命令:

# 清空指定网卡上所有 IP
$ ip addr flush dev eth0

查看网卡 IP:

# ip a s 
$ ip addr show
$ ip addr show dev eth0
# 带颜色展示,选项也可以缩写
$ ip -color a s 
# json形式输出,-j 一些低版本 ip 命令上没有 json 选项
$ ip -json a s 

ip 命令的最常见的一些选项:

  • -h:输出人类可读的统计信息和后缀,例如把单位转换成 M、G 之类的。
  • -s:输出更多信息。如果该选项出现两次或更多,则信息量会增加。通常,信息是统计信息或一些时间值
  • -d:输出详细信息
  • -4:使用网络层协议是 ipv4 过滤
  • -6:使用网络层协议是 ipv6 过滤

ip subCmd1 subCmd2 help 会打印帮助

另外要注意的是,一个网卡并不是只能有一个 IP,有多个 IP 很正常,在没有容器的时代都有这种场景。甚至网卡上的 IP 没有强制要求是 ABC 三类内网 IP,有些 IDC 把公网 IP 配置在网卡上很正常。

路由

除此之外,还要注意机器上的路由表:

# ip 命令很多都可以缩写
$ ip route show
$ ip r s 

还有确认某个 IP 从哪个网卡走,出去的源 IP 是多少,ip route get 会帮你从 IP/MASK 计算,例如下面看默认路由:

# 等同于 ip route get 1.0.0.0
$ ip route get 1
# 包含了下一跳的 IP 地址,从 eth0 发出去,来源 IP 是 2.112
1.0.0.0 via 192.168.2.1 dev eth0 src 192.168.2.112 uid 0 
    cache 

$ ip route add default via 192.168.2.1 dev eth0
$ ip route del default via 192.168.2.1 dev eth0

也需要注意的是,多网卡一般和多路由配合,例如服务器八个网卡,接了 6 根线做三个 bond,给:

  • 存储网:业务会使用 ceph rbd,10G 光口,专门的网络和交换机
  • 业务网:业务流量,默认路由,10G 光口,专门的交换机
  • 管理网:ssh 和管理使用,1G 电口,专门的交换机

这里暂不讨论非 main 路由表,具体见 cat /etc/iproute2/rt_tables 和 ip rule。

ip route 其他示例:

# 目标 IP 在 <net/mask> 的话发往 <ip> 去
$ ip route add <net/mask> via <ip>

网卡

addr 子命令是针对网卡地址,而 link 子命令是针对链路层和网卡属性:

$ ip link show

# 添加一个名为 kube-svc0 的 dummy 接口
$ ip link add kube-svc0 type dummy
# 添加一个网桥
$ ip link add xxx type bridge
$ ip link add xxx type veth ...
$ ip link add xxx type vxlan ...
$ ip link add xxx type vlan ...
$ ip link add xxx type gre ...
...

$ ip -d link show flannel.1
150: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default 
    link/ether 3a:b4:bd:ad:8b:8e brd ff:ff:ff:ff:ff:ff promiscuity 0 
    vxlan id 1 local 10.xxx.xx.xxx dev ens192 srcport 0 0 dstport 8472 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
# 可以看到 vxlan 属性网卡 id 为 1,从哪个网卡出去,vxlan vtep 端口 8472

# 由于 link 有 show 和 set ,所以不能缩写 s
$ ip link set xxx down/up
# 设置 eth0 混杂模式
$ ip link set eth0 promisc on/off
# 设置网卡队列长度
$ ip link set eth0 txqueuelen 1200
# 设置网卡最大传输单元
$ ip link set eth0 mtu 1450
# 修改网卡 MAC 地址
$ ip link set eth1 address 00:0c:29:f3:33:77
# 设置 eth0 桥接在 br0 下
$ ip link set eth0 master br0
...

$ ip -h -s link show ens192
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 00:50:56:99:xx:xx brd ff:ff:ff:ff:ff:ff
    RX: bytes  packets  errors  dropped overrun mcast   
    55.2G      108M     0       1.71M   0       2.37M   
    TX: bytes  packets  errors  dropped carrier collsns 
    3.63G      7.17M    0       0       0       0 

$ ip -s link show flannel.1
150: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default 
    link/ether 3a:b4:bd:ad:8b:8e brd ff:ff:ff:ff:ff:ff
    RX: bytes  packets  errors  dropped overrun mcast   
    0          0        0       0       0       0       
    TX: bytes  packets  errors  dropped carrier collsns 
    xxx        xxxxx    0       8       0       0

例如上面的接收 RX 一直为空,最后排查到客户的 VPC 网络没有放行 udp 8472 端口造成 K8S 跨节点通信问题。

端口

一般没 nat 表的 NAT 规则,想查看监听端口,netstat 很多新发行版不自带了,使用更强大的 ss 命令:

# ss 很多选项和 netstat 一样
# -n 不反向解析,以纯 IP 数字形式展示
# -l, --listening 显示监听 IP 地址
# -p 输出进程信息,pid 和进程名
# -t 是 tcp -u 是 udp
$ ss -nlpt

# ss 还支持过滤表达式
$ ss -nlpt 'sport = 80'
# 显示统计信息
$ ss -s

除了端口以外,还要注意监听的 IP 地址,常规监听为以下几种情况,当然程序也可以自定义混合使用:

  • 127.0.0.1 不允许外部访问进来,只在本机提供访问。
  • 单张网卡 IP, 只允许这张网卡进来
  • 0.0.0.0 监听所有网卡 IP

改内核参数绑定非本机 IP 暂不讨论。另外要注意的是 golang 一个在 netstat 下的特殊点:

package main

import ("net")

func main() {
	l, err := net.Listen("tcp", ":2000")
	if err != nil {
		log.Fatal(err)
	}
	defer l.Close()
	for {
		// Wait for a connection.
		conn, err := l.Accept()
		if err != nil {
			log.Fatal(err)
		}
		// Handle the connection in a new goroutine.
		// The loop then returns to accepting, so that
		// multiple connections may be served concurrently.
		go func(c net.Conn) {
			// Echo all incoming data.
			io.Copy(c, c)
			// Shut down the connection.
			c.Close()
		}(conn)
	}
}

以上代码 run 起来后,netstat 看到的 bind 的是 ipv6 ,实际是 ipv4 ipv6 都监听的,这是 golang 的一个小的显示问题。

$ netstat -nlpt | grep :2000
tcp6       0      0 :::2000                 :::*                    LISTEN      98822/main

tcp

很多时候不一定需要看 ping,特别是禁 ping 的时候,我们要检查远端端口是否可达,最常见的就是可以使用 telnet 协议,它在建立 TCP 握手后会维持一个类似 ssh 的虚拟终端。但是某些系统上 telnet 并不会出现 ],只要不是连接拒绝都会显示等待输入一样,可以尝试使用 tcping,通过建立 TCP 链接,查看握手时间,代码层面做探测使用对应的 socket 库即可。

udp

由于 udp 是无状态协议,测 udp 端口时候需要登录到两台机器上,一般是看中间有设备拦截否,可以用 nc:

# 机器 1 起 server
$ nc -l -u 8472
# 机器 2 作为 client 发送到 server 上
$ nc -u <dest_public_ip> 8472

上面机器 2 上的 nc 交互窗口输入字符串回车后,server 上的 nc 窗口就能收到了。怀疑 udp 丢包或者带宽低可以用 iperf3 两台机器压测下。

  • 无标签
写评论...