kata-agent 通过 netlink uevent 感知新网卡

下面是 kata-agent 通过 netlink uevent 感知新网卡 的完整源码位置(含调用链和行号)。

1. 启动:注册全局 uevent 监听任务

src/agent/src/main.rs:408

let uevents_handler_task = tokio::spawn(watch_uevents(sandbox.clone(), shutdown.clone()));
tasks.push(uevents_handler_task);

agent 启动时把 watch_uevents() 作为后台 task 拉起,shutdown 用于优雅退出。

2. 监听内核 uevent:watch_uevents()

src/agent/src/uevent.rs:162-226

位置 作用
uevent.rs:163-174 函数签名、取 logger、打印 "starting uevents handler"
uevent.rs:178-185 创建 netlink socket:AF_NETLINK + SOCK_DGRAM|SOCK_CLOEXEC + NETLINK_KOBJECT_UEVENT
uevent.rs:187 bind(&SocketAddr::new(0, 1)),端口 1 表示接收内核 uevent(组播到 userspace)
uevent.rs:189-194 select! + shutdown.changed(),收到退出信号就 break
uevent.rs:195-220 socket.recv_from_full() 收包 → addr.port_number() != 0 过滤掉非内核消息(uevent.rs:201-206)→ String::from_utf8 → Uevent::new(&text) → event.process(...)
uevent.rs:214-216 构造事件、打印 "got uevent message"、交给 process() 分发

3. 报文解析:Uevent::new()

src/agent/src/uevent.rs:42-63,结构体定义在 uevent.rs:27-35:

pub struct Uevent {
    pub action: String,
    pub devpath: String,
    pub devname: String,
    pub subsystem: String,
    seqnum: String,
    pub interface: String,
}

按 \0 切分 KEY=VALUE,键名常量在 src/agent/src/linux_abi.rs:149-157:

  • U_EVENT_ACTION = "ACTION"(add / remove,见 linux_abi.rs:151-152)
  • U_EVENT_DEV_PATH = "DEVPATH"
  • U_EVENT_SUB_SYSTEM = "SUBSYSTEM"
  • U_EVENT_DEV_NAME = "DEVNAME"
  • U_EVENT_INTERFACE = "INTERFACE" ← 网卡事件特有的字段(如 INTERFACE=eth0),解析在 uevent.rs:56

4. 动作分发与事件登记

src/agent/src/uevent.rs:103-111:

async fn process(&self, logger: &Logger, sandbox: &Arc<Mutex<Sandbox>>) {
    if self.action == U_EVENT_ACTION_ADD {
        return self.process_add(logger, sandbox).await;
    } else if self.action == U_EVENT_ACTION_REMOVE {
        return self.process_remove(logger, sandbox).await;
    }
    debug!(*logger, "ignoring event"; "uevent" => format!("{:?}", self));
}

process_add() 在 uevent.rs:65-95:

  • uevent.rs:67-79:先特判内存热插(SYSFS_MEMORY_ONLINE_PATH → online_device())
  • uevent.rs:84:sb.uevent_map.insert(self.devpath.clone(), self.clone()) —— 按 sysfs 路径缓存事件
  • uevent.rs:87-94:遍历 sb.uevent_watchers,在这里才做过滤:matcher.is_match(self) 命中就把 oneshot::Sender take 出来并 send()

process_remove() 在 uevent.rs:97-101(uevent_map.remove())。

缓存容器定义在 src/agent/src/sandbox.rs:125-126:

pub uevent_map: HashMap<String, Uevent>,
pub uevent_watchers: Vec<Option<UeventWatcher>>,

⚠️ 注意:全局监听器本身不按 subsystem 过滤,subsystem == "net" / action == "add" 的判断在每个 matcher 里做。

5. 网卡匹配器:subsystem == "net" && action == "add"

PCI 路径(x86/arm 等,非 s390x)

src/agent/src/device/network_device_handler.rs:78-86

