Repository: infrastructure-io/topohub
Pull request #102: fix  conflict between  subnet network and  host network
Author: login: JiaweiGithub
Labels: name: release/bug
Created: 2025-04-17T11:16:31Z | Merged: 2025-04-29T03:37:03Z

添加了校验：新建子网时，判断子网ip在子网选择的主机网卡（hostnetork）网段内时，只允许新建子网指定的ip和子网掩码和选择网卡完全一致才让通过，也不允许创建vlan子接口，否则可能会和主机上的已有网络冲突



Diff:
diff --git a/Makefile.def b/Makefile.def
index 65ca6753..fa15b05d 100644
--- a/Makefile.def
+++ b/Makefile.def
@@ -50,6 +50,12 @@ GIT_COMMIT_TIME = $(shell git show -s --format='format:%aI')
 
 DOCKER_BUILD_FLAGS := --build-arg GIT_COMMIT_VERSION=$(GIT_COMMIT_VERSION) --build-arg GIT_COMMIT_TIME=$(GIT_COMMIT_TIME) --build-arg PROJECT_VERSION=$(PROJECT_VERSION)
 
+#================== kind configuration
+KIND_NETWORK_SUBNET := 192.168.1.0/24
+KIND_NETWORK_GATEWAY := 192.168.1.1
+KIND_CONTROL_IP := 192.168.1.2
+KIND_WORKER1_IP := 192.168.1.3
+KIND_WORKER2_IP := 192.168.1.4
 
 #====== pyroscope
 PYROSCOPE_CONTAINER_NAME := e2e-pyroscope
diff --git a/pkg/subnet/dhcpserver/interface.go b/pkg/subnet/dhcpserver/interface.go
index e475d750..a2d2da9f 100644
--- a/pkg/subnet/dhcpserver/interface.go
+++ b/pkg/subnet/dhcpserver/interface.go
@@ -2,8 +2,8 @@ package dhcpserver
 
 import (
 	"fmt"
-	"strings"
 
+	"github.com/infrastructure-io/topohub/pkg/tools"
 	"github.com/vishvananda/netlink"
 )
 
@@ -32,6 +32,11 @@ func (s *dhcpServer) setupInterface() error {
 		}
 	}
 
+	// 验证接口配置是否和主机网络冲突
+	if err := tools.ValidateHostInterfaceSubnet(parent, &s.subnet.Spec.Interface); err != nil {
+		return fmt.Errorf("interface configuration conflicts with host network: %v", err)
+	}
+
 	// 根据配置创建接口
 	if s.subnet.Spec.Interface.VlanID != nil && *s.subnet.Spec.Interface.VlanID > 0 {
 		s.log.Infof("Creating VLAN interface: %s.topohub.%d on vlan %d", baseInterface, *s.subnet.Spec.Interface.VlanID, *s.subnet.Spec.Interface.VlanID)
@@ -80,9 +85,9 @@ func (s *dhcpServer) createVlanInterface(parent netlink.Link, name string, vlanI
 		},
 		VlanId: vlanId,
 	}
-	s.log.Infof("Creating VLAN interface: %+v",  vlan )
+	s.log.Infof("Creating VLAN interface: %+v", vlan)
 
