KVM 虚拟机网络调试的经历

警告
本文最后更新于 2024-04-12,文中内容可能已过时。

KVM 虚拟机网络调试的经历

这次网络问题的场景是,我通过 KVM 技术在一台 Linux 服务器上安装了一台虚拟机,但是从主机到虚拟机的网络不通,虽然通过内网穿透解决了问题,但是排查的过程依然非常有意义,遂记录下来。具体的调试过程基本上参考博客:单网口 ubuntu 主机配置 virt-manager 传统桥接 bridge 网络_enaftgm1i0-CSDN 博客 中的步骤

关于 KVM 虚拟化技术,请看《Linux 虚拟化技术 - KVM》

简单的心得体会

网桥、交换机(就是多端口的网桥)工作在二层:链路层,以太网中处理以太头部,转发。如何转发呢,在接收到以太帧之后,检查二层头部里的目的 mac,随后查看本地的 mac 转发表(也可以叫二层转发表,layer 2 FIB,不同厂商名称不一样),找到匹配的 mac 地址条目对应的端口,不对二层头部作任何修改从端口转发出去。arp命令查看的就是链路层的转发

注意,通过arp -s添加了链路层的规则在服务器重启之后会消失

路由route工作在网络层,处理 IP 头部,查路由表,作三层转发。连接在路由器不同端口的设备属于不同的 IP 子网。route 方法查看的,就是路由的转发

一个网络包的传递主要就是看这两块:从域名解析 IP,包发到了网络接口,也就是网卡上,他应该往哪里转发,route 命令查看此网卡的路由规则(其实数据包在不同的网卡之前传递,底层也是用的 Mac 地址),经过路由等包到了目标服务器所在局域网,根据 Mac 地址,确定最终发送的目标服务器,arp命令查看映射。


这个博客教我们做的,实际上是通过arp设置和route路由设置,手动搭建了一个通信链路

外部机器 a,本机 b,以及本机 b 下的虚拟机 b1

首先 a 在发送数据包到 b1 点时候,首先 a 中,b1 点 IP 对应的 Mac 地址必须是 b 的物理交换机的 Mac 地址,Mac 地址决定了一个数据包的下一站是谁。这样做到目的是让 a 发给 b 中的虚拟机的包,首先都发给 b,然后我们通过配置桥接,将的物理网卡桥接到一个虚拟网卡中,所有发送到 b 的物理网卡的包,都会通过桥接转发到虚拟网卡中,然后刚好,b 中所有的虚拟机都是以桥接方式连接着虚拟网卡的,因此,b 的物理网卡转发到虚拟网卡的包都会被虚拟机接受,然后在虚拟机内部,所有对外发送的 IP 的 Mac 地址,都是虚拟交换机,也就是通过虚拟交换机向外发送数据。虚拟交换机会转发到 b 的物理网卡

上面的配置保证了当 b 的网卡接受到消息的时候,能够通过网桥转发给虚拟机,那在 b 机器中,如何连接虚拟机呢,此时我们就只能通过搭建路由的方式,直接将对 b1 的访问转发到虚拟网卡,

内网穿透方案

现在的场景是,A、B 这两台物理机可以互通,在 B 中通过 KVM 虚拟化出虚拟机 B1,我们的设想是让 B1 以桥接方式与 B 连接,这样 ABC 都能互通,但是我们跟随 单网口 ubuntu 主机配置 virt-manager 传统桥接 bridge 网络_enaftgm1i0-CSDN 博客 的指引,解决不好这个问题,然后我们准备转变思路,让 B1 以(默认)NAT 方式与 B 连接,然后在 B 中通过 Nginx 进行 SSH 转发,实现 A 和 B1 互通。

步骤如下

  1. 在 B 中创建物理网卡的桥接,并设置一个新的 IP(我们可以通过这种方式为一个单网口的机器配置多个 IP),将来 A 就通过这个 IP 来连接 B1

  2. 修改 B 的 SSH 的配置文件/etc/ssh/sshd_config。修改ListenAddress为 B 的物理网卡的 IP,这样,B 中的 sshd 服务就只会监听物理网卡的IP:22,其他的 IP(比如桥接的 IP)不会被 sshd 处理,实际上最终会被 Nginx 转发到 B1

  3. 通过 KVM 技术创建虚拟机 B1,NAT 模式也是 B1 与 B 默认的网络连接模式,此时 B1 会分配到一个内网 IP,而且此时,B 与 B1 是互通的,而且 B1 到 A 是互通的,但 A 到 B1 不是互通的

  4. 安装 Nginx,并且配置 SSH 流量转发,所有访问网桥IP:22的流量都会被转发到 B1 的 22 端口,Nginx 配置文件如下,

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    http {
    }
    stream {
        upstream ssh {
            server <B1  IP>:22;
        }
        server {
            listen <网桥的 IP>:22;
            proxy_pass ssh;
            proxy_connect_timeout 1h;
            proxy_timeout 1h;
        }
    }
  5. A 现在可以通过网桥IP:22来 SSH 连接 B1 了

本质上,Ngin 的角色就是一个端口转发工具,实现了内网穿透,B 和 B1 可以看作是一个内网。

而且我发现 Nginx 作为端口转发工具实现内网穿透还挺普遍的,例如技术论坛里的这个帖子:今天整了个活,用纯 http post 做了一个反向代理/内网穿透的工具 - V2EX。这个帖子的作者通过 HTTP 协议实现了内网穿透,而且进行了开源:GitHub - codingmiao/hppt: 一款基于纯 HTTP 协议的内网穿透、反向代理工具,其核心也是用 Nginx 进行 SSH 流量转发。

虽然操作系统本身也可以做端口转发,但是用 Nginx 来实现却很普遍,可能是因为 Nginx 配置起来更简单。

还有一种场景,也适合用 Nginx 进行流量转发,即:服务器上开了防火墙,8080 端口开了,但是 22 端口没开,我们需要通过 SSH 远程连接这台服务器,此时我们同样可以用 Nginx 将 SSH 流量从 80 转发到 22 端口,然后用 80 端口 SSH 登录服务器。

拓展:

其他内网穿透工具:reGeorgreDuhTunna

其他基于HTTP实现的SSH工具:

基于HTTP进行SSH转发:

0%