impl UeventMatcher for NetPciMatcher {
    fn is_match(&self, uev: &Uevent) -> bool {
        uev.devpath.starts_with(self.devpath.as_str())
            && uev.subsystem == "net"
            && !uev.interface.is_empty()
            && uev.action == "add"
    }
}
  • NetPciMatcher 结构体:network_device_handler.rs:61-76,devpath = <pci root bus><pcipath 转 sysfs 相对路径>(new() 在 :69-75)
  • 要求 INTERFACE 非空,说明是网卡类型的 uevent(不是 PCI 设备本身的 uevent)

CCW 路径(s390x)

src/agent/src/device/network_device_handler.rs:117-125

impl UeventMatcher for NetCcwMatcher {
    fn is_match(&self, uev: &Uevent) -> bool {
        self.re.is_match(&uev.devpath)
            && uev.subsystem == "net"
            && !uev.interface.is_empty()
            && uev.action == "add"
    }
}

正则构造在 network_device_handler.rs:107-115:
"{root_bus_path}/0\.[0-3]\.[0-9a-f]{1,4}/{device}/virtio[0-9]+/net/"

6. 等待逻辑(含冷插兜底)

PCI:network_device_handler.rs:38-59

pub async fn wait_for_pci_net_interface(...) -> Result<()> {
    ...
    let matcher = NetPciMatcher::new(&sysfs_rel_path, root_complex);   // :46
    let pattern = format!(r"[./]+{}/[a-z0-9/]*net/[a-z0-9/]*", ...);   // :47-50
    let re = Regex::new(&pattern)...;                                  // :51
    if check_existing(re)? { return Ok(()); }                          // :52-54  ← 冷插/事件早于监听
    let _uev = wait_for_uevent(sandbox, matcher).await?;               // :56
    Ok(())
}
  • check_existing():network_device_handler.rs:19-36,扫描 SYSFS_NET_PATH(/sys/class/net)的 symlink,处理"网络在 uevent 监听启动前就已存在"的竞态
  • wait_for_ccw_net_interface():network_device_handler.rs:88-99

通用等待实现:src/agent/src/uevent.rs:114-160

  • :122-128 先查 uevent_map 里是否已有匹配事件(事件先到、请求后到)
  • :135-137 否则创建 oneshot channel 并把 (matcher, tx) 压入 uevent_watchers(持锁操作,避免漏事件)
  • :142-156 用 AGENT_CONFIG.hotplug_timeout 做超时,超时则把 watcher take() 掉并返回 "Timeout after ... waiting for uevent"

7. 上层调用入口:UpdateInterface RPC

src/agent/src/rpc.rs:1252-1287(update_interface 中)

if !interface.devicePath.is_empty() {
    #[cfg(not(target_arch = "s390x"))]
    {
        let (root_complex, pcipath) = pcipath_from_dev_tree_path(&interface.devicePath)...?;   // :1266-1273
        wait_for_pci_net_interface(&self.sandbox, root_complex, &pcipath)
            .await
            .map_ttrpc_err(|e| format!("interface not available: {e:?}"))?;                    // :1274-1276
    }
    #[cfg(target_arch = "s390x")]
    {
        let ccw_dev = ccw::Device::from_str(&interface.devicePath)...?;                        // :1279-1282
        wait_for_ccw_net_interface(&self.sandbox, &ccw_dev)
            .await
            .map_ttrpc_err(|e| format!("interface not available: {e:?}"))?;                    // :1283-1285
    }
}

import 位置:rpc.rs:73-76。等到网卡出现后才继续做 MAC 设置(rpc.rs:1291+ 的 set_link_mac_by_name)等操作。

完整调用链

