kata 创建网络相关代码
进入指定的 Linux 网络命名空间,扫描里面所有网卡,筛选合法网卡,把符合条件的网卡包装成 Endpoint(网络端点)添加到sandbox ;失败时支持回滚,避免只添加一半端点。
func (n *LinuxNetwork) scanEndpointsInNs(ctx context.Context, s *Sandbox, nsPath string, hotplug bool) ([]Endpoint, error) {
netnsHandle, err := netns.GetFromPath(nsPath)
if err != nil {
return nil, err
}
defer netnsHandle.Close()
netlinkHandle, err := netlink.NewHandleAt(netnsHandle)
if err != nil {
return nil, err
}
defer netlinkHandle.Close()
linkList, err := netlinkHandle.LinkList()
if err != nil {
return nil, err
}
epsBefore := len(n.eps)
var added []Endpoint
for _, link := range linkList {
netInfo, err := networkInfoFromLink(netlinkHandle, link)
if err != nil {
// No rollback needed — no endpoints were added in this iteration yet.
return nil, err
}
// Ignore unconfigured network interfaces. These are
// either base tunnel devices that are not namespaced
// like gre0, gretap0, sit0, ipip0, tunl0 or incorrectly
// setup interfaces.
if len(netInfo.Addrs) == 0 {
continue
}
// Skip any loopback interfaces:
if (netInfo.Iface.Flags & net.FlagLoopback) != 0 {
continue
}
// Skip any interfaces that are already added
if n.endpointAlreadyAdded(&netInfo) {
networkLogger().WithField("endpoint", netInfo.Iface.Name).Info("already added")
continue
}
if err := doNetNS(nsPath, func(_ ns.NetNS) error {
ep, addErr := n.addSingleEndpoint(ctx, s, netInfo, hotplug)
if addErr == nil {
added = append(added, ep)
}
return addErr
}); err != nil {
// Rollback: remove any endpoints added during this scan
// so that a failed scan does not leave partial side effects.
n.eps = n.eps[:epsBefore]
return nil, err
}
}
return added, nil
}
addSingleEndpoint() 方法除了 context 共传入了三个参数,分别是 sandbox 实例(s)、设备信息(netInfo)和是否支持热插拔(hotplug)。它做了两件事:(1)根据设备类型创建一个对应的 Endpoint;(2)将这个 Endpoint 绑定到 sandbox 中。
func (n *LinuxNetwork) addSingleEndpoint(ctx context.Context, s *Sandbox, netInfo NetworkInfo, hotplug bool) (Endpoint, error) {
var endpoint Endpoint
// TODO: This is the incoming interface
// based on the incoming interface we should create
// an appropriate EndPoint based on interface type
// This should be a switch
// Check if interface is a physical interface. Do not create
// tap interface/bridge if it is.
isPhysical, err := isPhysicalIface(netInfo.Iface.Name)
if err != nil {
return nil, err
}
if isPhysical {
if s.config.HypervisorConfig.ColdPlugVFIO == config.NoPort {
// When `cold_plug_vfio` is set to "no-port", the PhysicalEndpoint's VFIO device cannot be attached to the guest VM.
// Fail early to prevent the VF interface from being unbound and rebound to the VFIO driver.
return nil, fmt.Errorf("unable to add PhysicalEndpoint %s because cold_plug_vfio is disabled", netInfo.Iface.Name)
}
networkLogger().WithField("interface", netInfo.Iface.Name).Info("Physical network interface found")
endpoint, err = createPhysicalEndpoint(netInfo)
} else {
var socketPath string
idx := len(n.eps)
// Avoid endpoint naming conflicts
// When creating a new endpoint, we check existing endpoint names and automatically adjust the naming of the new endpoint to ensure uniqueness.
lastIdx := -1
if len(n.eps) > 0 {
lastEndpoint := n.eps[len(n.eps)-1]
re := regexp.MustCompile("[0-9]+")
matchStr := re.FindString(lastEndpoint.Name())
n, err := strconv.ParseInt(matchStr, 10, 64)
if err != nil {
return nil, err
}
lastIdx = int(n)
}
if idx <= lastIdx {
idx = lastIdx + 1
}
// Check if this is a dummy interface which has a vhost-user socket associated with it
socketPath, err = vhostUserSocketPath(netInfo)
if err != nil {
return nil, err
}
if socketPath != "" {
networkLogger().WithField("interface", netInfo.Iface.Name).Info("VhostUser network interface found")
endpoint, err = createVhostUserEndpoint(netInfo, socketPath)
} else if netInfo.Iface.Type == "macvlan" {
networkLogger().Infof("macvlan interface found")
endpoint, err = createMacvlanNetworkEndpoint(idx, netInfo.Iface.Name, n.interworkingModel)
} else if netInfo.Iface.Type == "macvtap" {
networkLogger().Infof("macvtap interface found")
endpoint, err = createMacvtapNetworkEndpoint(netInfo)
} else if netInfo.Iface.Type == "tap" {
networkLogger().Info("tap interface found")
endpoint, err = createTapNetworkEndpoint(idx, netInfo.Iface.Name)
} else if netInfo.Iface.Type == "tuntap" {
if netInfo.Link != nil {
switch netInfo.Link.(*netlink.Tuntap).Mode {
case 0:
// mount /sys/class/net to get links
return nil, fmt.Errorf("Network device mode not determined correctly. Mount sysfs in caller")
case 1:
return nil, fmt.Errorf("tun networking device not yet supported")
case 2:
networkLogger().Info("tuntap tap interface found")
endpoint, err = createTuntapNetworkEndpoint(idx, netInfo.Iface.Name, netInfo.Iface.HardwareAddr, n.interworkingModel)
default:
return nil, fmt.Errorf("tuntap network %v mode unsupported", netInfo.Link.(*netlink.Tuntap).Mode)
}
}
} else if netInfo.Iface.Type == "veth" {
networkLogger().Info("veth interface found")
endpoint, err = createVethNetworkEndpoint(idx, netInfo.Iface.Name, n.interworkingModel)
} else if netInfo.Iface.Type == "ipvlan" {
networkLogger().Info("ipvlan interface found")
endpoint, err = createIPVlanNetworkEndpoint(idx, netInfo.Iface.Name)
} else if netInfo.Iface.Type == "device" {
// "device" is the generic netlink type for interfaces whose
// drivers do not register a more specific kind. This includes
// mlx5 Scalable Functions (SFs), some SR-IOV VFs on arm64,
// and other non-PCI backed netdevs that isPhysicalIface()
// cannot classify via ethtool BusInfo. Handle them the same
// way as veth endpoints, following the configured interworking
// model (TC-filter by default).
networkLogger().Info("device interface found")
endpoint, err = createVethNetworkEndpoint(idx, netInfo.Iface.Name, n.interworkingModel)
} else {
return nil, fmt.Errorf("Unsupported network interface: %s", netInfo.Iface.Type)
}
}
if err != nil {
return nil, err
}
endpoint.SetProperties(netInfo)
networkLogger().WithField("endpoint-type", endpoint.Type()).WithField("hotplug", hotplug).Info("Attaching endpoint")
if hotplug {
if err := endpoint.HotAttach(ctx, s); err != nil {
return nil, err
}
} else {
if err := endpoint.Attach(ctx, s); err != nil {
return nil, err
}
}
if !s.hypervisor.IsRateLimiterBuiltin() {
rxRateLimiterMaxRate := s.hypervisor.HypervisorConfig().RxRateLimiterMaxRate
if rxRateLimiterMaxRate > 0 {
networkLogger().Info("Add Rx Rate Limiter")
if err := addRxRateLimiter(endpoint, rxRateLimiterMaxRate); err != nil {
return nil, err
}
}
txRateLimiterMaxRate := s.hypervisor.HypervisorConfig().TxRateLimiterMaxRate
if txRateLimiterMaxRate > 0 {
networkLogger().Info("Add Tx Rate Limiter")
if err := addTxRateLimiter(endpoint, txRateLimiterMaxRate); err != nil {
return nil, err
}
}
}
n.eps = append(n.eps, endpoint)
return endpoint, nil
}
func createVethNetworkEndpoint(idx int, ifName string, interworkingModel NetInterworkingModel) (*VethEndpoint, error) {
if idx < 0 {
return &VethEndpoint{}, fmt.Errorf("invalid network endpoint index: %d", idx)
}
netPair, err := createNetworkInterfacePair(idx, ifName, interworkingModel)
if err != nil {
return nil, err
}
endpoint := &VethEndpoint{
// TODO This is too specific. We may need to create multiple
// end point types here and then decide how to connect them
// at the time of hypervisor attach and not here
NetPair: netPair,
EndpointType: VethEndpointType,
}
if ifName != "" {
endpoint.NetPair.VirtIface.Name = ifName
}
return endpoint, nil
}
现在我有了完整的上下文。让我给出详细分析。
分析 createVethNetworkEndpoint
函数作用
createVethNetworkEndpoint 是 Kata 网络模型中创建 veth 端点(VethEndpoint)的工厂函数。它把 CNI/网络插件在容器 netns 里创建好的那个 veth 接口(如 eth0)封装成一个 VethEndpoint,为后续把该接口连接到 VM 做准备。
它接收三个参数:
idx int— 端点索引,用于生成网络对(pair)里各种接口的名称(如eth0、tap0_kata、br0_kata)ifName string— 原始接口名(如eth0),如果非空则覆盖默认命名interworkingModel NetInterworkingModel— VM 与容器网络之间的连接模型(macvtap / tcfilter / none)
逐步逻辑
1. 校验索引
if idx < 0 {
return &VethEndpoint{}, fmt.Errorf("invalid network endpoint index: %d", idx)
}
索引必须 ≥ 0,否则返回错误。这里的 idx 来自 addSingleEndpoint 里的 idx := len(n.eps)(已有端点数量),正常情况下都是非负的,这是防御性校验。
2. 创建网络对(NetworkInterfacePair)
netPair, err := createNetworkInterfacePair(idx, ifName, interworkingModel)
if err != nil {
return nil, err
}
调用 createNetworkInterfacePair(network.go:351)生成一个 NetworkInterfacePair。让我们看看这个函数生成的内容:
- 生成一个 UUID 作为网络对的唯一 ID
- 生成一个随机的私有 MAC 地址(
generateRandomPrivateMacAddr,把第一个字节的 local bit 置位,范围x2/x6/xA/xE,即本地管理的单播 MAC) - 构造
NetworkInterfacePair:- TapInterface(宿主机侧):
Name = "br<idx>_kata"(桥接口名,如br0_kata)TAPIface.Name = "tap<idx>_kata"(tap 接口名,如tap0_kata)
- VirtIface(VM 侧 / 原始 veth):
Name = "eth<idx>"(如eth0)HardAddr = <随机 MAC>
NetInterworkingModel = interworkingModel
- TapInterface(宿主机侧):
如果 ifName != "",则把 VirtIface.Name 覆盖为 ifName(即原始接口名)。
3. 构造 VethEndpoint
endpoint := &VethEndpoint{
NetPair: netPair,
EndpointType: VethEndpointType,
}
if ifName != "" {
endpoint.NetPair.VirtIface.Name = ifName
}
把 netPair 和端点类型 VethEndpointType 封装进 VethEndpoint。再次用 ifName 覆盖 VirtIface.Name(注意这里重复了一次 createNetworkInterfacePair 里已经做过的覆盖,属于冗余但无害)。
4. 返回端点
返回 *VethEndpoint,由调用方(addSingleEndpoint)继续做 SetProperties 和 Attach/HotAttach。
关于 NetworkInterfacePair 的理解
一个 veth 网络对包含两个关键侧:
宿主机侧 (TapInterface) VM侧 / 原始veth (VirtIface)
br<idx>_kata (桥) eth<idx> / ifName (原始接口)
tap<idx>_kata (tap)
VirtIface代表容器 netns 里那个真实接口(veth 的一端,通常是eth0)。HardAddr是随机生成的 MAC,后续在tapNetworkPair/setupTCFiltering中会把这个 MAC 设置到真实接口上,以保证 VM 内外的流量用同一个 MAC,避免防火墙/ARP 问题。TAPIface是之后连接到 VM 的 tap/macvtap 设备,命名带_kata后缀避免与宿主冲突。
调用链回顾(结合上一问)
addSingleEndpoint (Iface.Type == "veth")
↓
createVethNetworkEndpoint(idx, ifName=netInfo.Iface.Name, interworkingModel)
↓
createNetworkInterfacePair(...) // 生成命名 + 随机MAC
↓
endpoint.SetProperties(netInfo) // 记录IP/路由等
endpoint.Attach/HotAttach(ctx, s)
↓
xConnectVMNetwork → tapNetworkPair / setupTCFiltering // 真正建tap、做MAC/MTU设置、挂到hypervisor
几点观察
-
MAC 地址:在
createNetworkInterfacePair里给VirtIface预分配了随机 MAC,但setProperties时 CNI 传来的netInfo.Iface.HardwareAddr会通过NetworkInfo保存到EndpointProperties。真正生效的 MAC 是在tapNetworkPair/setupTCFiltering里把netPair.VirtIface.HardAddr设置到物理 veth 上时使用的。 -
命名规则依赖 idx:
eth<idx>、tap<idx>_kata、br<idx>_kata都基于idx,这与addSingleEndpoint里为规避命名冲突而调整idx的逻辑是配套的——保证每个端点名字唯一。 -
ifName覆盖:createNetworkInterfacePair和createVethNetworkEndpoint两处都对VirtIface.Name做了相同的ifName覆盖,属于冗余代码,但结果是确定性的:有ifName(CNI 场景恒有,即eth0)时用原始名,否则用eth<idx>。 -
TODO 注释:代码里标注了一个 TODO——"这里创建端点类型太具体,将来可能需要创建多种端点类型,并在 hypervisor attach 时才决定如何连接"。说明当前设计是"提前确定端点类型 + 连接模型",有一定耦合。
简而言之,这个函数的核心职责就是根据索引生成一套规范的接口命名(桥/tap/eth),并把这个 veth 端点初始化好,使其能够被后续的 attach 流程连接到 VM。
HotAttach 代码
Attach() 方法主要做了两件事情,一个是调用 xConnectVMNetwork() 方法让 veth 设备连接到 tap0_kata 设备(aka 虚拟机网络,如绿色链路所示),一个是调用 hypervisor 的 AddDevice() 方法,让启动 QEMU 虚拟机时增加一条 netdev 设置,让 tap 设备连接到虚拟机
// HotAttach for the veth endpoint uses hot plug device
func (endpoint *VethEndpoint) HotAttach(ctx context.Context, s *Sandbox) error {
span, ctx := vethTrace(ctx, "HotAttach", endpoint)
defer span.End()
h := s.hypervisor
if err := xConnectVMNetwork(ctx, endpoint, h); err != nil {
networkLogger().WithError(err).Error("Error bridging virtual ep")
return err
}
if _, err := h.HotplugAddDevice(ctx, endpoint, NetDev); err != nil {
networkLogger().WithError(err).Error("Error attach virtual ep")
return err
}
return nil
}
xConnectVMNetwork 逻辑
func xConnectVMNetwork(ctx context.Context, endpoint Endpoint, h Hypervisor) error {
var err error
span, ctx := networkTrace(ctx, "xConnectVMNetwork", endpoint)
defer closeSpan(span, err)
netPair := endpoint.NetworkPair()
queues := 0
caps := h.Capabilities(ctx)
if caps.IsMultiQueueSupported() {
queues = int(h.HypervisorConfig().NumVCPUs())
}
disableVhostNet := h.HypervisorConfig().DisableVhostNet
if netPair.NetInterworkingModel == NetXConnectDefaultModel {
netPair.NetInterworkingModel = DefaultNetInterworkingModel
}
switch netPair.NetInterworkingModel {
case NetXConnectMacVtapModel:
networkLogger().Info("connect macvtap to VM network")
err = tapNetworkPair(ctx, endpoint, queues, disableVhostNet)
case NetXConnectTCFilterModel:
networkLogger().Info("connect TCFilter to VM network")
err = setupTCFiltering(ctx, endpoint, queues, disableVhostNet)
default:
err = fmt.Errorf("Invalid internetworking model")
}
return err
}
现在我有了足够的上下文来全面分析这段代码。让我也看一下 veth_endpoint.go 的 Attach/HotAttach 调用链,以便完整说明 xConnectVMNetwork 在整个流程中的位置。
我已经有足够的上下文了。让我提供完整的分析。
xConnectVMNetwork 代码逻辑分析
函数定位
xConnectVMNetwork 是虚拟网络端点(veth、macvlan、ipvlan、tuntap 等)在宿主侧建立虚拟机网络连接的核心函数。它在各个端点的 Attach/HotAttach 方法中被调用(如 veth_endpoint.go:114、macvlan_endpoint.go:111、ipvlan_endpoint.go:114、tuntap_endpoint.go:93),负责在 VM 真正启动/热插拔前,把宿主侧的 tap 设备、文件描述符(fd)和 tc 过滤规则准备好。
逐段逻辑
1. 函数签名与追踪
func xConnectVMNetwork(ctx context.Context, endpoint Endpoint, h Hypervisor) error {
- 输入是
Endpoint(要连接的虚拟网络端点)和Hypervisor(用来查询能力/配置)。 - 通过
networkTrace创建 OpenTelemetry 追踪 span,用closeSpan在返回时记录错误。
2. 获取网络对(netPair)与多队列配置
netPair := endpoint.NetworkPair()
queues := 0
caps := h.Capabilities(ctx)
if caps.IsMultiQueueSupported() {
queues = int(h.HypervisorConfig().NumVCPUs())
}
disableVhostNet := h.HypervisorConfig().DisableVhostNet
NetworkPair()返回一个NetworkInterfacePair,包含TAPIface(宿主侧 tap)、VirtIface(VM 内虚拟网卡)、VMFds(tap 的 fd)、VhostFds(vhost-net 的 fd)等。- 若 hypervisor 支持多队列(
IsMultiQueueSupported()),则将队列数设为 vCPU 数(即每个 vCPU 一个队列);否则为 0(后续会被强制为 1,见createLink中的注释)。 disableVhostNet决定是否创建/dev/vhost-net的 fd(vhost 加速)。
3. 解析默认互连模型
if netPair.NetInterworkingModel == NetXConnectDefaultModel {
netPair.NetInterworkingModel = DefaultNetInterworkingModel
}
- 若端点未显式指定互连模型(
NetXConnectDefaultModel),则回退到全局默认值DefaultNetInterworkingModel(在network.go:183定义为NetXConnectTCFilterModel)。
4. 按互连模型分派
switch netPair.NetInterworkingModel {
case NetXConnectMacVtapModel:
err = tapNetworkPair(ctx, endpoint, queues, disableVhostNet)
case NetXConnectTCFilterModel:
err = setupTCFiltering(ctx, endpoint, queues, disableVhostNet)
default:
err = fmt.Errorf("Invalid internetworking model")
}
两种模型的核心区别:
模型 A:NetXConnectMacVtapModel → tapNetworkPair(macvtap 桥接)
- 用
createMacVtap在容器接口上创建 macvtap 设备(因为内核在 netns 里创建 macvtap 时链接索引有问题,所以通过hostLinkOffset偏移 + 随机索引 + 重试 128 次来规避——见macvtapWorkaround)。 - 把 veth 的 MAC 地址保存到 TAP(
netPair.TAPIface.HardAddr),这样 VM 内网卡用的 MAC 与宿主侧一致,避免防火墙/bridge 的 MAC 问题。 - 将 veth 的 MAC 设为
VirtIface.HardAddr,TAP 的 MAC 设为原 veth MAC(两者互换)。 - 清空 veth 上的 IP 地址(
clearIPs)以防止 ARP 冲突——IP 由 VM 内部负责配置。 - 打开 macvtap 的 fd(
createMacvtapFds)→VMFds;若未禁用 vhost,再打开/dev/vhost-net的 fd →VhostFds。 - 这是"直接桥接"方案:流量在数据链路层直接桥接,性能较好,但要求底层接口能被 macvtap 桥接。
模型 B:NetXConnectTCFilterModel → setupTCFiltering(tc 流量过滤,默认)
- 用
createLink创建 tuntap 设备(TAPIface),拿到 tap 的 fd →VMFds;同样可选创建 vhost fd。 - 同样保存 veth MAC 到 TAP。
- 设置 MTU、启用 TAP。
- 然后建立 双向 tc 重定向:
- 在 tap 和 veth 上都添加 ingress qdisc(
addQdiscIngress,等价于tc qdisc add dev X ingress)。 - 用
addRedirectTCFilter把 veth 的入站流量重定向到 tap(attrs.Index → tapAttrs.Index),再把 tap 的入站流量重定向到 veth(tapAttrs.Index → attrs.Index),形成双向转发。等价于tc filter add ... action mirred egress redirect。
- 在 tap 和 veth 上都添加 ingress qdisc(
- 这是"流量重定向"方案:通过 tc 在两层接口间互相转发,兼容性更好(ipvlan、macvlan 也能用),但有一点内核开销。这也是默认方案。
与 checkpoint/restore 的关联(结合上下文)
这段代码与你之前分析的 HotAttach 链直接相关:
xConnectVMNetwork只是宿主侧准备阶段——它创建 tap、vhost fd、tc 规则。- 这些准备好的
VMFds/VhostFds随后被 hypervisor 使用:- Cloud-Hypervisor:在
clh.go:2170用netPair.VMFds通过 REST API 调用vm.add-net添加网络设备。 - QEMU:在
qemu.go:2574的hotAddNetDevice中通过 QMPExecuteGetFD传递 fd,再netdev_add+device_add。
- Cloud-Hypervisor:在
在 checkpoint/restore 场景下,这些 fd 和网络设备属于 VM 的运行状态。CLH 的 checkpoint/restore 会保存 VM 的内存和状态;恢复时 start_for_restore 重新启动 VM 并重新连接 agent,此时网络端点需要通过上述 Attach/HotAttach 链重新建立(重新走 xConnectVMNetwork 准备宿主侧资源,再通过 vm.add-net 重新接入)。这也就是之前会话中提到的"restore 时网络需要重新 hotplug"的原因。
小结
| 项 | 说明 |
|---|---|
| 作用 | 在宿主侧为 VM 网络端点建立连接(tap + fd + tc 规则) |
| 分派依据 | NetworkPair().NetInterworkingModel |
| 默认模型 | NetXConnectTCFilterModel(tc 重定向) |
| macvtap 模型 | 直接桥接 + 交换 MAC + 清空 IP |
| tc 模型 | tuntap + ingress qdisc + 双向 mirred 重定向 |
| 多队列 | 受 IsMultiQueueSupported 控制,队列数 = vCPU 数 |
| vhost | 受 DisableVhostNet 控制,默认开启 /dev/vhost-net fd |
| 产出 | 填充 netPair.VMFds / VhostFds,供 hypervisor 消费 |
| 反操作 | xDisconnectVMNetwork → untapNetworkPair / removeTCFiltering |
需要我进一步深入某个子函数(如 createLink、createMacVtap 的索引规避逻辑,或 tc 双向重定向的流量路径),或者继续推进 checkpoint/restore 相关的验证工作吗?

浙公网安备 33010602011771号