Skip to content

Commit c7b50c6

Browse files
committed
host-device: copy host interface IP addresses and routes into container
Add a new configuration option `useInterfaceNetwork` that instructs the host-device plugin to capture the interface's IP addresses and routes from the host before moving the device into the container namespace, and then apply them inside the container. This is critical for virtual environments (AWS, IBM Cloud, GPC) where the cloud provider configures IP addresses and routes directly on the network device. In these environments, there is no traditional IPAM source; the ground truth for L3 configuration lives on the host interface itself. When `useInterfaceNetwork` is enabled, the plugin: - Captures all global-scope addresses and non-local routes from the host device before moving it into the container namespace. - Applies the captured addresses and routes to the interface inside the container. - Reports the addresses and routes in the CNI result (merged with any IPAM result if an IPAM plugin is also configured). NOTE: The interface configuration on the host node must be persistent. When the device is moved back to the host (via DEL) and renamed to its original name, the system's network management service (e.g. NetworkManager, systemd-networkd, cloud-init, or cloud-specific agents) is expected to detect the device and re-apply the IP addresses and routes. This plugin does NOT re-configure the host interface on DEL; it relies on the node's network configuration being declarative and reconciled by the platform's networking stack. Also implements the STATUS command to verify the host device exists, replacing the previous TODO stub. Signed-off-by: Sebastian Sch <sebassch@gmail.com>
1 parent 33cc6bd commit c7b50c6

5 files changed

Lines changed: 765 additions & 5 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ gopath/
2828
.vagrant
2929
.idea
3030
/release-*
31+
host-device