main.rs:408            tokio::spawn(watch_uevents(...))
  └─ uevent.rs:162     watch_uevents()
       ├─ uevent.rs:179   socket(AF_NETLINK, NETLINK_KOBJECT_UEVENT)
       ├─ uevent.rs:187   bind(port 1)
       ├─ uevent.rs:195   recv_from_full()
       ├─ uevent.rs:214   Uevent::new(text)            (uevent.rs:42 解析 KEY=VALUE)
       └─ uevent.rs:216   event.process()              (uevent.rs:104)
            └─ uevent.rs:66  process_add()
                 ├─ uevent.rs:84  sandbox.uevent_map.insert(devpath, uev)
                 └─ uevent.rs:87  for matcher in uevent_watchers:
                                    matcher.is_match()          (network_device_handler.rs:80 / :119)
                                    sender.send(uev)            ← 唤醒等待者

rpc.rs:1274  wait_for_pci_net_interface()
  ├─ network_device_handler.rs:52  check_existing()  冷插兜底
  └─ uevent.rs:115                 wait_for_uevent()
       ├─ uevent.rs:123  uevent_map 中已有匹配事件 → 直接返回
       └─ uevent.rs:137  注册 (matcher, oneshot::tx) 到 uevent_watchers

补充说明

  • 过滤条件的三处判据(network_device_handler.rs:80-84 和 :119-123):devpath 前缀/正则匹配 + subsystem == "net" + !interface.is_empty() + action == "add"。四者缺一不匹配。
  • 网络 handler 里 "net" 是硬编码字符串,而 block handler 用的是 BLOCK 常量(src/agent/src/linux_abi.rs),这是现状而非约定。
  • 事件先到/后到的两种时序都由 uevent_map(先到)和 uevent_watchers(后到)覆盖;两者都在 Sandbox 锁内操作,避免竞态。
  • 单元测试参考:network_device_handler.rs:127-311(test_net_pci_matcher_* / test_net_ccw_matcher_*),以及 uevent.rs:249-294 的 test_wait_for_uevent。

需要我继续深入某一段(比如 pcipath_from_dev_tree_path 的路径转换,或 uevent_map 的生命周期管理)吗?

我已把宿主机侧(Go/Rust runtime)和 guest 侧(agent)的完整链路读完了。下面是“VM 外部创建/插入 tap → guest 内核生成网卡 → agent 感知并配置”的全流程源码位置。

总体时序

CNI 插件 kata-runtime(host) VMM guest kernel kata-agent
│ │ │ │ │
│ 建 veth(eth0@pod netns)│ │ │ │
├─────────────────────>│ │ │ │
│ │ ① AddInterface() │ │ │
│ │ ② createNetworkInterfacePair() │ │ │
│ │ ③ createLink(Tuntap) → tapN_kata │ │ │
│ │ + open /dev/vhost-net │ │ │
│ │ ④ tc qdisc/filter 双向 redirect │ │ │
│ │ ⑤ HotplugAddDevice(NetDev) ─────>│ device_add │ │
│ │ (SEND_FD + netdev_add) ├─────────────────>│ │
│ │ │ virtio-net │ 注册 netdev │
│ │ │ probe │ + KOBJ_ADD(net) │
│ │ │ ├─────────────────>│
│ │ │ │ netlink uevent │
│ │ ⑥ updateInterface RPC (重试) ───────────────────────────────────────>
│ │ │ │ │ ⑦ 等 uevent
│ │ │ │ │ ⑧ rtnl 配置
阶段 A:宿主机侧创建 tap(“VM 外部插入 tap”)
A1. 入口:hotplug 加网卡
源码位置 作用
src/runtime/virtcontainers/sandbox.go:1252-1288 Sandbox.AddInterface():把 pbTypes.Interface 转成 NetworkInfo 后调 network.AddEndpoints(..., hotplug=true)
sandbox.go:1222-1250 generateNetInfo():解析 HwAddr、IPAddresses(address/mask → netlink.Addr)、Mtu
sandbox.go:1259 s.network.AddEndpoints(ctx, s, []NetworkInfo{netInfo}, true) ← hotplug=true
src/runtime/virtcontainers/network_linux.go:625-660 LinuxNetwork.AddEndpoints():endpointsInfo != nil 时对每个 ep 执行 doNetNS(n.netNSPath, ... n.addSingleEndpoint(...)) ← 进入 pod netns
network_linux.go:128-261 addSingleEndpoint():按 netInfo.Iface.Type 选 endpoint 类型(veth/tap/tuntap/macvlan/macvtap/ipvlan),network_linux.go:230-239 根据 hotplug 调 HotAttach() 或 Attach()
A2. endpoint 命名与 MAC 生成
src/runtime/virtcontainers/network.go:351-379 createNetworkInterfacePair():