-	if len(name)>15 {
+	if len(name) > 15 {
 		return fmt.Errorf("interface name %s is too long, it is must be less than 15 letters", name)
 	}
 
@@ -161,27 +166,8 @@ func (s *dhcpServer) configureIP(name, ipStr string) error {
 	return nil
 }
 
-// cleanupAllInterface removes all topohub interfaces on the base interface
+// TODO cleanupAllInterface removes all topohub interfaces on the base interface
 func (s *dhcpServer) cleanupAllInterface() error {
-	baseInterface := s.subnet.Spec.Interface.Interface
-
-	// 获取所有网络接口
-	links, err := netlink.LinkList()
-	if err != nil {
-		return fmt.Errorf("failed to list interfaces: %v", err)
-	}
-
-	// 查找并删除所有带有 .topohub 前缀的接口
-	for _, link := range links {
-		name := link.Attrs().Name
-		if strings.HasPrefix(name, baseInterface+".topohub") {
-			s.log.Debugf("Removing interface: %s", name)
-			if err := netlink.LinkDel(link); err != nil {
-				s.log.Warnf("Failed to delete interface %s: %v", name, err)
-			}
-		}
-	}
-
 	return nil
 }
 
diff --git a/pkg/tools/tools.go b/pkg/tools/tools.go
index 1971c3ac..dc1db0b7 100644
--- a/pkg/tools/tools.go
+++ b/pkg/tools/tools.go
@@ -6,6 +6,9 @@ import (
 	"net"
 	"regexp"
 	"strings"
+
+	topohubv1beta1 "github.com/infrastructure-io/topohub/pkg/k8s/apis/topohub.infrastructure.io/v1beta1"
+	"github.com/vishvananda/netlink"
 )
 
 // ValidateIPInSubnet checks if an IP address is within a subnet
@@ -309,7 +312,7 @@ func IsValidIPv4(ipStr string) bool {
 func IsValidUnicastMAC(macStr string) bool {
 	// Replace hyphens with colons for consistent parsing
 	macStr = strings.ReplaceAll(macStr, "-", ":")
-	
+
 	// Handle MAC address without separators
 	if len(macStr) == 12 && !strings.Contains(macStr, ":") {
 		// Insert colons every 2 characters
@@ -322,18 +325,18 @@ func IsValidUnicastMAC(macStr string) bool {
 		}
 		macStr = buffer.String()
 	}
-	
+
 	// Parse MAC address
 	mac, err := net.ParseMAC(macStr)
 	if err != nil {
 		return false
 	}
-	
+
 	// Check if it's a 48-bit MAC address
 	if len(mac) != 6 {
 		return false
 	}
-	
+
 	// Check if it's a unicast address (least significant bit of first octet is 0)
 	return (mac[0] & 1) == 0
 }
@@ -397,3 +400,46 @@ func ipToUint32(ip net.IP) uint32 {
 	ip = ip.To4()
 	return uint32(ip[0])<<24 | uint32(ip[1])<<16 | uint32(ip[2])<<8 | uint32(ip[3])
 }
+
+// ValidateHostInterfaceSubnet validates the matching relationship between host interface and subnet configuration
+// When the subnet IP is within the host network interface's subnet, it only allows the new subnet's IP and
+// subnet mask to exactly match the selected interface's configuration
+// avoid affecting the network of the host machine
+// Example:
+//   - Input:
+//     parent: netlink.Link
+//     iface: *topohubv1beta1.InterfaceSpec
+//   - Returns: nil if subnet IP matches the host interface
+//   - Error case: Returns error if interface does not exist, subnet IP is invalid or does not match
+func ValidateHostInterfaceSubnet(parent netlink.Link, iface *topohubv1beta1.InterfaceSpec) error {
+	// Get host interface IP addresses and subnet information
+	hostAddrs, err := netlink.AddrList(parent, netlink.FAMILY_V4)
+	if err != nil {
+		return fmt.Errorf("failed to get host interface IP addresses: %v", err)
+	}
+
+	// Parse subnet IP address
+	subnetAddr, err := netlink.ParseAddr(iface.IPv4)
+	if err != nil {
+		return fmt.Errorf("invalid subnet IP address %s: %v", iface.IPv4, err)
+	}
+
+	// Check if subnet IP is in the same subnet as host interface
+	for _, hostAddr := range hostAddrs {
+		// Check if in the same subnet
+		if hostAddr.IPNet.Contains(subnetAddr.IP) {
+			if iface.VlanID != nil && *iface.VlanID > 0 {
+				return fmt.Errorf("subnet IP %s is in the same subnet as host interface %s (%s), but VLAN ID is not allowed",
+					iface.IPv4, iface.Interface, hostAddr.String())
+			}
+			// If in the same subnet, check if IP addresses and subnet masks match exactly
+			if !hostAddr.Equal(*subnetAddr) {
+				return fmt.Errorf("subnet IP %s is in the same subnet as host interface %s (%s), but IP address or subnet mask doesn't match exactly",
+					iface.IPv4, iface.Interface, hostAddr.String())
+			}
+			return nil
+		}
+	}
+
+	return nil
+}
diff --git a/pkg/webhook/subnet/webhook.go b/pkg/webhook/subnet/webhook.go
index 18089c3e..081e2f88 100644
--- a/pkg/webhook/subnet/webhook.go
+++ b/pkg/webhook/subnet/webhook.go
@@ -17,6 +17,7 @@ import (
 	topohubv1beta1 "github.com/infrastructure-io/topohub/pkg/k8s/apis/topohub.infrastructure.io/v1beta1"
 	"github.com/infrastructure-io/topohub/pkg/log"
 	"github.com/infrastructure-io/topohub/pkg/tools"
+	"github.com/vishvananda/netlink"
 )
 
 // +kubebuilder:webhook:path=/validate-topohub-infrastructure-io-v1beta1-subnet,mutating=true,failurePolicy=fail,sideEffects=None,groups=topohub.infrastructure.io,resources=subnets,verbs=create;update,versions=v1beta1,name=vsubnet.kb.io,admissionReviewVersions=v1
@@ -192,6 +193,7 @@ func (w *SubnetWebhook) validateSubnet(ctx context.Context, subnet *topohubv1bet
 	return nil
 }
 
+// TODO some validations not applicable multiple agents.
 // validateInterface validates the InterfaceSpec
 func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, cidr *net.IPNet, subnet *topohubv1beta1.Subnet) error {
 	if iface == nil {
@@ -215,6 +217,17 @@ func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, c
 		return fmt.Errorf("interface IPv4 validation failed: %v", err)
 	}
 
+	// Get host interface
+	parent, err := netlink.LinkByName(iface.Interface)
+	if err != nil {
+		return fmt.Errorf("failed to get host interface: %v", err)
+	}
+
+	// Validate host interface subnet
+	if err := tools.ValidateHostInterfaceSubnet(parent, iface); err != nil {
+		return fmt.Errorf("interface IPv4 validation failed: %v", err)
+	}
+
 	// List all existing subnets to check for interface conflicts
 	existingSubnets := &topohubv1beta1.SubnetList{}
 	if err := w.Client.List(context.Background(), existingSubnets); err != nil {
diff --git a/test/Makefile b/test/Makefile
index d45b4b7c..3389b65c 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -15,10 +15,10 @@ init:
 	@echo "storageclass local-path does not allow pods with a specified NodeName, so it makes all pods to run on the worker node"
 	kubectl taint node $(KIND_CLUSTER_NAME)-control-plane local-path=nopod:NoSchedule
 	docker network rm $(KIND_CLUSTER_NAME)-secondary || true
-	docker network create --driver=bridge --subnet=192.168.1.0/24 --gateway=192.168.1.1 $(KIND_CLUSTER_NAME)-secondary
-	docker network connect $(KIND_CLUSTER_NAME)-secondary $(KIND_CLUSTER_NAME)-control-plane
-	docker network connect $(KIND_CLUSTER_NAME)-secondary $(KIND_CLUSTER_NAME)-worker
-	docker network connect $(KIND_CLUSTER_NAME)-secondary $(KIND_CLUSTER_NAME)-worker2
+	docker network create --driver=bridge --subnet=$(KIND_NETWORK_SUBNET) --gateway=$(KIND_NETWORK_GATEWAY) $(KIND_CLUSTER_NAME)-secondary
+	docker network connect --ip=$(KIND_CONTROL_IP) $(KIND_CLUSTER_NAME)-secondary $(KIND_CLUSTER_NAME)-control-plane
+	docker network connect --ip=$(KIND_WORKER1_IP) $(KIND_CLUSTER_NAME)-secondary $(KIND_CLUSTER_NAME)-worker
+	docker network connect --ip=$(KIND_WORKER2_IP) $(KIND_CLUSTER_NAME)-secondary $(KIND_CLUSTER_NAME)-worker2
 
 installSpiderpool:
 	@echo "Installing spiderpool..."
diff --git a/test/scripts/install-topohub.sh b/test/scripts/install-topohub.sh
index 63c1ab5e..029b985a 100755
--- a/test/scripts/install-topohub.sh
+++ b/test/scripts/install-topohub.sh
@@ -24,7 +24,6 @@ helm uninstall topohub -n topohub --wait &>/dev/null || true
 
 echo "run topo on worker nodes"
 kubectl label node ${CLUSTER_NAME}-worker topohub=true
-kubectl label node ${CLUSTER_NAME}-worker2 topohub=true
 
 cat <<EOF >/tmp/topo.yaml
 replicaCount: 1


Reviews:
user: login: cyclinder
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T02:02:10Z
user: login: JiaweiGithub
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-20T12:02:28Z
user: login: cyclinder
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T01:50:17Z
user: login: cyclinder
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T01:51:48Z
user: login: cyclinder
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-21T06:01:34Z
user: login: weizhoublue
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T06:13:48Z
user: login: cyclinder
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T07:17:10Z
user: login: cyclinder
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T07:18:42Z
user: login: cyclinder
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T07:40:28Z
user: login: JiaweiGithub
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T08:16:01Z
user: login: weizhoublue
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T02:58:39Z
user: login: weizhoublue
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T02:05:20Z
user: login: weizhoublue
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T03:21:14Z
user: login: cyclinder
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-29T03:36:18Z

Review comments:
diff_hunk: @@ -215,6 +216,44 @@ func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, c
 		return fmt.Errorf("interface IPv4 validation failed: %v", err)
 	}
 
+	// Get host interface
+	parent, err := netlink.LinkByName(iface.Interface)
+	if err != nil {
+		return fmt.Errorf("failed to get host interface: %v", err)
+	}
+
+	// Get host interface IP addresses and subnet information
+	hostAddrs, err := netlink.AddrList(parent, netlink.FAMILY_V4)
+	if err != nil {
+		return fmt.Errorf("failed to get host interface IP addresses: %v", err)
+	}
+
+	// Parse subnet IP address
+	subnetIP := iface.IPv4
+	subnetAddr, err := netlink.ParseAddr(subnetIP)
+	if err != nil {
+		return fmt.Errorf("invalid subnet IP address %s: %v", subnetIP, err)
+	}
+
+	// Check if subnet IP is in the same subnet as host interface
+	for _, hostAddr := range hostAddrs {
+		// Check if in the same subnet
+		if hostAddr.IPNet.Contains(subnetAddr.IP) {
+			if iface.VlanID != nil && *iface.VlanID > 0 {
+				return fmt.Errorf("subnet IP %s is in the same subnet as host interface %s (%s), but VLAN ID is not allowed",
+					subnetIP, iface.Interface, hostAddr.String())
+			}
+			// If in the same subnet, check if IP addresses match exactly
+			if !hostAddr.Equal(*subnetAddr) {
path: pkg/webhook/subnet/webhook.go
user: login: cyclinder
body: 你这里是检查子网而不是 IP 地址吧？hostAddr.Equal
created_at: 2025-04-18T02:02:02Z
author_association: COLLABORATOR
diff_hunk: @@ -215,6 +216,44 @@ func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, c
 		return fmt.Errorf("interface IPv4 validation failed: %v", err)
 	}
 
+	// Get host interface
+	parent, err := netlink.LinkByName(iface.Interface)
+	if err != nil {
+		return fmt.Errorf("failed to get host interface: %v", err)
+	}
+
+	// Get host interface IP addresses and subnet information
+	hostAddrs, err := netlink.AddrList(parent, netlink.FAMILY_V4)
+	if err != nil {
+		return fmt.Errorf("failed to get host interface IP addresses: %v", err)
+	}
+
+	// Parse subnet IP address
+	subnetIP := iface.IPv4
+	subnetAddr, err := netlink.ParseAddr(subnetIP)
+	if err != nil {
+		return fmt.Errorf("invalid subnet IP address %s: %v", subnetIP, err)
+	}
+
+	// Check if subnet IP is in the same subnet as host interface
+	for _, hostAddr := range hostAddrs {
+		// Check if in the same subnet
+		if hostAddr.IPNet.Contains(subnetAddr.IP) {
+			if iface.VlanID != nil && *iface.VlanID > 0 {
+				return fmt.Errorf("subnet IP %s is in the same subnet as host interface %s (%s), but VLAN ID is not allowed",
+					subnetIP, iface.Interface, hostAddr.String())
+			}
+			// If in the same subnet, check if IP addresses match exactly
+			if !hostAddr.Equal(*subnetAddr) {
path: pkg/webhook/subnet/webhook.go
user: login: JiaweiGithub
body: <img width="637" alt="image" src="https://github.com/user-attachments/assets/be3907f1-b6b3-4caa-8cd5-210e8f0343fc" />

这个方法没有问题 它是ip和子网都检查了
created_at: 2025-04-20T12:02:28Z
author_association: COLLABORATOR
diff_hunk: @@ -215,6 +216,44 @@ func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, c
 		return fmt.Errorf("interface IPv4 validation failed: %v", err)
 	}
 
+	// Get host interface
+	parent, err := netlink.LinkByName(iface.Interface)
+	if err != nil {
+		return fmt.Errorf("failed to get host interface: %v", err)
+	}
+
+	// Get host interface IP addresses and subnet information
+	hostAddrs, err := netlink.AddrList(parent, netlink.FAMILY_V4)
+	if err != nil {
+		return fmt.Errorf("failed to get host interface IP addresses: %v", err)
+	}
+
+	// Parse subnet IP address
+	subnetIP := iface.IPv4
+	subnetAddr, err := netlink.ParseAddr(subnetIP)
+	if err != nil {
+		return fmt.Errorf("invalid subnet IP address %s: %v", subnetIP, err)
+	}
+
+	// Check if subnet IP is in the same subnet as host interface
+	for _, hostAddr := range hostAddrs {
+		// Check if in the same subnet
+		if hostAddr.IPNet.Contains(subnetAddr.IP) {
+			if iface.VlanID != nil && *iface.VlanID > 0 {
+				return fmt.Errorf("subnet IP %s is in the same subnet as host interface %s (%s), but VLAN ID is not allowed",
+					subnetIP, iface.Interface, hostAddr.String())
+			}
+			// If in the same subnet, check if IP addresses match exactly
+			if !hostAddr.Equal(*subnetAddr) {
path: pkg/webhook/subnet/webhook.go
user: login: cyclinder
body: 看注释 if both addrs have the same net.IPNet value. 需要比较 IP 
created_at: 2025-04-21T01:50:17Z
author_association: COLLABORATOR
diff_hunk: @@ -16,9 +16,9 @@ init:
 	kubectl taint node $(KIND_CLUSTER_NAME)-control-plane local-path=nopod:NoSchedule
 	docker network rm $(KIND_CLUSTER_NAME)-secondary || true
 	docker network create --driver=bridge --subnet=192.168.1.0/24 --gateway=192.168.1.1 $(KIND_CLUSTER_NAME)-secondary
-	docker network connect $(KIND_CLUSTER_NAME)-secondary $(KIND_CLUSTER_NAME)-control-plane
-	docker network connect $(KIND_CLUSTER_NAME)-secondary $(KIND_CLUSTER_NAME)-worker
-	docker network connect $(KIND_CLUSTER_NAME)-secondary $(KIND_CLUSTER_NAME)-worker2
+	docker network connect --ip=192.168.1.2 $(KIND_CLUSTER_NAME)-secondary $(KIND_CLUSTER_NAME)-control-plane
path: test/Makefile
user: login: cyclinder
body: 不建议 hardcode IP, 如果docker没有默认值，可以通过一个变量来声明
created_at: 2025-04-21T01:51:48Z
author_association: COLLABORATOR
diff_hunk: @@ -215,6 +216,44 @@ func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, c
 		return fmt.Errorf("interface IPv4 validation failed: %v", err)
 	}
 
+	// Get host interface
+	parent, err := netlink.LinkByName(iface.Interface)
path: pkg/webhook/subnet/webhook.go
user: login: weizhoublue
body: webhook 的 方案 是有漏洞的，
这又变成了另外一个 部署要求 ，controller 和 agent 务必是 host network ， 且 在同一节点
created_at: 2025-04-21T06:13:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -215,6 +216,44 @@ func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, c
 		return fmt.Errorf("interface IPv4 validation failed: %v", err)
 	}
 
+	// Get host interface
+	parent, err := netlink.LinkByName(iface.Interface)
path: pkg/webhook/subnet/webhook.go
user: login: cyclinder
body: 目前 topohub 只有 agent？ https://github.com/infrastructure-io/topohub/blob/main/chart/templates/deployment.yaml 而且使用的是 hostNetwork
created_at: 2025-04-21T07:17:10Z
author_association: COLLABORATOR
diff_hunk: @@ -215,6 +216,44 @@ func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, c
 		return fmt.Errorf("interface IPv4 validation failed: %v", err)
 	}
 
+	// Get host interface
+	parent, err := netlink.LinkByName(iface.Interface)
path: pkg/webhook/subnet/webhook.go
user: login: cyclinder
body: 但确实 agent 也可能运行在 多个节点，放在 webhook 不太合适，应该在将 subnet assign 到 节点之前做一个判断
created_at: 2025-04-21T07:18:42Z
author_association: COLLABORATOR
diff_hunk: @@ -215,6 +216,44 @@ func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, c
 		return fmt.Errorf("interface IPv4 validation failed: %v", err)
 	}
 
+	// Get host interface
+	parent, err := netlink.LinkByName(iface.Interface)
path: pkg/webhook/subnet/webhook.go
user: login: cyclinder
body: 看之前也在 webhook 中做了类似的检测：https://github.com/infrastructure-io/topohub/blob/c792184c674807e157b89a85907a873953944baf/pkg/webhook/subnet/webhook.go#L188
created_at: 2025-04-21T07:40:27Z
author_association: COLLABORATOR
diff_hunk: @@ -215,6 +216,44 @@ func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, c
 		return fmt.Errorf("interface IPv4 validation failed: %v", err)
 	}
 
+	// Get host interface
+	parent, err := netlink.LinkByName(iface.Interface)
path: pkg/webhook/subnet/webhook.go
user: login: JiaweiGithub
body: 1.多agent目前有很多问题（比如webhook服务漂移，只有某个agent满足子网条件的轮询）是否可以先不考虑，只支持单agent
2.非host network的情况下，感觉也不好处理，是否可以先考虑就支持host network
3.如果在单agent和host network下，这个pr应该没问题  
created_at: 2025-04-21T08:16:01Z
author_association: COLLABORATOR
diff_hunk: @@ -215,6 +216,44 @@ func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, c
 		return fmt.Errorf("interface IPv4 validation failed: %v", err)
 	}
 
+	// Get host interface
+	parent, err := netlink.LinkByName(iface.Interface)
path: pkg/webhook/subnet/webhook.go
user: login: weizhoublue
body: make sense
created_at: 2025-04-23T02:58:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -215,6 +217,17 @@ func (w *SubnetWebhook) validateInterface(iface *topohubv1beta1.InterfaceSpec, c
 		return fmt.Errorf("interface IPv4 validation failed: %v", err)
 	}
 
+	// Get host interface
+	parent, err := netlink.LinkByName(iface.Interface)
+	if err != nil {
path: pkg/webhook/subnet/webhook.go
user: login: weizhoublue
body: 我建议 这里 返回给用户的错误中，加上如下信息，以将来 出现 多 agent 时，能快速回忆和识别出目前的考虑
“来自 哪个 node 的 agent 校验 。。。。。网卡不满足”
created_at: 2025-04-25T02:05:20Z
author_association: CONTRIBUTOR

PR comments:
user: login: weizhoublue
created_at: 2025-04-21T06:12:40Z
body: 这个 pr 没有相关的 文档说明，管理员是黑盒
这种到底是什么样的限制和允许
 conflict between subnet network and host network
author_association: CONTRIBUTOR
user: login: weizhoublue
created_at: 2025-04-23T02:58:32Z
body: wehbhook 只是确保了 crd 创建时的 没有 冲突，但 agent 在生效过程中，务必也要进行校验，以确保 后续 管理员在主机上 就行 网卡变更
author_association: CONTRIBUTOR
user: login: JiaweiGithub
created_at: 2025-04-24T06:51:39Z
body: > wehbhook 只是确保了 crd 创建时的 没有 冲突，但 agent 在生效过程中，务必也要进行校验，以确保 后续 管理员在主机上 就行 网卡变更

已修改
author_association: COLLABORATOR