- 创建者: 虚拟的现实,上次更新时间:9月 06, 2024 需要 5 分钟阅读时间
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 错误
- 机器没开机
参考教程
图文教程
- 大佬 无聊的闪客 公众号 如果让你来设计网络 文章,从两台机器互联到多台机器,衍生出二层三层协议的由来
视频动画教程
- 计算机网络-如何寻找目标计算机? 基于前面闪客大佬制作的视频动画
- IP 地址不够用怎么办 讲解了为啥分 ABC 三类 IP 地址以及 NAT、NAPT 转换
二层记住最常见的太网协议,它里面包含源目 MAC 地址就行了,就像上面说的 223.5.5.5 dns 请求封装后发出去:
- 交换机因为在机器的连接中间而收到报文,它会解析报文里的源目 MAC 地址,发往对应的网口,所以说交换机只解析查看二层协议部分,交换机是二层设备。利用转发特性,可以做到很多一个网口的设备,通过交换机连接在一起。
- 这点后面的 host-gw 和 calico 的 BGP 模式下,NODE 添加了路由表下,Pod 跨节点封装的报文内的源目 MAC 地址是源目 NODE 节点,网络层 IP 是 PodIP 就很好理解了。利用同一个二层转发过去。
扩展教程
协议和排查命令
二层 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 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
两台机器压测下。
- 无标签
添加评论