TapInterface{ Name: fmt.Sprintf("br%d_kata", idx), TAPIface: {Name: fmt.Sprintf("tap%d_kata", idx)} }
VirtIface: {Name: fmt.Sprintf("eth%d", idx), HardAddr: randomMacAddr}
network.go:381-395 generateRandomPrivateMacAddr():本地管理位 MAC(x2/x6/xA/xE 开头)
这个 MAC 会同时用于 host 侧 tap 和 guest 侧 virtio-net,避免宿主机 bridge 上的防火墙/反欺骗问题(见 network_linux.go:1120-1125 注释)
A3. 真正创建 tap 内核设备(三种 interworking model)
① TC filter 模型(默认)
VethEndpoint.HotAttach → xConnectVMNetwork → setupTCFiltering

源码位置 作用
src/runtime/virtcontainers/veth_endpoint.go:139-155 VethEndpoint.HotAttach():先 xConnectVMNetwork() 再 h.HotplugAddDevice(ctx, endpoint, NetDev)
network_linux.go:871-881 xConnectVMNetwork() 按 model 分派:NetXConnectTCFilterModel → setupTCFiltering();NetXConnectMacVtapModel → tapNetworkPair()
network_linux.go:1084-1154 setupTCFiltering():核心 tap 创建逻辑
network_linux.go:1096 createLink(netHandle, netPair.TAPIface.Name, &netlink.Tuntap{}, queues) ← 创建 tapN_kata
network_linux.go:1100 netPair.VMFds = fds ← 拿到 tap 的多队列 fd(每 queue 一个)
network_linux.go:1102-1108 createVhostFds(queues) → 打开 /dev/vhost-net(network_linux.go:913-916),除非 disable_vhost_net
network_linux.go:1113-1125 getLinkForEndpoint() 找到 pod netns 里的 eth0,把它的 MAC 抄给 tap
network_linux.go:1127-1133 LinkSetMTU + LinkSetUp
network_linux.go:1137-1151 双向 TC 重定向:addQdiscIngress(eth) + addQdiscIngress(tap) + addRedirectTCFilter(eth→tap) + addRedirectTCFilter(tap→eth)
network_linux.go:1162-1184 addQdiscIngress():等价 tc qdisc add dev X ingress(对 EBUSY 有 5 次线性退避重试)
network_linux.go:1192-1215 addRedirectTCFilter():等价 tc filter add dev src parent ffff: protocol all u32 match u8 0 0 action mirred egress redirect dev dst
tap 设备的实际构造
network_linux.go:729-754 createLink():

case (&netlink.Tuntap{}).Type():
flags := netlink.TUNTAP_VNET_HDR | netlink.TUNTAP_NO_PI
if queues > 0 { flags |= netlink.TUNTAP_MULTI_QUEUE_DEFAULTS }
newLink = &netlink.Tuntap{LinkAttrs{Name: name}, Mode: netlink.TUNTAP_MODE_TAP, Queues: queues, Flags: flags}
queues 由 xConnectVMNetwork 决定:caps.IsMultiQueueSupported() 为真则 = NumVCPUs()(network_linux.go:859-863)
TUNTAP_MULTI_QUEUE_DEFAULTS 让 netlink.LinkAdd() 直接返回 tap fd(network_linux.go:739-748 注释解释了为什么非多队列时必须强制 queues=1)
② macvtap 模型
network_linux.go:987-1082 tapNetworkPair():

