创建容器时候如何 用/dev/net/tun实例化为一个具体的 TAP 设备
完美。这就是从 /dev/net/tun 实例化 TAP 设备的核心内核交互代码。现在我有完整的链路了。让我把整个流程讲清楚。
从 /dev/net/tun 实例化出具体 TAP 设备 —— 完整链路
你问的是"创建容器时如何用 /dev/net/tun 实例化一个具体的 TAP 设备"。这其实是两层的协作:Kata 业务代码(createLink)决定 TAP 的参数,netlink 库(linkModify)执行内核 ioctl 真正实例化。
第 1 步:Kata 组装 TAP 配置(createLink,network_linux.go:729-786)
Kata 在创建网络端点时(setupTCFiltering → createLink),构造一个 netlink.Tuntap 结构体描述想要的 TAP 设备:
// network_linux.go:734-754
case (&netlink.Tuntap{}).Type():
flags := netlink.TUNTAP_VNET_HDR | netlink.TUNTAP_NO_PI // 带 vnet header、无 packet info
if queues > 0 {
flags |= netlink.TUNTAP_MULTI_QUEUE_DEFAULTS // 多队列
} else {
queues = 1 // 至少 1 个队列
}
newLink = &netlink.Tuntap{
LinkAttrs: netlink.LinkAttrs{Name: name}, // 设备名(如 tapXXX)
Mode: netlink.TUNTAP_MODE_TAP, // = IFF_TAP (2),类型是 TAP
Queues: queues, // 队列数
Flags: flags,
}
然后调用 netHandle.LinkAdd(newLink)(network_linux.go:775)——这一步触发内核实例化。
第 2 步:netlink 库打开 /dev/net/tun 并做 TUNSETIFF ioctl(linkModify,netlink@v1.3.1 link_linux.go:1392-1480)
LinkAdd 最终调用 linkModify。这里就是真正"把 /dev/net/tun 实例化成 TAP"的关键:
// link_linux.go:1430-1447 核心片段
req.Flags |= uint16(tuntap.Mode) // req.Flags |= IFF_TAP,加上 IFF_NO_PI | IFF_VNET_HDR
const TUN = "/dev/net/tun"
for i := 0; i < queues; i++ { // 每个队列一个 fd
fd, err := unix.Open(TUN, os.O_RDWR|syscall.O_CLOEXEC, 0) // ① 打开 /dev/net/tun
if err != nil { ... }
// ② 关键 ioctl:把 (ifreq{name, flags}) 发给内核
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.TUNSETIFF),
uintptr(unsafe.Pointer(&localReq)))
if errno != 0 { ... }
// ③ 可选:设置 owner/group 权限
// ④ 设为非阻塞
unix.SetNonblock(fd, true)
...
fds = append(fds, os.NewFile(uintptr(fd), TUN)) // 把 fd 包装成 *os.File
}
内核侧发生了什么(TUNSETIFF 的作用)
ioctl(fd, TUNSETIFF, &ifreq) 是 Linux tun 驱动提供的关键操作。/dev/net/tun 本身只是一个字符设备,打开它得到的 fd 还"不知道"自己要变成什么。TUNSETIFF 通过 struct ifreq 告诉内核:
struct ifreq {
char ifr_name[IFNAMSIZ]; // 想要创建的接口名(如 "tap0")
short ifr_flags; // IFF_TAP | IFF_NO_PI | IFF_VNET_HDR | IFF_MULTI_QUEUE
};
内核 tun_chr_ioctl → tun_set_iff() 会:
- 检查
CAP_NET_ADMIN权限 - 用
ifr_name和IFF_TAP在当前网络命名空间里查找或创建一个 TAP 网络接口(tun_net_xmit挂接) - 把这个 fd 绑定到该 TAP 接口 —— 从此 fd 就是该 TAP 设备的句柄
所以一个 /dev/net/tun fd + 一次 TUNSETIFF ioctl = 一个具体的 TAP 接口。同一个 /dev/net/tun 可以多次打开、每次用不同 name/flags 实例化出不同的 TAP。
第 3 步:拿到 fd 后传回给 VM(衔接前面的话题)
linkModify 返回 fds(每个队列一个 *os.File):
- 在
createLink(network_linux.go:779-782)里取出tuntapLink.Fds - 在
setupTCFiltering(network_linux.go:1100)里netPair.VMFds = fds - 最后通过
addNet→clh.netDevicesFiles[mac] = netPair.VMFds(clh.go:2173)→vmAddNetPutRequest用syscall.UnixRights(fds...)(clh.go:233)把 tap fd 传给 CLH
为什么这里用 TC-filtering 模型而非 vhost
setupTCFiltering(network_linux.go:1084)是 Kata 默认的网络互操作模型之一。它直接创建 TAP 并把 fd 给 VM(CLH 侧做 virtio-net),而不启用 vhost-net(disableVhostNet 时跳过 createVhostFds,network_linux.go:1102-1108)。
完整调用链总结
创建容器 → createNetwork → AddEndpoints → VethEndpoint.Attach
→ AddDevice(NetDev) → clh.addNet
→ setupTCFiltering (network_linux.go:1084)
→ createLink(&netlink.Tuntap{...}) (network_linux.go:729)
→ netHandle.LinkAdd(newLink) (network_linux.go:775)
→ linkModify (netlink 库)
→ unix.Open("/dev/net/tun") ← 打开字符设备
→ ioctl(TUNSETIFF, ifreq) ← 实例化为具体 TAP 接口(关键)
→ 返回 fds (每个队列一个 tap fd)
→ netPair.VMFds = fds (network_linux.go:1100)
→ clh.netDevicesFiles[mac] = VMFds (clh.go:2173)
→ vmAddNetPutRequest:
oob = syscall.UnixRights(fds...) (clh.go:233) ← fd 传给 CLH
一句话总结:Kata 通过 netlink.Tuntap 配置(名字、IFF_TAP 类型、队列、IFF_NO_PI|IFF_VNET_HDR 标志),调用 LinkAdd;netlink 库据此 open("/dev/net/tun") 后对每个队列执行一次 ioctl(TUNSETIFF, ifreq),内核据此在当前 netns 中创建并绑定一个具体的 TAP 接口,返回的 fd 就是该 TAP 的句柄,随后经 SCM_RIGHTS 传给 CLH 供 virtio-net 使用。

浙公网安备 33010602011771号