plugins/main/host-device/host-device.go

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ type NetConf struct {
5757
RuntimeConfig struct {
5858
DeviceID string `json:"deviceID,omitempty"`
5959
} `json:"runtimeConfig,omitempty"`
60+
// When true, capture the host interface's IP addresses and routes and apply
61+
// them inside the container. Useful in cloud/virtual environments where L3
62+
// config is provisioned directly on the host device. Can be combined with
63+
// IPAM to add extra addresses or routes on top of the host-provided ones.
64+
UseInterfaceNetwork bool `json:"useInterfaceNetwork,omitempty"`
6065

6166
// for internal use
6267
auxDevice string `json:"-"` // Auxiliary device name as appears on Auxiliary bus (/sys/bus/auxiliary)
@@ -125,6 +130,12 @@ func cmdAdd(args *skel.CmdArgs) error {
125130
if err != nil {
126131
return err
127132
}
133+
134+
interfaceNetworkEnabled := useInterfaceNetwork(cfg)
135+
if interfaceNetworkEnabled && cfg.DPDKMode {
136+
return fmt.Errorf("useInterfaceNetwork is not supported for dpdk-bound devices")
137+
}
138+
128139
containerNs, err := ns.GetNS(args.Netns)
129140
if err != nil {
130141
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
@@ -138,12 +149,24 @@ func cmdAdd(args *skel.CmdArgs) error {
138149
}}
139150

140151
var contDev netlink.Link
152+
var networkState *HostNetworkState
141153
if !cfg.DPDKMode {
142154
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr, cfg.auxDevice)
143155
if err != nil {
144156
return fmt.Errorf("failed to find host device: %v", err)
145157
}
146158

159+
networkState = &HostNetworkState{
160+
HostIfName: hostDev.Attrs().Name,
161+
HostLinkWasUp: hostDev.Attrs().Flags&net.FlagUp == net.FlagUp,
162+
}
163+
if interfaceNetworkEnabled {
164+
err = captureHostNetworkState(networkState, hostDev)
165+
if err != nil {
166+
return err
167+
}
168+
}
169+
147170
contDev, err = moveLinkIn(hostDev, containerNs, args.IfName)
148171
if err != nil {
149172
return fmt.Errorf("failed to move link %v", err)
@@ -153,6 +176,15 @@ func cmdAdd(args *skel.CmdArgs) error {
153176
result.Interfaces[0].Name = contDev.Attrs().Name
154177
// Set the MAC address of the interface
155178
result.Interfaces[0].Mac = contDev.Attrs().HardwareAddr.String()
179+
180+
if interfaceNetworkEnabled {
181+
if err := networkState.applyToPod(containerNs, contDev); err != nil {
182+
return err
183+
}
184+
if cfg.IPAM.Type == "" {
185+
return printLinkWithNetworkState(contDev, cfg.CNIVersion, containerNs, networkState)
186+
}
187+
}
156188
}
157189

158190
if cfg.IPAM.Type == "" {
@@ -181,7 +213,7 @@ func cmdAdd(args *skel.CmdArgs) error {
181213
return err
182214
}
183215

184-
if len(newResult.IPs) == 0 {
216+
if !interfaceNetworkEnabled && len(newResult.IPs) == 0 {
185217
return errors.New("IPAM plugin returned missing IP config")
186218
}
187219

@@ -201,6 +233,10 @@ func cmdAdd(args *skel.CmdArgs) error {
201233
}
202234
}
203235

236+
if interfaceNetworkEnabled {
237+
mergeNetworkStateIntoResult(newResult, networkState)
238+
}
239+
204240
newResult.DNS = cfg.DNS
205241

206242
return types.PrintResult(newResult, cfg.CNIVersion)
@@ -496,6 +532,76 @@ func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error
496532
return types.PrintResult(&result, cniVersion)
497533
}
498534

535+
func routeStateToCNIRoute(route routeState) *types.Route {
536+
var dst net.IPNet
537+
if route.Destination == "default" {
538+
var gw net.IP
539+
if route.Gateway != "" {
540+
gw = net.ParseIP(route.Gateway)
541+
}
542+
dst = net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)}
543+
if gw != nil && gw.To4() == nil {
544+
dst = net.IPNet{IP: net.IPv6zero, Mask: net.CIDRMask(0, 128)}
545+
}
546+
} else {
547+
_, parsedDst, err := net.ParseCIDR(route.Destination)
548+
if err != nil {
549+
return nil
550+
}
551+
dst = *parsedDst
552+
}
553+
554+
cniRoute := &types.Route{Dst: dst}
555+
if route.Gateway != "" {
556+
cniRoute.GW = net.ParseIP(route.Gateway)
557+
}
558+
if route.Table != 0 {
559+
cniRoute.Table = current.Int(route.Table)
560+
}
561+
if route.Scope != 0 {
562+
cniRoute.Scope = current.Int(int(route.Scope))
563+
}
564+
cniRoute.Priority = route.Metric
565+
return cniRoute
566+
}
567+
568+
func mergeNetworkStateIntoResult(result *current.Result, state *HostNetworkState) {
569+
if state == nil {
570+
return
571+
}
572+
for _, addr := range state.Addresses {
573+
hostIP, ipNet, err := net.ParseCIDR(addr)
574+
if err != nil {
575+
continue
576+
}
577+
ipNet.IP = hostIP
578+
result.IPs = append(result.IPs, &current.IPConfig{
579+
Interface: current.Int(0),
580+
Address: *ipNet,
581+
})
582+
}
583+
for _, route := range state.Routes {
584+
if cniRoute := routeStateToCNIRoute(route); cniRoute != nil {
585+
result.Routes = append(result.Routes, cniRoute)
586+
}
587+
}
588+
}
589+
590+
func printLinkWithNetworkState(dev netlink.Link, cniVersion string, containerNs ns.NetNS, state *HostNetworkState) error {
591+
result := &current.Result{
592+
CNIVersion: current.ImplementedSpecVersion,
593+
Interfaces: []*current.Interface{
594+
{
595+
Name: dev.Attrs().Name,
596+
Mac: dev.Attrs().HardwareAddr.String(),
597+
Sandbox: containerNs.Path(),
598+
},
599+
},
600+
}
601+
mergeNetworkStateIntoResult(result, state)
602+
return types.PrintResult(result, cniVersion)
603+
}
604+
499605
func linkFromPath(path string) (netlink.Link, error) {
500606
entries, err := os.ReadDir(path)
501607
if err != nil {
@@ -670,9 +776,9 @@ func validateCniContainerInterface(intf current.Interface) error {
670776
}
671777

672778
func cmdStatus(args *skel.CmdArgs) error {
673-
conf := NetConf{}
674-
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
675-
return fmt.Errorf("failed to load netconf: %w", err)
779+
conf, err := loadConf(args.StdinData)
780+
if err != nil {
781+
return err
676782
}
677783

678784
if conf.IPAM.Type != "" {
@@ -681,7 +787,11 @@ func cmdStatus(args *skel.CmdArgs) error {
681787
}
682788
}
683789

684-
// TODO: Check if host device exists.
790+
if !conf.DPDKMode {
791+
if _, err := getLink(conf.Device, conf.HWAddr, conf.KernelPath, conf.PCIAddr, conf.auxDevice); err != nil {
792+
return fmt.Errorf("failed to find host device: %v", err)
793+
}
794+
}
685795

686796
return nil
687797
}

0 commit comments

Comments
 (0)