network_linux.go:1008-1016 createMacVtap(... ParentIndex: attrs.Index ...) ← 直接在 pod netns 的 eth0 上挂 macvtap
network_linux.go:1034-1046 交换两端 MAC
network_linux.go:1052-1060 清空 veth 的 IP,避免 ARP 冲突
network_linux.go:1068 createMacvtapFds() → 打开 /dev/tap(network_linux.go:908-911)
③ 独立 tap endpoint(Type()==tap,docker --net=none 场景)
src/runtime/virtcontainers/tap_endpoint.go:105-123 TapEndpoint.HotAttach() → tap_endpoint.go:169-203 tapNetwork():

tap_endpoint.go:176 createLink(netHandle, endpoint.TapInterface.TAPIface.Name, &netlink.Tuntap{}, int(numCPUs))
tap_endpoint.go:182-187 createVhostFds()
tap_endpoint.go:195-201 MAC / MTU / LinkSetUp
⚠️ TapEndpoint.Attach()(非 hotplug)直接返回错误:tap_endpoint.go:86-88“TapEndpoint does not support Attach, if you're using docker please use --net none”。

阶段 B:把 tap fd “插入” VM(VMM 侧建 virtio-net 前端)
关键点:tap 本身从不进入 guest,交给 VMM 的只是 tap 的 fd + vhost-net 的 fd,VMM 用它创建 virtio-net 前端设备。

QEMU
源码位置 作用
src/runtime/virtcontainers/qemu.go:2690-2692 case NetDev: → q.hotplugNetDevice(ctx, device, op)
qemu.go:2601-2615 hotplugNetDevice():从 endpoint 取出 TapInterface(veth/ipvlan/macvlan/tuntap 走 NetworkPair(),tap 走 TapEndpoint.TapInterface)
qemu.go:2578-2599 hotAddNetDevice():ExecuteGetFD(fdName, VMFd) 把每个 fd 通过 SCM_RIGHTS 传给 QEMU;ExecuteGetFD(vhostfdN, ...) 传 vhost fd;最后 ExecuteNetdevAddByFds(ctx, "tap", name, VMFdNames, VhostFdNames)
qemu.go:2630-2638 QemuVirt 机器类型:直接在 root port 上热插
qemu.go:2640-2662 其他:addDeviceToBridge() 分配 PCI bridge/slot → setEndpointDevicePath() 回填 PCI path → ExecuteNetPCIDeviceAdd()(x86)或 ExecuteNetCCWDeviceAdd()(s390x,qemu.go:2658-2660)
src/runtime/pkg/govmm/qemu/qemu.go:919, :952 VETHTAP → -netdev type=tap + -device virtio-net[-pci]
setEndpointDevicePath 回填的 PCI path 就是后面 RPC 里的 devicePath(sandbox.go:1276)。

Cloud Hypervisor
源码位置 作用
src/runtime/virtcontainers/clh.go:1292-1294 case NetDev: → hotplugAddNetDevice(endpoint)
clh.go:1251-1279 hotplugAddNetDevice():addNet(e) 记录配置 → vmAddNetPut() 真正热插 → 用返回的 PCI info 回填 e.SetPciPath(...)
clh.go:2138-2170 addNet():把 netPair.VMFds 存入 clh.netDevicesFiles[mac],构造 chclient.NetConfig(MAC / rate limiter / IOMMU),append 到 clh.netDevices
runtime-rs(Rust shim)
源码位置 作用
src/runtime-rs/crates/hypervisor/src/qemu/qmp.rs:945 hotplug_network_device()(netdev_add + device_add,失败回滚 netdev_del 见 :1069)
src/runtime-rs/crates/hypervisor/src/qemu/inner.rs:1286 调用点
src/runtime-rs/crates/resource/src/network/network_pair.rs:154-179 create_link() → link::create_link(name, LinkType::Tap, queues) 创建 host 侧 tap(network_pair.rs:159)
src/runtime-rs/crates/resource/src/network/network_model/tc_filter_model.rs:44-76 双向 TC redirect(add_ingress_qdisc / traffic_filter / redirect)
阶段 C:guest 内核生成网卡(注意:不是 agent 创建)
VMM 执行 device_add virtio-net-pci / vm.add-net 后:

guest 内核收到 PCI 热插事件(pciehp)→ 枚举新设备
virtio-pci 驱动 probe → virtio_net 驱动 probe
alloc_netdev_mqs() / register_netdevice() 注册 netdev,生成 /sys/class/net/ethX
内核 kobject_uevent(KOBJ_ADD) 发出 uevent:

ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:02.0/.../virtio0/net/eth0
SUBSYSTEM=net
INTERFACE=eth0
SEQNUM=...
网卡名为什么是 eth0 而不是 ens/enp:kata 的 guest kernel cmdline 关掉了 predictable names —

源码位置 内容
src/runtime/virtcontainers/qemu_amd64.go:75 {"net.ifnames", "0"}
src/runtime/virtcontainers/qemu_ppc64le.go:46 同上
src/runtime/virtcontainers/fc.go:98 同上
src/runtime/virtcontainers/stratovirt.go:62 同上
所以 guest 侧名字与 runtime 侧 VirtIface.Name = "eth%d"(network.go:368)能对上,agent 的“按名字改名/找 link”逻辑才成立。

阶段 D:agent 感知 uevent
源码位置 作用
src/agent/src/main.rs:408 tokio::spawn(watch_uevents(sandbox.clone(), shutdown.clone()))
src/agent/src/uevent.rs:163-226 watch_uevents()
uevent.rs:178-187 socket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT) + bind(port 1)
uevent.rs:195-220 recv_from_full() → 校验 addr.port_number()0 → Uevent::new(&text) → event.process()
uevent.rs:42-63 Uevent::new() 解析 KEY=VALUE(ACTION/DEVPATH/SUBSYSTEM/DEVNAME/INTERFACE/SEQNUM)
uevent.rs:104-111 process():action == "add" → process_add()
uevent.rs:66-95 process_add():uevent_map.insert(devpath, uev);遍历 uevent_watchers 命中则 sender.send()
src/agent/src/device/network_device_handler.rs:78-86 NetPciMatcher::is_match():devpath.starts_with(..) && subsystem
"net" && !interface.is_empty() && action=="add"
network_device_handler.rs:117-125 NetCcwMatcher::is_match()(s390x)
阶段 E:runtime 发 UpdateInterface(异步 + 重试)
源码位置 作用
src/runtime/virtcontainers/sandbox.go:1276 inf.DevicePath = endpoints[0].PciPath().String() ← 把 QEMU/CLH 算出的 PCI path 填回请求
sandbox.go:1277 s.agent.updateInterface(ctx, inf)
sandbox.go:1264-1273 失败回滚:RemoveEndpoints(..., hotplug=true)
src/runtime/virtcontainers/kata_agent.go:615-658 updateInterface():retry.Do(..., Attempts(20), Delay(20ms)),只对 "Link not found" 重试(kata_agent.go:630-634)
kata_agent.go:620-623 注释说明原因:hotplug 是异步的,device_add 返回时 guest 内设备可能还没 probe 完
kata_agent.go:2521-2522 ttrpc handler 注册
src/runtime-rs/crates/resource/src/manager_inner.rs:283-324 runtime-rs 等价逻辑:update_interface 重试 10 次 × 100ms
阶段 F:agent 配置 guest 网卡
F1. RPC 入口
src/agent/src/rpc.rs:1252-1340 update_interface():

行号 逻辑
1259-1276 devicePath 非空 → pcipath_from_dev_tree_path() → wait_for_pci_net_interface()(即阶段 D 的 matcher 等待)
1279-1285 s390x:ccw::Device::from_str() → wait_for_ccw_net_interface()
1291-1331 VFIO 场景:netdev_name_from_pci_path() 按 sysfs .../net/ 目录反查 netdev → set_link_mac_by_name() 对齐 MAC(失败只 warn,回落到按 MAC 查找)
1333-1337 sandbox.rtnl.update_interface(&interface) ← 真正的配置
等待逻辑细节:network_device_handler.rs:38-59(check_existing 冷插兜底 + wait_for_uevent)、uevent.rs:114-160(先查 uevent_map,再注册 watcher,hotplug_timeout 超时)。

F2. 真正的 netlink 配置
src/agent/src/netlink.rs:82-226 Handle::update_interface():

行号 逻辑
100-110 先按 MAC 找 link(LinkFilter::Address);找不到就 set_link_mac_by_name(iface.name, hwAddr) 再找(VM template / 快照场景,注释见 :90-99)
112-115 若 link 已 UP,先 enable_link(idx, false) 拉下来
117-125 读 /proc/sys/net/ipv6/conf/{all,default}/disable_ipv6 判断是否支持 IPv6
127-142 逐个 add_addresses(link.index(), ip_network) 添加 IP
147-179 名字冲突处理:若已有同名 link,先重命名为 _temp
181-201 LinkUnspec::new_with_index(...).mtu(...).name(...).arp(...).up() 一次性设置 MTU / 名字 / ARP / UP
203-223 把临时名 _temp 换回原名
F3. 后续路由 / ARP
源码位置 作用
src/agent/src/rpc.rs:1342-1374 update_routes() → rtnl.update_routes() + list_routes()
src/runtime/virtcontainers/kata_agent.go:669+ runtime 侧 updateRoutes()
src/runtime-rs/crates/resource/src/manager_inner.rs:326-340 handle_neighbours() 添加静态 ARP
关键点 / 易混淆点
“宿主机插入 tap” ≠ “guest 里出现 tap”
host/pod netns 里有 tapN_kata;guest 里只有 virtio-net 对应的 ethN。中间桥接靠 tap fd 传进 VMM。

fd 的传递是 3 种来源

TC filter:createLink() 用 TUNTAP_MULTI_QUEUE_DEFAULTS 让 LinkAdd 返回 tap fd(network_linux.go:735-754)
macvtap:createMacvtapFds() 打开 /dev/tap(network_linux.go:908-911、:1068)
tap endpoint:同 TC filter 的 createLink()(tap_endpoint.go:176) 加上 vhost:createFds("/dev/vhost-net", queues)(network_linux.go:913-929)
MAC 三处一致性:createNetworkInterfacePair() 生成随机 MAC(network.go:354)→ host tap 继承 pod eth0 的 MAC(network_linux.go:1125)→ QEMU/CLH 用 endpoint.HardwareAddr() 下发给 virtio-net(qemu.go:2662、clh.go:2141)→ agent 用同一 MAC 找 link(netlink.rs:101)。

guest 内“创建网卡”是内核做的,agent 只做两件事:等(uevent,uevent.rs:114)+ 配(rtnl,netlink.rs:82)。没有 udev 参与重命名。

net.ifnames=0 是整条链路能对上名字的前提(qemu_amd64.go:75 等)。

不支持网卡热插的 VMM(如 Dragonball 的 virtio-mmio):NIC 冷插在 VM template 里,agent 走 netlink.rs:90-109 的“按名字改 MAC 再找”兜底。

s390x 有独立的 CCW 分支:qemu.go:2658-2660(ExecuteNetCCWDeviceAdd)、network_device_handler.rs:88-125、rpc.rs:1279-1285。

hotplug 回滚:sandbox.go:1264-1273(runtime 侧)+ qemu.go:2624-2628(netdev_del)+ qemu.go:2645-2649(removeDeviceFromBridge)。

posted @ 2026-10-23 21:48  rincloud  阅读(3)  评论(0)    收藏  举报